@myagentroam/node 0.9.124 → 0.9.126
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/dist/codex-app-server.js +30 -0
- package/dist/connector.js +217 -6
- package/dist/database.js +2 -1
- package/dist/migrations/v014.js +107 -0
- package/dist/runner/abstract-runner.js +11 -5
- package/dist/runner/claude-code-runner.js +3 -0
- package/dist/runner/codex/conversation-parser.js +142 -2
- package/dist/runner/codex/managed-run-controller.js +26 -18
- package/dist/runner/codex-runner.js +135 -72
- package/dist/runner/opencode-runner.js +14 -12
- package/dist/runtime-state.js +6 -1
- package/dist/service/continuous-run-coordinator.js +8 -2
- package/dist/service/conversation-history-service.js +14 -8
- package/dist/service/managed-runner-session-service.js +2 -1
- package/dist/service/native-session-projection-service.js +3 -1
- package/dist/service/node-control-message-service.js +2 -0
- package/dist/service/run-event-service.js +18 -2
- package/dist/service/runner-interaction-service.js +9 -8
- package/dist/service/scheduled-run-authorization-control.js +72 -0
- package/dist/service/scheduled-session-coordinator.js +95 -0
- package/dist/service/scheduled-session-recurrence.js +72 -0
- package/dist/service/scheduled-session-store.js +234 -0
- package/dist/service/scheduled-task-mcp-server.js +179 -0
- package/dist/service/scheduled-task-mcp-stdio.js +63 -0
- package/dist/service/session-catalog-service.js +3 -1
- package/dist/service/session-lifecycle-service.js +6 -0
- package/dist/service/session-message-service.js +25 -2
- package/dist/service/session-presentation-service.js +19 -2
- package/dist/service/session-schedule-service.js +153 -0
- package/dist/util/runner-native-session-parsers.js +19 -11
- package/package.json +4 -4
|
@@ -499,7 +499,14 @@ export function codexOfficialItem(session, nativeTurnId, value, turnSequence, it
|
|
|
499
499
|
summaryCode: 'CONTEXT_COMPACTED'
|
|
500
500
|
}
|
|
501
501
|
};
|
|
502
|
-
|
|
502
|
+
const special = codexSpecialItem(value);
|
|
503
|
+
return special === undefined
|
|
504
|
+
? codexUnknownOfficialItem(base, value)
|
|
505
|
+
: {
|
|
506
|
+
...base,
|
|
507
|
+
...special,
|
|
508
|
+
status: value.type === 'collabAgentToolCall' ? codexItemStatus(value.status, true) : base.status
|
|
509
|
+
};
|
|
503
510
|
}
|
|
504
511
|
export function codexLiveConversationItem(value, completed, imageAttachments = []) {
|
|
505
512
|
if (typeof value.type !== 'string')
|
|
@@ -687,7 +694,16 @@ export function codexLiveConversationItem(value, completed, imageAttachments = [
|
|
|
687
694
|
merge: true
|
|
688
695
|
};
|
|
689
696
|
}
|
|
690
|
-
|
|
697
|
+
const special = codexSpecialItem(value);
|
|
698
|
+
return special === undefined
|
|
699
|
+
? codexUnknownLiveItem(itemId, value.type, completed)
|
|
700
|
+
: {
|
|
701
|
+
itemId,
|
|
702
|
+
kind: special.kind,
|
|
703
|
+
status: special.kind === 'tool_call' ? codexItemStatus(value.status, completed) : 'COMPLETED',
|
|
704
|
+
payload: special.payload,
|
|
705
|
+
merge: special.kind === 'tool_call'
|
|
706
|
+
};
|
|
691
707
|
}
|
|
692
708
|
function normalizeCodexFileChanges(changes) {
|
|
693
709
|
return changes.flatMap((value) => {
|
|
@@ -718,6 +734,128 @@ function normalizeCodexFileChanges(changes) {
|
|
|
718
734
|
function nonnegativeIntegerOrNull(value) {
|
|
719
735
|
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
720
736
|
}
|
|
737
|
+
function codexSpecialItem(value) {
|
|
738
|
+
if (value.type === 'sleep') {
|
|
739
|
+
const durationMs = nonnegativeIntegerOrNull(value.durationMs);
|
|
740
|
+
return {
|
|
741
|
+
kind: 'tool_call',
|
|
742
|
+
payload: normalizeCodexTool({
|
|
743
|
+
name: 'clock.sleep',
|
|
744
|
+
namespace: 'clock',
|
|
745
|
+
inputSummary: durationMs === null ? null : `${durationMs} ms`
|
|
746
|
+
}).payload
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
if (value.type === 'imageView')
|
|
750
|
+
return {
|
|
751
|
+
kind: 'tool_call',
|
|
752
|
+
payload: normalizeCodexTool({
|
|
753
|
+
name: 'view_image',
|
|
754
|
+
namespace: 'codex',
|
|
755
|
+
inputSummary: typeof value.path === 'string' ? compactRunnerText(value.path, 4_096) : null
|
|
756
|
+
}).payload
|
|
757
|
+
};
|
|
758
|
+
if (value.type === 'subAgentActivity') {
|
|
759
|
+
const activity = typeof value.kind === 'string' ? value.kind : '';
|
|
760
|
+
const operation = activity === 'started'
|
|
761
|
+
? 'START'
|
|
762
|
+
: activity === 'interrupted'
|
|
763
|
+
? 'CANCEL'
|
|
764
|
+
: activity === 'interacted' || activity === 'completed'
|
|
765
|
+
? 'STATUS'
|
|
766
|
+
: undefined;
|
|
767
|
+
if (operation === undefined)
|
|
768
|
+
return undefined;
|
|
769
|
+
const agentId = typeof value.agentThreadId === 'string' ? compactRunnerText(value.agentThreadId, 128) : '';
|
|
770
|
+
const agentPath = typeof value.agentPath === 'string' ? compactRunnerText(value.agentPath, 500) : '';
|
|
771
|
+
return {
|
|
772
|
+
kind: 'tool_call',
|
|
773
|
+
payload: {
|
|
774
|
+
...normalizeCodexTool({
|
|
775
|
+
name: 'subagent_activity',
|
|
776
|
+
namespace: 'codex',
|
|
777
|
+
outputSummary: activity
|
|
778
|
+
}).payload,
|
|
779
|
+
...(agentPath ? { title: agentPath } : {}),
|
|
780
|
+
subagentOperation: operation,
|
|
781
|
+
subagentId: agentId || null
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
if (value.type === 'collabAgentToolCall') {
|
|
786
|
+
const names = {
|
|
787
|
+
spawnAgent: 'spawn_agent',
|
|
788
|
+
sendInput: 'send_input',
|
|
789
|
+
resumeAgent: 'resume_agent',
|
|
790
|
+
wait: 'wait_agent',
|
|
791
|
+
closeAgent: 'close_agent',
|
|
792
|
+
sendMessage: 'send_message',
|
|
793
|
+
followupTask: 'followup_task',
|
|
794
|
+
interruptAgent: 'interrupt_agent',
|
|
795
|
+
listAgents: 'list_agents'
|
|
796
|
+
};
|
|
797
|
+
const toolName = typeof value.tool === 'string' ? names[value.tool] : undefined;
|
|
798
|
+
if (toolName === undefined)
|
|
799
|
+
return undefined;
|
|
800
|
+
const targets = Array.isArray(value.receiverThreadIds)
|
|
801
|
+
? value.receiverThreadIds.filter((id) => typeof id === 'string')
|
|
802
|
+
: [];
|
|
803
|
+
const input = {
|
|
804
|
+
...(typeof value.prompt === 'string' ? { prompt: value.prompt } : {}),
|
|
805
|
+
...(targets.length === 1 ? { target: targets[0] } : {}),
|
|
806
|
+
...(targets.length > 1 ? { receiverThreadIds: targets } : {}),
|
|
807
|
+
...(typeof value.model === 'string' ? { model: value.model } : {})
|
|
808
|
+
};
|
|
809
|
+
const normalized = normalizeCodexTool({
|
|
810
|
+
name: toolName,
|
|
811
|
+
namespace: 'collaboration',
|
|
812
|
+
rawInput: input,
|
|
813
|
+
inputSummary: codexCompactJson(input) || null,
|
|
814
|
+
outputSummary: codexCompactJson({
|
|
815
|
+
status: value.status,
|
|
816
|
+
agentsStates: value.agentsStates
|
|
817
|
+
}) || null
|
|
818
|
+
});
|
|
819
|
+
const effort = value.reasoningEffort;
|
|
820
|
+
const modelId = typeof value.model === 'string' ? compactRunnerText(value.model, 128) : '';
|
|
821
|
+
return {
|
|
822
|
+
kind: 'tool_call',
|
|
823
|
+
payload: {
|
|
824
|
+
...normalized.payload,
|
|
825
|
+
...(modelId ? { subagentModelId: modelId } : {}),
|
|
826
|
+
...(typeof effort === 'string' &&
|
|
827
|
+
['low', 'medium', 'high', 'xhigh', 'max', 'ultra'].includes(effort)
|
|
828
|
+
? { subagentReasoningEffort: effort }
|
|
829
|
+
: {})
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
const notice = codexNotice(value);
|
|
834
|
+
return notice === undefined
|
|
835
|
+
? undefined
|
|
836
|
+
: {
|
|
837
|
+
kind: 'warning',
|
|
838
|
+
payload: { code: notice.code, message: notice.message, recoverable: true }
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
function codexNotice(value) {
|
|
842
|
+
if (value.type === 'hookPrompt') {
|
|
843
|
+
const count = Array.isArray(value.fragments) ? value.fragments.length : 0;
|
|
844
|
+
return {
|
|
845
|
+
code: 'CODEX_HOOK_PROMPT',
|
|
846
|
+
message: `Hook prompt added (${count} ${count === 1 ? 'fragment' : 'fragments'}).`
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
if (value.type === 'enteredReviewMode' || value.type === 'exitedReviewMode') {
|
|
850
|
+
const action = value.type === 'enteredReviewMode' ? 'Entered' : 'Exited';
|
|
851
|
+
const review = typeof value.review === 'string' ? compactRunnerText(value.review, 400) : '';
|
|
852
|
+
return {
|
|
853
|
+
code: value.type === 'enteredReviewMode' ? 'CODEX_REVIEW_STARTED' : 'CODEX_REVIEW_ENDED',
|
|
854
|
+
message: `${action} review mode${review ? `: ${review}` : ''}`
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
return undefined;
|
|
858
|
+
}
|
|
721
859
|
function codexUnknownOfficialItem(base, value) {
|
|
722
860
|
if (value.type === 'userMessage' || value.type === 'agentMessage')
|
|
723
861
|
return undefined;
|
|
@@ -835,6 +973,8 @@ export function codexItemStatus(value, completed) {
|
|
|
835
973
|
return 'FAILED';
|
|
836
974
|
if (value === 'declined')
|
|
837
975
|
return 'DECLINED';
|
|
976
|
+
if (value === 'interrupted')
|
|
977
|
+
return 'INTERRUPTED';
|
|
838
978
|
return completed || value === 'completed' ? 'COMPLETED' : 'IN_PROGRESS';
|
|
839
979
|
}
|
|
840
980
|
export function codexContentText(value) {
|
|
@@ -35,8 +35,8 @@ export class CodexManagedRunController {
|
|
|
35
35
|
this.runner.prepareEnvironment(context.secretEnvironment, environmentLeaseId);
|
|
36
36
|
let modes;
|
|
37
37
|
try {
|
|
38
|
-
await this.runner.start();
|
|
39
|
-
modes = await this.runner.listCollaborationModes();
|
|
38
|
+
await this.runner.start(environmentLeaseId);
|
|
39
|
+
modes = await this.runner.listCollaborationModes(environmentLeaseId);
|
|
40
40
|
}
|
|
41
41
|
finally {
|
|
42
42
|
this.runner.releaseEnvironment(environmentLeaseId);
|
|
@@ -67,7 +67,7 @@ export class CodexManagedRunController {
|
|
|
67
67
|
const runId = crypto.randomUUID();
|
|
68
68
|
this.runner.prepareEnvironment(context.secretEnvironment, runId);
|
|
69
69
|
try {
|
|
70
|
-
await this.runner.start();
|
|
70
|
+
await this.runner.start(runId);
|
|
71
71
|
return await this.compact(session, threadId, runId, context.initiatedByUserId);
|
|
72
72
|
}
|
|
73
73
|
catch (error) {
|
|
@@ -99,8 +99,8 @@ export class CodexManagedRunController {
|
|
|
99
99
|
});
|
|
100
100
|
this.host.emit(runId, 'run.started', { command: 'compact' }, 'STARTING');
|
|
101
101
|
try {
|
|
102
|
-
await this.runner.resumeThread(threadId, session.cwd);
|
|
103
|
-
await this.runner.request('thread/compact/start', { threadId });
|
|
102
|
+
await this.runner.resumeThread(threadId, session.cwd, undefined, runId);
|
|
103
|
+
await this.runner.request('thread/compact/start', { threadId }, runId);
|
|
104
104
|
return {
|
|
105
105
|
command: 'compact',
|
|
106
106
|
run: this.host.run(runId),
|
|
@@ -158,15 +158,15 @@ export class CodexManagedRunController {
|
|
|
158
158
|
}
|
|
159
159
|
if (notification.requestId !== undefined &&
|
|
160
160
|
notification.method === 'mcpServer/elicitation/request' &&
|
|
161
|
-
activeRun.goalMcp === true &&
|
|
162
|
-
|
|
161
|
+
((activeRun.goalMcp === true && params.serverName === 'myagentroam_goal') ||
|
|
162
|
+
(activeRun.scheduleMcp === true && params.serverName === 'mar_schedule')) &&
|
|
163
163
|
isPlainRecord(params._meta) &&
|
|
164
164
|
params._meta.codex_approval_kind === 'mcp_tool_call') {
|
|
165
165
|
this.runner.respond(notification.requestId, {
|
|
166
166
|
action: 'accept',
|
|
167
167
|
content: {},
|
|
168
168
|
_meta: null
|
|
169
|
-
});
|
|
169
|
+
}, runId);
|
|
170
170
|
return;
|
|
171
171
|
}
|
|
172
172
|
if (notification.method === 'item/agentMessage/delta' && typeof params.delta === 'string') {
|
|
@@ -252,9 +252,12 @@ export class CodexManagedRunController {
|
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
254
|
requestApproval(runId, notification) {
|
|
255
|
-
const approvalId = `codex:${notification.requestId}`;
|
|
255
|
+
const approvalId = `codex:${runId}:${notification.requestId}`;
|
|
256
256
|
const expiresAt = Date.now() + 10 * 60_000;
|
|
257
|
-
this.runner.execution.approvals.set(approvalId,
|
|
257
|
+
this.runner.execution.approvals.set(approvalId, {
|
|
258
|
+
runId,
|
|
259
|
+
requestId: notification.requestId
|
|
260
|
+
});
|
|
258
261
|
try {
|
|
259
262
|
this.host.createApproval({
|
|
260
263
|
runId,
|
|
@@ -278,7 +281,7 @@ export class CodexManagedRunController {
|
|
|
278
281
|
const questions = codexUserInputQuestions(params.questions);
|
|
279
282
|
if (questions.length === 0) {
|
|
280
283
|
try {
|
|
281
|
-
this.runner.respond(nativeRequestId, { answers: {} });
|
|
284
|
+
this.runner.respond(nativeRequestId, { answers: {} }, runId);
|
|
282
285
|
}
|
|
283
286
|
catch {
|
|
284
287
|
// A disconnected App Server cannot receive a malformed-request response.
|
|
@@ -286,7 +289,7 @@ export class CodexManagedRunController {
|
|
|
286
289
|
return;
|
|
287
290
|
}
|
|
288
291
|
const requestId = String(nativeRequestId);
|
|
289
|
-
this.runner.execution.userInputs.set(requestId
|
|
292
|
+
this.runner.execution.userInputs.set(`${runId}\0${requestId}`, {
|
|
290
293
|
runId,
|
|
291
294
|
requestId: nativeRequestId,
|
|
292
295
|
questions: questions.map((question) => ({
|
|
@@ -391,7 +394,7 @@ export class CodexManagedRunController {
|
|
|
391
394
|
async run(payload, attachments) {
|
|
392
395
|
const { runId, sessionId, input, cwd } = payload;
|
|
393
396
|
try {
|
|
394
|
-
await this.runner.start();
|
|
397
|
+
await this.runner.start(runId);
|
|
395
398
|
if (this.host.completeCancelled(runId, cwd)) {
|
|
396
399
|
this.runner.releaseEnvironment(runId);
|
|
397
400
|
return;
|
|
@@ -420,8 +423,8 @@ export class CodexManagedRunController {
|
|
|
420
423
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
421
424
|
try {
|
|
422
425
|
const threadResult = typeof payload.externalSessionId === 'string'
|
|
423
|
-
? await this.runner.resumeThread(payload.externalSessionId, cwd, configuration)
|
|
424
|
-
: await this.runner.startThread(cwd, configuration);
|
|
426
|
+
? await this.runner.resumeThread(payload.externalSessionId, cwd, configuration, runId)
|
|
427
|
+
: await this.runner.startThread(cwd, configuration, runId);
|
|
425
428
|
if (this.host.completeCancelled(runId, cwd)) {
|
|
426
429
|
this.runner.releaseEnvironment(runId);
|
|
427
430
|
return;
|
|
@@ -431,9 +434,10 @@ export class CodexManagedRunController {
|
|
|
431
434
|
sessionId,
|
|
432
435
|
threadId,
|
|
433
436
|
cwd,
|
|
434
|
-
goalMcp: hasGoalMcp(payload.mcpServers)
|
|
437
|
+
goalMcp: hasGoalMcp(payload.mcpServers),
|
|
438
|
+
scheduleMcp: hasScheduleMcp(payload.mcpServers)
|
|
435
439
|
});
|
|
436
|
-
turnResult = await this.runner.startTurn(threadId, input, cwd, turnConfiguration, materializedAttachments);
|
|
440
|
+
turnResult = await this.runner.startTurn(threadId, input, cwd, turnConfiguration, materializedAttachments, runId);
|
|
437
441
|
break;
|
|
438
442
|
}
|
|
439
443
|
catch (error) {
|
|
@@ -457,7 +461,8 @@ export class CodexManagedRunController {
|
|
|
457
461
|
threadId,
|
|
458
462
|
turnId,
|
|
459
463
|
cwd,
|
|
460
|
-
goalMcp: hasGoalMcp(payload.mcpServers)
|
|
464
|
+
goalMcp: hasGoalMcp(payload.mcpServers),
|
|
465
|
+
scheduleMcp: hasScheduleMcp(payload.mcpServers)
|
|
461
466
|
});
|
|
462
467
|
this.runner.execution.managedTurns.set(turnId, { sessionId: publicSessionId, runId });
|
|
463
468
|
if (this.host.runStatus(runId) === 'CANCELLING') {
|
|
@@ -492,3 +497,6 @@ export class CodexManagedRunController {
|
|
|
492
497
|
function hasGoalMcp(value) {
|
|
493
498
|
return isPlainRecord(value) && isPlainRecord(value.myagentroam_goal);
|
|
494
499
|
}
|
|
500
|
+
function hasScheduleMcp(value) {
|
|
501
|
+
return isPlainRecord(value) && isPlainRecord(value.mar_schedule);
|
|
502
|
+
}
|
|
@@ -3,7 +3,6 @@ import { AbstractRunner } from './abstract-runner.js';
|
|
|
3
3
|
import { declaredRunnerProfiles } from '../runner-profiles.js';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { resolve } from 'node:path';
|
|
6
|
-
import { createHash } from 'node:crypto';
|
|
7
6
|
import { discoverAllNativeSessions } from '../native-session-history.js';
|
|
8
7
|
import { readCodexNativeContextUsage } from '../native-session-history.js';
|
|
9
8
|
import { codexThreadCwd, deduplicateDirectSessions, extractCodexThreads, isWithinWorkspace, latestNativeActivity } from './codex/conversation-parser.js';
|
|
@@ -11,30 +10,35 @@ import { codexSessionControl } from '../codex-app-server.js';
|
|
|
11
10
|
import { extractId } from '../util/runner-native-session-parsers.js';
|
|
12
11
|
import { nodeLog } from '../operational.js';
|
|
13
12
|
const CODEX_MODEL_CACHE_MS = 10 * 60 * 1_000;
|
|
14
|
-
const CODEX_PROFILE_ENTRY_TTL_MS = 30 * 60 * 1_000;
|
|
15
13
|
const CODEX_MODEL_FAILURE_RETRY_MS = 30 * 1_000;
|
|
16
14
|
const CODEX_MODEL_PAGE_SIZE = 100;
|
|
17
15
|
const CODEX_MODEL_MAX_PAGES = 6;
|
|
18
16
|
export class CodexRunner extends AbstractRunner {
|
|
19
|
-
client;
|
|
20
17
|
profileClientFactory;
|
|
21
18
|
name = 'codex';
|
|
22
19
|
discoveryScope = 'ALL_WORKSPACES';
|
|
23
20
|
execution = new CodexExecutionState();
|
|
24
21
|
fastEnabled = false;
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
runClients = new Map();
|
|
23
|
+
notificationListeners = new Set();
|
|
24
|
+
clientNotificationDisposers = new Map();
|
|
27
25
|
profileCache;
|
|
28
26
|
profileCacheVersion;
|
|
29
27
|
profileCacheTimer;
|
|
30
28
|
profileRefresh;
|
|
29
|
+
profileRefreshVersion;
|
|
31
30
|
profileClients = new Set();
|
|
32
31
|
profileGeneration = 0;
|
|
33
|
-
|
|
32
|
+
runClientFactory;
|
|
33
|
+
constructor(client, profileClientFactory = () => new CodexAppServerClient(), runClientFactory) {
|
|
34
34
|
super();
|
|
35
|
-
this.client = client;
|
|
36
35
|
this.profileClientFactory = profileClientFactory;
|
|
36
|
+
this.client = client ?? new CodexAppServerClient();
|
|
37
|
+
this.runClientFactory =
|
|
38
|
+
runClientFactory ??
|
|
39
|
+
(client === undefined ? () => new CodexAppServerClient() : () => this.client);
|
|
37
40
|
}
|
|
41
|
+
client;
|
|
38
42
|
profile(capabilities) {
|
|
39
43
|
const base = declaredRunnerProfiles({
|
|
40
44
|
codexAvailable: capabilities.codex.available,
|
|
@@ -56,6 +60,14 @@ export class CodexRunner extends AbstractRunner {
|
|
|
56
60
|
if (!capabilities.codex.available)
|
|
57
61
|
return { ...base, models: [] };
|
|
58
62
|
const version = capabilities.codex.version;
|
|
63
|
+
if (this.profileRefresh !== undefined && this.profileRefreshVersion !== version) {
|
|
64
|
+
this.profileGeneration += 1;
|
|
65
|
+
this.profileRefresh = undefined;
|
|
66
|
+
this.profileRefreshVersion = undefined;
|
|
67
|
+
if (this.profileCacheTimer !== undefined)
|
|
68
|
+
clearTimeout(this.profileCacheTimer);
|
|
69
|
+
this.profileCacheTimer = undefined;
|
|
70
|
+
}
|
|
59
71
|
const now = Date.now();
|
|
60
72
|
const cached = this.profileCacheVersion === version ? this.profileCache : undefined;
|
|
61
73
|
if (cached?.successful === true && now < cached.fetchedAt + CODEX_MODEL_CACHE_MS)
|
|
@@ -85,8 +97,10 @@ export class CodexRunner extends AbstractRunner {
|
|
|
85
97
|
successful: true,
|
|
86
98
|
retryAt: 0
|
|
87
99
|
};
|
|
88
|
-
if (generation === this.profileGeneration)
|
|
100
|
+
if (generation === this.profileGeneration) {
|
|
89
101
|
this.cacheProfile(version, cache);
|
|
102
|
+
this.scheduleProfileRefresh(CODEX_MODEL_CACHE_MS, version, base, environment);
|
|
103
|
+
}
|
|
90
104
|
return cache.profile;
|
|
91
105
|
})
|
|
92
106
|
.catch((error) => {
|
|
@@ -98,6 +112,7 @@ export class CodexRunner extends AbstractRunner {
|
|
|
98
112
|
const cached = this.profileCacheVersion === version ? this.profileCache : undefined;
|
|
99
113
|
if (cached !== undefined) {
|
|
100
114
|
cached.retryAt = Date.now() + CODEX_MODEL_FAILURE_RETRY_MS;
|
|
115
|
+
this.scheduleProfileRefresh(CODEX_MODEL_FAILURE_RETRY_MS, version, base, environment);
|
|
101
116
|
return cached.profile;
|
|
102
117
|
}
|
|
103
118
|
this.cacheProfile(version, {
|
|
@@ -107,25 +122,30 @@ export class CodexRunner extends AbstractRunner {
|
|
|
107
122
|
successful: false,
|
|
108
123
|
retryAt: Date.now() + CODEX_MODEL_FAILURE_RETRY_MS
|
|
109
124
|
});
|
|
125
|
+
this.scheduleProfileRefresh(CODEX_MODEL_FAILURE_RETRY_MS, version, base, environment);
|
|
110
126
|
return { ...base, models: [] };
|
|
111
127
|
})
|
|
112
128
|
.finally(() => {
|
|
113
|
-
if (this.profileRefresh === refresh)
|
|
129
|
+
if (this.profileRefresh === refresh) {
|
|
114
130
|
this.profileRefresh = undefined;
|
|
131
|
+
this.profileRefreshVersion = undefined;
|
|
132
|
+
}
|
|
115
133
|
});
|
|
116
134
|
this.profileRefresh = refresh;
|
|
135
|
+
this.profileRefreshVersion = version;
|
|
117
136
|
return refresh;
|
|
118
137
|
}
|
|
119
138
|
cacheProfile(version, cache) {
|
|
120
|
-
if (this.profileCacheTimer !== undefined)
|
|
121
|
-
clearTimeout(this.profileCacheTimer);
|
|
122
139
|
this.profileCache = cache;
|
|
123
140
|
this.profileCacheVersion = version;
|
|
141
|
+
}
|
|
142
|
+
scheduleProfileRefresh(delayMs, version, base, environment) {
|
|
143
|
+
if (this.profileCacheTimer !== undefined)
|
|
144
|
+
clearTimeout(this.profileCacheTimer);
|
|
124
145
|
this.profileCacheTimer = setTimeout(() => {
|
|
125
146
|
this.profileCacheTimer = undefined;
|
|
126
|
-
this.
|
|
127
|
-
|
|
128
|
-
}, CODEX_PROFILE_ENTRY_TTL_MS);
|
|
147
|
+
void this.refreshCodexProfile(version, base, environment);
|
|
148
|
+
}, delayMs);
|
|
129
149
|
this.profileCacheTimer.unref();
|
|
130
150
|
}
|
|
131
151
|
async fetchCodexProfile(base, environment) {
|
|
@@ -255,7 +275,8 @@ export class CodexRunner extends AbstractRunner {
|
|
|
255
275
|
async readManagedRunnerTitle(session, nativeSessionId) {
|
|
256
276
|
void session;
|
|
257
277
|
await this.start();
|
|
258
|
-
|
|
278
|
+
const thread = extractCodexThreads(await this.listThreads()).find((candidate) => candidate.id === nativeSessionId);
|
|
279
|
+
return thread?.titleOrigin === 'OFFICIAL' ? thread.title : undefined;
|
|
259
280
|
}
|
|
260
281
|
nativeImageRoots(session) {
|
|
261
282
|
return [
|
|
@@ -295,13 +316,14 @@ export class CodexRunner extends AbstractRunner {
|
|
|
295
316
|
return this.profileCache;
|
|
296
317
|
}
|
|
297
318
|
async resumeExternal(external, context) {
|
|
319
|
+
const threadId = external.externalSessionId;
|
|
298
320
|
if (external.runner !== this.name ||
|
|
299
321
|
external.nativeControl !== 'EXTERNAL' ||
|
|
300
|
-
|
|
322
|
+
threadId === null ||
|
|
301
323
|
!context.available)
|
|
302
324
|
return { failureCode: 'RUNNER_UNAVAILABLE' };
|
|
303
325
|
try {
|
|
304
|
-
await this.resumeThread(
|
|
326
|
+
await this.withTemporaryClient((client) => client.resumeThread(threadId, external.cwd));
|
|
305
327
|
const projection = context.projectionFor(external);
|
|
306
328
|
if (projection !== undefined) {
|
|
307
329
|
const promoted = context.promote(projection.id);
|
|
@@ -343,7 +365,7 @@ export class CodexRunner extends AbstractRunner {
|
|
|
343
365
|
externalSessionId: thread.id,
|
|
344
366
|
cwd,
|
|
345
367
|
...(thread.title === undefined ? {} : { title: thread.title }),
|
|
346
|
-
titleOrigin:
|
|
368
|
+
...(thread.titleOrigin === undefined ? {} : { titleOrigin: thread.titleOrigin })
|
|
347
369
|
}));
|
|
348
370
|
}
|
|
349
371
|
for (const entry of await discoverAllNativeSessions(this.name)) {
|
|
@@ -412,107 +434,148 @@ export class CodexRunner extends AbstractRunner {
|
|
|
412
434
|
};
|
|
413
435
|
}
|
|
414
436
|
onNotification(listener) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
437
|
+
this.notificationListeners.add(listener);
|
|
438
|
+
this.attachNotifications(this.client);
|
|
439
|
+
for (const client of this.runClients.values())
|
|
440
|
+
this.attachNotifications(client);
|
|
441
|
+
return () => this.notificationListeners.delete(listener);
|
|
442
|
+
}
|
|
443
|
+
async start(runId) {
|
|
444
|
+
const client = this.clientFor(runId);
|
|
445
|
+
try {
|
|
446
|
+
await client.start();
|
|
447
|
+
}
|
|
448
|
+
catch (error) {
|
|
449
|
+
if (runId === undefined ||
|
|
450
|
+
!(error instanceof Error) ||
|
|
451
|
+
error.message !== 'CODEX_APP_SERVER_STOPPED' ||
|
|
452
|
+
this.runClients.get(runId) !== client)
|
|
453
|
+
throw error;
|
|
454
|
+
await client.start();
|
|
455
|
+
}
|
|
419
456
|
}
|
|
420
457
|
async recoverMissingRollout(runId, error) {
|
|
421
458
|
if (!isCodexRolloutMissingError(error))
|
|
422
459
|
return false;
|
|
423
|
-
|
|
460
|
+
const client = this.clientFor(runId);
|
|
461
|
+
if ([...this.runClients].some(([activeRunId, activeClient]) => activeRunId !== runId && activeClient === client))
|
|
424
462
|
return false;
|
|
425
|
-
if ([...this.execution.runs
|
|
463
|
+
if ([...this.execution.runs].some(([activeRunId]) => activeRunId !== runId && this.runClients.get(activeRunId) === client))
|
|
426
464
|
return false;
|
|
427
465
|
nodeLog('runner.codex.rollout.recovering', { runId });
|
|
428
|
-
await
|
|
466
|
+
await client.restart();
|
|
429
467
|
return true;
|
|
430
468
|
}
|
|
431
469
|
prepareEnvironment(environment, runId) {
|
|
432
|
-
if (
|
|
433
|
-
this.environmentRuns.add(runId);
|
|
470
|
+
if (this.runClients.has(runId))
|
|
434
471
|
return;
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
.
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
this.environmentRuns.add(runId);
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
if (this.environmentRuns.size > 0 || this.execution.runs.size > 0)
|
|
444
|
-
throw new Error('CODEX_SECRET_ENVIRONMENT_CONFLICT');
|
|
445
|
-
this.client.stop();
|
|
446
|
-
this.client.configureEnvironment(environment);
|
|
447
|
-
this.environmentFingerprint = fingerprint;
|
|
448
|
-
this.environmentRuns.add(runId);
|
|
472
|
+
const client = this.runClientFactory();
|
|
473
|
+
if (Object.keys(environment).length > 0)
|
|
474
|
+
client.configureEnvironment(environment);
|
|
475
|
+
this.attachNotifications(client);
|
|
476
|
+
this.runClients.set(runId, client);
|
|
449
477
|
}
|
|
450
478
|
releaseEnvironment(runId) {
|
|
451
|
-
this.
|
|
452
|
-
if (
|
|
479
|
+
const client = this.runClients.get(runId);
|
|
480
|
+
if (client === undefined)
|
|
453
481
|
return;
|
|
454
|
-
this.
|
|
455
|
-
this.client.
|
|
456
|
-
|
|
482
|
+
this.runClients.delete(runId);
|
|
483
|
+
if (client !== this.client && ![...this.runClients.values()].includes(client)) {
|
|
484
|
+
this.detachNotifications(client);
|
|
485
|
+
client.stop();
|
|
486
|
+
client.clearEnvironment();
|
|
487
|
+
}
|
|
457
488
|
}
|
|
458
489
|
stop() {
|
|
459
490
|
this.profileGeneration += 1;
|
|
460
491
|
for (const profileClient of this.profileClients)
|
|
461
492
|
profileClient.stop();
|
|
462
493
|
this.profileClients.clear();
|
|
494
|
+
for (const client of new Set(this.runClients.values()))
|
|
495
|
+
if (client !== this.client) {
|
|
496
|
+
this.detachNotifications(client);
|
|
497
|
+
client.stop();
|
|
498
|
+
client.clearEnvironment();
|
|
499
|
+
}
|
|
500
|
+
this.runClients.clear();
|
|
463
501
|
this.client.stop();
|
|
464
|
-
if (this.environmentFingerprint !== undefined)
|
|
465
|
-
this.client.clearEnvironment();
|
|
466
|
-
this.environmentFingerprint = undefined;
|
|
467
|
-
this.environmentRuns.clear();
|
|
468
502
|
if (this.profileCacheTimer !== undefined)
|
|
469
503
|
clearTimeout(this.profileCacheTimer);
|
|
470
504
|
this.profileCacheTimer = undefined;
|
|
471
505
|
this.profileCache = undefined;
|
|
472
506
|
this.profileCacheVersion = undefined;
|
|
473
507
|
this.profileRefresh = undefined;
|
|
508
|
+
this.profileRefreshVersion = undefined;
|
|
509
|
+
}
|
|
510
|
+
clientFor(runId) {
|
|
511
|
+
return runId === undefined ? this.client : (this.runClients.get(runId) ?? this.client);
|
|
512
|
+
}
|
|
513
|
+
async withTemporaryClient(operation) {
|
|
514
|
+
const client = this.runClientFactory();
|
|
515
|
+
try {
|
|
516
|
+
await client.start();
|
|
517
|
+
return await operation(client);
|
|
518
|
+
}
|
|
519
|
+
finally {
|
|
520
|
+
if (client !== this.client)
|
|
521
|
+
await client.stopAndWait();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
attachNotifications(client) {
|
|
525
|
+
if (this.clientNotificationDisposers.has(client))
|
|
526
|
+
return;
|
|
527
|
+
this.clientNotificationDisposers.set(client, client.onNotification((notification) => {
|
|
528
|
+
for (const listener of this.notificationListeners)
|
|
529
|
+
listener(notification);
|
|
530
|
+
}));
|
|
531
|
+
}
|
|
532
|
+
detachNotifications(client) {
|
|
533
|
+
this.clientNotificationDisposers.get(client)?.();
|
|
534
|
+
this.clientNotificationDisposers.delete(client);
|
|
474
535
|
}
|
|
475
|
-
request(method, params) {
|
|
476
|
-
return this.
|
|
536
|
+
request(method, params, runId) {
|
|
537
|
+
return this.clientFor(runId).request(method, params);
|
|
477
538
|
}
|
|
478
|
-
respond(requestId, result) {
|
|
479
|
-
this.
|
|
539
|
+
respond(requestId, result, runId) {
|
|
540
|
+
this.clientFor(runId).respond(requestId, result);
|
|
480
541
|
}
|
|
481
542
|
listThreads() {
|
|
482
543
|
return this.client.listThreads();
|
|
483
544
|
}
|
|
484
|
-
startThread(cwd, configuration) {
|
|
485
|
-
return this.
|
|
545
|
+
startThread(cwd, configuration, runId) {
|
|
546
|
+
return this.clientFor(runId).startThread(cwd, configuration);
|
|
486
547
|
}
|
|
487
548
|
forkThread(threadId, cwd, lastTurnId, configuration) {
|
|
488
549
|
return this.client.forkThread(threadId, cwd, lastTurnId, configuration);
|
|
489
550
|
}
|
|
490
551
|
async forkNative(session, boundary, title) {
|
|
491
|
-
|
|
552
|
+
const threadId = session.externalSessionId;
|
|
553
|
+
if (threadId === null)
|
|
492
554
|
throw new Error('SESSION_REWIND_UNAVAILABLE');
|
|
493
|
-
await this.start();
|
|
494
555
|
const configuration = { model: session.model, effort: session.effort, access: session.access };
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
556
|
+
return this.withTemporaryClient(async (client) => {
|
|
557
|
+
const result = boundary === null
|
|
558
|
+
? await client.startThread(session.cwd, configuration)
|
|
559
|
+
: await client.forkThread(threadId, session.cwd, boundary, configuration);
|
|
560
|
+
const forkedThreadId = extractId(result, 'thread');
|
|
561
|
+
await client.setThreadName(forkedThreadId, title);
|
|
562
|
+
return forkedThreadId;
|
|
563
|
+
});
|
|
501
564
|
}
|
|
502
|
-
resumeThread(threadId, cwd, configuration) {
|
|
503
|
-
return this.
|
|
565
|
+
resumeThread(threadId, cwd, configuration, runId) {
|
|
566
|
+
return this.clientFor(runId).resumeThread(threadId, cwd, configuration);
|
|
504
567
|
}
|
|
505
|
-
startTurn(threadId, text, cwd, configuration, attachments) {
|
|
506
|
-
return this.
|
|
568
|
+
startTurn(threadId, text, cwd, configuration, attachments, runId) {
|
|
569
|
+
return this.clientFor(runId).startTurn(threadId, text, cwd, configuration, attachments);
|
|
507
570
|
}
|
|
508
|
-
interruptTurn(threadId, turnId) {
|
|
509
|
-
return this.
|
|
571
|
+
interruptTurn(threadId, turnId, runId) {
|
|
572
|
+
return this.clientFor(runId).interruptTurn(threadId, turnId);
|
|
510
573
|
}
|
|
511
574
|
setThreadName(threadId, name) {
|
|
512
575
|
return this.client.setThreadName(threadId, name);
|
|
513
576
|
}
|
|
514
|
-
listCollaborationModes() {
|
|
515
|
-
return this.
|
|
577
|
+
listCollaborationModes(runId) {
|
|
578
|
+
return this.clientFor(runId).listCollaborationModes();
|
|
516
579
|
}
|
|
517
580
|
}
|
|
518
581
|
function emptyCodexConfiguration() {
|