remote-codex 0.11.2 → 0.11.4
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 +4 -0
- package/apps/relay-server/dist/index.d.ts +2 -0
- package/apps/relay-server/dist/index.js +1254 -0
- package/apps/supervisor-api/dist/chunk-ZWZQVPDT.js +27893 -0
- package/apps/supervisor-api/dist/index.js +4 -25183
- package/apps/supervisor-api/dist/worker-index.d.ts +2 -0
- package/apps/supervisor-api/dist/worker-index.js +197 -0
- package/apps/supervisor-web/dist/assets/index-CbdWtyx0.js +5 -0
- package/apps/supervisor-web/dist/assets/index-Di1JBevU.css +1 -0
- package/apps/supervisor-web/dist/assets/thread-ui-ICfwCbte.js +3604 -0
- package/apps/supervisor-web/dist/assets/ui-vendor-D1uxdi-d.js +430 -0
- package/apps/supervisor-web/dist/index.html +6 -7
- package/bin/remote-codex.mjs +593 -21
- package/package.json +42 -2
- package/packages/agent-runtime/src/types.ts +2 -1
- package/packages/codex/src/appServerManager.ts +1 -0
- package/packages/codex/src/historyItems.test.ts +45 -0
- package/packages/codex/src/historyItems.ts +22 -0
- package/packages/codex/src/runtimeAdapter.ts +6 -0
- package/packages/codex/src/types.ts +2 -1
- package/packages/db/migrations/0018_control_plane.sql +129 -0
- package/packages/db/migrations/0019_control_plane_projects.sql +19 -0
- package/packages/db/migrations/0020_control_workspace_status.sql +1 -0
- package/packages/db/migrations/0021_control_sandbox_lifecycle_fields.sql +3 -0
- package/packages/db/migrations/0022_control_sandbox_resource_profile.sql +1 -0
- package/packages/db/migrations/0023_control_usage_import_state.sql +18 -0
- package/packages/db/migrations/0024_control_auth.sql +23 -0
- package/packages/db/migrations/0025_control_harness_credentials.sql +29 -0
- package/packages/db/migrations/0026_control_harness_usage_events.sql +27 -0
- package/packages/db/src/schema.ts +305 -1
- package/packages/shared/src/index.ts +186 -0
- package/packages/shared/src/tokens.ts +137 -0
- package/apps/supervisor-web/dist/assets/index-CbDzXN9T.css +0 -1
- package/apps/supervisor-web/dist/assets/index-DQpHiQXN.js +0 -4
- package/apps/supervisor-web/dist/assets/thread-ui-BEieA99i.css +0 -1
- package/apps/supervisor-web/dist/assets/thread-ui-CDk3ExRH.js +0 -3516
- package/apps/supervisor-web/dist/assets/ui-vendor-CgOZX1B8.js +0 -425
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core';
|
|
1
|
+
import { integer, real, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core';
|
|
2
2
|
|
|
3
3
|
export const hosts = sqliteTable('hosts', {
|
|
4
4
|
id: text('id').primaryKey(),
|
|
@@ -176,3 +176,307 @@ export const policies = sqliteTable('policies', {
|
|
|
176
176
|
createdAt: text('created_at').notNull(),
|
|
177
177
|
updatedAt: text('updated_at').notNull()
|
|
178
178
|
});
|
|
179
|
+
|
|
180
|
+
export const controlUsers = sqliteTable(
|
|
181
|
+
'control_users',
|
|
182
|
+
{
|
|
183
|
+
id: text('id').primaryKey(),
|
|
184
|
+
authProvider: text('auth_provider').notNull(),
|
|
185
|
+
authSubject: text('auth_subject').notNull(),
|
|
186
|
+
email: text('email').notNull(),
|
|
187
|
+
displayName: text('display_name'),
|
|
188
|
+
status: text('status').notNull().default('active'),
|
|
189
|
+
plan: text('plan').notNull().default('developer'),
|
|
190
|
+
billingCustomerId: text('billing_customer_id'),
|
|
191
|
+
quotaProfile: text('quota_profile').notNull().default('developer'),
|
|
192
|
+
createdAt: text('created_at').notNull(),
|
|
193
|
+
updatedAt: text('updated_at').notNull(),
|
|
194
|
+
lastSeenAt: text('last_seen_at'),
|
|
195
|
+
},
|
|
196
|
+
(table) => ({
|
|
197
|
+
authSubjectUnique: uniqueIndex('control_users_auth_subject_idx').on(
|
|
198
|
+
table.authProvider,
|
|
199
|
+
table.authSubject,
|
|
200
|
+
),
|
|
201
|
+
}),
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
export const controlAuthIdentities = sqliteTable(
|
|
205
|
+
'control_auth_identities',
|
|
206
|
+
{
|
|
207
|
+
id: text('id').primaryKey(),
|
|
208
|
+
userId: text('user_id').notNull(),
|
|
209
|
+
authProvider: text('auth_provider').notNull(),
|
|
210
|
+
authSubject: text('auth_subject').notNull(),
|
|
211
|
+
email: text('email'),
|
|
212
|
+
displayName: text('display_name'),
|
|
213
|
+
createdAt: text('created_at').notNull(),
|
|
214
|
+
updatedAt: text('updated_at').notNull(),
|
|
215
|
+
},
|
|
216
|
+
(table) => ({
|
|
217
|
+
authSubjectUnique: uniqueIndex('control_auth_identities_subject_idx').on(
|
|
218
|
+
table.authProvider,
|
|
219
|
+
table.authSubject,
|
|
220
|
+
),
|
|
221
|
+
}),
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
export const controlPasswordCredentials = sqliteTable(
|
|
225
|
+
'control_password_credentials',
|
|
226
|
+
{
|
|
227
|
+
id: text('id').primaryKey(),
|
|
228
|
+
userId: text('user_id').notNull(),
|
|
229
|
+
email: text('email').notNull(),
|
|
230
|
+
passwordHash: text('password_hash').notNull(),
|
|
231
|
+
createdAt: text('created_at').notNull(),
|
|
232
|
+
updatedAt: text('updated_at').notNull(),
|
|
233
|
+
lastUsedAt: text('last_used_at'),
|
|
234
|
+
},
|
|
235
|
+
(table) => ({
|
|
236
|
+
emailUnique: uniqueIndex('control_password_credentials_email_idx').on(table.email),
|
|
237
|
+
userUnique: uniqueIndex('control_password_credentials_user_idx').on(table.userId),
|
|
238
|
+
}),
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
export const controlProjects = sqliteTable('control_projects', {
|
|
242
|
+
id: text('id').primaryKey(),
|
|
243
|
+
userId: text('user_id').notNull(),
|
|
244
|
+
name: text('name').notNull(),
|
|
245
|
+
slug: text('slug').notNull(),
|
|
246
|
+
status: text('status').notNull().default('active'),
|
|
247
|
+
createdAt: text('created_at').notNull(),
|
|
248
|
+
updatedAt: text('updated_at').notNull(),
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
export const controlSandboxes = sqliteTable('control_sandboxes', {
|
|
252
|
+
id: text('id').primaryKey(),
|
|
253
|
+
userId: text('user_id').notNull().unique(),
|
|
254
|
+
state: text('state').notNull(),
|
|
255
|
+
image: text('image').notNull(),
|
|
256
|
+
region: text('region').notNull(),
|
|
257
|
+
resourceProfile: text('resource_profile').notNull().default('standard'),
|
|
258
|
+
k8sNamespace: text('k8s_namespace'),
|
|
259
|
+
k8sPodName: text('k8s_pod_name'),
|
|
260
|
+
routerBaseUrl: text('router_base_url'),
|
|
261
|
+
workerServiceName: text('worker_service_name'),
|
|
262
|
+
s3Prefix: text('s3_prefix').notNull(),
|
|
263
|
+
gatewayKeyId: text('gateway_key_id'),
|
|
264
|
+
lastStartedAt: text('last_started_at'),
|
|
265
|
+
lastSeenAt: text('last_seen_at'),
|
|
266
|
+
idleTimeoutAt: text('idle_timeout_at'),
|
|
267
|
+
statusReason: text('status_reason'),
|
|
268
|
+
startupProgress: integer('startup_progress').notNull().default(0),
|
|
269
|
+
lastFailureCode: text('last_failure_code'),
|
|
270
|
+
lastFailureMessage: text('last_failure_message'),
|
|
271
|
+
createdAt: text('created_at').notNull(),
|
|
272
|
+
updatedAt: text('updated_at').notNull(),
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
export const controlWorkspaces = sqliteTable(
|
|
276
|
+
'control_workspaces',
|
|
277
|
+
{
|
|
278
|
+
id: text('id').primaryKey(),
|
|
279
|
+
userId: text('user_id').notNull(),
|
|
280
|
+
projectId: text('project_id'),
|
|
281
|
+
sandboxId: text('sandbox_id').notNull(),
|
|
282
|
+
name: text('name').notNull(),
|
|
283
|
+
slug: text('slug').notNull(),
|
|
284
|
+
status: text('status').notNull().default('active'),
|
|
285
|
+
path: text('path').notNull(),
|
|
286
|
+
sourceType: text('source_type').notNull(),
|
|
287
|
+
gitUrl: text('git_url'),
|
|
288
|
+
defaultBranch: text('default_branch'),
|
|
289
|
+
createdAt: text('created_at').notNull(),
|
|
290
|
+
updatedAt: text('updated_at').notNull(),
|
|
291
|
+
},
|
|
292
|
+
(table) => ({
|
|
293
|
+
sandboxSlugUnique: uniqueIndex('control_workspaces_sandbox_slug_idx').on(
|
|
294
|
+
table.sandboxId,
|
|
295
|
+
table.slug,
|
|
296
|
+
),
|
|
297
|
+
}),
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
export const controlSessions = sqliteTable('control_sessions', {
|
|
301
|
+
id: text('id').primaryKey(),
|
|
302
|
+
userId: text('user_id').notNull(),
|
|
303
|
+
sandboxId: text('sandbox_id').notNull(),
|
|
304
|
+
workspaceId: text('workspace_id').notNull(),
|
|
305
|
+
provider: text('provider').notNull(),
|
|
306
|
+
workerSessionId: text('worker_session_id'),
|
|
307
|
+
title: text('title').notNull(),
|
|
308
|
+
status: text('status').notNull(),
|
|
309
|
+
lastActivityAt: text('last_activity_at'),
|
|
310
|
+
createdAt: text('created_at').notNull(),
|
|
311
|
+
updatedAt: text('updated_at').notNull(),
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
export const controlGatewayUsers = sqliteTable(
|
|
315
|
+
'control_gateway_users',
|
|
316
|
+
{
|
|
317
|
+
id: text('id').primaryKey(),
|
|
318
|
+
userId: text('user_id').notNull(),
|
|
319
|
+
provider: text('provider').notNull(),
|
|
320
|
+
externalUserId: text('external_user_id').notNull(),
|
|
321
|
+
createdAt: text('created_at').notNull(),
|
|
322
|
+
},
|
|
323
|
+
(table) => ({
|
|
324
|
+
userProviderUnique: uniqueIndex('control_gateway_users_user_provider_idx').on(
|
|
325
|
+
table.userId,
|
|
326
|
+
table.provider,
|
|
327
|
+
),
|
|
328
|
+
providerExternalUnique: uniqueIndex('control_gateway_users_provider_external_idx').on(
|
|
329
|
+
table.provider,
|
|
330
|
+
table.externalUserId,
|
|
331
|
+
),
|
|
332
|
+
}),
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
export const controlGatewayKeys = sqliteTable(
|
|
336
|
+
'control_gateway_keys',
|
|
337
|
+
{
|
|
338
|
+
id: text('id').primaryKey(),
|
|
339
|
+
userId: text('user_id').notNull(),
|
|
340
|
+
sandboxId: text('sandbox_id').notNull(),
|
|
341
|
+
provider: text('provider').notNull(),
|
|
342
|
+
externalKeyId: text('external_key_id').notNull(),
|
|
343
|
+
keyCiphertext: text('key_ciphertext'),
|
|
344
|
+
status: text('status').notNull(),
|
|
345
|
+
createdAt: text('created_at').notNull(),
|
|
346
|
+
rotatedAt: text('rotated_at'),
|
|
347
|
+
revokedAt: text('revoked_at'),
|
|
348
|
+
},
|
|
349
|
+
(table) => ({
|
|
350
|
+
sandboxProviderUnique: uniqueIndex('control_gateway_keys_sandbox_provider_idx').on(
|
|
351
|
+
table.sandboxId,
|
|
352
|
+
table.provider,
|
|
353
|
+
),
|
|
354
|
+
providerExternalUnique: uniqueIndex('control_gateway_keys_provider_external_idx').on(
|
|
355
|
+
table.provider,
|
|
356
|
+
table.externalKeyId,
|
|
357
|
+
),
|
|
358
|
+
}),
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
export const controlHarnessUsers = sqliteTable(
|
|
362
|
+
'control_harness_users',
|
|
363
|
+
{
|
|
364
|
+
id: text('id').primaryKey(),
|
|
365
|
+
userId: text('user_id').notNull(),
|
|
366
|
+
provider: text('provider').notNull(),
|
|
367
|
+
externalUserId: text('external_user_id').notNull(),
|
|
368
|
+
createdAt: text('created_at').notNull(),
|
|
369
|
+
},
|
|
370
|
+
(table) => ({
|
|
371
|
+
userProviderUnique: uniqueIndex('control_harness_users_user_provider_idx').on(
|
|
372
|
+
table.userId,
|
|
373
|
+
table.provider,
|
|
374
|
+
),
|
|
375
|
+
providerExternalUnique: uniqueIndex('control_harness_users_provider_external_idx').on(
|
|
376
|
+
table.provider,
|
|
377
|
+
table.externalUserId,
|
|
378
|
+
),
|
|
379
|
+
}),
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
export const controlHarnessKeys = sqliteTable(
|
|
383
|
+
'control_harness_keys',
|
|
384
|
+
{
|
|
385
|
+
id: text('id').primaryKey(),
|
|
386
|
+
userId: text('user_id').notNull(),
|
|
387
|
+
sandboxId: text('sandbox_id').notNull(),
|
|
388
|
+
provider: text('provider').notNull(),
|
|
389
|
+
externalKeyId: text('external_key_id').notNull(),
|
|
390
|
+
keyCiphertext: text('key_ciphertext'),
|
|
391
|
+
secretName: text('secret_name'),
|
|
392
|
+
secretKey: text('secret_key'),
|
|
393
|
+
status: text('status').notNull(),
|
|
394
|
+
createdAt: text('created_at').notNull(),
|
|
395
|
+
rotatedAt: text('rotated_at'),
|
|
396
|
+
revokedAt: text('revoked_at'),
|
|
397
|
+
},
|
|
398
|
+
(table) => ({
|
|
399
|
+
sandboxProviderUnique: uniqueIndex('control_harness_keys_sandbox_provider_idx').on(
|
|
400
|
+
table.sandboxId,
|
|
401
|
+
table.provider,
|
|
402
|
+
),
|
|
403
|
+
providerExternalUnique: uniqueIndex('control_harness_keys_provider_external_idx').on(
|
|
404
|
+
table.provider,
|
|
405
|
+
table.externalKeyId,
|
|
406
|
+
),
|
|
407
|
+
}),
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
export const controlUsageEvents = sqliteTable('control_usage_events', {
|
|
411
|
+
id: text('id').primaryKey(),
|
|
412
|
+
userId: text('user_id').notNull(),
|
|
413
|
+
sandboxId: text('sandbox_id').notNull(),
|
|
414
|
+
workspaceId: text('workspace_id'),
|
|
415
|
+
sessionId: text('session_id'),
|
|
416
|
+
gatewayKeyId: text('gateway_key_id'),
|
|
417
|
+
provider: text('provider').notNull(),
|
|
418
|
+
model: text('model').notNull(),
|
|
419
|
+
inputTokens: integer('input_tokens').notNull().default(0),
|
|
420
|
+
outputTokens: integer('output_tokens').notNull().default(0),
|
|
421
|
+
cachedTokens: integer('cached_tokens').notNull().default(0),
|
|
422
|
+
costUsd: real('cost_usd').notNull().default(0),
|
|
423
|
+
externalRequestId: text('external_request_id'),
|
|
424
|
+
occurredAt: text('occurred_at').notNull(),
|
|
425
|
+
importedAt: text('imported_at').notNull(),
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
export const controlHarnessUsageEvents = sqliteTable(
|
|
429
|
+
'control_harness_usage_events',
|
|
430
|
+
{
|
|
431
|
+
id: text('id').primaryKey(),
|
|
432
|
+
userId: text('user_id').notNull(),
|
|
433
|
+
sandboxId: text('sandbox_id').notNull(),
|
|
434
|
+
workspaceId: text('workspace_id'),
|
|
435
|
+
sessionId: text('session_id'),
|
|
436
|
+
provider: text('provider').notNull(),
|
|
437
|
+
module: text('module').notNull(),
|
|
438
|
+
tool: text('tool'),
|
|
439
|
+
runId: text('run_id'),
|
|
440
|
+
jobId: text('job_id'),
|
|
441
|
+
externalEventId: text('external_event_id'),
|
|
442
|
+
computeUnits: real('compute_units').notNull().default(0),
|
|
443
|
+
costUsd: real('cost_usd').notNull().default(0),
|
|
444
|
+
status: text('status').notNull().default('unknown'),
|
|
445
|
+
metadataJson: text('metadata_json').notNull().default('{}'),
|
|
446
|
+
occurredAt: text('occurred_at').notNull(),
|
|
447
|
+
importedAt: text('imported_at').notNull(),
|
|
448
|
+
},
|
|
449
|
+
(table) => ({
|
|
450
|
+
providerExternalEventUnique: uniqueIndex('control_harness_usage_provider_event_idx')
|
|
451
|
+
.on(table.provider, table.externalEventId),
|
|
452
|
+
}),
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
export const controlUsageImportState = sqliteTable('control_usage_import_state', {
|
|
456
|
+
id: text('id').primaryKey(),
|
|
457
|
+
provider: text('provider').notNull(),
|
|
458
|
+
source: text('source').notNull(),
|
|
459
|
+
cursor: text('cursor'),
|
|
460
|
+
lastStartedAt: text('last_started_at'),
|
|
461
|
+
lastSucceededAt: text('last_succeeded_at'),
|
|
462
|
+
lastFailedAt: text('last_failed_at'),
|
|
463
|
+
lastFailureMessage: text('last_failure_message'),
|
|
464
|
+
lastSourceCount: integer('last_source_count').notNull().default(0),
|
|
465
|
+
lastImportedCount: integer('last_imported_count').notNull().default(0),
|
|
466
|
+
lastDuplicateCount: integer('last_duplicate_count').notNull().default(0),
|
|
467
|
+
lastFailureCount: integer('last_failure_count').notNull().default(0),
|
|
468
|
+
updatedAt: text('updated_at').notNull(),
|
|
469
|
+
}, (table) => ({
|
|
470
|
+
providerSourceIdx: uniqueIndex('control_usage_import_state_provider_source_idx')
|
|
471
|
+
.on(table.provider, table.source),
|
|
472
|
+
}));
|
|
473
|
+
|
|
474
|
+
export const controlAuditLogs = sqliteTable('control_audit_logs', {
|
|
475
|
+
id: text('id').primaryKey(),
|
|
476
|
+
userId: text('user_id'),
|
|
477
|
+
action: text('action').notNull(),
|
|
478
|
+
resourceType: text('resource_type').notNull(),
|
|
479
|
+
resourceId: text('resource_id'),
|
|
480
|
+
metadataJson: text('metadata_json').notNull(),
|
|
481
|
+
createdAt: text('created_at').notNull(),
|
|
482
|
+
});
|
|
@@ -16,10 +16,17 @@ export type {
|
|
|
16
16
|
|
|
17
17
|
export type ApiErrorCode =
|
|
18
18
|
| 'bad_request'
|
|
19
|
+
| 'unauthorized'
|
|
19
20
|
| 'not_found'
|
|
20
21
|
| 'conflict'
|
|
21
22
|
| 'provider_goal_error'
|
|
22
23
|
| 'forbidden'
|
|
24
|
+
| 'unauthorized'
|
|
25
|
+
| 'invalid_route_token'
|
|
26
|
+
| 'gateway_unavailable'
|
|
27
|
+
| 'account_inactive'
|
|
28
|
+
| 'quota_exceeded'
|
|
29
|
+
| 'harness_unavailable'
|
|
23
30
|
| 'goal_feature_disabled'
|
|
24
31
|
| 'internal_error'
|
|
25
32
|
| 'service_unavailable';
|
|
@@ -53,12 +60,165 @@ export function truncateAutoThreadTitle(value: string) {
|
|
|
53
60
|
export interface RuntimeConfigDto {
|
|
54
61
|
appName: string;
|
|
55
62
|
appVersion: string;
|
|
63
|
+
mode: 'local' | 'server' | 'relay';
|
|
56
64
|
host: string;
|
|
57
65
|
port: number;
|
|
58
66
|
workspaceRoot: string;
|
|
59
67
|
environment: string;
|
|
60
68
|
}
|
|
61
69
|
|
|
70
|
+
export interface AuthSessionDto {
|
|
71
|
+
authenticated: boolean;
|
|
72
|
+
username: string | null;
|
|
73
|
+
expiresAt: string | null;
|
|
74
|
+
mode: 'local' | 'server' | 'relay';
|
|
75
|
+
authRequired: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface AuthLoginResultDto {
|
|
79
|
+
token: string | null;
|
|
80
|
+
session: AuthSessionDto;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface RelayHealthDto {
|
|
84
|
+
status: 'ok';
|
|
85
|
+
supervisorConnected: boolean;
|
|
86
|
+
supervisorConnectedAt: string | null;
|
|
87
|
+
lastSupervisorHeartbeatAt: string | null;
|
|
88
|
+
supervisorCount?: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export type RelayUserRoleDto = 'admin' | 'user';
|
|
92
|
+
|
|
93
|
+
export interface RelayUserDto {
|
|
94
|
+
id: string;
|
|
95
|
+
email: string;
|
|
96
|
+
username: string;
|
|
97
|
+
role: RelayUserRoleDto;
|
|
98
|
+
enabled: boolean;
|
|
99
|
+
createdAt: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface RelayDeviceDto {
|
|
103
|
+
id: string;
|
|
104
|
+
ownerUserId: string;
|
|
105
|
+
name: string;
|
|
106
|
+
tokenPreview: string;
|
|
107
|
+
connected: boolean;
|
|
108
|
+
connectedAt: string | null;
|
|
109
|
+
lastHeartbeatAt: string | null;
|
|
110
|
+
createdAt: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface RelaySessionShareDto {
|
|
114
|
+
id: string;
|
|
115
|
+
ownerUserId: string;
|
|
116
|
+
ownerUsername: string;
|
|
117
|
+
targetUsername: string;
|
|
118
|
+
targetUserId: string;
|
|
119
|
+
deviceId: string;
|
|
120
|
+
deviceName: string;
|
|
121
|
+
threadId: string;
|
|
122
|
+
label: string | null;
|
|
123
|
+
createdAt: string;
|
|
124
|
+
revokedAt: string | null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface RelaySessionDto {
|
|
128
|
+
authenticated: boolean;
|
|
129
|
+
user: RelayUserDto | null;
|
|
130
|
+
registrationEnabled: boolean;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface RelayLoginResultDto {
|
|
134
|
+
token: string;
|
|
135
|
+
session: RelaySessionDto;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface RelayRegisterResultDto {
|
|
139
|
+
token: string;
|
|
140
|
+
session: RelaySessionDto;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface RelayCreateDeviceResultDto {
|
|
144
|
+
device: RelayDeviceDto;
|
|
145
|
+
token: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface RelayPortalSummaryDto {
|
|
149
|
+
user: RelayUserDto;
|
|
150
|
+
devices: RelayDeviceDto[];
|
|
151
|
+
sharedWithMe: RelaySessionShareDto[];
|
|
152
|
+
sharedByMe: RelaySessionShareDto[];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface RelayAdminSummaryDto {
|
|
156
|
+
users: RelayUserDto[];
|
|
157
|
+
devices: RelayDeviceDto[];
|
|
158
|
+
registrationEnabled: boolean;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export type RelaySupervisorEnvelope =
|
|
162
|
+
| {
|
|
163
|
+
type: 'relay.connected';
|
|
164
|
+
timestamp: string;
|
|
165
|
+
deviceId?: string;
|
|
166
|
+
}
|
|
167
|
+
| {
|
|
168
|
+
type: 'relay.heartbeat';
|
|
169
|
+
timestamp: string;
|
|
170
|
+
deviceId?: string;
|
|
171
|
+
}
|
|
172
|
+
| {
|
|
173
|
+
type: 'relay.request';
|
|
174
|
+
timestamp: string;
|
|
175
|
+
requestId: string;
|
|
176
|
+
deviceId?: string;
|
|
177
|
+
payload: RelayHttpRequestPayload;
|
|
178
|
+
}
|
|
179
|
+
| {
|
|
180
|
+
type: 'relay.response';
|
|
181
|
+
timestamp: string;
|
|
182
|
+
requestId: string;
|
|
183
|
+
deviceId?: string;
|
|
184
|
+
payload: RelayHttpResponsePayload;
|
|
185
|
+
}
|
|
186
|
+
| {
|
|
187
|
+
type: 'relay.client.connected';
|
|
188
|
+
timestamp: string;
|
|
189
|
+
clientId: string;
|
|
190
|
+
}
|
|
191
|
+
| {
|
|
192
|
+
type: 'relay.client.disconnected';
|
|
193
|
+
timestamp: string;
|
|
194
|
+
clientId: string;
|
|
195
|
+
}
|
|
196
|
+
| {
|
|
197
|
+
type: 'relay.client.message';
|
|
198
|
+
timestamp: string;
|
|
199
|
+
clientId: string;
|
|
200
|
+
payload: SupervisorSocketClientEnvelope;
|
|
201
|
+
}
|
|
202
|
+
| {
|
|
203
|
+
type: 'relay.server.message';
|
|
204
|
+
timestamp: string;
|
|
205
|
+
clientId: string;
|
|
206
|
+
payload: SupervisorSocketServerEnvelope;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export interface RelayHttpRequestPayload {
|
|
210
|
+
method: string;
|
|
211
|
+
path: string;
|
|
212
|
+
headers: Record<string, string>;
|
|
213
|
+
body: string | null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export interface RelayHttpResponsePayload {
|
|
217
|
+
statusCode: number;
|
|
218
|
+
headers: Record<string, string>;
|
|
219
|
+
body: string;
|
|
220
|
+
}
|
|
221
|
+
|
|
62
222
|
export interface AgentRuntimeStatusDto {
|
|
63
223
|
state: 'starting' | 'ready' | 'degraded' | 'stopped' | 'failed';
|
|
64
224
|
transport: 'stdio' | 'sdk' | 'none';
|
|
@@ -290,6 +450,30 @@ export interface WorkspaceTreeDto {
|
|
|
290
450
|
nodes: WorkspaceTreeNodeDto[];
|
|
291
451
|
}
|
|
292
452
|
|
|
453
|
+
export interface WorkspaceFileDto {
|
|
454
|
+
path: string;
|
|
455
|
+
absPath: string;
|
|
456
|
+
kind: 'file' | 'directory';
|
|
457
|
+
size: number;
|
|
458
|
+
updatedAt: string;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export interface WriteWorkspaceFileInput {
|
|
462
|
+
path: string;
|
|
463
|
+
content: string;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export interface MoveWorkspaceFileInput {
|
|
467
|
+
fromPath: string;
|
|
468
|
+
toPath: string;
|
|
469
|
+
overwrite?: boolean;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export interface DeleteWorkspaceFileInput {
|
|
473
|
+
path: string;
|
|
474
|
+
recursive?: boolean;
|
|
475
|
+
}
|
|
476
|
+
|
|
293
477
|
export interface ThreadWorkspaceTreeNodeDto {
|
|
294
478
|
name: string;
|
|
295
479
|
path: string;
|
|
@@ -508,6 +692,7 @@ export interface UpdatePluginInput {
|
|
|
508
692
|
export interface ImportPluginInput {
|
|
509
693
|
manifest?: unknown;
|
|
510
694
|
manifestJson?: string;
|
|
695
|
+
manifestUrl?: string;
|
|
511
696
|
enabled?: boolean;
|
|
512
697
|
}
|
|
513
698
|
|
|
@@ -918,6 +1103,7 @@ export interface CreateThreadInput {
|
|
|
918
1103
|
title?: string;
|
|
919
1104
|
provider?: AgentBackendIdDto;
|
|
920
1105
|
model: string;
|
|
1106
|
+
reasoningEffort?: ReasoningEffortDto | null;
|
|
921
1107
|
approvalMode: ApprovalMode;
|
|
922
1108
|
}
|
|
923
1109
|
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
function base64UrlEncode(value: Buffer | string): string {
|
|
4
|
+
return Buffer.from(value)
|
|
5
|
+
.toString('base64')
|
|
6
|
+
.replaceAll('+', '-')
|
|
7
|
+
.replaceAll('/', '_')
|
|
8
|
+
.replaceAll('=', '');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function base64UrlDecode(value: string): Buffer {
|
|
12
|
+
const normalized = value.replaceAll('-', '+').replaceAll('_', '/');
|
|
13
|
+
const padding = '='.repeat((4 - (normalized.length % 4)) % 4);
|
|
14
|
+
return Buffer.from(`${normalized}${padding}`, 'base64');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function sign(input: string, secret: string): string {
|
|
18
|
+
return base64UrlEncode(createHmac('sha256', secret).update(input).digest());
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface RouteTokenPayload extends SignedTokenPayload {
|
|
22
|
+
sandbox_id: string;
|
|
23
|
+
project_id?: string;
|
|
24
|
+
workspace_id?: string;
|
|
25
|
+
session_id?: string;
|
|
26
|
+
scopes: string[];
|
|
27
|
+
iat: number;
|
|
28
|
+
jti: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface SignedTokenPayload {
|
|
32
|
+
sub: string;
|
|
33
|
+
iat?: number;
|
|
34
|
+
exp: number;
|
|
35
|
+
jti?: string;
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SigningKey {
|
|
40
|
+
id: string;
|
|
41
|
+
secret: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createSignedToken(
|
|
45
|
+
payload: SignedTokenPayload,
|
|
46
|
+
secret: string,
|
|
47
|
+
options: { kid?: string } = {},
|
|
48
|
+
): string {
|
|
49
|
+
const header = {
|
|
50
|
+
alg: 'HS256',
|
|
51
|
+
typ: 'JWT',
|
|
52
|
+
...(options.kid ? { kid: options.kid } : {}),
|
|
53
|
+
};
|
|
54
|
+
const encodedHeader = base64UrlEncode(JSON.stringify(header));
|
|
55
|
+
const encodedPayload = base64UrlEncode(JSON.stringify(payload));
|
|
56
|
+
const signingInput = `${encodedHeader}.${encodedPayload}`;
|
|
57
|
+
return `${signingInput}.${sign(signingInput, secret)}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function decodeHeader(token: string) {
|
|
61
|
+
const [encodedHeader] = token.split('.');
|
|
62
|
+
if (!encodedHeader) {
|
|
63
|
+
throw new Error('Invalid token shape.');
|
|
64
|
+
}
|
|
65
|
+
return JSON.parse(base64UrlDecode(encodedHeader).toString('utf8')) as {
|
|
66
|
+
alg?: string;
|
|
67
|
+
typ?: string;
|
|
68
|
+
kid?: string;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function verifySignedToken<
|
|
73
|
+
TPayload extends { sub: string; exp: number } = RouteTokenPayload,
|
|
74
|
+
>(
|
|
75
|
+
token: string,
|
|
76
|
+
secret: string,
|
|
77
|
+
nowSeconds = Math.floor(Date.now() / 1000),
|
|
78
|
+
) {
|
|
79
|
+
const parts = token.split('.');
|
|
80
|
+
if (parts.length !== 3 || !parts[0] || !parts[1] || !parts[2]) {
|
|
81
|
+
throw new Error('Invalid token shape.');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const signingInput = `${parts[0]}.${parts[1]}`;
|
|
85
|
+
const expected = sign(signingInput, secret);
|
|
86
|
+
const actualBuffer = Buffer.from(parts[2]);
|
|
87
|
+
const expectedBuffer = Buffer.from(expected);
|
|
88
|
+
if (
|
|
89
|
+
actualBuffer.length !== expectedBuffer.length ||
|
|
90
|
+
!timingSafeEqual(actualBuffer, expectedBuffer)
|
|
91
|
+
) {
|
|
92
|
+
throw new Error('Invalid token signature.');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const payload = JSON.parse(base64UrlDecode(parts[1]).toString('utf8')) as TPayload;
|
|
96
|
+
if (payload.exp <= nowSeconds) {
|
|
97
|
+
throw new Error('Token expired.');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return payload;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function verifySignedTokenWithKeys<
|
|
104
|
+
TPayload extends { sub: string; exp: number } = RouteTokenPayload,
|
|
105
|
+
>(
|
|
106
|
+
token: string,
|
|
107
|
+
keys: SigningKey[],
|
|
108
|
+
nowSeconds = Math.floor(Date.now() / 1000),
|
|
109
|
+
) {
|
|
110
|
+
if (keys.length === 0) {
|
|
111
|
+
throw new Error('No signing keys configured.');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const header = decodeHeader(token);
|
|
115
|
+
if (header.alg !== 'HS256') {
|
|
116
|
+
throw new Error('Unsupported token algorithm.');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (header.kid) {
|
|
120
|
+
const key = keys.find((candidate) => candidate.id === header.kid);
|
|
121
|
+
if (!key) {
|
|
122
|
+
throw new Error('Unknown token key id.');
|
|
123
|
+
}
|
|
124
|
+
return verifySignedToken<TPayload>(token, key.secret, nowSeconds);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let lastError: unknown = null;
|
|
128
|
+
for (const key of keys) {
|
|
129
|
+
try {
|
|
130
|
+
return verifySignedToken<TPayload>(token, key.secret, nowSeconds);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
lastError = error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
throw lastError instanceof Error ? lastError : new Error('Invalid token.');
|
|
137
|
+
}
|