remote-codex 0.1.9 → 0.11.0

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 (45) hide show
  1. package/apps/supervisor-api/dist/index.js +11942 -6101
  2. package/apps/supervisor-web/dist/assets/{highlighted-body-OFNGDK62-BFD4Ytvg.js → highlighted-body-OFNGDK62-ChrwAL9u.js} +1 -1
  3. package/apps/supervisor-web/dist/assets/index-DHf2HOXx.js +381 -0
  4. package/apps/supervisor-web/dist/assets/index-DpWxXCgt.css +32 -0
  5. package/apps/supervisor-web/dist/assets/{xterm-CukFWbxr.js → xterm-D4sevve4.js} +1 -1
  6. package/apps/supervisor-web/dist/index.html +2 -2
  7. package/config/codex-model-pricing.json +63 -0
  8. package/package.json +5 -2
  9. package/packages/agent-runtime/src/index.ts +4 -0
  10. package/packages/agent-runtime/src/management-errors.ts +11 -0
  11. package/packages/agent-runtime/src/model-pricing.ts +312 -0
  12. package/packages/agent-runtime/src/registry.ts +19 -4
  13. package/packages/agent-runtime/src/runtime-errors.ts +97 -0
  14. package/packages/agent-runtime/src/types.ts +50 -4
  15. package/packages/agent-runtime/src/unavailable-runtime.ts +169 -0
  16. package/packages/claude/src/historyItems.ts +693 -0
  17. package/packages/claude/src/index.ts +2 -0
  18. package/packages/claude/src/runtimeAdapter.test.ts +2138 -0
  19. package/packages/claude/src/runtimeAdapter.ts +2145 -0
  20. package/packages/codex/src/appServerManager.ts +12 -3
  21. package/packages/codex/src/historyItems.test.ts +110 -0
  22. package/packages/codex/src/historyItems.ts +97 -16
  23. package/packages/codex/src/hookHistory.test.ts +59 -0
  24. package/packages/codex/src/index.ts +7 -0
  25. package/packages/codex/src/local-session-store.ts +390 -0
  26. package/packages/codex/src/management/codex-management-service.ts +454 -0
  27. package/packages/codex/src/management/codexHostConfig.test.ts +88 -0
  28. package/packages/codex/src/management/codexHostConfig.ts +188 -0
  29. package/packages/codex/src/management/errors.ts +20 -0
  30. package/packages/codex/src/modelPricing.test.ts +184 -0
  31. package/packages/codex/src/modelPricing.ts +9 -0
  32. package/packages/codex/src/runtime-errors.test.ts +72 -0
  33. package/packages/codex/src/runtime-errors.ts +37 -0
  34. package/packages/codex/src/runtimeAdapter.ts +25 -2
  35. package/packages/codex/src/thread-title.ts +1 -0
  36. package/packages/db/src/repositories.ts +30 -0
  37. package/packages/opencode/src/historyItems.test.ts +504 -0
  38. package/packages/opencode/src/historyItems.ts +896 -0
  39. package/packages/opencode/src/index.ts +2 -0
  40. package/packages/opencode/src/runtimeAdapter.test.ts +1355 -0
  41. package/packages/opencode/src/runtimeAdapter.ts +1469 -0
  42. package/packages/shared/src/agent-providers.ts +56 -0
  43. package/packages/shared/src/index.ts +174 -35
  44. package/apps/supervisor-web/dist/assets/index-CbIt0KnL.css +0 -32
  45. package/apps/supervisor-web/dist/assets/index-Rd2EBQac.js +0 -377
@@ -52,8 +52,15 @@ function mapThread(record: any): CodexThreadRecord {
52
52
  status: record.status,
53
53
  cwd: record.cwd,
54
54
  name: record.name ?? null,
55
+ totalTurnCount: typeof record.totalTurnCount === 'number'
56
+ ? record.totalTurnCount
57
+ : typeof record.total_turn_count === 'number'
58
+ ? record.total_turn_count
59
+ : typeof record.turnsTotal === 'number'
60
+ ? record.turnsTotal
61
+ : null,
55
62
  turns: Array.isArray(record.turns) ? record.turns.map(mapTurn) : []
56
- };
63
+ } as CodexThreadRecord;
57
64
  }
58
65
 
59
66
  function mapTurn(record: any): CodexTurnRecord {
@@ -395,11 +402,13 @@ export class CodexAppServerManager extends EventEmitter {
395
402
  };
396
403
  }
397
404
 
398
- async readThread(threadId: string) {
405
+ async readThread(threadId: string, input: { limit?: number; beforeTurnId?: string | null } = {}) {
399
406
  await this.ensureReady();
400
407
  const response = await this.client!.request<{ thread: any }>('thread/read', {
401
408
  threadId,
402
- includeTurns: true
409
+ includeTurns: true,
410
+ ...(input.limit !== undefined ? { limit: input.limit } : {}),
411
+ ...(input.beforeTurnId ? { beforeTurnId: input.beforeTurnId } : {}),
403
412
  });
404
413
  return mapThread(response.thread);
405
414
  }
@@ -0,0 +1,110 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import {
4
+ markTransientAgentHistoryItem,
5
+ type AgentHistoryItem,
6
+ } from '../../agent-runtime/src/index';
7
+ import {
8
+ agentTurnToThreadTurnDto,
9
+ codexTurnToAgentTurn,
10
+ liveCodexItemToHistoryItem,
11
+ shouldPersistLiveHistoryItem,
12
+ shouldPersistRuntimeFinalHistoryItem,
13
+ } from './historyItems';
14
+
15
+ describe('codex history item persistence policy', () => {
16
+ it('keeps streamed assistant text out of live history persistence', () => {
17
+ expect(
18
+ shouldPersistLiveHistoryItem({
19
+ id: 'agent-live-1',
20
+ kind: 'agentMessage',
21
+ text: 'Streaming draft',
22
+ }),
23
+ ).toBe(false);
24
+ });
25
+
26
+ it('keeps transient streamed assistant text out of final runtime persistence', () => {
27
+ const transientMessage = markTransientAgentHistoryItem<AgentHistoryItem>({
28
+ id: 'agent-live-1',
29
+ kind: 'agentMessage',
30
+ text: 'Streaming draft',
31
+ });
32
+
33
+ expect(shouldPersistRuntimeFinalHistoryItem(transientMessage, [transientMessage])).toBe(
34
+ false,
35
+ );
36
+ });
37
+
38
+ it('hides transient streamed assistant text once a final assistant message exists', () => {
39
+ const transientMessage = markTransientAgentHistoryItem<AgentHistoryItem>({
40
+ id: 'agent-live-1',
41
+ kind: 'agentMessage',
42
+ text: 'Streaming draft',
43
+ });
44
+ const finalMessage: AgentHistoryItem = {
45
+ id: 'agent-final-1',
46
+ kind: 'agentMessage',
47
+ text: 'Final answer',
48
+ };
49
+
50
+ expect(
51
+ agentTurnToThreadTurnDto({
52
+ providerTurnId: 'turn-1',
53
+ status: 'completed',
54
+ error: null,
55
+ items: [transientMessage, finalMessage],
56
+ }).items,
57
+ ).toEqual([finalMessage]);
58
+ });
59
+
60
+ it('maps Codex collab agent tool calls to dedicated agent tool call items', () => {
61
+ const turn = codexTurnToAgentTurn({
62
+ id: 'turn-1',
63
+ status: 'inProgress',
64
+ error: null,
65
+ items: [
66
+ {
67
+ id: 'agent-tool-1',
68
+ type: 'collabAgentToolCall',
69
+ status: 'running',
70
+ action: {
71
+ name: 'Review checkout flow',
72
+ input: {
73
+ prompt: 'Inspect checkout UI regressions.',
74
+ },
75
+ },
76
+ },
77
+ ],
78
+ });
79
+
80
+ expect(turn.items[0]).toMatchObject({
81
+ id: 'agent-tool-1',
82
+ kind: 'agentToolCall',
83
+ text: 'Agent: Review checkout flow',
84
+ previewText: 'Agent',
85
+ status: 'running',
86
+ });
87
+ });
88
+
89
+ it('keeps Codex collab agent tool calls visible while running', () => {
90
+ expect(
91
+ liveCodexItemToHistoryItem(
92
+ {
93
+ id: 'agent-tool-1',
94
+ type: 'collabAgentToolCall',
95
+ status: 'running',
96
+ action: {
97
+ name: 'Inspect backend runtime boundaries',
98
+ },
99
+ },
100
+ 'started',
101
+ ),
102
+ ).toMatchObject({
103
+ id: 'agent-tool-1',
104
+ kind: 'agentToolCall',
105
+ text: 'Agent: Inspect backend runtime boundaries',
106
+ previewText: 'Agent',
107
+ status: 'running',
108
+ });
109
+ });
110
+ });
@@ -3,6 +3,7 @@ import type {
3
3
  CodexTurnItem,
4
4
  } from './types';
5
5
  import type { AgentTurn } from '../../agent-runtime/src/index';
6
+ import { isTransientAgentHistoryItem } from '../../agent-runtime/src/index';
6
7
  import type {
7
8
  ThreadHistoryItemDetailDto,
8
9
  ThreadHistoryItemDto,
@@ -13,6 +14,7 @@ import { parseCodexHookPromptText } from './hookHistory';
13
14
 
14
15
  const DEFERRED_COMMAND_DETAIL_TITLE = 'Command Output';
15
16
  const DEFERRED_TOOL_DETAIL_TITLE = 'Tool Call Details';
17
+ const DEFERRED_AGENT_TOOL_DETAIL_TITLE = 'Agent Details';
16
18
 
17
19
  export type TurnItemOrderSnapshot = Map<string, Map<string, number>>;
18
20
 
@@ -213,14 +215,19 @@ function formatCommandHistoryItem(
213
215
  }
214
216
 
215
217
  function deferToolCallHistoryItem(
216
- item: ThreadHistoryItemDto & { kind: 'toolCall' },
218
+ item: ThreadHistoryItemDto & { kind: 'toolCall' | 'agentToolCall' | 'skillToolCall' },
217
219
  deferredDetails: Map<string, ThreadHistoryItemDetailDto>,
218
220
  ): ThreadHistoryItemDto {
219
221
  const fullText = item.detailText?.trim() || item.text || 'Tool call';
220
222
  deferredDetails.set(item.id, {
221
223
  id: item.id,
222
224
  kind: item.kind,
223
- title: DEFERRED_TOOL_DETAIL_TITLE,
225
+ title:
226
+ item.kind === 'agentToolCall'
227
+ ? DEFERRED_AGENT_TOOL_DETAIL_TITLE
228
+ : item.kind === 'skillToolCall'
229
+ ? 'Skill Details'
230
+ : DEFERRED_TOOL_DETAIL_TITLE,
224
231
  text: fullText,
225
232
  });
226
233
 
@@ -267,6 +274,7 @@ function valueFromNestedRecords(
267
274
  function formatToolCallHistoryItem(
268
275
  item: CodexTurnItem,
269
276
  deferredDetails?: Map<string, ThreadHistoryItemDetailDto>,
277
+ kind: 'toolCall' | 'agentToolCall' | 'skillToolCall' = 'toolCall',
270
278
  ): ThreadHistoryItemDto {
271
279
  const { action, result, input, output } = extractToolCallRecords(item);
272
280
  const nestedRecords = [item, action, result, input, output];
@@ -295,14 +303,18 @@ function formatToolCallHistoryItem(
295
303
  const summaryLine = serverName && toolName
296
304
  ? `${serverName}/${toolName}`
297
305
  : toolName ?? serverName ?? stringOrNull(item.text) ?? item.type;
306
+ const displaySummary =
307
+ kind === 'agentToolCall' && !/^agent\b/i.test(summaryLine)
308
+ ? `Agent: ${summaryLine}`
309
+ : summaryLine;
298
310
 
299
- const detailLines = [summaryLine];
311
+ const detailLines = [displaySummary];
300
312
  if (status) {
301
313
  detailLines.push(`Status: ${status}`);
302
314
  }
303
315
 
304
316
  const text = stringOrNull(item.text);
305
- if (text && text !== summaryLine) {
317
+ if (text && text !== summaryLine && text !== displaySummary) {
306
318
  detailLines.push('', text);
307
319
  }
308
320
 
@@ -320,9 +332,9 @@ function formatToolCallHistoryItem(
320
332
 
321
333
  const historyItem: ThreadHistoryItemDto = {
322
334
  id: item.id,
323
- kind: 'toolCall',
324
- text: summaryLine,
325
- previewText: summaryLine,
335
+ kind,
336
+ text: displaySummary,
337
+ previewText: kind === 'agentToolCall' ? 'Agent' : displaySummary,
326
338
  detailText: detailLines.join('\n'),
327
339
  status,
328
340
  };
@@ -334,7 +346,9 @@ function formatToolCallHistoryItem(
334
346
  (historyItem.detailText?.length ?? 0) > 240)
335
347
  ) {
336
348
  return deferToolCallHistoryItem(
337
- historyItem as ThreadHistoryItemDto & { kind: 'toolCall' },
349
+ historyItem as ThreadHistoryItemDto & {
350
+ kind: 'toolCall' | 'agentToolCall' | 'skillToolCall';
351
+ },
338
352
  deferredDetails,
339
353
  );
340
354
  }
@@ -355,8 +369,12 @@ export function deferLargeHistoryItemDetails(
355
369
  deferredDetails,
356
370
  )
357
371
  : item.kind === 'toolCall'
372
+ || item.kind === 'agentToolCall'
373
+ || item.kind === 'skillToolCall'
358
374
  ? deferToolCallHistoryItem(
359
- item as ThreadHistoryItemDto & { kind: 'toolCall' },
375
+ item as ThreadHistoryItemDto & {
376
+ kind: 'toolCall' | 'agentToolCall' | 'skillToolCall';
377
+ },
360
378
  deferredDetails,
361
379
  )
362
380
  : item,
@@ -366,15 +384,45 @@ export function deferLargeHistoryItemDetails(
366
384
 
367
385
  export function shouldPersistLiveHistoryItem(item: ThreadHistoryItemDto) {
368
386
  return (
369
- item.kind === 'agentMessage' ||
370
387
  item.kind === 'commandExecution' ||
371
388
  item.kind === 'fileChange' ||
389
+ item.kind === 'fileRead' ||
372
390
  item.kind === 'hook' ||
391
+ item.kind === 'agentToolCall' ||
392
+ item.kind === 'skillToolCall' ||
373
393
  item.kind === 'toolCall' ||
374
394
  item.kind === 'webSearch'
375
395
  );
376
396
  }
377
397
 
398
+ export function shouldPersistFinalHistoryItem(item: ThreadHistoryItemDto) {
399
+ return item.kind === 'agentMessage' || shouldPersistLiveHistoryItem(item);
400
+ }
401
+
402
+ export function shouldPersistRuntimeFinalHistoryItem(
403
+ item: ThreadHistoryItemDto,
404
+ allItems: ThreadHistoryItemDto[],
405
+ ) {
406
+ if (item.kind === 'agentMessage' && isTransientAgentHistoryItem(item)) {
407
+ return false;
408
+ }
409
+
410
+ return shouldPersistFinalHistoryItem(item);
411
+ }
412
+
413
+ function visibleRuntimeTurnItems(items: ThreadHistoryItemDto[]) {
414
+ const hasFinalAgentMessage = items.some(
415
+ (item) => item.kind === 'agentMessage' && !isTransientAgentHistoryItem(item),
416
+ );
417
+ if (!hasFinalAgentMessage) {
418
+ return items;
419
+ }
420
+
421
+ return items.filter(
422
+ (item) => !(item.kind === 'agentMessage' && isTransientAgentHistoryItem(item)),
423
+ );
424
+ }
425
+
378
426
  export function parseStoredHistoryItem(value: string): ThreadHistoryItemDto | null {
379
427
  try {
380
428
  const parsed = JSON.parse(value);
@@ -562,6 +610,24 @@ function copyPersistedSequence(
562
610
  return item.sequence === sequence ? item : { ...item, sequence };
563
611
  }
564
612
 
613
+ function shouldAppendPersistedMissingItem(
614
+ turn: ThreadTurnDto,
615
+ item: ThreadHistoryItemDto,
616
+ ) {
617
+ if (item.kind !== 'agentMessage') {
618
+ return true;
619
+ }
620
+
621
+ // Older builds persisted streaming assistant drafts. Once the provider
622
+ // transcript has final assistant text, do not resurrect those draft rows.
623
+ const isCrossTurnProjection = Boolean(item.sourceTurnId && item.sourceTurnId !== turn.id);
624
+ return !(
625
+ turn.status === 'completed' &&
626
+ turn.items.some((turnItem) => turnItem.kind === 'agentMessage') &&
627
+ !isCrossTurnProjection
628
+ );
629
+ }
630
+
565
631
  export function mergePersistedHistoryItemsIntoTurns(
566
632
  turns: ThreadTurnDto[],
567
633
  persistedItemsByTurnId: Map<string, ThreadHistoryItemDto[]>,
@@ -596,7 +662,12 @@ export function mergePersistedHistoryItemsIntoTurns(
596
662
  changed = true;
597
663
  }
598
664
 
599
- if (persistedItem.kind === 'commandExecution' || persistedItem.kind === 'toolCall') {
665
+ if (
666
+ persistedItem.kind === 'commandExecution' ||
667
+ persistedItem.kind === 'toolCall' ||
668
+ persistedItem.kind === 'agentToolCall' ||
669
+ persistedItem.kind === 'skillToolCall'
670
+ ) {
600
671
  const existingText = item.detailText?.trim() || item.text.trim();
601
672
  const persistedText = persistedItem.detailText?.trim() || persistedItem.text.trim();
602
673
  if (persistedText.length > existingText.length) {
@@ -607,7 +678,9 @@ export function mergePersistedHistoryItemsIntoTurns(
607
678
  deferredDetails,
608
679
  )
609
680
  : deferToolCallHistoryItem(
610
- persistedItem as ThreadHistoryItemDto & { kind: 'toolCall' },
681
+ persistedItem as ThreadHistoryItemDto & {
682
+ kind: 'toolCall' | 'agentToolCall' | 'skillToolCall';
683
+ },
611
684
  deferredDetails,
612
685
  );
613
686
  }
@@ -619,6 +692,7 @@ export function mergePersistedHistoryItemsIntoTurns(
619
692
  const existingItemIds = new Set(nextItems.map((item) => item.id));
620
693
  const missingItems = [...persistedItemsById.values()]
621
694
  .filter((item) => !existingItemIds.has(item.id))
695
+ .filter((item) => shouldAppendPersistedMissingItem(turn, item))
622
696
  .map((item) =>
623
697
  item.kind === 'commandExecution'
624
698
  ? deferCommandHistoryItem(
@@ -626,8 +700,12 @@ export function mergePersistedHistoryItemsIntoTurns(
626
700
  deferredDetails,
627
701
  )
628
702
  : item.kind === 'toolCall'
703
+ || item.kind === 'agentToolCall'
704
+ || item.kind === 'skillToolCall'
629
705
  ? deferToolCallHistoryItem(
630
- item as ThreadHistoryItemDto & { kind: 'toolCall' },
706
+ item as ThreadHistoryItemDto & {
707
+ kind: 'toolCall' | 'agentToolCall' | 'skillToolCall';
708
+ },
631
709
  deferredDetails,
632
710
  )
633
711
  : item,
@@ -1175,8 +1253,9 @@ function itemToHistoryItem(
1175
1253
  return formatFileChangeHistoryItem(item);
1176
1254
  case 'mcpToolCall':
1177
1255
  case 'dynamicToolCall':
1178
- case 'collabAgentToolCall':
1179
1256
  return formatToolCallHistoryItem(item, deferredDetails);
1257
+ case 'collabAgentToolCall':
1258
+ return formatToolCallHistoryItem(item, deferredDetails, 'agentToolCall');
1180
1259
  default:
1181
1260
  return {
1182
1261
  id: item.id,
@@ -1195,6 +1274,8 @@ export function liveCodexItemToHistoryItem(
1195
1274
  if (
1196
1275
  historyItem.kind !== 'commandExecution' &&
1197
1276
  historyItem.kind !== 'toolCall' &&
1277
+ historyItem.kind !== 'agentToolCall' &&
1278
+ historyItem.kind !== 'skillToolCall' &&
1198
1279
  historyItem.kind !== 'fileChange' &&
1199
1280
  historyItem.kind !== 'webSearch'
1200
1281
  ) {
@@ -1215,10 +1296,10 @@ export function agentTurnToThreadTurnDto(
1215
1296
  ): ThreadTurnDto {
1216
1297
  const baseTurn: ThreadTurnDto = {
1217
1298
  id: turn.providerTurnId,
1218
- startedAt: parseUuidV7Timestamp(turn.providerTurnId),
1299
+ startedAt: turn.startedAt ?? parseUuidV7Timestamp(turn.providerTurnId),
1219
1300
  status: turn.status,
1220
1301
  error: turn.error?.message ?? null,
1221
- items: turn.items,
1302
+ items: visibleRuntimeTurnItems(turn.items),
1222
1303
  };
1223
1304
 
1224
1305
  return deferredDetails ? deferLargeHistoryItemDetails(baseTurn, deferredDetails) : baseTurn;
@@ -0,0 +1,59 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import {
4
+ codexHookRunToHistoryItem,
5
+ parseCodexHookPromptText,
6
+ } from './index';
7
+
8
+ describe('codex hook history mapping', () => {
9
+ it('parses hook prompt XML emitted as assistant text', () => {
10
+ expect(
11
+ parseCodexHookPromptText(
12
+ '<hook_prompt hook_run_id="stop:0:/tmp/demo/.codex/hooks.json">remote-codex hook ran</hook_prompt>',
13
+ ),
14
+ ).toMatchObject({
15
+ hookRunId: 'stop:0:/tmp/demo/.codex/hooks.json',
16
+ output: 'remote-codex hook ran',
17
+ eventName: 'stop',
18
+ eventLabel: 'Stop',
19
+ sourcePath: '/tmp/demo/.codex/hooks.json',
20
+ outputEntries: [{ kind: 'warning', text: 'remote-codex hook ran' }],
21
+ });
22
+ });
23
+
24
+ it('maps Codex hook runs into provider-neutral history items', () => {
25
+ expect(
26
+ codexHookRunToHistoryItem({
27
+ id: 'run-1',
28
+ eventName: 'preToolUse',
29
+ handlerType: 'command',
30
+ executionMode: 'blocking',
31
+ scope: 'project',
32
+ sourcePath: '/tmp/demo/.codex/hooks.json',
33
+ source: 'project',
34
+ status: 'completed',
35
+ statusMessage: 'Checking Bash',
36
+ durationMs: 42,
37
+ outputEntries: [{ kind: 'context', text: 'allowed' }],
38
+ stderr: 'diagnostic',
39
+ }),
40
+ ).toMatchObject({
41
+ id: 'hook:run-1',
42
+ kind: 'hook',
43
+ text: 'PreToolUse hook',
44
+ previewText: 'Checking Bash',
45
+ status: 'Completed',
46
+ hookEventName: 'preToolUse',
47
+ hookEventLabel: 'PreToolUse',
48
+ hookHandlerType: 'command',
49
+ hookScope: 'project',
50
+ hookSource: 'project',
51
+ hookSourcePath: '/tmp/demo/.codex/hooks.json',
52
+ hookStatusMessage: 'Checking Bash',
53
+ hookOutputEntries: [
54
+ { kind: 'context', text: 'allowed' },
55
+ { kind: 'warning', text: 'diagnostic' },
56
+ ],
57
+ });
58
+ });
59
+ });
@@ -2,5 +2,12 @@ export * from './appServerManager';
2
2
  export * from './historyItems';
3
3
  export * from './hookHistory';
4
4
  export * from './jsonrpc';
5
+ export * from './local-session-store';
6
+ export * from './management/codex-management-service';
7
+ export * from './management/codexHostConfig';
8
+ export * from './management/errors';
9
+ export * from './modelPricing';
10
+ export * from './runtime-errors';
5
11
  export * from './runtimeAdapter';
12
+ export * from './thread-title';
6
13
  export * from './types';