@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
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.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export declare class NodeConnector {
|
|
|
27
27
|
private readonly sessionIdentityService;
|
|
28
28
|
private readonly nativeSessionProjectionService;
|
|
29
29
|
private readonly conversationHistoryService;
|
|
30
|
+
private readonly conversationSegmentService;
|
|
30
31
|
private readonly runnerInteractionService;
|
|
31
32
|
private readonly runnerChannelMessages;
|
|
32
33
|
private readonly managedRunnerSessions;
|
|
@@ -78,6 +79,7 @@ export declare class NodeConnector {
|
|
|
78
79
|
private readonly handleMessage;
|
|
79
80
|
private readonly emitRunEvent;
|
|
80
81
|
private readonly emitConversationItem;
|
|
82
|
+
private readonly emitConversationTurn;
|
|
81
83
|
private readonly emitWorkbenchEvent;
|
|
82
84
|
private readonly discoverWorkspaceSessions;
|
|
83
85
|
private readonly invalidateWorkspaceWatchTopics;
|
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';
|
|
@@ -43,6 +44,7 @@ import { WorkspaceQueueWorkbenchService } from './service/workspace-queue-workbe
|
|
|
43
44
|
import { SessionIdentityService } from './service/session-identity-service.js';
|
|
44
45
|
import { NativeSessionProjectionService } from './service/native-session-projection-service.js';
|
|
45
46
|
import { ConversationHistoryService, hasManagedActiveRun } from './service/conversation-history-service.js';
|
|
47
|
+
import { ConversationSegmentService, conversationSegmentIdentityForItem, conversationSegments } from './service/conversation-segment-service.js';
|
|
46
48
|
import { RunnerInteractionService } from './service/runner-interaction-service.js';
|
|
47
49
|
import { SessionLifecycleService } from './service/session-lifecycle-service.js';
|
|
48
50
|
import { ExternalSessionResumeService } from './service/external-session-resume-service.js';
|
|
@@ -50,7 +52,7 @@ import { SessionMessageService } from './service/session-message-service.js';
|
|
|
50
52
|
import { SessionCatalogService } from './service/session-catalog-service.js';
|
|
51
53
|
import { SessionCommandService } from './service/session-command-service.js';
|
|
52
54
|
import { SessionPresentationService } from './service/session-presentation-service.js';
|
|
53
|
-
import { isRunnerImageAttachment, parseRunnerImageAttachments } from './runner/codex/conversation-parser.js';
|
|
55
|
+
import { isPlainRecord, isRunnerImageAttachment, parseRunnerImageAttachments } from './runner/codex/conversation-parser.js';
|
|
54
56
|
export { nodeConnectEndpoint, validatedCapabilities } from './runner/codex/conversation-parser.js';
|
|
55
57
|
import { NodeControlChannel } from './service/node-control-channel.js';
|
|
56
58
|
import { NodeControlMessageService } from './service/node-control-message-service.js';
|
|
@@ -113,6 +115,7 @@ export class NodeConnector {
|
|
|
113
115
|
sessionIdentityService;
|
|
114
116
|
nativeSessionProjectionService;
|
|
115
117
|
conversationHistoryService;
|
|
118
|
+
conversationSegmentService;
|
|
116
119
|
runnerInteractionService;
|
|
117
120
|
runnerChannelMessages;
|
|
118
121
|
managedRunnerSessions;
|
|
@@ -172,7 +175,12 @@ export class NodeConnector {
|
|
|
172
175
|
isBootstrapSession: (sessionId) => this.sessionIdentityService.bootstrapIds.has(sessionId),
|
|
173
176
|
presentSession: (session) => this.sessionPresentationService.present(session),
|
|
174
177
|
rejectBootstrap: (sessionId, code) => this.sessionIdentityService.reject(sessionId, code),
|
|
175
|
-
emitWorkbench: (category, payload) =>
|
|
178
|
+
emitWorkbench: (category, payload) => {
|
|
179
|
+
if (category === 'conversation' && isPlainRecord(payload) && 'turn' in payload)
|
|
180
|
+
this.emitConversationTurn(payload.turn);
|
|
181
|
+
else
|
|
182
|
+
this.emitWorkbenchEvent(category, payload);
|
|
183
|
+
},
|
|
176
184
|
cleanupAttachments: (runId) => this.runAttachmentService.cleanup(runId),
|
|
177
185
|
expireImages: (runId) => this.runAttachmentService.expire(runId),
|
|
178
186
|
scheduleContextRefresh: (sessionId) => this.sessionContextService.schedule(sessionId),
|
|
@@ -189,8 +197,9 @@ export class NodeConnector {
|
|
|
189
197
|
refreshActivity: (session) => this.conversationHistoryService.refreshExternalActivity(session),
|
|
190
198
|
present: (session) => this.sessionPresentationService.present(session),
|
|
191
199
|
readPage: (session, limit) => this.conversationHistoryService.read(session, { limit }),
|
|
200
|
+
projectInitialPage: (session, page, _unit, limit) => this.conversationSegmentService.projectInitial(session, page, limit),
|
|
192
201
|
emitSession: (session) => this.emitWorkbenchEvent('session', { session }),
|
|
193
|
-
emitTurn: (turn) => this.
|
|
202
|
+
emitTurn: (turn) => this.emitConversationTurn(turn)
|
|
194
203
|
});
|
|
195
204
|
constructor(options = {}) {
|
|
196
205
|
this.config = options.config;
|
|
@@ -229,8 +238,8 @@ export class NodeConnector {
|
|
|
229
238
|
this.fakeRunner =
|
|
230
239
|
options.fakeRunner === undefined ? undefined : new FakeTestRunner(options.fakeRunner);
|
|
231
240
|
this.fakeScript = options.fakeScript;
|
|
232
|
-
this.runAttachmentService = new RunAttachmentService((session) => this.runners.require(session.runner).
|
|
233
|
-
this.codexClient = new CodexRunner(options.codexClient);
|
|
241
|
+
this.runAttachmentService = new RunAttachmentService((session) => this.runners.require(session.runner).nativeImageRoots(session));
|
|
242
|
+
this.codexClient = new CodexRunner(options.codexClient, options.codexProfileClientFactory);
|
|
234
243
|
this.codexRunController = new CodexManagedRunController(this.codexClient, {
|
|
235
244
|
available: () => this.capabilities.codex.available,
|
|
236
245
|
intercepted: () => this.fakeRunner !== undefined,
|
|
@@ -251,7 +260,7 @@ export class NodeConnector {
|
|
|
251
260
|
cancel: (runId, threadId, turnId, cwd, status) => this.runnerInteractionService.cancelCodexTurn(runId, threadId, turnId, cwd, status),
|
|
252
261
|
createApproval: (input) => this.runtime.createApproval(input),
|
|
253
262
|
publishApproval: (input) => this.runEventBridge.publishApproval(input),
|
|
254
|
-
emitItem: (runId, item) => this.
|
|
263
|
+
emitItem: (runId, item) => this.emitConversationItem(runId, item),
|
|
255
264
|
appendImages: (runId, images) => this.runAttachmentService.append(runId, images.filter(isRunnerImageAttachment)),
|
|
256
265
|
captureSnapshot: (runId, cwd) => this.workspaceCoordinator.captureRunSnapshot(runId, cwd),
|
|
257
266
|
syncTitle: (sessionId, nativeSessionId, cwd) => this.managedRunnerSessions.refreshTitle(sessionId, nativeSessionId, cwd),
|
|
@@ -272,8 +281,9 @@ export class NodeConnector {
|
|
|
272
281
|
intercepted: () => this.fakeRunner !== undefined,
|
|
273
282
|
active: this.runState.active,
|
|
274
283
|
parseAttachments: parseRunnerImageAttachments,
|
|
284
|
+
persistAttachments: (cwd, runId, attachments) => this.runAttachmentService.persistFiles(cwd, `${runId}-opencode-images`, attachments),
|
|
275
285
|
emit: (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status),
|
|
276
|
-
emitItem: (runId, item) => this.
|
|
286
|
+
emitItem: (runId, item) => this.emitConversationItem(runId, item),
|
|
277
287
|
adopt: (input) => this.runtime.adoptRun(input),
|
|
278
288
|
promote: (sessionId, nativeSessionId) => this.sessionIdentityService.promote(sessionId, nativeSessionId),
|
|
279
289
|
rememberNative: (nativeSessionId, sessionId) => this.managedRunnerSessions.remember(nativeSessionId, sessionId),
|
|
@@ -305,7 +315,7 @@ export class NodeConnector {
|
|
|
305
315
|
rememberNative: (nativeSessionId, sessionId) => this.managedRunnerSessions.remember(nativeSessionId, sessionId),
|
|
306
316
|
promote: (sessionId, nativeSessionId) => this.sessionIdentityService.promote(sessionId, nativeSessionId),
|
|
307
317
|
send: (type, payload) => this.send(type, payload),
|
|
308
|
-
emitItem: (runId, item) => this.
|
|
318
|
+
emitItem: (runId, item) => this.emitConversationItem(runId, item),
|
|
309
319
|
syncTitle: (sessionId, nativeSessionId, cwd) => this.managedRunnerSessions.refreshTitle(sessionId, nativeSessionId, cwd),
|
|
310
320
|
setChannelToken: (sessionId, token) => this.runtime.setChannelToken(sessionId, token),
|
|
311
321
|
commandState: (sessionId, commandId) => this.commandStates.get(sessionId, commandId),
|
|
@@ -329,7 +339,7 @@ export class NodeConnector {
|
|
|
329
339
|
claude: () => this.claudeClient,
|
|
330
340
|
runners: this.runners,
|
|
331
341
|
emitRun: (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status),
|
|
332
|
-
emitItem: (runId, item) => this.
|
|
342
|
+
emitItem: (runId, item) => this.emitConversationItem(runId, item),
|
|
333
343
|
captureSnapshot: (runId, cwd) => this.workspaceCoordinator.captureRunSnapshot(runId, cwd),
|
|
334
344
|
approvalTimeoutMs: this.claudeApprovalTimeoutMs,
|
|
335
345
|
publishApproval: (input) => this.runEventBridge.publishApproval(input)
|
|
@@ -367,6 +377,7 @@ export class NodeConnector {
|
|
|
367
377
|
hasManagedActiveRun: (session) => hasManagedActiveRun(this.runtime, session),
|
|
368
378
|
setExternalActivity: (sessionId, activity) => this.sessionState.externalActivity.set(sessionId, activity)
|
|
369
379
|
});
|
|
380
|
+
this.conversationSegmentService = new ConversationSegmentService((session, input) => this.conversationHistoryService.read(session, input));
|
|
370
381
|
this.sessionContextService = new SessionContextService({
|
|
371
382
|
runners: this.runners,
|
|
372
383
|
session: (sessionId) => this.runtime.getAgentSession(sessionId),
|
|
@@ -391,6 +402,7 @@ export class NodeConnector {
|
|
|
391
402
|
resolve: (sessionId) => this.nativeSessionProjectionService.resolve(sessionId),
|
|
392
403
|
present: (session) => this.sessionPresentationService.present(session),
|
|
393
404
|
history: (session, input) => this.conversationHistoryService.read(session, input),
|
|
405
|
+
segments: (session, input) => this.conversationSegmentService.read(session, input),
|
|
394
406
|
context: this.sessionContextService,
|
|
395
407
|
discover: (runner, workspaceId) => this.nativeSessionProjectionService.discover(runner, workspaceId),
|
|
396
408
|
workspaceIdForCwd: (cwd) => this.database?.listWorkspaces().find((candidate) => candidate.path === cwd)?.id
|
|
@@ -400,11 +412,12 @@ export class NodeConnector {
|
|
|
400
412
|
emit: (workspaceId, sessionId) => this.runEventBridge.emitQueue(workspaceId, sessionId),
|
|
401
413
|
clearImages: (runId) => this.runAttachmentService.clear(runId),
|
|
402
414
|
emitRun: (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status),
|
|
403
|
-
emitConversation: (turn) => this.
|
|
415
|
+
emitConversation: (turn) => this.emitConversationTurn(turn),
|
|
404
416
|
control: (operation, payload) => this.controlMessages.handle(JSON.stringify(createEnvelope(operation, payload))),
|
|
405
417
|
stopped: () => this.stopped,
|
|
406
418
|
markInterrupted: (runId) => this.runState.interruptRequested.add(runId),
|
|
407
|
-
prepareInput: (runner, input, collaborationMode) => this.runners.require(runner).prepareMessageInput(input, collaborationMode)
|
|
419
|
+
prepareInput: (runner, input, collaborationMode) => this.runners.require(runner).prepareMessageInput(input, collaborationMode),
|
|
420
|
+
mcps: (workspaceId) => this.database?.effectiveMcpInstallations(workspaceId) ?? []
|
|
408
421
|
});
|
|
409
422
|
this.sessionIdentityService = new SessionIdentityService({
|
|
410
423
|
runtime: this.runtime,
|
|
@@ -426,7 +439,7 @@ export class NodeConnector {
|
|
|
426
439
|
emitSession: (session) => this.emitWorkbenchEvent('session', { session }),
|
|
427
440
|
emitRun: (run) => this.emitWorkbenchEvent('run', { run }),
|
|
428
441
|
emitQueue: (workspaceId, sessionId) => this.runEventBridge.emitQueue(workspaceId, sessionId),
|
|
429
|
-
emitTurn: (turn) => this.
|
|
442
|
+
emitTurn: (turn) => this.emitConversationTurn(turn)
|
|
430
443
|
});
|
|
431
444
|
this.sessionLifecycleService = new SessionLifecycleService({
|
|
432
445
|
database: () => {
|
|
@@ -594,7 +607,7 @@ export class NodeConnector {
|
|
|
594
607
|
this.terminalChannel.disconnect();
|
|
595
608
|
if (code === 4000 && reason === 'NODE_CONNECTION_REPLACED')
|
|
596
609
|
nodeLog('node.connection.replaced', {
|
|
597
|
-
message: '
|
|
610
|
+
message: 'This Node connection was replaced by a newer instance with the same identity. Check for duplicate processes.'
|
|
598
611
|
});
|
|
599
612
|
if (code === 4003 || code === 1001)
|
|
600
613
|
void this.terminalManager.shutdown();
|
|
@@ -714,6 +727,7 @@ export class NodeConnector {
|
|
|
714
727
|
throw new Error('NODE_CONFIG_UNAVAILABLE');
|
|
715
728
|
return this.config;
|
|
716
729
|
}).operations());
|
|
730
|
+
this.operationRouter.registerAll(new McpNodeOperationService(requireDatabase).operations());
|
|
717
731
|
this.operationRouter.register('node.metrics', () => ({
|
|
718
732
|
metrics: {
|
|
719
733
|
...this.metrics.snapshot(),
|
|
@@ -778,7 +792,22 @@ export class NodeConnector {
|
|
|
778
792
|
/** @internal Thin compatibility delegates; all behavior remains in the extracted services. */
|
|
779
793
|
handleMessage = (raw) => this.controlMessages.handle(raw);
|
|
780
794
|
emitRunEvent = (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status);
|
|
781
|
-
emitConversationItem = (runId, item) =>
|
|
795
|
+
emitConversationItem = (runId, item) => {
|
|
796
|
+
this.runEventService.flushPendingText(runId);
|
|
797
|
+
const itemId = isPlainRecord(item) && typeof item.itemId === 'string' ? item.itemId : undefined;
|
|
798
|
+
const itemKind = isPlainRecord(item) && typeof item.kind === 'string' ? item.kind : undefined;
|
|
799
|
+
const turn = this.runtime.conversationTurnForRun(runId);
|
|
800
|
+
const segment = itemId === undefined || turn === undefined
|
|
801
|
+
? undefined
|
|
802
|
+
: conversationSegmentIdentityForItem(turn, itemId, itemKind);
|
|
803
|
+
this.runEventBridge.emitRun(runId, 'conversation.item', segment === undefined || !isPlainRecord(item)
|
|
804
|
+
? item
|
|
805
|
+
: { ...item, segmentId: segment.id, segmentIndex: segment.index });
|
|
806
|
+
};
|
|
807
|
+
emitConversationTurn = (turn) => {
|
|
808
|
+
const segments = isPlainRecord(turn) && Array.isArray(turn.items) ? conversationSegments(turn) : [];
|
|
809
|
+
this.emitWorkbenchEvent('conversation', { turn, segments });
|
|
810
|
+
};
|
|
782
811
|
emitWorkbenchEvent = (category, payload) => this.runEventBridge.emitWorkbench(category, payload);
|
|
783
812
|
discoverWorkspaceSessions = (workspaceId) => this.nativeSessionProjectionService.discoverWorkspace(workspaceId);
|
|
784
813
|
invalidateWorkspaceWatchTopics = (workspaceId) => this.workspaceCoordinator.invalidate(workspaceId);
|
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
|
+
}
|