@myagentroam/node 0.9.62 → 0.9.65
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/LICENSE +202 -0
- package/NOTICE +4 -0
- package/dist/capabilities.js +110 -2
- package/dist/claude-agent-sdk.js +330 -2
- package/dist/claude-channel.js +44 -2
- package/dist/codex-app-server.js +510 -2
- package/dist/config.js +106 -2
- package/dist/connector/node-connector-options.js +1 -2
- package/dist/connector/node-operation-router.js +22 -2
- package/dist/connector.js +1736 -2
- package/dist/control-outbound-scheduler.js +153 -2
- package/dist/database.js +639 -2
- package/dist/fake-runner.js +129 -2
- package/dist/git-askpass.js +12 -0
- package/dist/git-execution-helper.js +266 -0
- package/dist/health.js +3 -2
- package/dist/main.js +57 -2
- package/dist/mcp-installation-model.js +1 -2
- package/dist/migrations/v004.js +55 -3
- package/dist/migrations/v005.js +13 -3
- package/dist/migrations/v006.js +90 -3
- package/dist/migrations/v007.js +22 -3
- package/dist/migrations/v008.js +18 -3
- package/dist/migrations/v009.js +70 -4
- package/dist/migrations/v010.js +43 -3
- package/dist/migrations/v011.js +20 -3
- package/dist/migrations/v012.js +8 -0
- package/dist/native-jsonl-reader.js +186 -2
- package/dist/native-session-history.js +1143 -2
- package/dist/opencode-runtime.js +80 -2
- package/dist/opencode-server.js +259 -2
- package/dist/operational.js +122 -2
- package/dist/process-tree.js +18 -2
- package/dist/ripgrep-binary.js +31 -2
- package/dist/rotating-log.js +67 -2
- package/dist/runner/abstract-runner.js +260 -2
- package/dist/runner/claude/managed-run-controller.js +355 -2
- package/dist/runner/claude/tool-normalizer.js +120 -2
- package/dist/runner/claude-code-runner.js +303 -2
- package/dist/runner/codex/composer-image-markers.js +8 -2
- package/dist/runner/codex/conversation-parser.js +913 -2
- package/dist/runner/codex/managed-run-controller.js +494 -2
- package/dist/runner/codex/tool-normalizer.js +214 -2
- package/dist/runner/codex-runner.js +585 -2
- package/dist/runner/fake-test-runner.js +47 -2
- package/dist/runner/mar-agent/conversation-normalizer.js +644 -2
- package/dist/runner/mar-agent/executor-stdio-client-transport.js +139 -2
- package/dist/runner/mar-agent/generated-images.js +41 -2
- package/dist/runner/mar-agent/history-projection.js +106 -2
- package/dist/runner/mar-agent/hosted-conversation-runtime.js +446 -2
- package/dist/runner/mar-agent/managed-run-controller.js +785 -2
- package/dist/runner/mar-agent/mcp-client-pool.js +233 -2
- package/dist/runner/mar-agent/runtime-model-factory.js +326 -2
- package/dist/runner/mar-agent-runner.js +458 -2
- package/dist/runner/opencode/conversation-parser.js +221 -2
- package/dist/runner/opencode/managed-run-controller.js +588 -2
- package/dist/runner/opencode/tool-normalizer.js +80 -2
- package/dist/runner/opencode-runner.js +426 -2
- package/dist/runner/runner-registry.js +87 -2
- package/dist/runner-command-engine.js +71 -2
- package/dist/runner-profiles.js +323 -2
- package/dist/runner-usage.js +190 -2
- package/dist/runtime-command-detector.js +73 -2
- package/dist/runtime-model.js +1 -2
- package/dist/runtime-state.js +1509 -2
- package/dist/service/attachment-domain-state.js +7 -2
- package/dist/service/bubblewrap-sandbox-executor.js +543 -2
- package/dist/service/continuous-run-coordinator.js +425 -2
- package/dist/service/conversation-history-service.js +552 -2
- package/dist/service/conversation-hosting-capability-service.js +61 -2
- package/dist/service/conversation-hosting-enablement-service.js +91 -2
- package/dist/service/conversation-hosting-maintenance-service.js +174 -2
- package/dist/service/conversation-segment-service.js +162 -2
- package/dist/service/conversation-template-node-service.js +992 -2
- package/dist/service/external-session-resume-service.js +152 -2
- package/dist/service/fake-managed-run-service.js +60 -2
- package/dist/service/git-execution-service.js +418 -0
- package/dist/service/git-operation-service.js +57 -0
- package/dist/service/hosted-conversation-node-service.js +1982 -2
- package/dist/service/hosted-conversation-store.js +1405 -2
- package/dist/service/managed-runner-session-service.js +35 -2
- package/dist/service/mar-agent-model-transport-test-service.js +88 -2
- package/dist/service/mcp-installation-verifier.js +87 -2
- package/dist/service/mcp-node-operation-service.js +90 -2
- package/dist/service/mission-mcp-server.js +124 -2
- package/dist/service/mission-mcp-stdio.js +40 -2
- package/dist/service/mission-runtime.js +142 -2
- package/dist/service/native-session-projection-service.js +274 -2
- package/dist/service/native-session-watch-service.js +683 -2
- package/dist/service/node-connection-lifecycle-service.js +88 -2
- package/dist/service/node-control-channel.js +85 -2
- package/dist/service/node-control-message-service.js +109 -2
- package/dist/service/node-request-service.js +62 -2
- package/dist/service/node-run-event-bridge.js +88 -2
- package/dist/service/node-terminal-channel.js +202 -2
- package/dist/service/node-upgrade-coordinator.js +137 -2
- package/dist/service/node-workspace-coordinator.js +97 -2
- package/dist/service/recent-run-projection-service.js +99 -2
- package/dist/service/run-attachment-service.js +321 -2
- package/dist/service/run-domain-state.js +4 -2
- package/dist/service/run-event-service.js +246 -2
- package/dist/service/run-ownership-policy.js +22 -2
- package/dist/service/run-workbench-service.js +120 -2
- package/dist/service/runner-channel-message-service.js +20 -2
- package/dist/service/runner-interaction-service.js +243 -2
- package/dist/service/runner-service.js +69 -2
- package/dist/service/runtime-credential-control-service.js +95 -2
- package/dist/service/session-catalog-service.js +404 -2
- package/dist/service/session-command-service.js +125 -2
- package/dist/service/session-context-service.js +128 -2
- package/dist/service/session-domain-state.js +9 -2
- package/dist/service/session-identity-service.js +86 -2
- package/dist/service/session-lifecycle-service.js +248 -2
- package/dist/service/session-message-service.js +270 -2
- package/dist/service/session-presentation-service.js +43 -2
- package/dist/service/session-query-service.js +82 -2
- package/dist/service/session-watch-slot-coordinator.js +21 -2
- package/dist/service/skill-directory-service.js +238 -2
- package/dist/service/skill-install-service.js +434 -2
- package/dist/service/skill-node-operation-service.js +206 -2
- package/dist/service/terminal-relay-state.js +3 -2
- package/dist/service/terminal-service.js +90 -2
- package/dist/service/web-search-error.js +40 -2
- package/dist/service/workbench-event-service.js +21 -2
- package/dist/service/workbench-manifest-service.js +69 -2
- package/dist/service/workspace-change-service.js +290 -2
- package/dist/service/workspace-content-search-service.js +457 -2
- package/dist/service/workspace-domain-state.js +41 -2
- package/dist/service/workspace-file-service.js +150 -2
- package/dist/service/workspace-git-exclude-service.js +119 -2
- package/dist/service/workspace-queue-workbench-service.js +176 -2
- package/dist/service/workspace-service.js +92 -2
- package/dist/service/workspace-session-service.js +154 -2
- package/dist/service/workspace-upload-service.js +262 -2
- package/dist/service/workspace-watch-controller.js +68 -2
- package/dist/service/workspace-watch-service.js +170 -2
- package/dist/service/workspace-watch-snapshot-service.js +218 -2
- package/dist/service/workspace-workbench-service.js +239 -2
- package/dist/service.js +91 -2
- package/dist/storage.js +32 -2
- package/dist/supervisor.js +136 -2
- package/dist/terminal.js +496 -2
- package/dist/util/git-credential-client.js +32 -0
- package/dist/util/node-operation-data.js +9 -2
- package/dist/util/node-operation-parsers.js +382 -2
- package/dist/util/personal-instructions.js +34 -2
- package/dist/util/runner-error.js +70 -2
- package/dist/util/runner-native-session-parsers.js +510 -2
- package/dist/util/safe-error.js +8 -2
- package/dist/util/secret-environment.js +25 -2
- package/dist/workspace.js +1793 -2
- package/package.json +11 -8
- package/vendor/ripgrep/darwin-arm64/rg +0 -0
- package/vendor/ripgrep/darwin-x64/rg +0 -0
- package/vendor/ripgrep/manifest.json +12 -0
- package/README.md +0 -42
- package/dist/event-buffer.js +0 -2
- package/dist/migrations/v001.js +0 -107
- package/dist/migrations/v002.js +0 -2
- package/dist/migrations/v003.js +0 -2
- package/dist/service/mcp-package-installer.js +0 -2
|
@@ -1,2 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/** Owns managed native-session identity and best-effort native title refresh. */
|
|
2
|
+
export class ManagedRunnerSessionService {
|
|
3
|
+
runtime;
|
|
4
|
+
runners;
|
|
5
|
+
emitSession;
|
|
6
|
+
constructor(runtime, runners, emitSession) {
|
|
7
|
+
this.runtime = runtime;
|
|
8
|
+
this.runners = runners;
|
|
9
|
+
this.emitSession = emitSession;
|
|
10
|
+
}
|
|
11
|
+
remember(nativeSessionId, sessionId) {
|
|
12
|
+
const session = this.runtime.getAgentSession(sessionId);
|
|
13
|
+
if (session === undefined || session.nativeControl !== 'MAR_MANAGED')
|
|
14
|
+
return;
|
|
15
|
+
this.runners.require(session.runner).rememberManagedNative(session, nativeSessionId);
|
|
16
|
+
}
|
|
17
|
+
async refreshTitle(sessionId, externalSessionId, cwd) {
|
|
18
|
+
const session = this.runtime.getAgentSession(sessionId);
|
|
19
|
+
if (session === undefined || session.nativeControl !== 'MAR_MANAGED')
|
|
20
|
+
return;
|
|
21
|
+
try {
|
|
22
|
+
const title = await this.runners
|
|
23
|
+
.require(session.runner)
|
|
24
|
+
.readManagedRunnerTitle({ ...session, cwd }, externalSessionId);
|
|
25
|
+
if (title === undefined)
|
|
26
|
+
return;
|
|
27
|
+
const updated = this.runtime.setRunnerTitleFromNative(session.id, title);
|
|
28
|
+
if (updated.runnerTitle !== session.runnerTitle)
|
|
29
|
+
this.emitSession(updated);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Native history is a best-effort source and cannot alter the completed Run result.
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -1,2 +1,88 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { AnthropicMessagesAdapter, OpenAiResponsesAdapter } from '@myagentroam/agent';
|
|
2
|
+
import { marAgentModelTransportTestInputSchema, marAgentModelTransportTestResultSchema } from '@myagentroam/protocol';
|
|
3
|
+
import { runnerErrorProjection } from '../util/runner-error.js';
|
|
4
|
+
export class MarAgentModelTransportTestService {
|
|
5
|
+
options;
|
|
6
|
+
#adapterFactory;
|
|
7
|
+
#timeoutMs;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.options = options;
|
|
10
|
+
this.#adapterFactory = options.adapterFactory ?? defaultAdapter;
|
|
11
|
+
this.#timeoutMs = options.timeoutMs ?? 30_000;
|
|
12
|
+
}
|
|
13
|
+
operations() {
|
|
14
|
+
return {
|
|
15
|
+
'mar-agent.model.test': (raw) => this.test(raw)
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
async test(raw) {
|
|
19
|
+
const parsed = marAgentModelTransportTestInputSchema.safeParse(withoutNodeRequestMetadata(raw));
|
|
20
|
+
if (!parsed.success)
|
|
21
|
+
throw new Error('MAR_AGENT_MODEL_TEST_INPUT_INVALID');
|
|
22
|
+
const [configuration] = this.options.modelFactory.create([parsed.data.model], {
|
|
23
|
+
runtimeScopeId: parsed.data.runtimeScopeId,
|
|
24
|
+
executionId: parsed.data.executionId
|
|
25
|
+
});
|
|
26
|
+
if (configuration === undefined)
|
|
27
|
+
throw new Error('MAR_AGENT_MODEL_TEST_INPUT_INVALID');
|
|
28
|
+
try {
|
|
29
|
+
const adapter = this.#adapterFactory(configuration);
|
|
30
|
+
let completed = false;
|
|
31
|
+
for await (const event of adapter.start({
|
|
32
|
+
system: 'You are performing a model transport connectivity test.',
|
|
33
|
+
messages: [{ role: 'user', content: 'Reply with OK.' }],
|
|
34
|
+
tools: [],
|
|
35
|
+
allowTools: false
|
|
36
|
+
}, AbortSignal.timeout(this.#timeoutMs))) {
|
|
37
|
+
if (event.type === 'completed')
|
|
38
|
+
completed = true;
|
|
39
|
+
}
|
|
40
|
+
return marAgentModelTransportTestResultSchema.parse(completed
|
|
41
|
+
? { success: true, code: null, message: null, httpStatus: null }
|
|
42
|
+
: {
|
|
43
|
+
success: false,
|
|
44
|
+
code: 'MAR_AGENT_MODEL_CONNECTION_FAILED',
|
|
45
|
+
message: 'Model transport test ended without completion.',
|
|
46
|
+
httpStatus: null
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const projection = modelTestErrorProjection(error);
|
|
51
|
+
return marAgentModelTransportTestResultSchema.parse({
|
|
52
|
+
success: false,
|
|
53
|
+
...projection
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
await configuration.credentialProvider?.close().catch(() => undefined);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function withoutNodeRequestMetadata(raw) {
|
|
62
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
|
|
63
|
+
return raw;
|
|
64
|
+
const payload = { ...raw };
|
|
65
|
+
delete payload.__requestUserId;
|
|
66
|
+
delete payload.__workspaceWatchSessionHash;
|
|
67
|
+
return payload;
|
|
68
|
+
}
|
|
69
|
+
function defaultAdapter(configuration) {
|
|
70
|
+
return configuration.protocol === 'OPENAI_RESPONSES'
|
|
71
|
+
? new OpenAiResponsesAdapter(configuration)
|
|
72
|
+
: new AnthropicMessagesAdapter(configuration);
|
|
73
|
+
}
|
|
74
|
+
function modelTestErrorProjection(error) {
|
|
75
|
+
if (error === null || typeof error !== 'object')
|
|
76
|
+
return runnerErrorProjection(error, 'MAR_AGENT_MODEL_CONNECTION_FAILED');
|
|
77
|
+
const candidate = error;
|
|
78
|
+
if (typeof candidate.code !== 'string' || typeof candidate.message !== 'string')
|
|
79
|
+
return runnerErrorProjection(error, 'MAR_AGENT_MODEL_CONNECTION_FAILED');
|
|
80
|
+
const prefix = `${candidate.code}: `;
|
|
81
|
+
return runnerErrorProjection({
|
|
82
|
+
code: candidate.code,
|
|
83
|
+
message: candidate.message.startsWith(prefix)
|
|
84
|
+
? candidate.message.slice(prefix.length)
|
|
85
|
+
: candidate.message,
|
|
86
|
+
status: candidate.details?.['status']
|
|
87
|
+
}, 'MAR_AGENT_MODEL_CONNECTION_FAILED');
|
|
88
|
+
}
|
|
@@ -1,2 +1,87 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import { getDefaultEnvironment, StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
3
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
4
|
+
const INSTALL_TIMEOUT_MS = 90_000;
|
|
5
|
+
export class McpInstallationVerifier {
|
|
6
|
+
async verify(input) {
|
|
7
|
+
const client = new Client({ name: 'myagentroam-node-installer', version: '0.9.1' });
|
|
8
|
+
try {
|
|
9
|
+
await withTimeout((async () => {
|
|
10
|
+
if (input.entry.runtime.transport === 'STDIO') {
|
|
11
|
+
await client.connect(new StdioClientTransport({
|
|
12
|
+
command: input.entry.runtime.command,
|
|
13
|
+
args: [...input.entry.runtime.args, ...(input.configuration.arguments ?? [])],
|
|
14
|
+
env: {
|
|
15
|
+
...getDefaultEnvironment(),
|
|
16
|
+
...resolveReferences(input.entry.runtime.environment, input.secrets),
|
|
17
|
+
...resolveReferences(input.configuration.environment, input.secrets)
|
|
18
|
+
},
|
|
19
|
+
cwd: input.cwd,
|
|
20
|
+
stderr: 'pipe'
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
await client.connect(new StreamableHTTPClientTransport(new URL(input.entry.runtime.url), {
|
|
25
|
+
requestInit: {
|
|
26
|
+
headers: {
|
|
27
|
+
...resolveReferences(input.entry.runtime.headers, input.secrets),
|
|
28
|
+
...resolveReferences(input.configuration.headers, input.secrets)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
const tools = await client.listTools();
|
|
34
|
+
if (tools.tools.length === 0)
|
|
35
|
+
throw new Error('MCP_TOOLS_UNAVAILABLE');
|
|
36
|
+
})());
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
throw installError(error);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
await Promise.race([client.close().catch(() => undefined), delay(2_000)]);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function resolveReferences(references, secrets) {
|
|
47
|
+
const result = {};
|
|
48
|
+
for (const [name, reference] of Object.entries(references ?? {})) {
|
|
49
|
+
const value = secrets[reference.secretName];
|
|
50
|
+
if (value === undefined)
|
|
51
|
+
throw new Error('MCP_SECRET_MISSING');
|
|
52
|
+
result[name] = value;
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
async function withTimeout(operation) {
|
|
57
|
+
let timer;
|
|
58
|
+
try {
|
|
59
|
+
await Promise.race([
|
|
60
|
+
operation,
|
|
61
|
+
new Promise((_resolve, reject) => {
|
|
62
|
+
timer = setTimeout(() => reject(new Error('MCP_INSTALL_TIMEOUT')), INSTALL_TIMEOUT_MS);
|
|
63
|
+
})
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
if (timer !== undefined)
|
|
68
|
+
clearTimeout(timer);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function installError(error) {
|
|
72
|
+
const message = error instanceof Error ? error.message : '';
|
|
73
|
+
if (message === 'MCP_INSTALL_TIMEOUT' ||
|
|
74
|
+
message === 'MCP_SECRET_MISSING' ||
|
|
75
|
+
message === 'MCP_TOOLS_UNAVAILABLE')
|
|
76
|
+
return new Error(message);
|
|
77
|
+
if (/401|403|unauthorized|forbidden/iu.test(message))
|
|
78
|
+
return new Error('MCP_AUTH_FAILED');
|
|
79
|
+
if (/ENOENT|spawn/iu.test(message))
|
|
80
|
+
return new Error('MCP_COMMAND_UNAVAILABLE');
|
|
81
|
+
if (/fetch|connect|network|ECONN|ENOTFOUND/iu.test(message))
|
|
82
|
+
return new Error('MCP_CONNECTION_FAILED');
|
|
83
|
+
return new Error('MCP_PROTOCOL_VALIDATION_FAILED');
|
|
84
|
+
}
|
|
85
|
+
function delay(milliseconds) {
|
|
86
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
87
|
+
}
|
|
@@ -1,2 +1,90 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { mcpCatalogEntrySchema, mcpInstallationInputSchema, mcpTargetInspectionSchema } from '@myagentroam/protocol';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { McpInstallationVerifier } from './mcp-installation-verifier.js';
|
|
4
|
+
export class McpNodeOperationService {
|
|
5
|
+
requireDatabase;
|
|
6
|
+
verifier;
|
|
7
|
+
constructor(requireDatabase, verifier = new McpInstallationVerifier()) {
|
|
8
|
+
this.requireDatabase = requireDatabase;
|
|
9
|
+
this.verifier = verifier;
|
|
10
|
+
}
|
|
11
|
+
operations() {
|
|
12
|
+
return {
|
|
13
|
+
'node.mcps.inspect': () => this.inspect('NODE', ''),
|
|
14
|
+
'node.mcps.install': (data) => this.install('NODE', '', data),
|
|
15
|
+
'node.mcps.remove': (data) => this.remove('NODE', '', data),
|
|
16
|
+
'workspace.mcps.inspect': (data) => this.inspect('WORKSPACE', workspaceId(data)),
|
|
17
|
+
'workspace.mcps.install': (data) => this.install('WORKSPACE', workspaceId(data), data),
|
|
18
|
+
'workspace.mcps.remove': (data) => this.remove('WORKSPACE', workspaceId(data), data)
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
inspect(targetKind, targetId) {
|
|
22
|
+
const database = this.requireDatabase();
|
|
23
|
+
const own = database.listMcpInstallations(targetKind, targetId);
|
|
24
|
+
const ownNames = new Set(own.map((installation) => installation.serverName));
|
|
25
|
+
const installations = [
|
|
26
|
+
...(targetKind === 'WORKSPACE'
|
|
27
|
+
? database
|
|
28
|
+
.listMcpInstallations('NODE')
|
|
29
|
+
.filter((installation) => !ownNames.has(installation.serverName))
|
|
30
|
+
: []),
|
|
31
|
+
...own
|
|
32
|
+
].map((installation) => ({
|
|
33
|
+
catalogEntryId: installation.catalogEntryId,
|
|
34
|
+
serverName: installation.serverName,
|
|
35
|
+
displayName: installation.displayName,
|
|
36
|
+
version: installation.version,
|
|
37
|
+
origin: installation.targetKind,
|
|
38
|
+
enabled: installation.enabled,
|
|
39
|
+
status: 'CURRENT',
|
|
40
|
+
installedAt: installation.installedAt
|
|
41
|
+
}));
|
|
42
|
+
return mcpTargetInspectionSchema.parse({ targetKind, installations });
|
|
43
|
+
}
|
|
44
|
+
async install(targetKind, targetId, raw) {
|
|
45
|
+
if (!isRecord(raw))
|
|
46
|
+
throw new Error('MCP_INSTALL_INPUT_INVALID');
|
|
47
|
+
const entry = mcpCatalogEntrySchema.parse(raw['entry']);
|
|
48
|
+
const input = mcpInstallationInputSchema.parse({
|
|
49
|
+
catalogEntryId: entry.id,
|
|
50
|
+
enabled: raw['enabled'] ?? true,
|
|
51
|
+
configuration: raw['configuration'] ?? {}
|
|
52
|
+
});
|
|
53
|
+
const database = this.requireDatabase();
|
|
54
|
+
const secretEnvironment = isRecord(raw['secretEnvironment'])
|
|
55
|
+
? Object.fromEntries(Object.entries(raw['secretEnvironment']).filter((entry) => typeof entry[1] === 'string'))
|
|
56
|
+
: {};
|
|
57
|
+
const cwd = targetKind === 'WORKSPACE' ? database.getWorkspace(targetId)?.path : homedir();
|
|
58
|
+
if (cwd === undefined)
|
|
59
|
+
throw new Error('WORKSPACE_NOT_FOUND');
|
|
60
|
+
if (input.enabled)
|
|
61
|
+
await this.verifier.verify({
|
|
62
|
+
entry,
|
|
63
|
+
configuration: input.configuration,
|
|
64
|
+
secrets: secretEnvironment,
|
|
65
|
+
cwd
|
|
66
|
+
});
|
|
67
|
+
database.saveMcpInstallation({
|
|
68
|
+
targetKind,
|
|
69
|
+
...(targetKind === 'WORKSPACE' ? { targetId } : {}),
|
|
70
|
+
entry,
|
|
71
|
+
enabled: input.enabled,
|
|
72
|
+
configuration: input.configuration
|
|
73
|
+
});
|
|
74
|
+
return this.inspect(targetKind, targetId);
|
|
75
|
+
}
|
|
76
|
+
remove(targetKind, targetId, raw) {
|
|
77
|
+
if (!isRecord(raw) || typeof raw['serverName'] !== 'string')
|
|
78
|
+
throw new Error('MCP_INSTALL_INPUT_INVALID');
|
|
79
|
+
this.requireDatabase().removeMcpInstallation(targetKind, targetId, raw['serverName']);
|
|
80
|
+
return this.inspect(targetKind, targetId);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function workspaceId(raw) {
|
|
84
|
+
if (!isRecord(raw) || typeof raw['workspaceId'] !== 'string' || raw['workspaceId'] === '')
|
|
85
|
+
throw new Error('WORKSPACE_INVALID');
|
|
86
|
+
return raw['workspaceId'];
|
|
87
|
+
}
|
|
88
|
+
function isRecord(value) {
|
|
89
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
90
|
+
}
|
|
@@ -1,2 +1,124 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { randomBytes, randomUUID } from 'node:crypto';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { createServer } from 'node:net';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { unlink } from 'node:fs/promises';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
export class MissionMcpServer {
|
|
9
|
+
missions;
|
|
10
|
+
changed;
|
|
11
|
+
capabilities = new Map();
|
|
12
|
+
endpoint = process.platform === 'win32'
|
|
13
|
+
? `\\\\.\\pipe\\mar-mission-${process.pid}-${randomUUID()}`
|
|
14
|
+
: join(tmpdir(), `mar-mission-${process.pid}-${randomUUID()}.sock`);
|
|
15
|
+
listening;
|
|
16
|
+
constructor(missions, changed) {
|
|
17
|
+
this.missions = missions;
|
|
18
|
+
this.changed = changed;
|
|
19
|
+
}
|
|
20
|
+
listen() {
|
|
21
|
+
this.listening ??= new Promise((resolve, reject) => {
|
|
22
|
+
const server = createServer((socket) => this.handle(socket));
|
|
23
|
+
server.once('error', reject);
|
|
24
|
+
server.listen(this.endpoint, () => resolve(server));
|
|
25
|
+
});
|
|
26
|
+
return this.listening;
|
|
27
|
+
}
|
|
28
|
+
async installation(capability) {
|
|
29
|
+
await this.listen();
|
|
30
|
+
const token = randomBytes(32).toString('base64url');
|
|
31
|
+
const secretName = `MAR_MISSION_MCP_${randomBytes(16).toString('hex').toUpperCase()}`;
|
|
32
|
+
this.capabilities.set(token, capability);
|
|
33
|
+
return {
|
|
34
|
+
installation: {
|
|
35
|
+
__marMission: true,
|
|
36
|
+
serverName: 'myagentroam_mission',
|
|
37
|
+
enabled: true,
|
|
38
|
+
command: process.execPath,
|
|
39
|
+
args: missionMcpStdioArgs(),
|
|
40
|
+
endpoint: this.endpoint,
|
|
41
|
+
secretName
|
|
42
|
+
},
|
|
43
|
+
secrets: { [secretName]: token }
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async close() {
|
|
47
|
+
if (this.listening === undefined)
|
|
48
|
+
return;
|
|
49
|
+
const server = await this.listening;
|
|
50
|
+
this.capabilities.clear();
|
|
51
|
+
await new Promise((resolve, reject) => server.close((error) => (error === undefined ? resolve() : reject(error))));
|
|
52
|
+
if (process.platform !== 'win32')
|
|
53
|
+
await unlink(this.endpoint).catch(() => undefined);
|
|
54
|
+
}
|
|
55
|
+
revoke(executionId) {
|
|
56
|
+
for (const [token, capability] of this.capabilities)
|
|
57
|
+
if (capability.executionId === executionId)
|
|
58
|
+
this.capabilities.delete(token);
|
|
59
|
+
}
|
|
60
|
+
migrateSession(previousId, nextId) {
|
|
61
|
+
if (previousId === nextId)
|
|
62
|
+
return;
|
|
63
|
+
for (const [token, capability] of this.capabilities)
|
|
64
|
+
if (capability.sessionId === previousId)
|
|
65
|
+
this.capabilities.set(token, { ...capability, sessionId: nextId });
|
|
66
|
+
}
|
|
67
|
+
handle(socket) {
|
|
68
|
+
let body = '';
|
|
69
|
+
socket.setEncoding('utf8');
|
|
70
|
+
socket.on('data', (chunk) => {
|
|
71
|
+
body += chunk;
|
|
72
|
+
if (body.length > 64_000)
|
|
73
|
+
socket.destroy();
|
|
74
|
+
if (!body.includes('\n'))
|
|
75
|
+
return;
|
|
76
|
+
void this.respond(socket, body.slice(0, body.indexOf('\n')));
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async respond(socket, body) {
|
|
80
|
+
try {
|
|
81
|
+
const request = JSON.parse(body);
|
|
82
|
+
const capability = typeof request.token === 'string' ? this.capabilities.get(request.token) : undefined;
|
|
83
|
+
if (capability === undefined)
|
|
84
|
+
throw new Error('MISSION_CAPABILITY_INVALID');
|
|
85
|
+
const current = this.current(capability);
|
|
86
|
+
if (request.tool === 'mar_get_mission') {
|
|
87
|
+
socket.end(`${JSON.stringify({ result: current })}\n`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (request.tool !== 'mar_update_mission')
|
|
91
|
+
throw new Error('MISSION_TOOL_INVALID');
|
|
92
|
+
const status = request.arguments !== null &&
|
|
93
|
+
typeof request.arguments === 'object' &&
|
|
94
|
+
'status' in request.arguments
|
|
95
|
+
? request.arguments.status
|
|
96
|
+
: undefined;
|
|
97
|
+
if (status !== 'completed' && status !== 'blocked')
|
|
98
|
+
throw new Error('MISSION_STATUS_INVALID');
|
|
99
|
+
const mission = this.missions.update(capability.sessionId, capability.missionId, capability.revision, status);
|
|
100
|
+
this.changed(capability.sessionId);
|
|
101
|
+
socket.end(`${JSON.stringify({ result: mission })}\n`);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
socket.end(`${JSON.stringify({ error: error instanceof Error ? error.message : 'MISSION_IPC_FAILED' })}\n`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
current(capability) {
|
|
108
|
+
const mission = this.missions.get(capability.sessionId);
|
|
109
|
+
if (mission === undefined ||
|
|
110
|
+
mission.id !== capability.missionId ||
|
|
111
|
+
mission.revision !== capability.revision)
|
|
112
|
+
throw new Error('MISSION_CAPABILITY_STALE');
|
|
113
|
+
return mission;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function missionMcpStdioArgs() {
|
|
117
|
+
const javascript = fileURLToPath(new URL('./mission-mcp-stdio.js', import.meta.url));
|
|
118
|
+
if (existsSync(javascript))
|
|
119
|
+
return [javascript];
|
|
120
|
+
const typescript = fileURLToPath(new URL('./mission-mcp-stdio.ts', import.meta.url));
|
|
121
|
+
if (!existsSync(typescript))
|
|
122
|
+
throw new Error('MISSION_MCP_STDIO_ENTRY_UNAVAILABLE');
|
|
123
|
+
return ['--import', import.meta.resolve('tsx'), typescript];
|
|
124
|
+
}
|
|
@@ -1,2 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { connect } from 'node:net';
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
const endpoint = process.env['MAR_MISSION_MCP_ENDPOINT'];
|
|
6
|
+
const token = process.env['MAR_MISSION_MCP_TOKEN'];
|
|
7
|
+
if (endpoint === undefined || token === undefined)
|
|
8
|
+
throw new Error('MISSION_CAPABILITY_INVALID');
|
|
9
|
+
const server = new McpServer({ name: 'myagentroam_mission', version: '1.0.0' });
|
|
10
|
+
server.registerTool('mar_get_mission', { description: 'Get the authoritative current Mission, status, and objective.' }, async () => result(await request('mar_get_mission', {})));
|
|
11
|
+
server.registerTool('mar_update_mission', {
|
|
12
|
+
description: 'Explicitly mark the current Mission completed or blocked.',
|
|
13
|
+
inputSchema: { status: z.enum(['completed', 'blocked']) }
|
|
14
|
+
}, async ({ status }) => result(await request('mar_update_mission', { status })));
|
|
15
|
+
await server.connect(new StdioServerTransport());
|
|
16
|
+
function request(tool, args) {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const socket = connect(endpoint);
|
|
19
|
+
let body = '';
|
|
20
|
+
socket.setEncoding('utf8');
|
|
21
|
+
socket.once('connect', () => socket.end(`${JSON.stringify({ token, tool, arguments: args })}\n`));
|
|
22
|
+
socket.on('data', (chunk) => (body += chunk));
|
|
23
|
+
socket.once('error', reject);
|
|
24
|
+
socket.once('end', () => {
|
|
25
|
+
try {
|
|
26
|
+
const payload = JSON.parse(body);
|
|
27
|
+
if (payload.error !== undefined)
|
|
28
|
+
reject(new Error(payload.error));
|
|
29
|
+
else
|
|
30
|
+
resolve(payload.result);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
reject(error);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function result(value) {
|
|
39
|
+
return { content: [{ type: 'text', text: JSON.stringify(value) }] };
|
|
40
|
+
}
|