@myagentroam/node 0.1.8 → 0.9.2
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/capabilities.js +6 -3
- package/dist/claude-agent-sdk.d.ts +1 -0
- package/dist/claude-agent-sdk.js +8 -1
- package/dist/codex-app-server.d.ts +6 -0
- package/dist/codex-app-server.js +6 -0
- package/dist/connector/node-connector-options.d.ts +1 -0
- package/dist/connector.d.ts +2 -0
- package/dist/connector.js +43 -14
- package/dist/database.d.ts +23 -0
- package/dist/database.js +84 -2
- package/dist/migrations/v004.d.ts +4 -0
- package/dist/migrations/v004.js +35 -0
- package/dist/native-session-history.js +72 -14
- package/dist/opencode-server.d.ts +1 -0
- package/dist/opencode-server.js +12 -0
- package/dist/runner/abstract-runner.d.ts +13 -3
- package/dist/runner/abstract-runner.js +78 -4
- package/dist/runner/claude/managed-run-controller.js +18 -7
- package/dist/runner/claude-code-runner.d.ts +1 -0
- package/dist/runner/claude-code-runner.js +17 -4
- package/dist/runner/codex/conversation-parser.d.ts +2 -2
- package/dist/runner/codex/conversation-parser.js +30 -12
- package/dist/runner/codex/managed-run-controller.js +11 -8
- package/dist/runner/codex-runner.d.ts +17 -3
- package/dist/runner/codex-runner.js +225 -12
- package/dist/runner/opencode/conversation-parser.d.ts +11 -7
- package/dist/runner/opencode/conversation-parser.js +15 -15
- package/dist/runner/opencode/managed-run-controller.d.ts +2 -1
- package/dist/runner/opencode/managed-run-controller.js +36 -12
- package/dist/runner/opencode-runner.d.ts +2 -1
- package/dist/runner/opencode-runner.js +23 -3
- package/dist/runner/runner-registry.d.ts +1 -1
- package/dist/runner/runner-registry.js +2 -2
- package/dist/runner-command-engine.js +1 -1
- package/dist/runner-profiles.js +29 -41
- package/dist/runner-usage.js +5 -5
- package/dist/runtime-command-detector.d.ts +3 -0
- package/dist/runtime-command-detector.js +78 -0
- package/dist/service/conversation-history-service.js +55 -24
- package/dist/service/conversation-segment-service.d.ts +20 -0
- package/dist/service/conversation-segment-service.js +155 -0
- package/dist/service/mcp-installation-verifier.d.ts +9 -0
- package/dist/service/mcp-installation-verifier.js +87 -0
- package/dist/service/mcp-node-operation-service.d.ts +11 -0
- package/dist/service/mcp-node-operation-service.js +90 -0
- package/dist/service/mcp-package-installer.d.ts +8 -0
- package/dist/service/mcp-package-installer.js +136 -0
- package/dist/service/native-session-watch-service.d.ts +1 -0
- package/dist/service/native-session-watch-service.js +21 -4
- package/dist/service/node-request-service.js +2 -1
- package/dist/service/run-attachment-service.d.ts +3 -2
- package/dist/service/run-attachment-service.js +21 -11
- package/dist/service/run-event-service.d.ts +1 -0
- package/dist/service/run-event-service.js +8 -2
- package/dist/service/runner-service.d.ts +2 -0
- package/dist/service/runner-service.js +5 -2
- package/dist/service/session-catalog-service.js +4 -4
- package/dist/service/session-lifecycle-service.js +7 -4
- package/dist/service/session-presentation-service.d.ts +1 -0
- package/dist/service/session-presentation-service.js +8 -1
- package/dist/service/session-query-service.d.ts +4 -0
- package/dist/service/session-query-service.js +6 -1
- package/dist/service/skill-directory-service.js +3 -26
- package/dist/service/skill-install-service.js +4 -74
- package/dist/service/skill-node-operation-service.js +2 -1
- package/dist/service/workbench-manifest-service.d.ts +2 -0
- package/dist/service/workspace-git-exclude-service.d.ts +4 -0
- package/dist/service/workspace-git-exclude-service.js +119 -0
- package/dist/service/workspace-queue-workbench-service.d.ts +2 -0
- package/dist/service/workspace-queue-workbench-service.js +5 -3
- package/dist/service/workspace-service.js +2 -0
- package/dist/supervisor.js +2 -1
- package/dist/terminal.js +1 -1
- package/dist/util/node-operation-parsers.js +2 -2
- package/dist/util/runner-native-session-parsers.d.ts +2 -3
- package/dist/util/runner-native-session-parsers.js +5 -14
- package/dist/util/safe-error.d.ts +2 -0
- package/dist/util/safe-error.js +5 -0
- package/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { extractId } from '../../util/runner-native-session-parsers.js';
|
|
2
2
|
import { parseSecretEnvironment } from '../../util/secret-environment.js';
|
|
3
|
+
import { safeErrorCode } from '../../util/safe-error.js';
|
|
3
4
|
import { boundedConversationItemId, codexImageAttachments, codexLiveConversationItem, codexUserInputQuestions, compactRunnerText, isPlainRecord } from './conversation-parser.js';
|
|
4
5
|
export class CodexManagedRunController {
|
|
5
6
|
runner;
|
|
@@ -32,10 +33,10 @@ export class CodexManagedRunController {
|
|
|
32
33
|
commandId: 'plan',
|
|
33
34
|
active: true,
|
|
34
35
|
label: 'Plan Mode',
|
|
35
|
-
detail: '
|
|
36
|
+
detail: 'Subsequent messages run in collaborative Plan Mode.',
|
|
36
37
|
closable: true,
|
|
37
38
|
closeInput: '/plan',
|
|
38
|
-
continuation: { label: 'Implement', prompt: '
|
|
39
|
+
continuation: { label: 'Implement', prompt: 'Implement the plan above.' }
|
|
39
40
|
});
|
|
40
41
|
return { command: command.id, states: this.host.commandStates(session.id) };
|
|
41
42
|
}
|
|
@@ -83,7 +84,7 @@ export class CodexManagedRunController {
|
|
|
83
84
|
};
|
|
84
85
|
}
|
|
85
86
|
catch (error) {
|
|
86
|
-
this.host.emit(runId, 'run.failed', { code: error
|
|
87
|
+
this.host.emit(runId, 'run.failed', { code: safeErrorCode(error, 'CODEX_COMPACT_FAILED') }, 'FAILED');
|
|
87
88
|
this.runner.execution.runs.delete(runId);
|
|
88
89
|
this.runner.releaseEnvironment(runId);
|
|
89
90
|
this.host.removeActive(runId);
|
|
@@ -283,7 +284,7 @@ export class CodexManagedRunController {
|
|
|
283
284
|
secretEnvironment = parseSecretEnvironment(payload.secretEnvironment);
|
|
284
285
|
}
|
|
285
286
|
catch (error) {
|
|
286
|
-
this.host.emit(runId, 'run.rejected', { code: error
|
|
287
|
+
this.host.emit(runId, 'run.rejected', { code: safeErrorCode(error, 'MESSAGE_ATTACHMENTS_INVALID') }, 'FAILED');
|
|
287
288
|
return;
|
|
288
289
|
}
|
|
289
290
|
this.host.adopt({
|
|
@@ -311,7 +312,7 @@ export class CodexManagedRunController {
|
|
|
311
312
|
this.runner.prepareEnvironment(secretEnvironment, runId);
|
|
312
313
|
}
|
|
313
314
|
catch (error) {
|
|
314
|
-
this.host.emit(runId, 'run.rejected', { code: error
|
|
315
|
+
this.host.emit(runId, 'run.rejected', { code: safeErrorCode(error, 'SECRET_ENVIRONMENT_INVALID') }, 'FAILED');
|
|
315
316
|
return;
|
|
316
317
|
}
|
|
317
318
|
this.host.addActive(runId);
|
|
@@ -325,7 +326,8 @@ export class CodexManagedRunController {
|
|
|
325
326
|
effort: payload.effort,
|
|
326
327
|
access: payload.access,
|
|
327
328
|
collaborationMode: payload.collaborationMode,
|
|
328
|
-
serviceTier: payload.serviceTier
|
|
329
|
+
serviceTier: payload.serviceTier,
|
|
330
|
+
mcpServers: this.runner.mcpConfiguration(payload.mcpInstallations, secretEnvironment)
|
|
329
331
|
}, attachments);
|
|
330
332
|
}
|
|
331
333
|
async run(payload, attachments) {
|
|
@@ -340,7 +342,8 @@ export class CodexManagedRunController {
|
|
|
340
342
|
const configuration = {
|
|
341
343
|
...(typeof payload.model === 'string' ? { model: payload.model } : {}),
|
|
342
344
|
...(typeof payload.effort === 'string' ? { effort: payload.effort } : {}),
|
|
343
|
-
...(typeof payload.access === 'string' ? { access: payload.access } : {})
|
|
345
|
+
...(typeof payload.access === 'string' ? { access: payload.access } : {}),
|
|
346
|
+
...(payload.mcpServers === undefined ? {} : { mcpServers: payload.mcpServers })
|
|
344
347
|
};
|
|
345
348
|
const threadResult = typeof payload.externalSessionId === 'string'
|
|
346
349
|
? await this.runner.resumeThread(payload.externalSessionId, cwd, configuration)
|
|
@@ -379,7 +382,7 @@ export class CodexManagedRunController {
|
|
|
379
382
|
this.runner.releaseEnvironment(runId);
|
|
380
383
|
return;
|
|
381
384
|
}
|
|
382
|
-
this.host.emit(runId, 'run.failed', { code: error
|
|
385
|
+
this.host.emit(runId, 'run.failed', { code: safeErrorCode(error, 'CODEX_RUN_FAILED') }, 'FAILED');
|
|
383
386
|
this.runner.execution.runs.delete(runId);
|
|
384
387
|
this.runner.releaseEnvironment(runId);
|
|
385
388
|
this.host.removeActive(runId);
|
|
@@ -5,17 +5,29 @@ import type { NodeAgentSession, NodeDatabase } from '../database.js';
|
|
|
5
5
|
import type { NodeWorkspace } from '../database.js';
|
|
6
6
|
export declare class CodexRunner extends AbstractRunner<'codex'> {
|
|
7
7
|
private readonly client;
|
|
8
|
+
private readonly profileClientFactory;
|
|
8
9
|
readonly name: "codex";
|
|
9
10
|
readonly discoveryScope: "ALL_WORKSPACES";
|
|
10
11
|
readonly execution: CodexExecutionState;
|
|
11
12
|
private fastEnabled;
|
|
12
13
|
private environmentFingerprint;
|
|
13
14
|
private readonly environmentRuns;
|
|
14
|
-
|
|
15
|
+
private readonly profileCache;
|
|
16
|
+
private readonly profileRefreshes;
|
|
17
|
+
private readonly profileClients;
|
|
18
|
+
private readonly profileKeyByEnvironment;
|
|
19
|
+
private latestProfileCache;
|
|
20
|
+
private selectedProfileKey;
|
|
21
|
+
constructor(client?: CodexAppServerClient, profileClientFactory?: () => CodexAppServerClient);
|
|
15
22
|
profile(capabilities: NodeCapabilities): RunnerProfile;
|
|
23
|
+
refreshProfile(capabilities: NodeCapabilities, workspace?: NodeWorkspace, environment?: Readonly<Record<string, string>>): Promise<RunnerProfile>;
|
|
24
|
+
private refreshCodexProfile;
|
|
25
|
+
private selectCachedProfile;
|
|
26
|
+
private fetchCodexProfile;
|
|
16
27
|
available(capabilities: NodeCapabilities): boolean;
|
|
17
28
|
executeGlobalCommand(commandId: string, input: string): unknown;
|
|
18
29
|
serviceTier(): 'fast' | undefined;
|
|
30
|
+
mcpConfiguration(raw: unknown, secrets: Readonly<Record<string, string>>): unknown;
|
|
19
31
|
usesRunnerTitleForRename(): boolean;
|
|
20
32
|
renameNative(session: NodeAgentSession, input: {
|
|
21
33
|
readonly title?: string | null;
|
|
@@ -32,10 +44,12 @@ export declare class CodexRunner extends AbstractRunner<'codex'> {
|
|
|
32
44
|
readExternalActivity(session: NodeAgentSession): Promise<"UNAVAILABLE" | "MANAGED_ACTIVE" | "EXTERNAL_ACTIVE" | "IDLE" | undefined>;
|
|
33
45
|
acceptsTruncatedNativeHistory(): boolean;
|
|
34
46
|
readManagedRunnerTitle(session: NodeAgentSession, nativeSessionId: string): Promise<string | undefined>;
|
|
35
|
-
|
|
47
|
+
nativeImageRoots(session: NodeAgentSession): readonly string[];
|
|
36
48
|
managedRunForNativeTurn(nativeTurnId: string): string | undefined;
|
|
37
49
|
protected profileForValidation(): RunnerProfile;
|
|
38
|
-
defaultConfiguration(): RunnerDefaultConfiguration;
|
|
50
|
+
defaultConfiguration(_workspace?: NodeWorkspace, environment?: Readonly<Record<string, string>>): RunnerDefaultConfiguration;
|
|
51
|
+
supportsConfiguration(configuration: RunnerDefaultConfiguration, _workspace?: NodeWorkspace, environment?: Readonly<Record<string, string>>): boolean;
|
|
52
|
+
private profileCacheForEnvironment;
|
|
39
53
|
resumeExternal(external: NodeAgentSession, context: ExternalResumeContext, force?: boolean): Promise<ExternalResumeResult>;
|
|
40
54
|
discoverSessions(context: RunnerDiscoveryContext, workspace?: NodeWorkspace): Promise<readonly NodeAgentSession[]>;
|
|
41
55
|
readContextUsage(session: NodeAgentSession): Promise<import("../native-session-history.js").NativeCodexContextUsage | undefined>;
|
|
@@ -6,27 +6,150 @@ import { resolve } from 'node:path';
|
|
|
6
6
|
import { createHash } from 'node:crypto';
|
|
7
7
|
import { discoverAllNativeSessions } from '../native-session-history.js';
|
|
8
8
|
import { readCodexNativeContextUsage } from '../native-session-history.js';
|
|
9
|
-
import { codexActiveTurnId, codexOfficialConversationPage, codexThreadCwd, codexThreadActivity, deduplicateDirectSessions,
|
|
9
|
+
import { codexActiveTurnId, codexOfficialConversationPage, codexThreadCwd, codexThreadActivity, deduplicateDirectSessions, extractCodexThreads, isWithinWorkspace, latestNativeActivity } from './codex/conversation-parser.js';
|
|
10
10
|
import { codexSessionControl } from '../codex-app-server.js';
|
|
11
11
|
import { extractId } from '../util/runner-native-session-parsers.js';
|
|
12
|
+
import { nodeLog } from '../operational.js';
|
|
13
|
+
const CODEX_MODEL_CACHE_MS = 10 * 60 * 1_000;
|
|
14
|
+
const CODEX_MODEL_FAILURE_RETRY_MS = 30 * 1_000;
|
|
15
|
+
const CODEX_MODEL_PAGE_SIZE = 100;
|
|
12
16
|
export class CodexRunner extends AbstractRunner {
|
|
13
17
|
client;
|
|
18
|
+
profileClientFactory;
|
|
14
19
|
name = 'codex';
|
|
15
20
|
discoveryScope = 'ALL_WORKSPACES';
|
|
16
21
|
execution = new CodexExecutionState();
|
|
17
22
|
fastEnabled = false;
|
|
18
23
|
environmentFingerprint;
|
|
19
24
|
environmentRuns = new Set();
|
|
20
|
-
|
|
25
|
+
profileCache = new Map();
|
|
26
|
+
profileRefreshes = new Map();
|
|
27
|
+
profileClients = new Set();
|
|
28
|
+
profileKeyByEnvironment = new Map();
|
|
29
|
+
latestProfileCache;
|
|
30
|
+
selectedProfileKey;
|
|
31
|
+
constructor(client = new CodexAppServerClient(), profileClientFactory = () => new CodexAppServerClient()) {
|
|
21
32
|
super();
|
|
22
33
|
this.client = client;
|
|
34
|
+
this.profileClientFactory = profileClientFactory;
|
|
23
35
|
}
|
|
24
36
|
profile(capabilities) {
|
|
25
|
-
|
|
37
|
+
const base = declaredRunnerProfiles({
|
|
26
38
|
codexAvailable: capabilities.codex.available,
|
|
27
39
|
claudeCodeAvailable: capabilities.claudeCode.available,
|
|
28
40
|
openCodeAvailable: capabilities.openCode?.available === true
|
|
29
41
|
}).find((profile) => profile.runner === this.name);
|
|
42
|
+
return { ...base, models: this.latestProfileCache?.profile.models ?? [] };
|
|
43
|
+
}
|
|
44
|
+
async refreshProfile(capabilities, workspace, environment = {}) {
|
|
45
|
+
void workspace;
|
|
46
|
+
const base = this.profile(capabilities);
|
|
47
|
+
if (!capabilities.codex.available)
|
|
48
|
+
return { ...base, models: [] };
|
|
49
|
+
const key = codexEnvironmentFingerprint(environment, capabilities.codex.version);
|
|
50
|
+
this.profileKeyByEnvironment.set(codexSecretEnvironmentFingerprint(environment), key);
|
|
51
|
+
this.selectedProfileKey = key;
|
|
52
|
+
const now = Date.now();
|
|
53
|
+
const cached = this.profileCache.get(key);
|
|
54
|
+
if (cached?.successful === true && now < cached.fetchedAt + CODEX_MODEL_CACHE_MS)
|
|
55
|
+
return this.selectCachedProfile(key, cached);
|
|
56
|
+
if (cached?.successful === true) {
|
|
57
|
+
if (now >= cached.retryAt)
|
|
58
|
+
void this.refreshCodexProfile(key, base, environment);
|
|
59
|
+
return this.selectCachedProfile(key, cached);
|
|
60
|
+
}
|
|
61
|
+
if (cached !== undefined && now < cached.retryAt)
|
|
62
|
+
return cached.profile;
|
|
63
|
+
return this.refreshCodexProfile(key, base, environment);
|
|
64
|
+
}
|
|
65
|
+
refreshCodexProfile(key, base, environment) {
|
|
66
|
+
const existing = this.profileRefreshes.get(key);
|
|
67
|
+
if (existing !== undefined)
|
|
68
|
+
return existing;
|
|
69
|
+
const refresh = this.fetchCodexProfile(base, environment)
|
|
70
|
+
.then((value) => {
|
|
71
|
+
const cache = {
|
|
72
|
+
...value,
|
|
73
|
+
fetchedAt: Date.now(),
|
|
74
|
+
successful: true,
|
|
75
|
+
retryAt: 0
|
|
76
|
+
};
|
|
77
|
+
this.profileCache.set(key, cache);
|
|
78
|
+
if (this.selectedProfileKey === key)
|
|
79
|
+
this.latestProfileCache = cache;
|
|
80
|
+
return cache.profile;
|
|
81
|
+
})
|
|
82
|
+
.catch((error) => {
|
|
83
|
+
nodeLog('runner.codex.models.refresh-failed', {
|
|
84
|
+
error: error instanceof Error ? error.message : String(error)
|
|
85
|
+
});
|
|
86
|
+
const cached = this.profileCache.get(key);
|
|
87
|
+
if (cached !== undefined) {
|
|
88
|
+
cached.retryAt = Date.now() + CODEX_MODEL_FAILURE_RETRY_MS;
|
|
89
|
+
return cached.profile;
|
|
90
|
+
}
|
|
91
|
+
this.profileCache.set(key, {
|
|
92
|
+
profile: { ...base, models: [] },
|
|
93
|
+
defaultConfiguration: emptyCodexConfiguration(),
|
|
94
|
+
fetchedAt: 0,
|
|
95
|
+
successful: false,
|
|
96
|
+
retryAt: Date.now() + CODEX_MODEL_FAILURE_RETRY_MS
|
|
97
|
+
});
|
|
98
|
+
return { ...base, models: [] };
|
|
99
|
+
})
|
|
100
|
+
.finally(() => this.profileRefreshes.delete(key));
|
|
101
|
+
this.profileRefreshes.set(key, refresh);
|
|
102
|
+
return refresh;
|
|
103
|
+
}
|
|
104
|
+
selectCachedProfile(key, cached) {
|
|
105
|
+
if (cached.successful && this.selectedProfileKey === key)
|
|
106
|
+
this.latestProfileCache = cached;
|
|
107
|
+
return cached.profile;
|
|
108
|
+
}
|
|
109
|
+
async fetchCodexProfile(base, environment) {
|
|
110
|
+
const client = this.profileClientFactory();
|
|
111
|
+
this.profileClients.add(client);
|
|
112
|
+
if (Object.keys(environment).length > 0)
|
|
113
|
+
client.configureEnvironment(environment);
|
|
114
|
+
try {
|
|
115
|
+
await client.start();
|
|
116
|
+
const models = [];
|
|
117
|
+
let cursor = null;
|
|
118
|
+
const cursors = new Set();
|
|
119
|
+
do {
|
|
120
|
+
const page = codexModelPage(await client.listModels({ cursor, limit: CODEX_MODEL_PAGE_SIZE, includeHidden: false }));
|
|
121
|
+
models.push(...page.models);
|
|
122
|
+
cursor = page.nextCursor;
|
|
123
|
+
if (cursor !== null && cursors.has(cursor))
|
|
124
|
+
throw new Error('CODEX_MODELS_CURSOR_REPEATED');
|
|
125
|
+
if (cursor !== null)
|
|
126
|
+
cursors.add(cursor);
|
|
127
|
+
} while (cursor !== null);
|
|
128
|
+
const uniqueModels = models.filter((model, index) => models.findIndex((candidate) => candidate.model === model.model) === index);
|
|
129
|
+
const profileModels = uniqueModels.map((model) => ({
|
|
130
|
+
id: model.model,
|
|
131
|
+
label: model.displayName,
|
|
132
|
+
supportedEfforts: [...model.supportedEfforts],
|
|
133
|
+
isDefault: model.isDefault,
|
|
134
|
+
defaultEffort: model.defaultEffort
|
|
135
|
+
}));
|
|
136
|
+
const selected = uniqueModels.find((model) => model.isDefault) ?? uniqueModels[0];
|
|
137
|
+
return {
|
|
138
|
+
profile: { ...base, models: profileModels },
|
|
139
|
+
defaultConfiguration: selected === undefined
|
|
140
|
+
? emptyCodexConfiguration()
|
|
141
|
+
: {
|
|
142
|
+
runner: this.name,
|
|
143
|
+
model: selected.model,
|
|
144
|
+
effort: selected.defaultEffort,
|
|
145
|
+
access: 'on-request'
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
if (this.profileClients.delete(client))
|
|
151
|
+
client.stop();
|
|
152
|
+
}
|
|
30
153
|
}
|
|
31
154
|
available(capabilities) {
|
|
32
155
|
return capabilities.codex.available;
|
|
@@ -44,6 +167,19 @@ export class CodexRunner extends AbstractRunner {
|
|
|
44
167
|
serviceTier() {
|
|
45
168
|
return this.fastEnabled ? 'fast' : undefined;
|
|
46
169
|
}
|
|
170
|
+
mcpConfiguration(raw, secrets) {
|
|
171
|
+
const servers = super.mcpConfiguration(raw, secrets);
|
|
172
|
+
return Object.fromEntries(servers.map((server) => [
|
|
173
|
+
server.name,
|
|
174
|
+
server.transport === 'STDIO'
|
|
175
|
+
? {
|
|
176
|
+
command: server.command,
|
|
177
|
+
args: [...(server.args ?? [])],
|
|
178
|
+
env: { ...(server.environment ?? {}) }
|
|
179
|
+
}
|
|
180
|
+
: { url: server.url, http_headers: { ...(server.headers ?? {}) } }
|
|
181
|
+
]));
|
|
182
|
+
}
|
|
47
183
|
usesRunnerTitleForRename() {
|
|
48
184
|
return true;
|
|
49
185
|
}
|
|
@@ -112,10 +248,13 @@ export class CodexRunner extends AbstractRunner {
|
|
|
112
248
|
await this.start();
|
|
113
249
|
return extractCodexThreads(await this.listThreads()).find((thread) => thread.id === nativeSessionId)?.title;
|
|
114
250
|
}
|
|
115
|
-
|
|
116
|
-
return
|
|
117
|
-
|
|
118
|
-
|
|
251
|
+
nativeImageRoots(session) {
|
|
252
|
+
return [
|
|
253
|
+
session.cwd,
|
|
254
|
+
...(session.externalSessionId === null
|
|
255
|
+
? []
|
|
256
|
+
: [resolve(homedir(), '.codex', 'generated_images', session.externalSessionId)])
|
|
257
|
+
];
|
|
119
258
|
}
|
|
120
259
|
managedRunForNativeTurn(nativeTurnId) {
|
|
121
260
|
return this.execution.managedTurns.get(nativeTurnId)?.runId;
|
|
@@ -131,8 +270,23 @@ export class CodexRunner extends AbstractRunner {
|
|
|
131
270
|
openCode: { available: false }
|
|
132
271
|
});
|
|
133
272
|
}
|
|
134
|
-
defaultConfiguration() {
|
|
135
|
-
return
|
|
273
|
+
defaultConfiguration(_workspace, environment) {
|
|
274
|
+
return (this.profileCacheForEnvironment(environment)?.defaultConfiguration ??
|
|
275
|
+
emptyCodexConfiguration());
|
|
276
|
+
}
|
|
277
|
+
supportsConfiguration(configuration, _workspace, environment) {
|
|
278
|
+
if (configuration.runner !== this.name)
|
|
279
|
+
return false;
|
|
280
|
+
const profile = this.profileCacheForEnvironment(environment)?.profile;
|
|
281
|
+
return (profile !== undefined &&
|
|
282
|
+
profile.models.some((model) => model.id === configuration.model && model.supportedEfforts.includes(configuration.effort)) &&
|
|
283
|
+
profile.accessOptions.some((option) => option.id === configuration.access));
|
|
284
|
+
}
|
|
285
|
+
profileCacheForEnvironment(environment) {
|
|
286
|
+
if (environment === undefined)
|
|
287
|
+
return this.latestProfileCache;
|
|
288
|
+
const key = this.profileKeyByEnvironment.get(codexSecretEnvironmentFingerprint(environment));
|
|
289
|
+
return key === undefined ? undefined : this.profileCache.get(key);
|
|
136
290
|
}
|
|
137
291
|
async resumeExternal(external, context, force = false) {
|
|
138
292
|
if (external.runner !== this.name ||
|
|
@@ -162,11 +316,10 @@ export class CodexRunner extends AbstractRunner {
|
|
|
162
316
|
}
|
|
163
317
|
return { activity: 'IDLE', session: context.create(external) };
|
|
164
318
|
}
|
|
165
|
-
catch
|
|
319
|
+
catch {
|
|
166
320
|
return {
|
|
167
321
|
activity: 'UNAVAILABLE',
|
|
168
|
-
failureCode: 'RUNNER_RESUME_FAILED'
|
|
169
|
-
failureDetail: externalResumeFailureDetail(error)
|
|
322
|
+
failureCode: 'RUNNER_RESUME_FAILED'
|
|
170
323
|
};
|
|
171
324
|
}
|
|
172
325
|
}
|
|
@@ -305,6 +458,9 @@ export class CodexRunner extends AbstractRunner {
|
|
|
305
458
|
this.environmentFingerprint = undefined;
|
|
306
459
|
}
|
|
307
460
|
stop() {
|
|
461
|
+
for (const profileClient of this.profileClients)
|
|
462
|
+
profileClient.stop();
|
|
463
|
+
this.profileClients.clear();
|
|
308
464
|
this.client.stop();
|
|
309
465
|
if (this.environmentFingerprint !== undefined)
|
|
310
466
|
this.client.clearEnvironment();
|
|
@@ -357,6 +513,63 @@ export class CodexRunner extends AbstractRunner {
|
|
|
357
513
|
return this.client.listCollaborationModes();
|
|
358
514
|
}
|
|
359
515
|
}
|
|
516
|
+
function emptyCodexConfiguration() {
|
|
517
|
+
return { runner: 'codex', model: '', effort: '', access: 'on-request' };
|
|
518
|
+
}
|
|
519
|
+
function codexEnvironmentFingerprint(environment, version) {
|
|
520
|
+
return createHash('sha256')
|
|
521
|
+
.update(JSON.stringify({
|
|
522
|
+
environment: Object.entries(environment).sort(([left], [right]) => left.localeCompare(right)),
|
|
523
|
+
version: version ?? null
|
|
524
|
+
}))
|
|
525
|
+
.digest('base64url');
|
|
526
|
+
}
|
|
527
|
+
function codexSecretEnvironmentFingerprint(environment) {
|
|
528
|
+
return createHash('sha256')
|
|
529
|
+
.update(JSON.stringify(Object.entries(environment).sort(([left], [right]) => left.localeCompare(right))))
|
|
530
|
+
.digest('base64url');
|
|
531
|
+
}
|
|
532
|
+
function codexModelPage(value) {
|
|
533
|
+
if (!isPlainRecord(value) || !Array.isArray(value.data))
|
|
534
|
+
throw new Error('CODEX_MODELS_INVALID');
|
|
535
|
+
return {
|
|
536
|
+
models: value.data.flatMap((entry) => {
|
|
537
|
+
if (!isPlainRecord(entry) || entry.hidden === true)
|
|
538
|
+
return [];
|
|
539
|
+
const model = typeof entry.model === 'string' ? entry.model.trim() : '';
|
|
540
|
+
const displayName = typeof entry.displayName === 'string' ? entry.displayName.trim() : '';
|
|
541
|
+
const defaultEffort = typeof entry.defaultReasoningEffort === 'string' ? entry.defaultReasoningEffort.trim() : '';
|
|
542
|
+
const supportedEfforts = Array.isArray(entry.supportedReasoningEfforts)
|
|
543
|
+
? entry.supportedReasoningEfforts.flatMap((option) => isPlainRecord(option) && typeof option.reasoningEffort === 'string'
|
|
544
|
+
? [option.reasoningEffort.trim()]
|
|
545
|
+
: [])
|
|
546
|
+
: [];
|
|
547
|
+
if (model.length === 0 ||
|
|
548
|
+
model.length > 128 ||
|
|
549
|
+
displayName.length === 0 ||
|
|
550
|
+
displayName.length > 128 ||
|
|
551
|
+
defaultEffort.length === 0 ||
|
|
552
|
+
defaultEffort.length > 64 ||
|
|
553
|
+
supportedEfforts.length > 32 ||
|
|
554
|
+
supportedEfforts.some((effort) => effort.length === 0 || effort.length > 64) ||
|
|
555
|
+
!supportedEfforts.includes(defaultEffort))
|
|
556
|
+
return [];
|
|
557
|
+
return [
|
|
558
|
+
{
|
|
559
|
+
model,
|
|
560
|
+
displayName,
|
|
561
|
+
isDefault: entry.isDefault === true,
|
|
562
|
+
defaultEffort,
|
|
563
|
+
supportedEfforts: [...new Set(supportedEfforts.filter(Boolean))]
|
|
564
|
+
}
|
|
565
|
+
];
|
|
566
|
+
}),
|
|
567
|
+
nextCursor: typeof value.nextCursor === 'string' && value.nextCursor.length > 0 ? value.nextCursor : null
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
function isPlainRecord(value) {
|
|
571
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
572
|
+
}
|
|
360
573
|
export class CodexExecutionState {
|
|
361
574
|
runs = new Map();
|
|
362
575
|
managedTurns = new Map();
|
|
@@ -18,10 +18,14 @@ export declare function openCodeQuestions(value: unknown): {
|
|
|
18
18
|
allowOther: boolean;
|
|
19
19
|
multiSelect: boolean;
|
|
20
20
|
}[];
|
|
21
|
-
export declare function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
export declare function openCodePatchToolPayload(): {
|
|
22
|
+
namespace: string;
|
|
23
|
+
toolName: string;
|
|
24
|
+
title: string;
|
|
25
|
+
inputSummary: null;
|
|
26
|
+
outputSummary: string;
|
|
27
|
+
outputSummaryCode: string;
|
|
28
|
+
progressLabel: null;
|
|
29
|
+
errorCode: null;
|
|
30
|
+
truncated: boolean;
|
|
31
|
+
};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { basename } from 'node:path';
|
|
2
1
|
import { boundedConversationItemId, compactRunnerText, compactRunnerValue } from '../codex/conversation-parser.js';
|
|
3
2
|
export function openCodeConversationPage(session, messages, input, managedTurn) {
|
|
4
3
|
const records = messages.flatMap((value) => {
|
|
@@ -114,10 +113,11 @@ function partItem(session, turnId, raw, planMode, startedAt, completedAt, sequen
|
|
|
114
113
|
if (part.type === 'compaction')
|
|
115
114
|
return item(session, turnId, part.id, 'context_compaction', 'COMPLETED', {
|
|
116
115
|
phase: 'COMPLETED',
|
|
117
|
-
summary: '
|
|
116
|
+
summary: 'Context compacted',
|
|
117
|
+
summaryCode: 'CONTEXT_COMPACTED'
|
|
118
118
|
}, startedAt, completedAt, sequence);
|
|
119
119
|
if (part.type === 'patch')
|
|
120
|
-
return item(session, turnId, part.id, '
|
|
120
|
+
return item(session, turnId, part.id, 'tool_call', 'COMPLETED', openCodePatchToolPayload(), startedAt, completedAt, sequence);
|
|
121
121
|
if (part.type === 'step-start' || part.type === 'step-finish')
|
|
122
122
|
return undefined;
|
|
123
123
|
if (part.type === 'text' || part.type === 'reasoning')
|
|
@@ -162,18 +162,18 @@ export function openCodeQuestions(value) {
|
|
|
162
162
|
];
|
|
163
163
|
});
|
|
164
164
|
}
|
|
165
|
-
export function
|
|
166
|
-
return
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
165
|
+
export function openCodePatchToolPayload() {
|
|
166
|
+
return {
|
|
167
|
+
namespace: 'opencode',
|
|
168
|
+
toolName: 'patch',
|
|
169
|
+
title: 'patch',
|
|
170
|
+
inputSummary: null,
|
|
171
|
+
outputSummary: 'OpenCode applied the patch',
|
|
172
|
+
outputSummaryCode: 'OPENCODE_PATCH_APPLIED',
|
|
173
|
+
progressLabel: null,
|
|
174
|
+
errorCode: null,
|
|
175
|
+
truncated: false
|
|
176
|
+
};
|
|
177
177
|
}
|
|
178
178
|
function item(session, turnId, nativeId, kind, status, payload, startedAt, completedAt, sequence) {
|
|
179
179
|
return {
|
|
@@ -7,6 +7,7 @@ interface OpenCodeManagedRunHost {
|
|
|
7
7
|
intercepted(): boolean;
|
|
8
8
|
readonly active: Set<string>;
|
|
9
9
|
parseAttachments(value: unknown): readonly RunnerImageAttachment[];
|
|
10
|
+
persistAttachments(cwd: string, runId: string, attachments: readonly RunnerImageAttachment[]): Promise<readonly string[]>;
|
|
10
11
|
emit(runId: string, eventType: string, payload: unknown, status?: FakeRunnerStatus): void;
|
|
11
12
|
emitItem(runId: string, item: OpenCodePublicItem): void;
|
|
12
13
|
adopt(input: {
|
|
@@ -40,7 +41,7 @@ interface OpenCodeManagedRunHost {
|
|
|
40
41
|
}
|
|
41
42
|
type OpenCodePublicItem = {
|
|
42
43
|
readonly itemId: string;
|
|
43
|
-
readonly kind: 'assistant_message' | 'plan' | 'reasoning_summary' | 'tool_call' | 'user_input_request' | 'context_compaction' | '
|
|
44
|
+
readonly kind: 'assistant_message' | 'plan' | 'reasoning_summary' | 'tool_call' | 'user_input_request' | 'context_compaction' | 'unknown';
|
|
44
45
|
readonly status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED';
|
|
45
46
|
readonly payload: Record<string, unknown>;
|
|
46
47
|
readonly merge?: boolean;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseSecretEnvironment } from '../../util/secret-environment.js';
|
|
2
|
+
import { safeErrorCode } from '../../util/safe-error.js';
|
|
2
3
|
import { boundedConversationItemId, compactRunnerText, compactRunnerValue } from '../codex/conversation-parser.js';
|
|
3
|
-
import {
|
|
4
|
+
import { openCodePatchToolPayload, openCodeQuestions } from './conversation-parser.js';
|
|
4
5
|
export class OpenCodeManagedRunController {
|
|
5
6
|
runner;
|
|
6
7
|
host;
|
|
@@ -29,10 +30,10 @@ export class OpenCodeManagedRunController {
|
|
|
29
30
|
commandId: 'plan',
|
|
30
31
|
active: true,
|
|
31
32
|
label: 'Plan Mode',
|
|
32
|
-
detail: '
|
|
33
|
+
detail: 'Subsequent messages use the OpenCode Plan Agent for read-only analysis.',
|
|
33
34
|
closable: true,
|
|
34
35
|
closeInput: '/plan',
|
|
35
|
-
continuation: { label: 'Implement', prompt: '
|
|
36
|
+
continuation: { label: 'Implement', prompt: 'Implement the plan above.' }
|
|
36
37
|
});
|
|
37
38
|
return { command: command.id, states: this.host.commandStates(session.id) };
|
|
38
39
|
}
|
|
@@ -48,13 +49,15 @@ export class OpenCodeManagedRunController {
|
|
|
48
49
|
this.host.active.has(payload.runId))
|
|
49
50
|
return;
|
|
50
51
|
let environment;
|
|
52
|
+
let mcp;
|
|
51
53
|
let attachments;
|
|
52
54
|
try {
|
|
53
55
|
environment = parseSecretEnvironment(payload.secretEnvironment);
|
|
56
|
+
mcp = this.runner.mcpConfiguration(payload.mcpInstallations, environment);
|
|
54
57
|
attachments = this.host.parseAttachments(payload.attachments);
|
|
55
58
|
}
|
|
56
59
|
catch (error) {
|
|
57
|
-
this.host.emit(payload.runId, 'run.rejected', { code: error
|
|
60
|
+
this.host.emit(payload.runId, 'run.rejected', { code: safeErrorCode(error, 'RUNNER_INPUT_INVALID') }, 'FAILED');
|
|
58
61
|
return;
|
|
59
62
|
}
|
|
60
63
|
void this.run({
|
|
@@ -70,7 +73,8 @@ export class OpenCodeManagedRunController {
|
|
|
70
73
|
environment,
|
|
71
74
|
attachments,
|
|
72
75
|
access: typeof payload.access === 'string' ? payload.access : 'default',
|
|
73
|
-
agent: payload.collaborationMode === 'plan' ? 'plan' : 'build'
|
|
76
|
+
agent: payload.collaborationMode === 'plan' ? 'plan' : 'build',
|
|
77
|
+
mcp
|
|
74
78
|
});
|
|
75
79
|
}
|
|
76
80
|
cancel(runId, intent) {
|
|
@@ -119,12 +123,16 @@ export class OpenCodeManagedRunController {
|
|
|
119
123
|
return true;
|
|
120
124
|
}
|
|
121
125
|
async run(input) {
|
|
122
|
-
|
|
126
|
+
// OpenCode dynamic MCP registration belongs to Server process state. Runs that load MCP use an
|
|
127
|
+
// isolated Server so parallel sessions in one workspace do not share MCP processes or context.
|
|
128
|
+
const client = this.runner.managedClient(input.environment, hasMcpConfiguration(input.mcp) ? input.runId : undefined);
|
|
123
129
|
this.startingClients.set(input.runId, client);
|
|
124
130
|
this.startingCwds.set(input.runId, input.cwd);
|
|
125
131
|
this.host.active.add(input.runId);
|
|
126
132
|
this.host.emit(input.runId, 'run.started', {}, 'STARTING');
|
|
127
133
|
try {
|
|
134
|
+
if (hasMcpConfiguration(input.mcp))
|
|
135
|
+
await client.configureMcp(input.cwd, input.mcp);
|
|
128
136
|
const nativeSessionId = input.externalSessionId ?? openCodeSessionId(await client.createSession(input.cwd));
|
|
129
137
|
if (nativeSessionId === undefined)
|
|
130
138
|
throw new Error('OPENCODE_SESSION_INVALID');
|
|
@@ -174,17 +182,20 @@ export class OpenCodeManagedRunController {
|
|
|
174
182
|
return;
|
|
175
183
|
}
|
|
176
184
|
this.finish(input.runId, 'FAILED', 'run.failed', {
|
|
177
|
-
code: error
|
|
185
|
+
code: safeErrorCode(error, 'OPENCODE_EVENT_FAILED')
|
|
178
186
|
});
|
|
179
187
|
});
|
|
180
188
|
await subscriptionReady;
|
|
181
189
|
this.host.emit(input.runId, 'run.running', {}, 'RUNNING');
|
|
182
190
|
// The initial SSE snapshot can contain an idle event from before this prompt.
|
|
183
191
|
this.pendingIdle.delete(input.runId);
|
|
192
|
+
const attachmentPaths = input.attachments.length === 0
|
|
193
|
+
? []
|
|
194
|
+
: await this.host.persistAttachments(input.cwd, input.runId, input.attachments);
|
|
184
195
|
await client.prompt({
|
|
185
196
|
directory: input.cwd,
|
|
186
197
|
sessionID: nativeSessionId,
|
|
187
|
-
text: input.input,
|
|
198
|
+
text: openCodeImageInput(input.input, attachmentPaths),
|
|
188
199
|
model: input.model,
|
|
189
200
|
variant: input.effort,
|
|
190
201
|
agent: input.agent,
|
|
@@ -196,7 +207,7 @@ export class OpenCodeManagedRunController {
|
|
|
196
207
|
}
|
|
197
208
|
catch (error) {
|
|
198
209
|
this.finish(input.runId, 'FAILED', 'run.failed', {
|
|
199
|
-
code: error
|
|
210
|
+
code: safeErrorCode(error, 'OPENCODE_RUN_START_FAILED')
|
|
200
211
|
});
|
|
201
212
|
}
|
|
202
213
|
}
|
|
@@ -359,6 +370,15 @@ export class OpenCodeManagedRunController {
|
|
|
359
370
|
void this.host.captureSnapshot(runId, cwd);
|
|
360
371
|
}
|
|
361
372
|
}
|
|
373
|
+
function openCodeImageInput(input, attachmentPaths) {
|
|
374
|
+
if (attachmentPaths.length === 0)
|
|
375
|
+
return input;
|
|
376
|
+
const paths = attachmentPaths.map((path) => `- ${JSON.stringify(path)}`).join('\n');
|
|
377
|
+
return `${input}\n\nIf an inline image in this message is unavailable, use the read tool on the corresponding copy below. Do not read it again when the inline image is already visible:\n${paths}`;
|
|
378
|
+
}
|
|
379
|
+
function hasMcpConfiguration(value) {
|
|
380
|
+
return value !== null && typeof value === 'object' && Object.keys(value).length > 0;
|
|
381
|
+
}
|
|
362
382
|
function openCodePart(part, planMode = false) {
|
|
363
383
|
const id = typeof part?.id === 'string' ? part.id : undefined;
|
|
364
384
|
if (id === undefined || typeof part?.type !== 'string')
|
|
@@ -415,14 +435,18 @@ function openCodePart(part, planMode = false) {
|
|
|
415
435
|
itemId,
|
|
416
436
|
kind: 'context_compaction',
|
|
417
437
|
status: 'COMPLETED',
|
|
418
|
-
payload: {
|
|
438
|
+
payload: {
|
|
439
|
+
phase: 'COMPLETED',
|
|
440
|
+
summary: 'Context compacted',
|
|
441
|
+
summaryCode: 'CONTEXT_COMPACTED'
|
|
442
|
+
}
|
|
419
443
|
};
|
|
420
444
|
if (part.type === 'patch')
|
|
421
445
|
return {
|
|
422
446
|
itemId,
|
|
423
|
-
kind: '
|
|
447
|
+
kind: 'tool_call',
|
|
424
448
|
status: 'COMPLETED',
|
|
425
|
-
payload:
|
|
449
|
+
payload: openCodePatchToolPayload(),
|
|
426
450
|
merge: true
|
|
427
451
|
};
|
|
428
452
|
if (part.type === 'step-start' || part.type === 'step-finish')
|