remote-codex 0.1.6 → 0.1.7

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 (34) hide show
  1. package/apps/supervisor-api/dist/index.js +6786 -5630
  2. package/apps/supervisor-web/dist/assets/{highlighted-body-OFNGDK62-DvEvXPo5.js → highlighted-body-OFNGDK62-0cYcfOfd.js} +1 -1
  3. package/apps/supervisor-web/dist/assets/index-CbIt0KnL.css +32 -0
  4. package/apps/supervisor-web/dist/assets/index-nH6a8Wwn.js +377 -0
  5. package/apps/supervisor-web/dist/assets/{xterm-CWQ1ih_R.js → xterm-DisVWgDR.js} +1 -1
  6. package/apps/supervisor-web/dist/index.html +2 -2
  7. package/package.json +5 -1
  8. package/packages/agent-runtime/src/index.ts +2 -0
  9. package/packages/agent-runtime/src/registry.ts +44 -0
  10. package/packages/agent-runtime/src/types.ts +531 -0
  11. package/packages/codex/src/appServerManager.test.ts +328 -0
  12. package/packages/codex/src/appServerManager.ts +656 -0
  13. package/packages/codex/src/historyItems.ts +1185 -0
  14. package/packages/codex/src/hookHistory.ts +224 -0
  15. package/packages/codex/src/index.ts +6 -0
  16. package/packages/codex/src/jsonrpc.test.ts +58 -0
  17. package/packages/codex/src/jsonrpc.ts +198 -0
  18. package/packages/codex/src/requestMapper.test.ts +127 -0
  19. package/packages/codex/src/requestMapper.ts +511 -0
  20. package/packages/codex/src/runtimeAdapter.ts +692 -0
  21. package/packages/codex/src/types.ts +403 -0
  22. package/packages/db/migrations/0015_agent_provider_fields.sql +14 -0
  23. package/packages/db/migrations/0016_remove_codex_thread_goal_id.sql +46 -0
  24. package/packages/db/migrations/0017_remove_codex_thread_columns.sql +85 -0
  25. package/packages/db/src/client.ts +53 -0
  26. package/packages/db/src/index.ts +5 -0
  27. package/packages/db/src/migrate.test.ts +36 -0
  28. package/packages/db/src/migrate.ts +84 -0
  29. package/packages/db/src/repositories.ts +893 -0
  30. package/packages/db/src/schema.ts +177 -0
  31. package/packages/db/src/seed.ts +51 -0
  32. package/packages/shared/src/index.ts +878 -0
  33. package/apps/supervisor-web/dist/assets/index-CQu6sRq7.css +0 -32
  34. package/apps/supervisor-web/dist/assets/index-MELw9ga_.js +0 -377
@@ -0,0 +1,511 @@
1
+ import type {
2
+ AgentActionRequest,
3
+ AgentActionRequestResponseInput,
4
+ AgentPendingProviderRequest,
5
+ AgentProviderRequest,
6
+ AgentProviderRequestMapping,
7
+ } from '../../agent-runtime/src/index';
8
+ import type { CodexServerRequest } from './types';
9
+
10
+ type CodexPendingRequestResponseKind =
11
+ | 'answers'
12
+ | 'mcpElicitation'
13
+ | 'commandExecutionApproval'
14
+ | 'fileChangeApproval'
15
+ | 'permissionsApproval'
16
+ | 'legacyExecApproval'
17
+ | 'legacyApplyPatchApproval';
18
+
19
+ function isRecord(value: unknown): value is Record<string, unknown> {
20
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
21
+ }
22
+
23
+ function stringOrNull(value: unknown) {
24
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
25
+ }
26
+
27
+ function normalizeOptionLabelForApproval(value: string) {
28
+ return value
29
+ .replace(/\(recommended\)\s*$/i, '')
30
+ .trim()
31
+ .toLowerCase();
32
+ }
33
+
34
+ function isAllowOptionLabel(value: string) {
35
+ return /^(allow|approve|yes|continue|proceed|trust)\b/.test(value);
36
+ }
37
+
38
+ function isLikelyPositiveApprovalOption(value: string) {
39
+ const normalized = normalizeOptionLabelForApproval(value);
40
+ return (
41
+ isAllowOptionLabel(normalized) ||
42
+ /\b(allow|approve|yes|continue|proceed|trust)\b/.test(normalized)
43
+ );
44
+ }
45
+
46
+ function isLikelyApprovalPrompt(
47
+ requestMethod: string,
48
+ questions: Array<{
49
+ header: string;
50
+ question: string;
51
+ options: Array<{ label: string; description: string }> | null;
52
+ }>,
53
+ ) {
54
+ const methodText = requestMethod.toLowerCase();
55
+ if (
56
+ methodText.includes('approval') ||
57
+ methodText.includes('authorize') ||
58
+ methodText.includes('requestuserinput')
59
+ ) {
60
+ return true;
61
+ }
62
+
63
+ const combinedText = questions
64
+ .flatMap((question) => [
65
+ question.header,
66
+ question.question,
67
+ ...(question.options?.map((option) => option.label) ?? []),
68
+ ])
69
+ .join(' ')
70
+ .toLowerCase();
71
+
72
+ return /(allow|approve|permission|authorize|authorization|auth|mcp|tool)/.test(
73
+ combinedText,
74
+ );
75
+ }
76
+
77
+ function buildAutoApprovedAnswersForServerQuestions(
78
+ requestMethod: string,
79
+ questions: Array<{
80
+ id: string;
81
+ header: string;
82
+ question: string;
83
+ isOther: boolean;
84
+ isSecret: boolean;
85
+ options: Array<{ label: string; description: string }> | null;
86
+ }>,
87
+ ) {
88
+ if (!isLikelyApprovalPrompt(requestMethod, questions)) {
89
+ return null;
90
+ }
91
+
92
+ const answers: Record<string, { answers: string[] }> = {};
93
+
94
+ for (const question of questions) {
95
+ if (!question.options || question.options.length === 0) {
96
+ return null;
97
+ }
98
+
99
+ const recommendedOption = question.options.find((option) =>
100
+ /\(recommended\)\s*$/i.test(option.label),
101
+ );
102
+ const allowOption =
103
+ recommendedOption && isLikelyPositiveApprovalOption(recommendedOption.label)
104
+ ? recommendedOption
105
+ : question.options.find((option) =>
106
+ isLikelyPositiveApprovalOption(option.label),
107
+ );
108
+
109
+ if (!allowOption) {
110
+ return null;
111
+ }
112
+
113
+ answers[question.id] = {
114
+ answers: [allowOption.label],
115
+ };
116
+ }
117
+
118
+ return answers;
119
+ }
120
+
121
+ function isMcpElicitationRequest(
122
+ request: CodexServerRequest,
123
+ ): request is CodexServerRequest & {
124
+ params: {
125
+ threadId: string;
126
+ turnId?: string;
127
+ serverName?: string;
128
+ mode?: string;
129
+ message?: string;
130
+ requestedSchema?: Record<string, unknown>;
131
+ _meta?: Record<string, unknown>;
132
+ };
133
+ } {
134
+ return request.method === 'mcpServer/elicitation/request';
135
+ }
136
+
137
+ function buildAutoApprovedMcpElicitationResult(
138
+ request: CodexServerRequest,
139
+ ) {
140
+ if (!isMcpElicitationRequest(request)) {
141
+ return null;
142
+ }
143
+
144
+ return {
145
+ action: 'accept',
146
+ content: {},
147
+ } as const;
148
+ }
149
+
150
+ function stringFromUnknown(value: unknown) {
151
+ return typeof value === 'string' && value.trim() ? value : null;
152
+ }
153
+
154
+ function arrayTextFromUnknown(value: unknown) {
155
+ if (!Array.isArray(value)) {
156
+ return null;
157
+ }
158
+
159
+ const parts = value
160
+ .map((entry) => (typeof entry === 'string' ? entry : null))
161
+ .filter((entry): entry is string => Boolean(entry));
162
+ return parts.length > 0 ? parts.join(' ') : null;
163
+ }
164
+
165
+ function commandTextFromApprovalParams(params: Record<string, unknown>) {
166
+ return stringFromUnknown(params.command) ?? arrayTextFromUnknown(params.command);
167
+ }
168
+
169
+ function buildApprovalRequestDescription(params: Record<string, unknown>) {
170
+ return [
171
+ stringFromUnknown(params.reason),
172
+ commandTextFromApprovalParams(params)
173
+ ? `Command: ${commandTextFromApprovalParams(params)}`
174
+ : null,
175
+ stringFromUnknown(params.cwd) ? `CWD: ${stringFromUnknown(params.cwd)}` : null,
176
+ ]
177
+ .filter(Boolean)
178
+ .join('\n');
179
+ }
180
+
181
+ function buildGenericApprovalThreadRequest(
182
+ request: CodexServerRequest,
183
+ options: {
184
+ title: string;
185
+ descriptionFallback: string;
186
+ },
187
+ ): AgentActionRequest {
188
+ const params = request.params as {
189
+ turnId?: string;
190
+ itemId?: string;
191
+ };
192
+ const description = buildApprovalRequestDescription(request.params);
193
+
194
+ return {
195
+ id: String(request.id),
196
+ kind: 'requestUserInput',
197
+ title: options.title,
198
+ description: description || options.descriptionFallback,
199
+ turnId: params.turnId ?? null,
200
+ itemId: params.itemId ?? null,
201
+ createdAt: new Date().toISOString(),
202
+ questions: [
203
+ {
204
+ id: 'approval',
205
+ header: options.title,
206
+ question: description || options.descriptionFallback,
207
+ isOther: false,
208
+ isSecret: false,
209
+ options: [
210
+ {
211
+ label: 'Allow',
212
+ description: 'Permit this action and continue the current turn.',
213
+ },
214
+ {
215
+ label: 'Deny',
216
+ description: 'Decline this action.',
217
+ },
218
+ ],
219
+ },
220
+ ],
221
+ };
222
+ }
223
+
224
+ function yoloApprovalResultForServerRequest(request: CodexServerRequest) {
225
+ switch (request.method) {
226
+ case 'item/commandExecution/requestApproval':
227
+ return { decision: 'accept' };
228
+ case 'item/fileChange/requestApproval':
229
+ return { decision: 'accept' };
230
+ case 'item/permissions/requestApproval': {
231
+ const params = request.params as { permissions?: unknown };
232
+ return {
233
+ permissions: isRecord(params.permissions) ? params.permissions : {},
234
+ scope: 'turn',
235
+ };
236
+ }
237
+ case 'execCommandApproval':
238
+ return { decision: 'approved' };
239
+ case 'applyPatchApproval':
240
+ return { decision: 'approved' };
241
+ default:
242
+ return null;
243
+ }
244
+ }
245
+
246
+ function responseKindForApprovalRequest(
247
+ request: CodexServerRequest,
248
+ ): CodexPendingRequestResponseKind | null {
249
+ switch (request.method) {
250
+ case 'item/commandExecution/requestApproval':
251
+ return 'commandExecutionApproval';
252
+ case 'item/fileChange/requestApproval':
253
+ return 'fileChangeApproval';
254
+ case 'item/permissions/requestApproval':
255
+ return 'permissionsApproval';
256
+ case 'execCommandApproval':
257
+ return 'legacyExecApproval';
258
+ case 'applyPatchApproval':
259
+ return 'legacyApplyPatchApproval';
260
+ default:
261
+ return null;
262
+ }
263
+ }
264
+
265
+ function buildThreadRequestFromMcpElicitation(
266
+ request: CodexServerRequest & {
267
+ params: {
268
+ threadId: string;
269
+ turnId?: string;
270
+ serverName?: string;
271
+ mode?: string;
272
+ message?: string;
273
+ requestedSchema?: Record<string, unknown>;
274
+ _meta?: Record<string, unknown>;
275
+ };
276
+ },
277
+ ): AgentActionRequest {
278
+ const meta = isRecord(request.params._meta) ? request.params._meta : null;
279
+ const toolTitle = stringOrNull(meta?.tool_title);
280
+ const toolDescription = stringOrNull(meta?.tool_description);
281
+ const serverName = stringOrNull(request.params.serverName) ?? 'MCP';
282
+ const message =
283
+ stringOrNull(request.params.message) ??
284
+ `Allow the ${serverName} MCP server to continue?`;
285
+
286
+ return {
287
+ id: String(request.id),
288
+ kind: 'requestUserInput',
289
+ title: toolTitle ?? `${serverName} MCP`,
290
+ description: toolDescription ?? message,
291
+ turnId: request.params.turnId ?? null,
292
+ itemId: null,
293
+ createdAt: new Date().toISOString(),
294
+ questions: [
295
+ {
296
+ id: 'decision',
297
+ header: toolTitle ?? `${serverName} MCP`,
298
+ question: message,
299
+ isOther: false,
300
+ isSecret: false,
301
+ options: [
302
+ {
303
+ label: 'Allow',
304
+ description: 'Permit this MCP tool call.',
305
+ },
306
+ {
307
+ label: 'Deny',
308
+ description: 'Reject this MCP tool call.',
309
+ },
310
+ ],
311
+ },
312
+ ],
313
+ };
314
+ }
315
+
316
+ export function buildCodexProviderRequestResponse(
317
+ pending: AgentPendingProviderRequest,
318
+ input: AgentActionRequestResponseInput,
319
+ ) {
320
+ const selectedAnswer = Object.values(input.answers)[0]?.answers[0]?.trim().toLowerCase();
321
+ const allowed = Boolean(selectedAnswer && /^(allow|approve|yes|continue|proceed)\b/.test(selectedAnswer));
322
+
323
+ switch (pending.responseKind as CodexPendingRequestResponseKind) {
324
+ case 'mcpElicitation':
325
+ return {
326
+ action: allowed ? 'accept' : 'decline',
327
+ content: {},
328
+ };
329
+ case 'commandExecutionApproval':
330
+ return { decision: allowed ? 'accept' : 'decline' };
331
+ case 'fileChangeApproval':
332
+ return { decision: allowed ? 'accept' : 'decline' };
333
+ case 'permissionsApproval':
334
+ return allowed
335
+ ? {
336
+ permissions:
337
+ isRecord(pending.responsePayload?.permissions)
338
+ ? pending.responsePayload.permissions
339
+ : {},
340
+ scope: 'turn',
341
+ }
342
+ : {
343
+ permissions: {},
344
+ scope: 'turn',
345
+ };
346
+ case 'legacyExecApproval':
347
+ case 'legacyApplyPatchApproval':
348
+ return { decision: allowed ? 'approved' : 'denied' };
349
+ case 'answers':
350
+ default:
351
+ return {
352
+ answers: input.answers,
353
+ };
354
+ }
355
+ }
356
+
357
+ export function mapCodexProviderRequest(
358
+ providerRequest: AgentProviderRequest,
359
+ approvalMode: 'yolo' | 'guarded',
360
+ ): AgentProviderRequestMapping | null {
361
+ const request: CodexServerRequest = {
362
+ id: Number(providerRequest.id),
363
+ method: providerRequest.method,
364
+ params: isRecord(providerRequest.params) ? providerRequest.params : {},
365
+ };
366
+ const providerRequestId = providerRequest.id;
367
+ const approvalResponseKind = responseKindForApprovalRequest(request);
368
+ if (approvalResponseKind) {
369
+ const params = request.params as {
370
+ threadId?: string;
371
+ conversationId?: string;
372
+ permissions?: unknown;
373
+ };
374
+ const providerSessionId = params.threadId ?? params.conversationId;
375
+ if (!providerSessionId) {
376
+ return null;
377
+ }
378
+
379
+ const autoApprovedResult =
380
+ approvalMode === 'yolo' ? yoloApprovalResultForServerRequest(request) : null;
381
+ if (autoApprovedResult) {
382
+ return {
383
+ providerRequestId,
384
+ providerSessionId,
385
+ autoApprovedResult,
386
+ pendingRequest: null,
387
+ };
388
+ }
389
+
390
+ const title =
391
+ approvalResponseKind === 'commandExecutionApproval' ||
392
+ approvalResponseKind === 'legacyExecApproval'
393
+ ? 'Command approval required'
394
+ : approvalResponseKind === 'fileChangeApproval' ||
395
+ approvalResponseKind === 'legacyApplyPatchApproval'
396
+ ? 'File change approval required'
397
+ : 'Permissions approval required';
398
+ const threadRequest = buildGenericApprovalThreadRequest(request, {
399
+ title,
400
+ descriptionFallback: 'Codex needs approval before it can continue this action.',
401
+ });
402
+ const pendingRequest: AgentPendingProviderRequest = {
403
+ providerRequestId,
404
+ responseKind: approvalResponseKind,
405
+ request: threadRequest,
406
+ };
407
+ if (approvalResponseKind === 'permissionsApproval' && isRecord(params.permissions)) {
408
+ pendingRequest.responsePayload = { permissions: params.permissions };
409
+ }
410
+
411
+ return {
412
+ providerRequestId,
413
+ providerSessionId,
414
+ autoApprovedResult: null,
415
+ pendingRequest,
416
+ };
417
+ }
418
+
419
+ if (isMcpElicitationRequest(request)) {
420
+ const autoApprovedResult =
421
+ approvalMode === 'yolo'
422
+ ? buildAutoApprovedMcpElicitationResult(request)
423
+ : null;
424
+ if (autoApprovedResult) {
425
+ return {
426
+ providerRequestId,
427
+ providerSessionId: request.params.threadId,
428
+ autoApprovedResult,
429
+ pendingRequest: null,
430
+ };
431
+ }
432
+
433
+ return {
434
+ providerRequestId,
435
+ providerSessionId: request.params.threadId,
436
+ autoApprovedResult: null,
437
+ pendingRequest: {
438
+ providerRequestId,
439
+ responseKind: 'mcpElicitation',
440
+ request: buildThreadRequestFromMcpElicitation(request),
441
+ },
442
+ };
443
+ }
444
+
445
+ const params = request.params as {
446
+ threadId?: string;
447
+ turnId?: string;
448
+ itemId?: string;
449
+ questions?: Array<{
450
+ id: string;
451
+ header: string;
452
+ question: string;
453
+ isOther: boolean;
454
+ isSecret: boolean;
455
+ options: Array<{ label: string; description: string }> | null;
456
+ }>;
457
+ };
458
+
459
+ if (!params.threadId || !Array.isArray(params.questions)) {
460
+ return null;
461
+ }
462
+
463
+ const autoApprovedAnswers =
464
+ approvalMode === 'yolo'
465
+ ? buildAutoApprovedAnswersForServerQuestions(
466
+ request.method,
467
+ params.questions,
468
+ )
469
+ : null;
470
+ if (autoApprovedAnswers) {
471
+ return {
472
+ providerRequestId,
473
+ providerSessionId: params.threadId,
474
+ autoApprovedResult: { answers: autoApprovedAnswers },
475
+ pendingRequest: null,
476
+ };
477
+ }
478
+
479
+ const questions = params.questions.map((question) => ({
480
+ id: question.id,
481
+ header: question.header,
482
+ question: question.question,
483
+ isOther: question.isOther,
484
+ isSecret: question.isSecret,
485
+ options: question.options?.map((option) => ({
486
+ label: option.label,
487
+ description: option.description,
488
+ })) ?? null,
489
+ }));
490
+ const threadRequest: AgentActionRequest = {
491
+ id: String(request.id),
492
+ kind: 'requestUserInput',
493
+ title: questions[0]?.header || 'User input required',
494
+ description: questions[0]?.question ?? null,
495
+ turnId: params.turnId ?? null,
496
+ itemId: params.itemId ?? null,
497
+ createdAt: new Date().toISOString(),
498
+ questions,
499
+ };
500
+
501
+ return {
502
+ providerRequestId,
503
+ providerSessionId: params.threadId,
504
+ autoApprovedResult: null,
505
+ pendingRequest: {
506
+ providerRequestId,
507
+ responseKind: 'answers',
508
+ request: threadRequest,
509
+ },
510
+ };
511
+ }