@myagentroam/node 0.1.8 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.js +5 -2
- 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/opencode-server.d.ts +1 -0
- package/dist/opencode-server.js +12 -0
- package/dist/runner/abstract-runner.d.ts +12 -2
- package/dist/runner/abstract-runner.js +76 -2
- package/dist/runner/claude/managed-run-controller.js +2 -1
- package/dist/runner/claude-code-runner.d.ts +1 -0
- package/dist/runner/claude-code-runner.js +14 -0
- package/dist/runner/codex/managed-run-controller.js +5 -3
- package/dist/runner/codex-runner.d.ts +16 -2
- package/dist/runner/codex-runner.js +215 -4
- package/dist/runner/opencode/managed-run-controller.js +13 -3
- 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-profiles.js +5 -25
- package/dist/runtime-command-detector.d.ts +3 -0
- package/dist/runtime-command-detector.js +78 -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/runner-service.d.ts +2 -0
- package/dist/service/runner-service.js +5 -2
- package/dist/service/session-lifecycle-service.js +7 -4
- package/dist/service/workbench-manifest-service.d.ts +2 -0
- package/dist/service/workspace-queue-workbench-service.d.ts +2 -0
- package/dist/service/workspace-queue-workbench-service.js +2 -1
- package/package.json +2 -2
package/dist/capabilities.js
CHANGED
|
@@ -3,6 +3,7 @@ import nodePackage from '../package.json' with { type: 'json' };
|
|
|
3
3
|
import { probeClaudeCode, probeClaudeCodeAsync } from './claude-agent-sdk.js';
|
|
4
4
|
import { probeCodex, probeCodexAsync } from './codex-app-server.js';
|
|
5
5
|
import { probeOpenCode, probeOpenCodeAsync } from './opencode-runtime.js';
|
|
6
|
+
import { detectRuntimeCommands } from './runtime-command-detector.js';
|
|
6
7
|
const nodeAppVersion = typeof nodePackage.version === 'string' ? nodePackage.version : 'unknown';
|
|
7
8
|
function reportedPlatform() {
|
|
8
9
|
return process.platform === 'win32' ? 'windows' : 'linux';
|
|
@@ -59,10 +60,11 @@ export async function detectCapabilitiesAsync(commands = {}) {
|
|
|
59
60
|
if (process.platform !== 'win32' && process.platform !== 'linux') {
|
|
60
61
|
throw new Error('NODE_PLATFORM_UNSUPPORTED');
|
|
61
62
|
}
|
|
62
|
-
const [codex, claudeCode, openCode] = await Promise.all([
|
|
63
|
+
const [codex, claudeCode, openCode, runtimeCommands] = await Promise.all([
|
|
63
64
|
probeCodexAsync(commands.codex),
|
|
64
65
|
probeClaudeCodeAsync(commands.claudeCode),
|
|
65
|
-
probeOpenCodeAsync(commands.openCode)
|
|
66
|
+
probeOpenCodeAsync(commands.openCode),
|
|
67
|
+
detectRuntimeCommands()
|
|
66
68
|
]);
|
|
67
69
|
return {
|
|
68
70
|
platform: reportedPlatform(),
|
|
@@ -71,7 +73,8 @@ export async function detectCapabilitiesAsync(commands = {}) {
|
|
|
71
73
|
nodeVersion: process.version,
|
|
72
74
|
metadata: {
|
|
73
75
|
osRelease: release(),
|
|
74
|
-
osVersion: version()
|
|
76
|
+
osVersion: version(),
|
|
77
|
+
runtimeCommands
|
|
75
78
|
},
|
|
76
79
|
codex: {
|
|
77
80
|
available: codex.available && codex.compatible,
|
|
@@ -22,6 +22,7 @@ export interface ClaudeQueryInput {
|
|
|
22
22
|
readonly onMessage: (message: SDKMessage) => void;
|
|
23
23
|
readonly onChannelReply?: (content: string) => void;
|
|
24
24
|
readonly environment?: Readonly<Record<string, string>>;
|
|
25
|
+
readonly mcpServers?: NonNullable<Options['mcpServers']>;
|
|
25
26
|
}
|
|
26
27
|
/** Image blocks accepted by the Claude Agent SDK message input. */
|
|
27
28
|
export interface ClaudeImageAttachment {
|
package/dist/claude-agent-sdk.js
CHANGED
|
@@ -66,7 +66,14 @@ export class ClaudeAgentSdkAdapter {
|
|
|
66
66
|
settings: { showThinkingSummaries: true },
|
|
67
67
|
abortController,
|
|
68
68
|
canUseTool: input.onPermission,
|
|
69
|
-
...(
|
|
69
|
+
...(input.mcpServers !== undefined || channelEnabled
|
|
70
|
+
? {
|
|
71
|
+
mcpServers: {
|
|
72
|
+
...(input.mcpServers ?? {}),
|
|
73
|
+
...(channelEnabled ? { myagentroam: channelMcpServer(input) } : {})
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
: {})
|
|
70
77
|
}
|
|
71
78
|
});
|
|
72
79
|
const completed = consume(stream, input.onMessage);
|
|
@@ -49,6 +49,7 @@ export interface CodexRunConfiguration {
|
|
|
49
49
|
readonly access?: string | null;
|
|
50
50
|
readonly serviceTier?: 'fast';
|
|
51
51
|
readonly collaborationMode?: 'default' | 'plan';
|
|
52
|
+
readonly mcpServers?: unknown;
|
|
52
53
|
}
|
|
53
54
|
/** Public App Server turn inputs supported by the Workbench Composer. */
|
|
54
55
|
export type CodexComposerInput = {
|
|
@@ -84,6 +85,11 @@ export declare class CodexAppServerClient {
|
|
|
84
85
|
startThread(cwd: string, configuration?: CodexRunConfiguration): Promise<unknown>;
|
|
85
86
|
listThreads(): Promise<unknown>;
|
|
86
87
|
listCollaborationModes(): Promise<unknown>;
|
|
88
|
+
listModels(params: {
|
|
89
|
+
readonly cursor: string | null;
|
|
90
|
+
readonly limit: number;
|
|
91
|
+
readonly includeHidden: boolean;
|
|
92
|
+
}): Promise<unknown>;
|
|
87
93
|
readThread(threadId: string, includeTurns?: boolean): Promise<unknown>;
|
|
88
94
|
forkThread(threadId: string, cwd: string, lastTurnId: string, configuration?: CodexRunConfiguration): Promise<unknown>;
|
|
89
95
|
setThreadName(threadId: string, name: string): Promise<unknown>;
|
package/dist/codex-app-server.js
CHANGED
|
@@ -170,6 +170,9 @@ export class CodexAppServerClient {
|
|
|
170
170
|
listCollaborationModes() {
|
|
171
171
|
return this.request('collaborationMode/list', {});
|
|
172
172
|
}
|
|
173
|
+
listModels(params) {
|
|
174
|
+
return this.request('model/list', params);
|
|
175
|
+
}
|
|
173
176
|
readThread(threadId, includeTurns = true) {
|
|
174
177
|
return this.request('thread/read', { threadId, includeTurns });
|
|
175
178
|
}
|
|
@@ -263,6 +266,9 @@ export class CodexAppServerClient {
|
|
|
263
266
|
function codexThreadConfiguration(cwd, configuration) {
|
|
264
267
|
return {
|
|
265
268
|
...modelOption(configuration.model),
|
|
269
|
+
...(configuration.mcpServers === undefined
|
|
270
|
+
? {}
|
|
271
|
+
: { config: { mcp_servers: configuration.mcpServers } }),
|
|
266
272
|
...codexPermissionConfiguration(cwd, configuration.access, 'thread')
|
|
267
273
|
};
|
|
268
274
|
}
|
|
@@ -16,6 +16,7 @@ export interface NodeConnectorOptions {
|
|
|
16
16
|
readonly fakeRunner?: FakeRunner;
|
|
17
17
|
readonly fakeScript?: (input: unknown, runId: string) => readonly FakeRunnerStep[];
|
|
18
18
|
readonly codexClient?: CodexAppServerClient;
|
|
19
|
+
readonly codexProfileClientFactory?: () => CodexAppServerClient;
|
|
19
20
|
readonly claudeClient?: ClaudeAgentSdkAdapter;
|
|
20
21
|
readonly openCodeClient?: OpenCodeServerClient;
|
|
21
22
|
readonly database?: NodeDatabase;
|
package/dist/connector.js
CHANGED
|
@@ -19,6 +19,7 @@ import { OpenCodeRunner } from './runner/opencode-runner.js';
|
|
|
19
19
|
import { OpenCodeManagedRunController } from './runner/opencode/managed-run-controller.js';
|
|
20
20
|
import { NodeOperationRouter } from './connector/node-operation-router.js';
|
|
21
21
|
import { SkillNodeOperationService } from './service/skill-node-operation-service.js';
|
|
22
|
+
import { McpNodeOperationService } from './service/mcp-node-operation-service.js';
|
|
22
23
|
import { RunnerService } from './service/runner-service.js';
|
|
23
24
|
import { TerminalService } from './service/terminal-service.js';
|
|
24
25
|
import { WorkspaceService } from './service/workspace-service.js';
|
|
@@ -230,7 +231,7 @@ export class NodeConnector {
|
|
|
230
231
|
options.fakeRunner === undefined ? undefined : new FakeTestRunner(options.fakeRunner);
|
|
231
232
|
this.fakeScript = options.fakeScript;
|
|
232
233
|
this.runAttachmentService = new RunAttachmentService((session) => this.runners.require(session.runner).nativeImageRoot(session));
|
|
233
|
-
this.codexClient = new CodexRunner(options.codexClient);
|
|
234
|
+
this.codexClient = new CodexRunner(options.codexClient, options.codexProfileClientFactory);
|
|
234
235
|
this.codexRunController = new CodexManagedRunController(this.codexClient, {
|
|
235
236
|
available: () => this.capabilities.codex.available,
|
|
236
237
|
intercepted: () => this.fakeRunner !== undefined,
|
|
@@ -404,7 +405,8 @@ export class NodeConnector {
|
|
|
404
405
|
control: (operation, payload) => this.controlMessages.handle(JSON.stringify(createEnvelope(operation, payload))),
|
|
405
406
|
stopped: () => this.stopped,
|
|
406
407
|
markInterrupted: (runId) => this.runState.interruptRequested.add(runId),
|
|
407
|
-
prepareInput: (runner, input, collaborationMode) => this.runners.require(runner).prepareMessageInput(input, collaborationMode)
|
|
408
|
+
prepareInput: (runner, input, collaborationMode) => this.runners.require(runner).prepareMessageInput(input, collaborationMode),
|
|
409
|
+
mcps: (workspaceId) => this.database?.effectiveMcpInstallations(workspaceId) ?? []
|
|
408
410
|
});
|
|
409
411
|
this.sessionIdentityService = new SessionIdentityService({
|
|
410
412
|
runtime: this.runtime,
|
|
@@ -714,6 +716,7 @@ export class NodeConnector {
|
|
|
714
716
|
throw new Error('NODE_CONFIG_UNAVAILABLE');
|
|
715
717
|
return this.config;
|
|
716
718
|
}).operations());
|
|
719
|
+
this.operationRouter.registerAll(new McpNodeOperationService(requireDatabase).operations());
|
|
717
720
|
this.operationRouter.register('node.metrics', () => ({
|
|
718
721
|
metrics: {
|
|
719
722
|
...this.metrics.snapshot(),
|
package/dist/database.d.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { DatabaseSync } from 'node:sqlite';
|
|
2
2
|
import { type AgentRunStatus, type RunnerDefaultConfiguration, type RunnerName } from '@myagentroam/protocol';
|
|
3
|
+
import { type McpCatalogEntry, type McpInstallationConfiguration, type McpRuntimeDescriptor } from '@myagentroam/protocol';
|
|
3
4
|
import type { NativeSessionHistory } from './native-session-history.js';
|
|
5
|
+
export interface NodeMcpInstallation {
|
|
6
|
+
readonly targetKind: 'NODE' | 'WORKSPACE';
|
|
7
|
+
readonly targetId: string;
|
|
8
|
+
readonly catalogEntryId: string;
|
|
9
|
+
readonly serverName: string;
|
|
10
|
+
readonly displayName: string;
|
|
11
|
+
readonly version: string;
|
|
12
|
+
readonly enabled: boolean;
|
|
13
|
+
readonly runtime: McpRuntimeDescriptor;
|
|
14
|
+
readonly configuration: McpInstallationConfiguration;
|
|
15
|
+
readonly installedAt: number;
|
|
16
|
+
}
|
|
4
17
|
export type RunRecoveryState = 'ACTIVE' | 'SUSPECT' | 'LOST';
|
|
5
18
|
export type ApprovalStatus = 'PENDING' | 'APPROVED' | 'REJECTED' | 'EXPIRED';
|
|
6
19
|
/** Whether a native session ID was created by this Node or merely discovered. */
|
|
@@ -153,6 +166,16 @@ export declare class NodeDatabase {
|
|
|
153
166
|
close(): void;
|
|
154
167
|
getRunnerDefaults(): readonly RunnerDefaultConfiguration[];
|
|
155
168
|
saveRunnerDefaults(input: RunnerDefaultConfiguration): RunnerDefaultConfiguration;
|
|
169
|
+
listMcpInstallations(targetKind: 'NODE' | 'WORKSPACE', targetId?: string): readonly NodeMcpInstallation[];
|
|
170
|
+
saveMcpInstallation(input: {
|
|
171
|
+
readonly targetKind: 'NODE' | 'WORKSPACE';
|
|
172
|
+
readonly targetId?: string;
|
|
173
|
+
readonly entry: McpCatalogEntry;
|
|
174
|
+
readonly enabled: boolean;
|
|
175
|
+
readonly configuration: NodeMcpInstallation['configuration'];
|
|
176
|
+
}): NodeMcpInstallation;
|
|
177
|
+
removeMcpInstallation(targetKind: 'NODE' | 'WORKSPACE', targetId: string, serverName: string): boolean;
|
|
178
|
+
effectiveMcpInstallations(workspaceId: string): readonly NodeMcpInstallation[];
|
|
156
179
|
/**
|
|
157
180
|
* Stores only terminal lifecycle metadata. PTY input/output must remain in
|
|
158
181
|
* the Node-local bounded replay buffer and must never be passed here.
|
package/dist/database.js
CHANGED
|
@@ -3,10 +3,12 @@ import { closeSync, existsSync, mkdirSync, openSync, rmSync } from 'node:fs';
|
|
|
3
3
|
import { dirname } from 'node:path';
|
|
4
4
|
import { DatabaseSync } from 'node:sqlite';
|
|
5
5
|
import { runnerDefaultConfigurationSchema } from '@myagentroam/protocol';
|
|
6
|
+
import { mcpCatalogEntrySchema, mcpInstallationConfigurationSchema, mcpRuntimeDescriptorSchema } from '@myagentroam/protocol';
|
|
6
7
|
import { assertNodeSchema, NODE_SCHEMA_V1 } from './migrations/v001.js';
|
|
7
8
|
import { assertNodeSchemaV2, migrateNodeSchemaV2 } from './migrations/v002.js';
|
|
8
|
-
import { assertNodeSchemaV3, migrateNodeSchemaV3
|
|
9
|
-
|
|
9
|
+
import { assertNodeSchemaV3, migrateNodeSchemaV3 } from './migrations/v003.js';
|
|
10
|
+
import { assertNodeSchemaV4, migrateNodeSchemaV4, NODE_SCHEMA_VERSION_V4 } from './migrations/v004.js';
|
|
11
|
+
const NODE_SCHEMA_VERSION = NODE_SCHEMA_VERSION_V4;
|
|
10
12
|
export class NodeDatabase {
|
|
11
13
|
now;
|
|
12
14
|
activeWorkspaceLeaseCount() {
|
|
@@ -63,6 +65,57 @@ export class NodeDatabase {
|
|
|
63
65
|
.run(input.runner, input.model, input.effort, input.access);
|
|
64
66
|
return input;
|
|
65
67
|
}
|
|
68
|
+
listMcpInstallations(targetKind, targetId = '') {
|
|
69
|
+
const rows = this.raw
|
|
70
|
+
.prepare(`SELECT target_kind,target_id,catalog_entry_id,server_name,display_name,version,
|
|
71
|
+
enabled,runtime,configuration,installed_at
|
|
72
|
+
FROM mcp_installations WHERE target_kind=? AND target_id=? ORDER BY display_name,server_name`)
|
|
73
|
+
.all(targetKind, targetKind === 'NODE' ? '' : targetId);
|
|
74
|
+
return rows.map(mcpInstallation);
|
|
75
|
+
}
|
|
76
|
+
saveMcpInstallation(input) {
|
|
77
|
+
const entry = mcpCatalogEntrySchema.parse(input.entry);
|
|
78
|
+
const configuration = mcpInstallationConfigurationSchema.parse(input.configuration);
|
|
79
|
+
const targetId = input.targetKind === 'NODE' ? '' : (input.targetId ?? '');
|
|
80
|
+
if (input.targetKind === 'WORKSPACE' && this.getWorkspace(targetId) === undefined)
|
|
81
|
+
throw new Error('WORKSPACE_NOT_FOUND');
|
|
82
|
+
const installedAt = this.now();
|
|
83
|
+
this.raw
|
|
84
|
+
.prepare(`INSERT INTO mcp_installations
|
|
85
|
+
(target_kind,target_id,catalog_entry_id,server_name,display_name,version,enabled,runtime,configuration,installed_at)
|
|
86
|
+
VALUES (?,?,?,?,?,?,?,?,?,?)
|
|
87
|
+
ON CONFLICT(target_kind,target_id,server_name) DO UPDATE SET
|
|
88
|
+
catalog_entry_id=excluded.catalog_entry_id,display_name=excluded.display_name,
|
|
89
|
+
version=excluded.version,enabled=excluded.enabled,runtime=excluded.runtime,
|
|
90
|
+
configuration=excluded.configuration,installed_at=excluded.installed_at`)
|
|
91
|
+
.run(input.targetKind, targetId, entry.id, entry.serverName, entry.displayName, entry.version, input.enabled ? 1 : 0, JSON.stringify(entry.runtime), JSON.stringify(configuration), installedAt);
|
|
92
|
+
return {
|
|
93
|
+
targetKind: input.targetKind,
|
|
94
|
+
targetId,
|
|
95
|
+
catalogEntryId: entry.id,
|
|
96
|
+
serverName: entry.serverName,
|
|
97
|
+
displayName: entry.displayName,
|
|
98
|
+
version: entry.version,
|
|
99
|
+
enabled: input.enabled,
|
|
100
|
+
runtime: entry.runtime,
|
|
101
|
+
configuration,
|
|
102
|
+
installedAt
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
removeMcpInstallation(targetKind, targetId, serverName) {
|
|
106
|
+
return (Number(this.raw
|
|
107
|
+
.prepare('DELETE FROM mcp_installations WHERE target_kind=? AND target_id=? AND server_name=?')
|
|
108
|
+
.run(targetKind, targetKind === 'NODE' ? '' : targetId, serverName).changes) > 0);
|
|
109
|
+
}
|
|
110
|
+
effectiveMcpInstallations(workspaceId) {
|
|
111
|
+
const effective = new Map(this.listMcpInstallations('NODE').map((installation) => [
|
|
112
|
+
installation.serverName,
|
|
113
|
+
installation
|
|
114
|
+
]));
|
|
115
|
+
for (const installation of this.listMcpInstallations('WORKSPACE', workspaceId))
|
|
116
|
+
effective.set(installation.serverName, installation);
|
|
117
|
+
return [...effective.values()].filter((installation) => installation.enabled);
|
|
118
|
+
}
|
|
66
119
|
/**
|
|
67
120
|
* Stores only terminal lifecycle metadata. PTY input/output must remain in
|
|
68
121
|
* the Node-local bounded replay buffer and must never be passed here.
|
|
@@ -138,6 +191,9 @@ export class NodeDatabase {
|
|
|
138
191
|
// record itself never represents a filesystem deletion.
|
|
139
192
|
this.raw.prepare('DELETE FROM agent_sessions WHERE workspace_id = ?').run(id);
|
|
140
193
|
this.raw.prepare('DELETE FROM legacy_agent_sessions WHERE workspace_id = ?').run(id);
|
|
194
|
+
this.raw
|
|
195
|
+
.prepare("DELETE FROM mcp_installations WHERE target_kind='WORKSPACE' AND target_id = ?")
|
|
196
|
+
.run(id);
|
|
141
197
|
this.raw.prepare('DELETE FROM workspaces WHERE id = ?').run(id);
|
|
142
198
|
this.raw.exec('COMMIT');
|
|
143
199
|
return current;
|
|
@@ -1028,6 +1084,8 @@ function migrate(database) {
|
|
|
1028
1084
|
database.exec('COMMIT');
|
|
1029
1085
|
migrateNodeSchemaV3(database);
|
|
1030
1086
|
assertNodeSchemaV3(database);
|
|
1087
|
+
migrateNodeSchemaV4(database);
|
|
1088
|
+
assertNodeSchemaV4(database);
|
|
1031
1089
|
return;
|
|
1032
1090
|
}
|
|
1033
1091
|
catch (error) {
|
|
@@ -1066,6 +1124,8 @@ function migrate(database) {
|
|
|
1066
1124
|
assertNodeSchemaV2(database);
|
|
1067
1125
|
migrateNodeSchemaV3(database);
|
|
1068
1126
|
assertNodeSchemaV3(database);
|
|
1127
|
+
migrateNodeSchemaV4(database);
|
|
1128
|
+
assertNodeSchemaV4(database);
|
|
1069
1129
|
return;
|
|
1070
1130
|
}
|
|
1071
1131
|
if (current === 0) {
|
|
@@ -1092,7 +1152,29 @@ function migrate(database) {
|
|
|
1092
1152
|
.get();
|
|
1093
1153
|
if (numberValue(afterV2, 'version') === 2)
|
|
1094
1154
|
migrateNodeSchemaV3(database);
|
|
1155
|
+
const afterV3 = database
|
|
1156
|
+
.prepare('SELECT MAX(version) AS version FROM node_schema_migrations')
|
|
1157
|
+
.get();
|
|
1158
|
+
if (numberValue(afterV3, 'version') === 3)
|
|
1159
|
+
migrateNodeSchemaV4(database);
|
|
1095
1160
|
assertNodeSchemaV3(database);
|
|
1161
|
+
assertNodeSchemaV4(database);
|
|
1162
|
+
}
|
|
1163
|
+
function mcpInstallation(row) {
|
|
1164
|
+
const runtime = mcpRuntimeDescriptorSchema.parse(JSON.parse(stringValue(row, 'runtime')));
|
|
1165
|
+
const configuration = mcpInstallationConfigurationSchema.parse(JSON.parse(stringValue(row, 'configuration')));
|
|
1166
|
+
return {
|
|
1167
|
+
targetKind: stringValue(row, 'target_kind'),
|
|
1168
|
+
targetId: stringValue(row, 'target_id'),
|
|
1169
|
+
catalogEntryId: stringValue(row, 'catalog_entry_id'),
|
|
1170
|
+
serverName: stringValue(row, 'server_name'),
|
|
1171
|
+
displayName: stringValue(row, 'display_name'),
|
|
1172
|
+
version: stringValue(row, 'version'),
|
|
1173
|
+
enabled: numberValue(row, 'enabled') === 1,
|
|
1174
|
+
runtime,
|
|
1175
|
+
configuration,
|
|
1176
|
+
installedAt: numberValue(row, 'installed_at')
|
|
1177
|
+
};
|
|
1096
1178
|
}
|
|
1097
1179
|
function sessionMetadata(row) {
|
|
1098
1180
|
const raw = JSON.parse(stringValue(row, 'metadata'));
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const NODE_SCHEMA_VERSION_V4 = 4;
|
|
2
|
+
export function migrateNodeSchemaV4(database) {
|
|
3
|
+
database.exec('BEGIN IMMEDIATE');
|
|
4
|
+
try {
|
|
5
|
+
database.exec(`CREATE TABLE mcp_installations (
|
|
6
|
+
target_kind TEXT NOT NULL CHECK (target_kind IN ('NODE','WORKSPACE')),
|
|
7
|
+
target_id TEXT NOT NULL,
|
|
8
|
+
catalog_entry_id TEXT NOT NULL,
|
|
9
|
+
server_name TEXT NOT NULL,
|
|
10
|
+
display_name TEXT NOT NULL,
|
|
11
|
+
version TEXT NOT NULL,
|
|
12
|
+
enabled INTEGER NOT NULL CHECK (enabled IN (0,1)),
|
|
13
|
+
runtime TEXT NOT NULL CHECK (json_valid(runtime)),
|
|
14
|
+
configuration TEXT NOT NULL CHECK (json_valid(configuration)),
|
|
15
|
+
installed_at INTEGER NOT NULL,
|
|
16
|
+
PRIMARY KEY (target_kind,target_id,server_name),
|
|
17
|
+
CHECK ((target_kind='NODE' AND target_id='') OR (target_kind='WORKSPACE' AND target_id<>''))
|
|
18
|
+
) STRICT;`);
|
|
19
|
+
database
|
|
20
|
+
.prepare('INSERT INTO node_schema_migrations (version, applied_at) VALUES (?, ?)')
|
|
21
|
+
.run(NODE_SCHEMA_VERSION_V4, Date.now());
|
|
22
|
+
database.exec('COMMIT');
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
database.exec('ROLLBACK');
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function assertNodeSchemaV4(database) {
|
|
30
|
+
const row = database
|
|
31
|
+
.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='mcp_installations'")
|
|
32
|
+
.get();
|
|
33
|
+
if (typeof row?.sql !== 'string' || !row.sql.includes("target_kind IN ('NODE','WORKSPACE')"))
|
|
34
|
+
throw new Error('NODE_DATABASE_SCHEMA_INVALID:mcp_installations');
|
|
35
|
+
}
|
|
@@ -16,6 +16,7 @@ export declare class OpenCodeServerClient {
|
|
|
16
16
|
start(): Promise<void>;
|
|
17
17
|
private startServer;
|
|
18
18
|
providers(directory: string): Promise<OpenCodeProviderCatalog>;
|
|
19
|
+
configureMcp(directory: string, mcp: unknown): Promise<void>;
|
|
19
20
|
sessions(directory: string, limit?: number): Promise<readonly unknown[]>;
|
|
20
21
|
sessionStatus(directory: string): Promise<Readonly<Record<string, unknown>>>;
|
|
21
22
|
createSession(directory: string, title?: string): Promise<unknown>;
|
package/dist/opencode-server.js
CHANGED
|
@@ -77,6 +77,18 @@ export class OpenCodeServerClient {
|
|
|
77
77
|
const client = await this.requireClient();
|
|
78
78
|
return unwrap(await client.provider.list({ directory }));
|
|
79
79
|
}
|
|
80
|
+
async configureMcp(directory, mcp) {
|
|
81
|
+
const client = await this.requireClient();
|
|
82
|
+
if (typeof mcp !== 'object' || mcp === null || Array.isArray(mcp))
|
|
83
|
+
return;
|
|
84
|
+
for (const [name, config] of Object.entries(mcp)) {
|
|
85
|
+
await client.mcp.add({
|
|
86
|
+
directory,
|
|
87
|
+
name,
|
|
88
|
+
config: config
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
80
92
|
async sessions(directory, limit = 100) {
|
|
81
93
|
const client = await this.requireClient();
|
|
82
94
|
return unwrap(await client.session.list({ directory, limit }));
|
|
@@ -21,14 +21,15 @@ export declare abstract class AbstractRunner<TName extends RegisteredRunnerName
|
|
|
21
21
|
abstract profile(capabilities: NodeCapabilities): RunnerProfile | undefined;
|
|
22
22
|
refreshProfile(capabilities: NodeCapabilities, workspace?: NodeWorkspace, environment?: Readonly<Record<string, string>>): Promise<RunnerProfile | undefined>;
|
|
23
23
|
abstract available(capabilities: NodeCapabilities): boolean;
|
|
24
|
-
abstract defaultConfiguration(workspace?: NodeWorkspace): RunnerDefaultConfiguration;
|
|
25
|
-
supportsConfiguration(configuration: RunnerDefaultConfiguration, workspace?: NodeWorkspace): boolean;
|
|
24
|
+
abstract defaultConfiguration(workspace?: NodeWorkspace, environment?: Readonly<Record<string, string>>): RunnerDefaultConfiguration;
|
|
25
|
+
supportsConfiguration(configuration: RunnerDefaultConfiguration, workspace?: NodeWorkspace, environment?: Readonly<Record<string, string>>): boolean;
|
|
26
26
|
protected abstract profileForValidation(): RunnerProfile;
|
|
27
27
|
abstract resumeExternal(session: NodeAgentSession, context: ExternalResumeContext, force?: boolean): Promise<ExternalResumeResult>;
|
|
28
28
|
abstract discoverSessions(context: RunnerDiscoveryContext, workspace?: NodeWorkspace): Promise<readonly NodeAgentSession[]>;
|
|
29
29
|
abstract readContextUsage(session: NodeAgentSession): Promise<RunnerContextUsage | undefined>;
|
|
30
30
|
presentSession(session: NodeAgentSession, capabilities: NodeCapabilities, context: RunnerSessionPresentationContext): RunnerSessionPresentation;
|
|
31
31
|
prepareMessageInput(input: string, collaborationMode: 'default' | 'plan'): string;
|
|
32
|
+
mcpConfiguration(rawInstallations: unknown, secretEnvironment: Readonly<Record<string, string>>): unknown;
|
|
32
33
|
channelToken(sessionId: string): string | undefined;
|
|
33
34
|
deliverChannel(sessionId: string, messageId: string, senderId: string, token: string, content: string, notify: (runId: string, messageId: string, delivery: ChannelDelivery) => void): ChannelDelivery;
|
|
34
35
|
executeGlobalCommand(commandId: string, input: string): unknown;
|
|
@@ -66,6 +67,15 @@ export declare abstract class AbstractRunner<TName extends RegisteredRunnerName
|
|
|
66
67
|
readonly secretEnvironment: Readonly<Record<string, string>>;
|
|
67
68
|
}): Promise<unknown>;
|
|
68
69
|
}
|
|
70
|
+
export interface ResolvedMcpServer {
|
|
71
|
+
readonly name: string;
|
|
72
|
+
readonly transport: 'STDIO' | 'HTTP';
|
|
73
|
+
readonly command?: string;
|
|
74
|
+
readonly args?: readonly string[];
|
|
75
|
+
readonly environment?: Readonly<Record<string, string>>;
|
|
76
|
+
readonly url?: string;
|
|
77
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
78
|
+
}
|
|
69
79
|
export type RunnerCancellationIntent = 'CANCEL' | 'INTERRUPT';
|
|
70
80
|
export interface RunnerControl {
|
|
71
81
|
start(envelope: NodeEnvelope): void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mcpRuntimeDescriptorSchema } from '@myagentroam/protocol';
|
|
1
2
|
/**
|
|
2
3
|
* Stable lifecycle boundary for every Node Runner implementation.
|
|
3
4
|
*
|
|
@@ -15,8 +16,8 @@ export class AbstractRunner {
|
|
|
15
16
|
void [workspace, environment];
|
|
16
17
|
return this.profile(capabilities);
|
|
17
18
|
}
|
|
18
|
-
supportsConfiguration(configuration, workspace) {
|
|
19
|
-
void workspace;
|
|
19
|
+
supportsConfiguration(configuration, workspace, environment) {
|
|
20
|
+
void [workspace, environment];
|
|
20
21
|
const profile = this.profileForValidation();
|
|
21
22
|
const model = profile.models.find((candidate) => candidate.id === configuration.model);
|
|
22
23
|
return (configuration.runner === this.name &&
|
|
@@ -32,6 +33,9 @@ export class AbstractRunner {
|
|
|
32
33
|
void collaborationMode;
|
|
33
34
|
return input;
|
|
34
35
|
}
|
|
36
|
+
mcpConfiguration(rawInstallations, secretEnvironment) {
|
|
37
|
+
return resolvedMcpServers(rawInstallations, secretEnvironment);
|
|
38
|
+
}
|
|
35
39
|
channelToken(sessionId) {
|
|
36
40
|
void sessionId;
|
|
37
41
|
return undefined;
|
|
@@ -143,6 +147,76 @@ export class AbstractRunner {
|
|
|
143
147
|
return this.commandControl.execute(session, command, input, context);
|
|
144
148
|
}
|
|
145
149
|
}
|
|
150
|
+
function resolvedMcpServers(raw, secrets) {
|
|
151
|
+
if (!Array.isArray(raw))
|
|
152
|
+
return [];
|
|
153
|
+
const result = [];
|
|
154
|
+
for (const value of raw) {
|
|
155
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
156
|
+
continue;
|
|
157
|
+
const record = value;
|
|
158
|
+
if (typeof record['serverName'] !== 'string' || record['enabled'] !== true)
|
|
159
|
+
continue;
|
|
160
|
+
const runtime = mcpRuntimeDescriptorSchema.safeParse(record['runtime']);
|
|
161
|
+
if (!runtime.success)
|
|
162
|
+
continue;
|
|
163
|
+
const configuration = isRecord(record['configuration']) ? record['configuration'] : {};
|
|
164
|
+
const resolveReferences = (candidate) => {
|
|
165
|
+
if (!isRecord(candidate))
|
|
166
|
+
return {};
|
|
167
|
+
const result = {};
|
|
168
|
+
for (const [name, reference] of Object.entries(candidate)) {
|
|
169
|
+
if (!isRecord(reference) || typeof reference['secretName'] !== 'string')
|
|
170
|
+
return undefined;
|
|
171
|
+
const secret = secrets[reference['secretName']];
|
|
172
|
+
if (secret === undefined)
|
|
173
|
+
return undefined;
|
|
174
|
+
result[name] = secret;
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
};
|
|
178
|
+
if (runtime.data.transport === 'STDIO') {
|
|
179
|
+
const runtimeEnvironment = resolveReferences(runtime.data.environment);
|
|
180
|
+
const configuredEnvironment = resolveReferences(configuration['environment']);
|
|
181
|
+
if (runtimeEnvironment === undefined || configuredEnvironment === undefined)
|
|
182
|
+
continue;
|
|
183
|
+
result.push({
|
|
184
|
+
name: record['serverName'],
|
|
185
|
+
transport: 'STDIO',
|
|
186
|
+
command: runtime.data.command,
|
|
187
|
+
args: [
|
|
188
|
+
...runtime.data.args,
|
|
189
|
+
...(Array.isArray(configuration['arguments'])
|
|
190
|
+
? configuration['arguments'].filter((item) => typeof item === 'string')
|
|
191
|
+
: [])
|
|
192
|
+
],
|
|
193
|
+
environment: {
|
|
194
|
+
...runtimeEnvironment,
|
|
195
|
+
...configuredEnvironment
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
const runtimeHeaders = resolveReferences(runtime.data.headers);
|
|
201
|
+
const configuredHeaders = resolveReferences(configuration['headers']);
|
|
202
|
+
if (runtimeHeaders === undefined || configuredHeaders === undefined)
|
|
203
|
+
continue;
|
|
204
|
+
result.push({
|
|
205
|
+
name: record['serverName'],
|
|
206
|
+
transport: 'HTTP',
|
|
207
|
+
url: runtime.data.url,
|
|
208
|
+
headers: {
|
|
209
|
+
...runtimeHeaders,
|
|
210
|
+
...configuredHeaders
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
function isRecord(value) {
|
|
218
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
219
|
+
}
|
|
146
220
|
export class RunnerCapabilityError extends Error {
|
|
147
221
|
runner;
|
|
148
222
|
capability;
|
|
@@ -27,7 +27,7 @@ export class ClaudeManagedRunController {
|
|
|
27
27
|
detail: '后续消息将以标签式规划模式运行,只读规划并输出计划卡。',
|
|
28
28
|
closable: true,
|
|
29
29
|
closeInput: '/plan',
|
|
30
|
-
continuation: { label: '
|
|
30
|
+
continuation: { label: '执行', prompt: '请根据上述计划开始实施。' }
|
|
31
31
|
});
|
|
32
32
|
return { command: command.id, states: this.host.commandStates(session.id) };
|
|
33
33
|
}
|
|
@@ -138,6 +138,7 @@ export class ClaudeManagedRunController {
|
|
|
138
138
|
...(typeof payload.effort === 'string' ? { effort: payload.effort } : {}),
|
|
139
139
|
...(typeof payload.access === 'string' ? { access: payload.access } : {}),
|
|
140
140
|
environment: secretEnvironment,
|
|
141
|
+
mcpServers: this.runner.mcpConfiguration(payload.mcpInstallations, secretEnvironment),
|
|
141
142
|
...(attachments.length === 0
|
|
142
143
|
? {}
|
|
143
144
|
: {
|
|
@@ -17,6 +17,7 @@ export declare class ClaudeCodeRunner extends AbstractRunner<'claude-code'> {
|
|
|
17
17
|
available(capabilities: NodeCapabilities): boolean;
|
|
18
18
|
protected profileForValidation(): RunnerProfile;
|
|
19
19
|
defaultConfiguration(): RunnerDefaultConfiguration;
|
|
20
|
+
mcpConfiguration(raw: unknown, secrets: Readonly<Record<string, string>>): unknown;
|
|
20
21
|
resumeExternal(external: NodeAgentSession, context: ExternalResumeContext): Promise<ExternalResumeResult>;
|
|
21
22
|
discoverSessions(context: RunnerDiscoveryContext, workspace?: NodeWorkspace): Promise<readonly NodeAgentSession[]>;
|
|
22
23
|
readContextUsage(session: NodeAgentSession): Promise<import("../native-session-history.js").NativeClaudeContextUsage | undefined>;
|
|
@@ -45,6 +45,20 @@ export class ClaudeCodeRunner extends AbstractRunner {
|
|
|
45
45
|
access: 'default'
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
|
+
mcpConfiguration(raw, secrets) {
|
|
49
|
+
const servers = super.mcpConfiguration(raw, secrets);
|
|
50
|
+
return Object.fromEntries(servers.map((server) => [
|
|
51
|
+
server.name,
|
|
52
|
+
server.transport === 'STDIO'
|
|
53
|
+
? {
|
|
54
|
+
type: 'stdio',
|
|
55
|
+
command: server.command,
|
|
56
|
+
args: [...(server.args ?? [])],
|
|
57
|
+
env: { ...(server.environment ?? {}) }
|
|
58
|
+
}
|
|
59
|
+
: { type: 'http', url: server.url, headers: { ...(server.headers ?? {}) } }
|
|
60
|
+
]));
|
|
61
|
+
}
|
|
48
62
|
async resumeExternal(external, context) {
|
|
49
63
|
if (external.runner !== this.name ||
|
|
50
64
|
external.nativeControl !== 'EXTERNAL' ||
|
|
@@ -35,7 +35,7 @@ export class CodexManagedRunController {
|
|
|
35
35
|
detail: '后续消息将以协作规划模式运行。',
|
|
36
36
|
closable: true,
|
|
37
37
|
closeInput: '/plan',
|
|
38
|
-
continuation: { label: '
|
|
38
|
+
continuation: { label: '执行', prompt: '请根据上述计划开始实施。' }
|
|
39
39
|
});
|
|
40
40
|
return { command: command.id, states: this.host.commandStates(session.id) };
|
|
41
41
|
}
|
|
@@ -325,7 +325,8 @@ export class CodexManagedRunController {
|
|
|
325
325
|
effort: payload.effort,
|
|
326
326
|
access: payload.access,
|
|
327
327
|
collaborationMode: payload.collaborationMode,
|
|
328
|
-
serviceTier: payload.serviceTier
|
|
328
|
+
serviceTier: payload.serviceTier,
|
|
329
|
+
mcpServers: this.runner.mcpConfiguration(payload.mcpInstallations, secretEnvironment)
|
|
329
330
|
}, attachments);
|
|
330
331
|
}
|
|
331
332
|
async run(payload, attachments) {
|
|
@@ -340,7 +341,8 @@ export class CodexManagedRunController {
|
|
|
340
341
|
const configuration = {
|
|
341
342
|
...(typeof payload.model === 'string' ? { model: payload.model } : {}),
|
|
342
343
|
...(typeof payload.effort === 'string' ? { effort: payload.effort } : {}),
|
|
343
|
-
...(typeof payload.access === 'string' ? { access: payload.access } : {})
|
|
344
|
+
...(typeof payload.access === 'string' ? { access: payload.access } : {}),
|
|
345
|
+
...(payload.mcpServers === undefined ? {} : { mcpServers: payload.mcpServers })
|
|
344
346
|
};
|
|
345
347
|
const threadResult = typeof payload.externalSessionId === 'string'
|
|
346
348
|
? await this.runner.resumeThread(payload.externalSessionId, cwd, configuration)
|
|
@@ -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;
|
|
@@ -35,7 +47,9 @@ export declare class CodexRunner extends AbstractRunner<'codex'> {
|
|
|
35
47
|
nativeImageRoot(session: NodeAgentSession): string | undefined;
|
|
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>;
|