@myagentroam/node 0.9.122 → 0.9.125
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 +6 -79
- 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 +127 -57
- 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 +51 -10
- 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
|
+
}
|
|
@@ -11,30 +11,36 @@ import { codexSessionControl } from '../codex-app-server.js';
|
|
|
11
11
|
import { extractId } from '../util/runner-native-session-parsers.js';
|
|
12
12
|
import { nodeLog } from '../operational.js';
|
|
13
13
|
const CODEX_MODEL_CACHE_MS = 10 * 60 * 1_000;
|
|
14
|
-
const CODEX_PROFILE_ENTRY_TTL_MS = 30 * 60 * 1_000;
|
|
15
14
|
const CODEX_MODEL_FAILURE_RETRY_MS = 30 * 1_000;
|
|
16
15
|
const CODEX_MODEL_PAGE_SIZE = 100;
|
|
17
16
|
const CODEX_MODEL_MAX_PAGES = 6;
|
|
18
17
|
export class CodexRunner extends AbstractRunner {
|
|
19
|
-
client;
|
|
20
18
|
profileClientFactory;
|
|
21
19
|
name = 'codex';
|
|
22
20
|
discoveryScope = 'ALL_WORKSPACES';
|
|
23
21
|
execution = new CodexExecutionState();
|
|
24
22
|
fastEnabled = false;
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
environmentClients = new Map();
|
|
24
|
+
runClients = new Map();
|
|
25
|
+
notificationListeners = new Set();
|
|
26
|
+
clientNotificationDisposers = new Map();
|
|
27
27
|
profileCache;
|
|
28
28
|
profileCacheVersion;
|
|
29
29
|
profileCacheTimer;
|
|
30
30
|
profileRefresh;
|
|
31
|
+
profileRefreshVersion;
|
|
31
32
|
profileClients = new Set();
|
|
32
33
|
profileGeneration = 0;
|
|
33
|
-
|
|
34
|
+
runClientFactory;
|
|
35
|
+
constructor(client, profileClientFactory = () => new CodexAppServerClient(), runClientFactory) {
|
|
34
36
|
super();
|
|
35
|
-
this.client = client;
|
|
36
37
|
this.profileClientFactory = profileClientFactory;
|
|
38
|
+
this.client = client ?? new CodexAppServerClient();
|
|
39
|
+
this.runClientFactory =
|
|
40
|
+
runClientFactory ??
|
|
41
|
+
(client === undefined ? () => new CodexAppServerClient() : () => this.client);
|
|
37
42
|
}
|
|
43
|
+
client;
|
|
38
44
|
profile(capabilities) {
|
|
39
45
|
const base = declaredRunnerProfiles({
|
|
40
46
|
codexAvailable: capabilities.codex.available,
|
|
@@ -56,6 +62,14 @@ export class CodexRunner extends AbstractRunner {
|
|
|
56
62
|
if (!capabilities.codex.available)
|
|
57
63
|
return { ...base, models: [] };
|
|
58
64
|
const version = capabilities.codex.version;
|
|
65
|
+
if (this.profileRefresh !== undefined && this.profileRefreshVersion !== version) {
|
|
66
|
+
this.profileGeneration += 1;
|
|
67
|
+
this.profileRefresh = undefined;
|
|
68
|
+
this.profileRefreshVersion = undefined;
|
|
69
|
+
if (this.profileCacheTimer !== undefined)
|
|
70
|
+
clearTimeout(this.profileCacheTimer);
|
|
71
|
+
this.profileCacheTimer = undefined;
|
|
72
|
+
}
|
|
59
73
|
const now = Date.now();
|
|
60
74
|
const cached = this.profileCacheVersion === version ? this.profileCache : undefined;
|
|
61
75
|
if (cached?.successful === true && now < cached.fetchedAt + CODEX_MODEL_CACHE_MS)
|
|
@@ -85,8 +99,10 @@ export class CodexRunner extends AbstractRunner {
|
|
|
85
99
|
successful: true,
|
|
86
100
|
retryAt: 0
|
|
87
101
|
};
|
|
88
|
-
if (generation === this.profileGeneration)
|
|
102
|
+
if (generation === this.profileGeneration) {
|
|
89
103
|
this.cacheProfile(version, cache);
|
|
104
|
+
this.scheduleProfileRefresh(CODEX_MODEL_CACHE_MS, version, base, environment);
|
|
105
|
+
}
|
|
90
106
|
return cache.profile;
|
|
91
107
|
})
|
|
92
108
|
.catch((error) => {
|
|
@@ -98,6 +114,7 @@ export class CodexRunner extends AbstractRunner {
|
|
|
98
114
|
const cached = this.profileCacheVersion === version ? this.profileCache : undefined;
|
|
99
115
|
if (cached !== undefined) {
|
|
100
116
|
cached.retryAt = Date.now() + CODEX_MODEL_FAILURE_RETRY_MS;
|
|
117
|
+
this.scheduleProfileRefresh(CODEX_MODEL_FAILURE_RETRY_MS, version, base, environment);
|
|
101
118
|
return cached.profile;
|
|
102
119
|
}
|
|
103
120
|
this.cacheProfile(version, {
|
|
@@ -107,25 +124,30 @@ export class CodexRunner extends AbstractRunner {
|
|
|
107
124
|
successful: false,
|
|
108
125
|
retryAt: Date.now() + CODEX_MODEL_FAILURE_RETRY_MS
|
|
109
126
|
});
|
|
127
|
+
this.scheduleProfileRefresh(CODEX_MODEL_FAILURE_RETRY_MS, version, base, environment);
|
|
110
128
|
return { ...base, models: [] };
|
|
111
129
|
})
|
|
112
130
|
.finally(() => {
|
|
113
|
-
if (this.profileRefresh === refresh)
|
|
131
|
+
if (this.profileRefresh === refresh) {
|
|
114
132
|
this.profileRefresh = undefined;
|
|
133
|
+
this.profileRefreshVersion = undefined;
|
|
134
|
+
}
|
|
115
135
|
});
|
|
116
136
|
this.profileRefresh = refresh;
|
|
137
|
+
this.profileRefreshVersion = version;
|
|
117
138
|
return refresh;
|
|
118
139
|
}
|
|
119
140
|
cacheProfile(version, cache) {
|
|
120
|
-
if (this.profileCacheTimer !== undefined)
|
|
121
|
-
clearTimeout(this.profileCacheTimer);
|
|
122
141
|
this.profileCache = cache;
|
|
123
142
|
this.profileCacheVersion = version;
|
|
143
|
+
}
|
|
144
|
+
scheduleProfileRefresh(delayMs, version, base, environment) {
|
|
145
|
+
if (this.profileCacheTimer !== undefined)
|
|
146
|
+
clearTimeout(this.profileCacheTimer);
|
|
124
147
|
this.profileCacheTimer = setTimeout(() => {
|
|
125
148
|
this.profileCacheTimer = undefined;
|
|
126
|
-
this.
|
|
127
|
-
|
|
128
|
-
}, CODEX_PROFILE_ENTRY_TTL_MS);
|
|
149
|
+
void this.refreshCodexProfile(version, base, environment);
|
|
150
|
+
}, delayMs);
|
|
129
151
|
this.profileCacheTimer.unref();
|
|
130
152
|
}
|
|
131
153
|
async fetchCodexProfile(base, environment) {
|
|
@@ -255,7 +277,8 @@ export class CodexRunner extends AbstractRunner {
|
|
|
255
277
|
async readManagedRunnerTitle(session, nativeSessionId) {
|
|
256
278
|
void session;
|
|
257
279
|
await this.start();
|
|
258
|
-
|
|
280
|
+
const thread = extractCodexThreads(await this.listThreads()).find((candidate) => candidate.id === nativeSessionId);
|
|
281
|
+
return thread?.titleOrigin === 'OFFICIAL' ? thread.title : undefined;
|
|
259
282
|
}
|
|
260
283
|
nativeImageRoots(session) {
|
|
261
284
|
return [
|
|
@@ -343,7 +366,7 @@ export class CodexRunner extends AbstractRunner {
|
|
|
343
366
|
externalSessionId: thread.id,
|
|
344
367
|
cwd,
|
|
345
368
|
...(thread.title === undefined ? {} : { title: thread.title }),
|
|
346
|
-
titleOrigin:
|
|
369
|
+
...(thread.titleOrigin === undefined ? {} : { titleOrigin: thread.titleOrigin })
|
|
347
370
|
}));
|
|
348
371
|
}
|
|
349
372
|
for (const entry of await discoverAllNativeSessions(this.name)) {
|
|
@@ -412,77 +435,124 @@ export class CodexRunner extends AbstractRunner {
|
|
|
412
435
|
};
|
|
413
436
|
}
|
|
414
437
|
onNotification(listener) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
438
|
+
this.notificationListeners.add(listener);
|
|
439
|
+
this.attachNotifications(this.client);
|
|
440
|
+
for (const { client } of this.environmentClients.values())
|
|
441
|
+
this.attachNotifications(client);
|
|
442
|
+
return () => this.notificationListeners.delete(listener);
|
|
443
|
+
}
|
|
444
|
+
async start(runId) {
|
|
445
|
+
const client = this.clientFor(runId);
|
|
446
|
+
try {
|
|
447
|
+
await client.start();
|
|
448
|
+
}
|
|
449
|
+
catch (error) {
|
|
450
|
+
if (runId === undefined ||
|
|
451
|
+
!(error instanceof Error) ||
|
|
452
|
+
error.message !== 'CODEX_APP_SERVER_STOPPED' ||
|
|
453
|
+
this.runClients.get(runId) !== client)
|
|
454
|
+
throw error;
|
|
455
|
+
await client.start();
|
|
456
|
+
}
|
|
419
457
|
}
|
|
420
458
|
async recoverMissingRollout(runId, error) {
|
|
421
459
|
if (!isCodexRolloutMissingError(error))
|
|
422
460
|
return false;
|
|
423
|
-
|
|
461
|
+
const client = this.clientFor(runId);
|
|
462
|
+
const entry = [...this.environmentClients.values()].find((value) => value.client === client);
|
|
463
|
+
if (entry !== undefined && [...entry.runIds].some((activeRunId) => activeRunId !== runId))
|
|
424
464
|
return false;
|
|
425
|
-
if ([...this.execution.runs
|
|
465
|
+
if ([...this.execution.runs].some(([activeRunId]) => activeRunId !== runId && this.runClients.get(activeRunId) === client))
|
|
426
466
|
return false;
|
|
427
467
|
nodeLog('runner.codex.rollout.recovering', { runId });
|
|
428
|
-
await
|
|
468
|
+
await client.restart();
|
|
429
469
|
return true;
|
|
430
470
|
}
|
|
431
471
|
prepareEnvironment(environment, runId) {
|
|
432
|
-
if (Object.keys(environment).length === 0 && this.environmentFingerprint === undefined) {
|
|
433
|
-
this.environmentRuns.add(runId);
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
472
|
const fingerprint = createHash('sha256')
|
|
437
473
|
.update(JSON.stringify(Object.entries(environment).sort(([left], [right]) => left.localeCompare(right))))
|
|
438
474
|
.digest('base64url');
|
|
439
|
-
|
|
440
|
-
|
|
475
|
+
const existing = this.environmentClients.get(fingerprint);
|
|
476
|
+
if (existing !== undefined) {
|
|
477
|
+
existing.runIds.add(runId);
|
|
478
|
+
this.runClients.set(runId, existing.client);
|
|
441
479
|
return;
|
|
442
480
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
this.client
|
|
447
|
-
this.
|
|
448
|
-
this.
|
|
481
|
+
const client = this.runClientFactory();
|
|
482
|
+
if (Object.keys(environment).length > 0)
|
|
483
|
+
client.configureEnvironment(environment);
|
|
484
|
+
this.attachNotifications(client);
|
|
485
|
+
this.environmentClients.set(fingerprint, { client, runIds: new Set([runId]) });
|
|
486
|
+
this.runClients.set(runId, client);
|
|
449
487
|
}
|
|
450
488
|
releaseEnvironment(runId) {
|
|
451
|
-
this.
|
|
452
|
-
if (
|
|
489
|
+
const client = this.runClients.get(runId);
|
|
490
|
+
if (client === undefined)
|
|
453
491
|
return;
|
|
454
|
-
this.
|
|
455
|
-
this.
|
|
456
|
-
|
|
492
|
+
this.runClients.delete(runId);
|
|
493
|
+
for (const [fingerprint, entry] of this.environmentClients) {
|
|
494
|
+
if (entry.client !== client)
|
|
495
|
+
continue;
|
|
496
|
+
entry.runIds.delete(runId);
|
|
497
|
+
if (entry.runIds.size === 0) {
|
|
498
|
+
this.environmentClients.delete(fingerprint);
|
|
499
|
+
if (client !== this.client) {
|
|
500
|
+
this.detachNotifications(client);
|
|
501
|
+
client.stop();
|
|
502
|
+
client.clearEnvironment();
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
457
507
|
}
|
|
458
508
|
stop() {
|
|
459
509
|
this.profileGeneration += 1;
|
|
460
510
|
for (const profileClient of this.profileClients)
|
|
461
511
|
profileClient.stop();
|
|
462
512
|
this.profileClients.clear();
|
|
513
|
+
for (const { client } of this.environmentClients.values())
|
|
514
|
+
if (client !== this.client) {
|
|
515
|
+
this.detachNotifications(client);
|
|
516
|
+
client.stop();
|
|
517
|
+
client.clearEnvironment();
|
|
518
|
+
}
|
|
519
|
+
this.environmentClients.clear();
|
|
520
|
+
this.runClients.clear();
|
|
463
521
|
this.client.stop();
|
|
464
|
-
if (this.environmentFingerprint !== undefined)
|
|
465
|
-
this.client.clearEnvironment();
|
|
466
|
-
this.environmentFingerprint = undefined;
|
|
467
|
-
this.environmentRuns.clear();
|
|
468
522
|
if (this.profileCacheTimer !== undefined)
|
|
469
523
|
clearTimeout(this.profileCacheTimer);
|
|
470
524
|
this.profileCacheTimer = undefined;
|
|
471
525
|
this.profileCache = undefined;
|
|
472
526
|
this.profileCacheVersion = undefined;
|
|
473
527
|
this.profileRefresh = undefined;
|
|
528
|
+
this.profileRefreshVersion = undefined;
|
|
529
|
+
}
|
|
530
|
+
clientFor(runId) {
|
|
531
|
+
return runId === undefined ? this.client : (this.runClients.get(runId) ?? this.client);
|
|
532
|
+
}
|
|
533
|
+
attachNotifications(client) {
|
|
534
|
+
if (this.clientNotificationDisposers.has(client))
|
|
535
|
+
return;
|
|
536
|
+
this.clientNotificationDisposers.set(client, client.onNotification((notification) => {
|
|
537
|
+
for (const listener of this.notificationListeners)
|
|
538
|
+
listener(notification);
|
|
539
|
+
}));
|
|
540
|
+
}
|
|
541
|
+
detachNotifications(client) {
|
|
542
|
+
this.clientNotificationDisposers.get(client)?.();
|
|
543
|
+
this.clientNotificationDisposers.delete(client);
|
|
474
544
|
}
|
|
475
|
-
request(method, params) {
|
|
476
|
-
return this.
|
|
545
|
+
request(method, params, runId) {
|
|
546
|
+
return this.clientFor(runId).request(method, params);
|
|
477
547
|
}
|
|
478
|
-
respond(requestId, result) {
|
|
479
|
-
this.
|
|
548
|
+
respond(requestId, result, runId) {
|
|
549
|
+
this.clientFor(runId).respond(requestId, result);
|
|
480
550
|
}
|
|
481
551
|
listThreads() {
|
|
482
552
|
return this.client.listThreads();
|
|
483
553
|
}
|
|
484
|
-
startThread(cwd, configuration) {
|
|
485
|
-
return this.
|
|
554
|
+
startThread(cwd, configuration, runId) {
|
|
555
|
+
return this.clientFor(runId).startThread(cwd, configuration);
|
|
486
556
|
}
|
|
487
557
|
forkThread(threadId, cwd, lastTurnId, configuration) {
|
|
488
558
|
return this.client.forkThread(threadId, cwd, lastTurnId, configuration);
|
|
@@ -499,20 +569,20 @@ export class CodexRunner extends AbstractRunner {
|
|
|
499
569
|
await this.setThreadName(threadId, title);
|
|
500
570
|
return threadId;
|
|
501
571
|
}
|
|
502
|
-
resumeThread(threadId, cwd, configuration) {
|
|
503
|
-
return this.
|
|
572
|
+
resumeThread(threadId, cwd, configuration, runId) {
|
|
573
|
+
return this.clientFor(runId).resumeThread(threadId, cwd, configuration);
|
|
504
574
|
}
|
|
505
|
-
startTurn(threadId, text, cwd, configuration, attachments) {
|
|
506
|
-
return this.
|
|
575
|
+
startTurn(threadId, text, cwd, configuration, attachments, runId) {
|
|
576
|
+
return this.clientFor(runId).startTurn(threadId, text, cwd, configuration, attachments);
|
|
507
577
|
}
|
|
508
|
-
interruptTurn(threadId, turnId) {
|
|
509
|
-
return this.
|
|
578
|
+
interruptTurn(threadId, turnId, runId) {
|
|
579
|
+
return this.clientFor(runId).interruptTurn(threadId, turnId);
|
|
510
580
|
}
|
|
511
581
|
setThreadName(threadId, name) {
|
|
512
582
|
return this.client.setThreadName(threadId, name);
|
|
513
583
|
}
|
|
514
|
-
listCollaborationModes() {
|
|
515
|
-
return this.
|
|
584
|
+
listCollaborationModes(runId) {
|
|
585
|
+
return this.clientFor(runId).listCollaborationModes();
|
|
516
586
|
}
|
|
517
587
|
}
|
|
518
588
|
function emptyCodexConfiguration() {
|