remote-codex 0.11.19 → 0.11.20

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.
Files changed (35) hide show
  1. package/apps/relay-server/dist/index.js +48 -1
  2. package/apps/supervisor-api/dist/index.js +27939 -4
  3. package/apps/supervisor-web/dist/assets/index-BdeDlmJY.js +5 -0
  4. package/apps/supervisor-web/dist/assets/index-BmBS1Wzk.css +1 -0
  5. package/apps/supervisor-web/dist/assets/thread-ui-C5nUCiEf.js +3624 -0
  6. package/apps/supervisor-web/dist/index.html +3 -3
  7. package/package.json +1 -39
  8. package/packages/agent-runtime/src/types.ts +3 -1
  9. package/packages/claude/src/runtimeAdapter.test.ts +25 -0
  10. package/packages/claude/src/runtimeAdapter.ts +85 -24
  11. package/packages/codex/src/appServerManager.test.ts +2 -1
  12. package/packages/codex/src/historyItems.test.ts +26 -0
  13. package/packages/codex/src/historyItems.ts +17 -0
  14. package/packages/codex/src/runtimeAdapter.ts +1 -1
  15. package/packages/db/src/repositories.ts +0 -123
  16. package/packages/db/src/schema.ts +1 -334
  17. package/packages/opencode/src/runtimeAdapter.test.ts +13 -1
  18. package/packages/opencode/src/runtimeAdapter.ts +1 -2
  19. package/packages/shared/src/index.ts +4 -1
  20. package/apps/supervisor-api/dist/chunk-IZBNFCMP.js +0 -29346
  21. package/apps/supervisor-api/dist/worker-index.d.ts +0 -2
  22. package/apps/supervisor-api/dist/worker-index.js +0 -197
  23. package/apps/supervisor-web/dist/assets/index-CUnlRID-.js +0 -6
  24. package/apps/supervisor-web/dist/assets/index-D3I41SIH.css +0 -1
  25. package/apps/supervisor-web/dist/assets/thread-ui-TBhog-RK.js +0 -3614
  26. package/packages/db/migrations/0018_control_plane.sql +0 -129
  27. package/packages/db/migrations/0019_control_plane_projects.sql +0 -19
  28. package/packages/db/migrations/0020_control_workspace_status.sql +0 -1
  29. package/packages/db/migrations/0021_control_sandbox_lifecycle_fields.sql +0 -3
  30. package/packages/db/migrations/0022_control_sandbox_resource_profile.sql +0 -1
  31. package/packages/db/migrations/0023_control_usage_import_state.sql +0 -18
  32. package/packages/db/migrations/0024_control_auth.sql +0 -23
  33. package/packages/db/migrations/0025_control_harness_credentials.sql +0 -29
  34. package/packages/db/migrations/0026_control_harness_usage_events.sql +0 -27
  35. package/packages/db/migrations/0027_harness_job_watches.sql +0 -24
@@ -4,16 +4,16 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Remote Codex Supervisor</title>
7
- <script type="module" crossorigin src="/assets/index-CUnlRID-.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-BdeDlmJY.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="/assets/react-vendor-CgLzZcV4.js">
9
9
  <link rel="modulepreload" crossorigin href="/assets/ui-vendor-CeKGesq3.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/graph-vendor-DVPtkh3h.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/terminal-vendor-B365Go3Z.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/markdown-vendor-BQJfKm05.js">
13
- <link rel="modulepreload" crossorigin href="/assets/thread-ui-TBhog-RK.js">
13
+ <link rel="modulepreload" crossorigin href="/assets/thread-ui-C5nUCiEf.js">
14
14
  <link rel="stylesheet" crossorigin href="/assets/graph-vendor-C5ap-Sga.css">
15
15
  <link rel="stylesheet" crossorigin href="/assets/terminal-vendor-Beg8tuEN.css">
16
- <link rel="stylesheet" crossorigin href="/assets/index-D3I41SIH.css">
16
+ <link rel="stylesheet" crossorigin href="/assets/index-BmBS1Wzk.css">
17
17
  </head>
18
18
  <body class="bg-stone-950">
19
19
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remote-codex",
3
- "version": "0.11.19",
3
+ "version": "0.11.20",
4
4
  "description": "Local web supervisor for Codex workspaces and threads.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -40,44 +40,8 @@
40
40
  },
41
41
  "scripts": {
42
42
  "dev": "concurrently -k -n api,web -c blue,green \"pnpm --filter @remote-codex/supervisor-api dev\" \"pnpm --filter @remote-codex/supervisor-web dev\"",
43
- "dev:control-plane": "pnpm --filter @remote-codex/control-plane-api dev",
44
43
  "dev:stop": "node scripts/stop-dev-servers.mjs",
45
44
  "dev:reset": "pnpm dev:stop && pnpm dev",
46
- "smoke:local-route-token": "tsx scripts/local-route-token-smoke.ts",
47
- "smoke:local-worker-checkpoint": "tsx scripts/local-worker-checkpoint-smoke.ts",
48
- "smoke:production-auth": "tsx scripts/local-production-auth-smoke.ts",
49
- "smoke:harness-admin-contract": "tsx scripts/harness-admin-contract-smoke.ts",
50
- "smoke:harness-k8s-secret": "tsx scripts/harness-k8s-secret-smoke.ts",
51
- "smoke:provider-gateway": "tsx scripts/provider-gateway-smoke.ts",
52
- "smoke:staging-phase-one": "tsx scripts/staging-phase-one-smoke.ts",
53
- "collect:harness-integration-evidence": "tsx scripts/collect-harness-integration-evidence.ts",
54
- "collect:aws-staging-preflight-evidence": "tsx scripts/collect-aws-staging-preflight-evidence.ts",
55
- "collect:phase-zero-six-evidence": "tsx scripts/run-phase-zero-six-staging-evidence.ts",
56
- "verify:aws-staging-preflight-evidence": "tsx scripts/verify-aws-staging-preflight-evidence.ts",
57
- "verify:staging-phase-one-evidence": "tsx scripts/verify-staging-phase-one-evidence.ts",
58
- "verify:phase-zero-six-env-ready": "tsx scripts/verify-phase-zero-six-env-ready.ts",
59
- "verify:phase-zero-six-evidence": "tsx scripts/verify-phase-zero-six-evidence.ts",
60
- "verify:harness-integration-evidence": "tsx scripts/verify-harness-integration-evidence.ts",
61
- "verify:harness-evidence-review": "tsx scripts/verify-harness-evidence-review.ts",
62
- "verify:harness-evidence-env": "tsx scripts/verify-harness-evidence-env.ts",
63
- "verify:phase-zero-six-artifacts-safe": "tsx scripts/verify-phase-zero-six-artifacts-safe.ts",
64
- "verify:github-staging-evidence-env": "tsx scripts/verify-github-staging-evidence-env.ts",
65
- "phase-zero-six:env": "pnpm verify:phase-zero-six-env-ready",
66
- "phase-zero-six:env:aws": "pnpm verify:phase-zero-six-env-ready -- --skip-staging-smoke",
67
- "phase-zero-six:env:report": "pnpm verify:phase-zero-six-env-ready -- --format text --no-fail",
68
- "phase-zero-six:env:aws:report": "pnpm verify:phase-zero-six-env-ready -- --skip-staging-smoke --format text --no-fail",
69
- "phase-zero-six:template": "pnpm verify:phase-zero-six-env-ready -- --write-env-template ./.temp/phase-zero-six-evidence/phase-zero-six.env.sh",
70
- "phase-zero-six:template:aws": "pnpm verify:phase-zero-six-env-ready -- --skip-staging-smoke --write-env-template ./.temp/phase-zero-six-evidence/aws-preflight.env.sh",
71
- "phase-zero-six:collect": "pnpm collect:phase-zero-six-evidence -- --output-dir ./.temp/phase-zero-six-evidence/latest",
72
- "phase-zero-six:collect:aws": "pnpm collect:phase-zero-six-evidence -- --output-dir ./.temp/phase-zero-six-evidence/latest-aws --skip-staging-smoke",
73
- "phase-zero-six:apply": "pnpm collect:phase-zero-six-evidence -- --from-output-dir ./.temp/phase-zero-six-evidence/latest --output-dir ./.temp/phase-zero-six-evidence/latest-apply --apply-ready",
74
- "phase-zero-six:apply:aws": "pnpm collect:phase-zero-six-evidence -- --from-output-dir ./.temp/phase-zero-six-evidence/latest-aws --output-dir ./.temp/phase-zero-six-evidence/latest-aws-apply --apply-ready --skip-staging-smoke",
75
- "phase-zero-six:audit": "pnpm verify:phase-zero-six-evidence",
76
- "phase-zero-six:audit:report": "pnpm verify:phase-zero-six-evidence -- --format text",
77
- "phase-zero-six:github-env": "pnpm verify:github-staging-evidence-env",
78
- "phase-zero-six:github-env:report": "pnpm verify:github-staging-evidence-env -- --format text --no-fail",
79
- "phase-zero-six:github-env:template": "tsx scripts/configure-github-staging-evidence-env.ts --write-template ./.temp/phase-zero-six-evidence/github-staging.env.sh",
80
- "phase-zero-six:github-env:configure": "tsx scripts/configure-github-staging-evidence-env.ts",
81
45
  "service:start": "node scripts/service-manager.mjs start",
82
46
  "service:stop": "node scripts/service-manager.mjs stop",
83
47
  "service:status": "node scripts/service-manager.mjs status",
@@ -85,11 +49,9 @@
85
49
  "prepack": "pnpm build:package",
86
50
  "build": "pnpm -r --if-present build",
87
51
  "build:package": "pnpm --filter @remote-codex/supervisor-api build && pnpm --filter @remote-codex/relay-server build && pnpm --filter @remote-codex/supervisor-web build",
88
- "build:worker-image": "docker build -f Dockerfile.worker -t remote-codex-worker:local .",
89
52
  "lint": "pnpm -r --if-present lint",
90
53
  "typecheck": "pnpm -r --if-present typecheck",
91
54
  "test": "NODE_ENV=test pnpm -r --if-present test",
92
- "test:phase-zero-six-evidence": "vitest run scripts/phase-zero-six-evidence.test.ts",
93
55
  "test:e2e": "playwright test",
94
56
  "db:migrate": "pnpm --filter @remote-codex/db db:migrate",
95
57
  "impeccable": "impeccable",
@@ -109,7 +109,9 @@ export type AgentRuntimeToolboxAction =
109
109
  | 'fork'
110
110
  | 'skills'
111
111
  | 'mcp'
112
- | 'hooks';
112
+ | 'hooks'
113
+ | 'prompt'
114
+ | 'unsupported';
113
115
 
114
116
  export interface AgentRuntimeToolboxItemSchema {
115
117
  action: AgentRuntimeToolboxAction;
@@ -250,6 +250,31 @@ describe('ClaudeRuntimeAdapter', () => {
250
250
  expect(sdkOptions[0]).not.toHaveProperty('tools');
251
251
  });
252
252
 
253
+ it('updates slash toolbox items from Claude SDK system init commands', async () => {
254
+ const adapter = makeAdapter(() => [
255
+ {
256
+ ...systemInit(),
257
+ slash_commands: ['compact', 'usage', 'code-review'],
258
+ },
259
+ result(),
260
+ ]);
261
+
262
+ await adapter.startSession({
263
+ cwd: '/tmp/workspace',
264
+ model: 'sonnet',
265
+ approvalMode: 'guarded',
266
+ sandboxMode: 'workspace-write',
267
+ });
268
+
269
+ expect(adapter.managementSchema.toolboxItems).toEqual([
270
+ expect.objectContaining({ action: 'mcp', command: '/mcp', panel: 'mcp' }),
271
+ expect.objectContaining({ action: 'prompt', command: '/code-review' }),
272
+ expect.objectContaining({ action: 'prompt', command: '/compact' }),
273
+ expect.objectContaining({ action: 'prompt', command: '/usage' }),
274
+ expect.objectContaining({ action: 'unsupported', command: '/btw' }),
275
+ ]);
276
+ });
277
+
253
278
  it('maps thread sandbox modes to Claude permission and sandbox settings', async () => {
254
279
  const turnOptions: Record<string, unknown>[] = [];
255
280
  const adapter = makeAdapter((_prompt, options) => {
@@ -20,6 +20,7 @@ import type {
20
20
  AgentRuntime,
21
21
  AgentRuntimeEvent,
22
22
  AgentRuntimeManagementSchema,
23
+ AgentRuntimeToolboxItemSchema,
23
24
  AgentRuntimeStatus,
24
25
  AgentSessionDetail,
25
26
  AgentSessionSummary,
@@ -103,6 +104,7 @@ interface SDKMessage {
103
104
  session_id?: string;
104
105
  cwd?: string;
105
106
  model?: string;
107
+ slash_commands?: unknown;
106
108
  uuid?: string;
107
109
  message?: unknown;
108
110
  event?: unknown;
@@ -387,7 +389,7 @@ export const claudeCapabilities: AgentProviderCapabilities = {
387
389
  controls: {
388
390
  planMode: true,
389
391
  permissionRequests: false,
390
- sandboxMode: true,
392
+ sandboxMode: false,
391
393
  performanceMode: false,
392
394
  goals: false,
393
395
  },
@@ -407,6 +409,78 @@ export const claudeCapabilities: AgentProviderCapabilities = {
407
409
  },
408
410
  };
409
411
 
412
+ function normalizeClaudeSlashCommands(value: unknown) {
413
+ if (!Array.isArray(value)) {
414
+ return [];
415
+ }
416
+
417
+ const commands = new Set<string>();
418
+ for (const entry of value) {
419
+ if (typeof entry !== 'string') {
420
+ continue;
421
+ }
422
+ const command = entry.trim().replace(/^\/+/, '');
423
+ if (command) {
424
+ commands.add(command);
425
+ }
426
+ }
427
+ return [...commands].sort((left, right) => left.localeCompare(right));
428
+ }
429
+
430
+ function claudeSlashLabel(command: string) {
431
+ switch (command) {
432
+ case 'compact':
433
+ return '/compact';
434
+ case 'clear':
435
+ return '/clear';
436
+ case 'context':
437
+ return '/context';
438
+ case 'usage':
439
+ return '/usage';
440
+ default:
441
+ return `/${command}`;
442
+ }
443
+ }
444
+
445
+ function buildClaudeToolboxItems(
446
+ slashCommands: string[],
447
+ ): AgentRuntimeToolboxItemSchema[] {
448
+ const discovered = new Set(slashCommands);
449
+ const items: AgentRuntimeToolboxItemSchema[] = [
450
+ {
451
+ action: 'mcp',
452
+ command: '/mcp',
453
+ label: 'MCP',
454
+ description: 'Open MCP status for this Claude Code session.',
455
+ panel: 'mcp',
456
+ },
457
+ ];
458
+
459
+ for (const command of slashCommands) {
460
+ if (command === 'mcp') {
461
+ continue;
462
+ }
463
+ items.push({
464
+ action: 'prompt',
465
+ command: `/${command}`,
466
+ label: claudeSlashLabel(command),
467
+ description: 'Claude Code slash command discovered from the SDK session.',
468
+ });
469
+ }
470
+
471
+ if (!discovered.has('btw')) {
472
+ items.push({
473
+ action: 'unsupported',
474
+ command: '/btw',
475
+ label: '/btw',
476
+ description:
477
+ 'Not listed by the current Claude Agent SDK session; it may require the interactive Claude TTY or a different Claude Code version.',
478
+ });
479
+ }
480
+
481
+ return items;
482
+ }
483
+
410
484
  const DEFAULT_CLAUDE_MODELS: AgentModel[] = [
411
485
  {
412
486
  id: 'sonnet',
@@ -959,8 +1033,6 @@ function queryOptionsForRuntime(
959
1033
  },
960
1034
  env: {
961
1035
  ...process.env,
962
- CLAUDE_CONFIG_DIR: input.home,
963
- CLAUDE_HOME: input.home,
964
1036
  CLAUDE_AGENT_SDK_CLIENT_APP: input.clientApp,
965
1037
  },
966
1038
  };
@@ -1022,9 +1094,7 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1022
1094
  };
1023
1095
  readonly managementSchema: AgentRuntimeManagementSchema = {
1024
1096
  hostConfigFiles: [],
1025
- toolboxItems: [
1026
- { action: 'mcp', command: '/mcp', label: 'MCP', panel: 'mcp' },
1027
- ],
1097
+ toolboxItems: buildClaudeToolboxItems([]),
1028
1098
  hookCommandTemplates: [],
1029
1099
  providerConfigFormat: 'none',
1030
1100
  mcpConfigFormat: 'none',
@@ -1069,6 +1139,12 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1069
1139
  return { ...this.status };
1070
1140
  }
1071
1141
 
1142
+ private updateToolboxItemsFromSystemInit(message: SDKMessage) {
1143
+ this.managementSchema.toolboxItems = buildClaudeToolboxItems(
1144
+ normalizeClaudeSlashCommands(message.slash_commands),
1145
+ );
1146
+ }
1147
+
1072
1148
  async start() {
1073
1149
  await fs.mkdir(this.options.home, { recursive: true });
1074
1150
  if (!this.options.query && !this.options.sdk) {
@@ -1222,6 +1298,7 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1222
1298
  for await (const message of query) {
1223
1299
  rawMessages.push(message);
1224
1300
  if (message.type === 'system' && message.subtype === 'init') {
1301
+ this.updateToolboxItemsFromSystemInit(message);
1225
1302
  const sessionId = message.session_id;
1226
1303
  if (sessionId) {
1227
1304
  providerSessionId = sessionId;
@@ -1558,6 +1635,7 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1558
1635
  this.captureUsage(state, message);
1559
1636
 
1560
1637
  if (message.type === 'system' && message.subtype === 'init') {
1638
+ this.updateToolboxItemsFromSystemInit(message);
1561
1639
  if (message.session_id) {
1562
1640
  this.sessionCwds.set(message.session_id, message.cwd ?? '');
1563
1641
  this.sessionModels.set(message.session_id, message.model ?? null);
@@ -2084,24 +2162,7 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
2084
2162
  }
2085
2163
 
2086
2164
  private async withClaudeConfigEnv<T>(callback: () => Promise<T>): Promise<T> {
2087
- const previousConfigDir = process.env.CLAUDE_CONFIG_DIR;
2088
- const previousClaudeHome = process.env.CLAUDE_HOME;
2089
- process.env.CLAUDE_CONFIG_DIR = this.options.home;
2090
- process.env.CLAUDE_HOME = this.options.home;
2091
- try {
2092
- return await callback();
2093
- } finally {
2094
- if (previousConfigDir === undefined) {
2095
- delete process.env.CLAUDE_CONFIG_DIR;
2096
- } else {
2097
- process.env.CLAUDE_CONFIG_DIR = previousConfigDir;
2098
- }
2099
- if (previousClaudeHome === undefined) {
2100
- delete process.env.CLAUDE_HOME;
2101
- } else {
2102
- process.env.CLAUDE_HOME = previousClaudeHome;
2103
- }
2104
- }
2165
+ return callback();
2105
2166
  }
2106
2167
 
2107
2168
  private markFailed(error: unknown) {
@@ -5,6 +5,7 @@ import { PassThrough, Writable } from 'node:stream';
5
5
  import { describe, expect, it } from 'vitest';
6
6
 
7
7
  import { CodexAppServerManager } from './appServerManager';
8
+ import type { CodexUserInput } from './types';
8
9
 
9
10
  async function waitForCondition(
10
11
  condition: () => boolean,
@@ -198,7 +199,7 @@ describe('CodexAppServerManager', () => {
198
199
  });
199
200
 
200
201
  it('forwards structured image input when starting a turn', async () => {
201
- const expectedInput = [
202
+ const expectedInput: CodexUserInput[] = [
202
203
  { type: 'text', text: 'Inspect this ', text_elements: [] },
203
204
  { type: 'localImage', path: '/tmp/workspace/photo.png' },
204
205
  { type: 'text', text: ' and summarize.', text_elements: [] },
@@ -117,6 +117,32 @@ describe('codex history item persistence policy', () => {
117
117
  ]);
118
118
  });
119
119
 
120
+ it('maps local image content with a path back to a photo token', () => {
121
+ const turn = codexTurnToAgentTurn({
122
+ id: 'turn-1',
123
+ status: 'completed',
124
+ error: null,
125
+ items: [
126
+ {
127
+ id: 'user-1',
128
+ type: 'userMessage',
129
+ content: [
130
+ { type: 'text', text: 'Inspect this' },
131
+ {
132
+ type: 'localImage',
133
+ path: '/tmp/workspace/.temp/threads/thread-1/photo.png',
134
+ } as any,
135
+ ],
136
+ },
137
+ ],
138
+ });
139
+
140
+ expect(turn.items[0]).toMatchObject({
141
+ kind: 'userMessage',
142
+ text: 'Inspect this\n[PHOTO /tmp/workspace/.temp/threads/thread-1/photo.png]',
143
+ });
144
+ });
145
+
120
146
  it('keeps MCP tool result text extractable for plugin artifacts', () => {
121
147
  const artifactText = [
122
148
  'Created a 3D molecule artifact for Methane.',
@@ -178,6 +178,23 @@ function textFromContentEntries(
178
178
  return entry.text;
179
179
  }
180
180
 
181
+ const assetEntry = entry as {
182
+ path?: unknown;
183
+ imagePath?: unknown;
184
+ filePath?: unknown;
185
+ };
186
+ const assetPath =
187
+ typeof assetEntry.path === 'string'
188
+ ? assetEntry.path
189
+ : typeof assetEntry.imagePath === 'string'
190
+ ? assetEntry.imagePath
191
+ : typeof assetEntry.filePath === 'string'
192
+ ? assetEntry.filePath
193
+ : null;
194
+ if (entry.type === 'localImage' && assetPath) {
195
+ return `[PHOTO ${assetPath}]`;
196
+ }
197
+
181
198
  return `[${entry.type}]`;
182
199
  })
183
200
  .join('\n')
@@ -137,7 +137,7 @@ export const codexCapabilities: AgentProviderCapabilities = {
137
137
  controls: {
138
138
  planMode: true,
139
139
  permissionRequests: true,
140
- sandboxMode: true,
140
+ sandboxMode: false,
141
141
  performanceMode: true,
142
142
  goals: true,
143
143
  },
@@ -5,8 +5,6 @@ import { and, desc, eq, inArray } from 'drizzle-orm';
5
5
  import { DatabaseClient } from './client';
6
6
  import { getDefaultHostRecord } from './client';
7
7
  import {
8
- harnessJobWatches,
9
- harnessNotifyRegistrations,
10
8
  notifications,
11
9
  shellSessions,
12
10
  threadActivityNotes,
@@ -946,124 +944,3 @@ export function deleteNotificationsByThreadId(db: DatabaseClient, threadId: stri
946
944
  export function deleteWorkspaceRecord(db: DatabaseClient, id: string) {
947
945
  db.delete(workspaces).where(eq(workspaces.id, id)).run();
948
946
  }
949
-
950
- export type HarnessJobWatchStatus = 'pending' | 'delivered' | 'failed';
951
-
952
- export interface UpsertHarnessNotifyRegistrationInput {
953
- agentId: string;
954
- hookToken: string;
955
- secret: string;
956
- callbackUrl: string;
957
- }
958
-
959
- export function getHarnessNotifyRegistration(db: DatabaseClient) {
960
- return db
961
- .select()
962
- .from(harnessNotifyRegistrations)
963
- .where(eq(harnessNotifyRegistrations.id, 'default'))
964
- .get();
965
- }
966
-
967
- export function upsertHarnessNotifyRegistration(
968
- db: DatabaseClient,
969
- input: UpsertHarnessNotifyRegistrationInput,
970
- ) {
971
- const now = new Date().toISOString();
972
- const existing = getHarnessNotifyRegistration(db);
973
- if (existing) {
974
- db.update(harnessNotifyRegistrations)
975
- .set({
976
- agentId: input.agentId,
977
- hookToken: input.hookToken,
978
- secret: input.secret,
979
- callbackUrl: input.callbackUrl,
980
- updatedAt: now,
981
- })
982
- .where(eq(harnessNotifyRegistrations.id, 'default'))
983
- .run();
984
- return getHarnessNotifyRegistration(db)!;
985
- }
986
- const record = {
987
- id: 'default',
988
- agentId: input.agentId,
989
- hookToken: input.hookToken,
990
- secret: input.secret,
991
- callbackUrl: input.callbackUrl,
992
- registeredAt: now,
993
- updatedAt: now,
994
- };
995
- db.insert(harnessNotifyRegistrations).values(record).run();
996
- return record;
997
- }
998
-
999
- export interface CreateHarnessJobWatchInput {
1000
- jobId: string;
1001
- threadId: string;
1002
- title?: string | null;
1003
- }
1004
-
1005
- export function getHarnessJobWatchByJobId(db: DatabaseClient, jobId: string) {
1006
- return db
1007
- .select()
1008
- .from(harnessJobWatches)
1009
- .where(eq(harnessJobWatches.jobId, jobId))
1010
- .get();
1011
- }
1012
-
1013
- export function listHarnessJobWatches(db: DatabaseClient) {
1014
- return db.select().from(harnessJobWatches).orderBy(desc(harnessJobWatches.createdAt)).all();
1015
- }
1016
-
1017
- export function listPendingHarnessJobWatches(db: DatabaseClient) {
1018
- return db
1019
- .select()
1020
- .from(harnessJobWatches)
1021
- .where(eq(harnessJobWatches.status, 'pending'))
1022
- .all();
1023
- }
1024
-
1025
- export function upsertHarnessJobWatch(db: DatabaseClient, input: CreateHarnessJobWatchInput) {
1026
- const now = new Date().toISOString();
1027
- const existing = getHarnessJobWatchByJobId(db, input.jobId);
1028
- if (existing) {
1029
- db.update(harnessJobWatches)
1030
- .set({
1031
- threadId: input.threadId,
1032
- title: input.title ?? existing.title,
1033
- updatedAt: now,
1034
- })
1035
- .where(eq(harnessJobWatches.id, existing.id))
1036
- .run();
1037
- return getHarnessJobWatchByJobId(db, input.jobId)!;
1038
- }
1039
- const record = {
1040
- id: randomUUID(),
1041
- jobId: input.jobId,
1042
- threadId: input.threadId,
1043
- title: input.title ?? null,
1044
- status: 'pending',
1045
- lastJobStatus: null,
1046
- lastError: null,
1047
- createdAt: now,
1048
- updatedAt: now,
1049
- deliveredAt: null,
1050
- };
1051
- db.insert(harnessJobWatches).values(record).run();
1052
- return record;
1053
- }
1054
-
1055
- export function updateHarnessJobWatch(
1056
- db: DatabaseClient,
1057
- id: string,
1058
- input: {
1059
- status?: HarnessJobWatchStatus;
1060
- lastJobStatus?: string | null;
1061
- lastError?: string | null;
1062
- deliveredAt?: string | null;
1063
- },
1064
- ) {
1065
- db.update(harnessJobWatches)
1066
- .set({ ...input, updatedAt: new Date().toISOString() })
1067
- .where(eq(harnessJobWatches.id, id))
1068
- .run();
1069
- }