@chatroomcp/chatroom 0.1.4 → 0.1.6
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/cli/index.js +0 -0
- package/dist/mcp/server/plugin-mcp-registrar.d.ts +26 -0
- package/dist/mcp/server/plugin-mcp-registrar.js +63 -0
- package/dist/mcp/server/tool-support.d.ts +1 -1
- package/dist/operations/operation-log.d.ts +4 -0
- package/dist/operations/operation-log.js +10 -0
- package/dist/plugins/cloud/api-client.d.ts +0 -6
- package/dist/plugins/cloud/api-client.js +0 -11
- package/dist/plugins/cloud/controller.d.ts +0 -1
- package/dist/plugins/cloud/controller.js +0 -12
- package/dist/plugins/cloud/tunnel-client.js +12 -5
- package/dist/plugins/plugin-manager.js +6 -2
- package/dist/plugins/process/mcp.d.ts +2 -3
- package/dist/plugins/process/mcp.js +25 -36
- package/dist/plugins/process/plugin.js +3 -6
- package/dist/plugins/process/process-supervisor.d.ts +1 -0
- package/dist/plugins/process/process-supervisor.js +21 -16
- package/dist/plugins/types.d.ts +2 -2
- package/dist/plugins/web/http/cloud-api-router.js +0 -12
- package/dist/plugins/workspace/mcp.d.ts +2 -3
- package/dist/plugins/workspace/mcp.js +38 -67
- package/dist/plugins/workspace/plugin.js +3 -6
- package/dist/web/assets/index-B7jL3mhD.css +1 -0
- package/dist/web/assets/index-L4-YdZVN.js +26 -0
- package/dist/web/index.html +2 -2
- package/package.json +2 -2
- package/dist/web/assets/index-Cgwn3ot-.css +0 -1
- package/dist/web/assets/index-ClD90giy.js +0 -26
package/dist/cli/index.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { McpServer, StandardSchemaWithJSON, ToolAnnotations } from "@modelcontextprotocol/server";
|
|
2
|
+
import type { OperationLog } from "../../operations/operation-log.js";
|
|
3
|
+
type PluginToolInput<Schema extends StandardSchemaWithJSON> = StandardSchemaWithJSON.InferOutput<Schema>;
|
|
4
|
+
type PluginToolAction<Input> = string | ((input: Input) => string);
|
|
5
|
+
export interface PluginToolExecution {
|
|
6
|
+
readonly operationId: string;
|
|
7
|
+
/** Keep the operation running after the MCP handler returns. The owner must finish it later. */
|
|
8
|
+
deferCompletion(): void;
|
|
9
|
+
}
|
|
10
|
+
export interface PluginToolConfig<InputSchema extends StandardSchemaWithJSON, OutputSchema extends StandardSchemaWithJSON> {
|
|
11
|
+
title: string;
|
|
12
|
+
description: string;
|
|
13
|
+
inputSchema: InputSchema;
|
|
14
|
+
outputSchema: OutputSchema;
|
|
15
|
+
annotations: ToolAnnotations;
|
|
16
|
+
action: PluginToolAction<PluginToolInput<InputSchema>>;
|
|
17
|
+
}
|
|
18
|
+
/** Framework-owned MCP registration boundary that guarantees every plugin tool is audited. */
|
|
19
|
+
export declare class PluginMcpRegistrar {
|
|
20
|
+
private readonly server;
|
|
21
|
+
private readonly operations;
|
|
22
|
+
private readonly pluginId;
|
|
23
|
+
constructor(server: McpServer, operations: OperationLog, pluginId: string);
|
|
24
|
+
registerTool<InputSchema extends StandardSchemaWithJSON, OutputSchema extends StandardSchemaWithJSON>(name: string, config: PluginToolConfig<InputSchema, OutputSchema>, handler: (input: PluginToolInput<InputSchema>, execution: PluginToolExecution) => Promise<unknown> | unknown): void;
|
|
25
|
+
}
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { asChatRoomError } from "../../core/errors/chatroom-error.js";
|
|
2
|
+
import { mcpTool } from "./tool-support.js";
|
|
3
|
+
/** Framework-owned MCP registration boundary that guarantees every plugin tool is audited. */
|
|
4
|
+
export class PluginMcpRegistrar {
|
|
5
|
+
server;
|
|
6
|
+
operations;
|
|
7
|
+
pluginId;
|
|
8
|
+
constructor(server, operations, pluginId) {
|
|
9
|
+
this.server = server;
|
|
10
|
+
this.operations = operations;
|
|
11
|
+
this.pluginId = pluginId;
|
|
12
|
+
}
|
|
13
|
+
registerTool(name, config, handler) {
|
|
14
|
+
const { action, ...toolConfig } = config;
|
|
15
|
+
const callback = mcpTool(async (input) => {
|
|
16
|
+
const operation = this.operations.start({
|
|
17
|
+
pluginId: this.pluginId,
|
|
18
|
+
source: "mcp",
|
|
19
|
+
action: typeof action === "function" ? action(input) : action,
|
|
20
|
+
input,
|
|
21
|
+
...operationReferences(input),
|
|
22
|
+
});
|
|
23
|
+
let deferred = false;
|
|
24
|
+
const execution = {
|
|
25
|
+
operationId: operation.operationId,
|
|
26
|
+
deferCompletion() {
|
|
27
|
+
deferred = true;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
try {
|
|
31
|
+
const result = await handler(input, execution);
|
|
32
|
+
if (!deferred)
|
|
33
|
+
this.operations.finish(operation.operationId, "success", result);
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (this.operations.get(operation.operationId)?.status === "running") {
|
|
38
|
+
const normalized = asChatRoomError(error);
|
|
39
|
+
this.operations.finish(operation.operationId, "error", null, {
|
|
40
|
+
code: normalized.code,
|
|
41
|
+
message: normalized.message,
|
|
42
|
+
details: normalized.details,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
this.server.registerTool(name, toolConfig, callback);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function operationReferences(input) {
|
|
52
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
53
|
+
return {};
|
|
54
|
+
const record = input;
|
|
55
|
+
return {
|
|
56
|
+
...(typeof record.workspaceId === "string"
|
|
57
|
+
? { workspaceId: record.workspaceId }
|
|
58
|
+
: {}),
|
|
59
|
+
...(typeof record.processId === "string"
|
|
60
|
+
? { processId: record.processId }
|
|
61
|
+
: {}),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -35,8 +35,8 @@ export declare const workspaceOutputSchema: z.ZodObject<{
|
|
|
35
35
|
root: z.ZodString;
|
|
36
36
|
sourceRoot: z.ZodString;
|
|
37
37
|
mode: z.ZodEnum<{
|
|
38
|
-
worktree: "worktree";
|
|
39
38
|
checkout: "checkout";
|
|
39
|
+
worktree: "worktree";
|
|
40
40
|
}>;
|
|
41
41
|
createdAt: z.ZodString;
|
|
42
42
|
lastUsedAt: z.ZodString;
|
|
@@ -14,6 +14,10 @@ export declare class OperationLog {
|
|
|
14
14
|
run<T>(request: OperationStart, action: () => Promise<T>): Promise<T>;
|
|
15
15
|
list(query?: OperationQuery): Operation[];
|
|
16
16
|
get(operationId: string): Operation | null;
|
|
17
|
+
associate(operationId: string, references: {
|
|
18
|
+
workspaceId?: string | null;
|
|
19
|
+
processId?: string | null;
|
|
20
|
+
}): Operation;
|
|
17
21
|
clearHistory(): {
|
|
18
22
|
deleted: number;
|
|
19
23
|
preserved: number;
|
|
@@ -86,6 +86,16 @@ export class OperationLog {
|
|
|
86
86
|
get(operationId) {
|
|
87
87
|
return this.repository.get(operationId);
|
|
88
88
|
}
|
|
89
|
+
associate(operationId, references) {
|
|
90
|
+
const operation = this.require(operationId);
|
|
91
|
+
if (references.workspaceId !== undefined)
|
|
92
|
+
operation.workspaceId = references.workspaceId;
|
|
93
|
+
if (references.processId !== undefined)
|
|
94
|
+
operation.processId = references.processId;
|
|
95
|
+
this.repository.update(operation);
|
|
96
|
+
this.eventBus.emit({ type: "operation", operation });
|
|
97
|
+
return operation;
|
|
98
|
+
}
|
|
89
99
|
clearHistory() {
|
|
90
100
|
const result = this.repository.clearHistory();
|
|
91
101
|
this.eventBus.emit({ type: "operations-cleared", ...result });
|
|
@@ -25,11 +25,6 @@ declare const restoreSchema: z.ZodObject<{
|
|
|
25
25
|
publicPrefix: z.ZodNullable<z.ZodString>;
|
|
26
26
|
recoveryKey: z.ZodString;
|
|
27
27
|
}, z.core.$strict>;
|
|
28
|
-
declare const prefixSchema: z.ZodObject<{
|
|
29
|
-
publicPrefix: z.ZodString;
|
|
30
|
-
mcpUrl: z.ZodString;
|
|
31
|
-
webUrl: z.ZodString;
|
|
32
|
-
}, z.core.$strict>;
|
|
33
28
|
export declare class CloudApiClient {
|
|
34
29
|
private readonly baseUrl;
|
|
35
30
|
constructor(baseUrl: string);
|
|
@@ -44,7 +39,6 @@ export declare class CloudApiClient {
|
|
|
44
39
|
}>;
|
|
45
40
|
restore(identity: DeviceIdentity, recoveryKey: string): Promise<z.infer<typeof restoreSchema>>;
|
|
46
41
|
replaceRecoveryKey(identity: DeviceIdentity): Promise<z.infer<typeof recoveryCredentialSchema>>;
|
|
47
|
-
setPrefix(identity: DeviceIdentity, prefix: string): Promise<z.infer<typeof prefixSchema>>;
|
|
48
42
|
lease(identity: DeviceIdentity, requestedServices: CloudServiceId[]): Promise<CloudLeaseState>;
|
|
49
43
|
private devicePost;
|
|
50
44
|
}
|
|
@@ -30,13 +30,6 @@ const restoreSchema = z
|
|
|
30
30
|
recoveryKey: z.string().regex(/^crr\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/),
|
|
31
31
|
})
|
|
32
32
|
.strict();
|
|
33
|
-
const prefixSchema = z
|
|
34
|
-
.object({
|
|
35
|
-
publicPrefix: z.string().min(1),
|
|
36
|
-
mcpUrl: z.string().url(),
|
|
37
|
-
webUrl: z.string().url(),
|
|
38
|
-
})
|
|
39
|
-
.strict();
|
|
40
33
|
export class CloudApiClient {
|
|
41
34
|
baseUrl;
|
|
42
35
|
constructor(baseUrl) {
|
|
@@ -61,10 +54,6 @@ export class CloudApiClient {
|
|
|
61
54
|
const payload = { devicePublicKey: identity.devicePublicKey };
|
|
62
55
|
return recoveryCredentialSchema.parse(await this.devicePost("/v1/device/recovery-key", "recovery-key", identity, payload));
|
|
63
56
|
}
|
|
64
|
-
async setPrefix(identity, prefix) {
|
|
65
|
-
const payload = { devicePublicKey: identity.devicePublicKey, prefix };
|
|
66
|
-
return prefixSchema.parse(await this.devicePost("/v1/device/prefix", "prefix", identity, payload));
|
|
67
|
-
}
|
|
68
57
|
async lease(identity, requestedServices) {
|
|
69
58
|
const payload = {
|
|
70
59
|
devicePublicKey: identity.devicePublicKey,
|
|
@@ -34,7 +34,6 @@ export declare class CloudController {
|
|
|
34
34
|
recoveryKey: string;
|
|
35
35
|
}>;
|
|
36
36
|
replaceRecoveryKey(): Promise<string>;
|
|
37
|
-
setPrefix(prefix: string): Promise<CloudStatus>;
|
|
38
37
|
setService(service: CloudServiceId, enabled: boolean): Promise<CloudStatus>;
|
|
39
38
|
private refreshLeaseIfNeeded;
|
|
40
39
|
private connectTunnel;
|
|
@@ -134,18 +134,6 @@ export class CloudController {
|
|
|
134
134
|
const result = await this.api.replaceRecoveryKey(this.identity());
|
|
135
135
|
return result.recoveryKey;
|
|
136
136
|
}
|
|
137
|
-
async setPrefix(prefix) {
|
|
138
|
-
const result = await this.api.setPrefix(this.identity(), prefix);
|
|
139
|
-
this.state = { ...this.state, publicPrefix: result.publicPrefix };
|
|
140
|
-
await this.store.save(this.state);
|
|
141
|
-
if (!this.stopped && this.state.entitlements.length > 0) {
|
|
142
|
-
await this.refreshLeaseIfNeeded(true);
|
|
143
|
-
this.connectTunnel();
|
|
144
|
-
this.scheduleRenewal();
|
|
145
|
-
}
|
|
146
|
-
this.lastError = null;
|
|
147
|
-
return this.status();
|
|
148
|
-
}
|
|
149
137
|
async setService(service, enabled) {
|
|
150
138
|
if (!CLOUD_SERVICES.includes(service))
|
|
151
139
|
throw new Error(`Unknown Cloud service: ${service}`);
|
|
@@ -59,14 +59,21 @@ export class CloudTunnelClient {
|
|
|
59
59
|
this.closeWhenIdle = "stop";
|
|
60
60
|
}
|
|
61
61
|
updateLease(lease) {
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
lease.tunnelUrl !== this.lease.tunnelUrl;
|
|
65
|
-
const removesService = previousServices.some((service) => !lease.services.includes(service));
|
|
62
|
+
const tunnelChanged = lease.tunnelUrl !== this.lease.tunnelUrl;
|
|
63
|
+
const removesService = this.lease.services.some((service) => !lease.services.includes(service));
|
|
66
64
|
this.lease = lease;
|
|
67
65
|
this.acceptingServices = new Set(lease.services);
|
|
68
|
-
if (
|
|
66
|
+
if (this.stopped)
|
|
69
67
|
return;
|
|
68
|
+
// A new signed lease on the same tunnel is applied in-band. This keeps the
|
|
69
|
+
// current WebUI/API request alive while service permissions change.
|
|
70
|
+
if (!tunnelChanged) {
|
|
71
|
+
if (this.socket?.readyState === WebSocket.OPEN)
|
|
72
|
+
this.send({ type: "update-lease", lease: lease.token });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// Changing the tunnel endpoint still requires a reconnect. If a service is
|
|
76
|
+
// being removed, let current streams finish before moving the connection.
|
|
70
77
|
if (!removesService || this.streams.size === 0) {
|
|
71
78
|
this.socket?.close();
|
|
72
79
|
return;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PluginMcpRegistrar } from "../mcp/server/plugin-mcp-registrar.js";
|
|
1
2
|
export class PluginManager {
|
|
2
3
|
context;
|
|
3
4
|
plugins;
|
|
@@ -13,8 +14,11 @@ export class PluginManager {
|
|
|
13
14
|
}
|
|
14
15
|
}
|
|
15
16
|
registerMcp(server) {
|
|
16
|
-
for (const plugin of this.active)
|
|
17
|
-
plugin.registerMcp
|
|
17
|
+
for (const plugin of this.active) {
|
|
18
|
+
if (!plugin.registerMcp)
|
|
19
|
+
continue;
|
|
20
|
+
plugin.registerMcp(new PluginMcpRegistrar(server, this.context.operations, plugin.id));
|
|
21
|
+
}
|
|
18
22
|
}
|
|
19
23
|
async stop() {
|
|
20
24
|
for (const plugin of [...this.active].reverse())
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { OperationLog } from "../../operations/operation-log.js";
|
|
1
|
+
import type { PluginMcpRegistrar } from "../../mcp/server/plugin-mcp-registrar.js";
|
|
3
2
|
import type { ProcessSupervisor } from "./process-supervisor.js";
|
|
4
|
-
export declare function registerProcessTools(
|
|
3
|
+
export declare function registerProcessTools(mcp: PluginMcpRegistrar, processes: ProcessSupervisor): void;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { closedRead, destructiveLocalMutation,
|
|
3
|
-
export function registerProcessTools(
|
|
4
|
-
|
|
2
|
+
import { closedRead, destructiveLocalMutation, openWorldMutation, processSnapshotSchema, } from "../../mcp/server/tool-support.js";
|
|
3
|
+
export function registerProcessTools(mcp, processes) {
|
|
4
|
+
mcp.registerTool("process_start", {
|
|
5
5
|
title: "Start process",
|
|
6
|
-
description: "Start a supervised
|
|
6
|
+
description: "Start a supervised executable directly and return a ProcessId immediately. Prefer invoking the target executable without a shell. For compound shell commands, use bash -c rather than bash -lc unless login-shell semantics are explicitly required, because a login shell may replace the inherited PATH (for example, removing NVM-managed Node.js commands).",
|
|
7
7
|
inputSchema: z.object({
|
|
8
8
|
command: z.string().min(1),
|
|
9
9
|
args: z.array(z.string()).default([]),
|
|
@@ -14,43 +14,37 @@ export function registerProcessTools(server, processes, operations) {
|
|
|
14
14
|
}),
|
|
15
15
|
outputSchema: processSnapshotSchema,
|
|
16
16
|
annotations: openWorldMutation,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
? {}
|
|
25
|
-
:
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
action: "start",
|
|
18
|
+
}, (input, execution) => {
|
|
19
|
+
execution.deferCompletion();
|
|
20
|
+
return processes.start({
|
|
21
|
+
cwd: input.cwd,
|
|
22
|
+
command: input.command,
|
|
23
|
+
args: input.args,
|
|
24
|
+
...(input.env ? { env: input.env } : {}),
|
|
25
|
+
pty: input.pty,
|
|
26
|
+
...(input.timeoutMs === undefined
|
|
27
|
+
? {}
|
|
28
|
+
: { timeoutMs: input.timeoutMs }),
|
|
29
|
+
}, { source: "mcp", operationId: execution.operationId });
|
|
30
|
+
});
|
|
31
|
+
mcp.registerTool("process_read", {
|
|
28
32
|
title: "Read process",
|
|
29
33
|
description: "Read bounded stdout/stderr and current process state.",
|
|
30
34
|
inputSchema: z.object({ processId: z.string() }),
|
|
31
35
|
outputSchema: processSnapshotSchema,
|
|
32
36
|
annotations: closedRead,
|
|
33
|
-
}, mcpTool((input) => operations.run({
|
|
34
|
-
pluginId: "process",
|
|
35
|
-
source: "mcp",
|
|
36
37
|
action: "read",
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}, () => Promise.resolve(processes.read(input.processId)))));
|
|
40
|
-
server.registerTool("process_write", {
|
|
38
|
+
}, (input) => processes.read(input.processId));
|
|
39
|
+
mcp.registerTool("process_write", {
|
|
41
40
|
title: "Write process stdin",
|
|
42
41
|
description: "Write to stdin of a running process.",
|
|
43
42
|
inputSchema: z.object({ processId: z.string(), data: z.string() }),
|
|
44
43
|
outputSchema: processSnapshotSchema,
|
|
45
44
|
annotations: openWorldMutation,
|
|
46
|
-
}, mcpTool((input) => operations.run({
|
|
47
|
-
pluginId: "process",
|
|
48
|
-
source: "mcp",
|
|
49
45
|
action: "write",
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}, () => Promise.resolve(processes.write(input.processId, input.data)))));
|
|
53
|
-
server.registerTool("process_kill", {
|
|
46
|
+
}, (input) => processes.write(input.processId, input.data));
|
|
47
|
+
mcp.registerTool("process_kill", {
|
|
54
48
|
title: "Stop process",
|
|
55
49
|
description: "Terminate or force-kill a supervised process.",
|
|
56
50
|
inputSchema: z.object({
|
|
@@ -59,11 +53,6 @@ export function registerProcessTools(server, processes, operations) {
|
|
|
59
53
|
}),
|
|
60
54
|
outputSchema: processSnapshotSchema,
|
|
61
55
|
annotations: destructiveLocalMutation,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
source: "mcp",
|
|
65
|
-
action: input.force ? "kill" : "terminate",
|
|
66
|
-
processId: input.processId,
|
|
67
|
-
input,
|
|
68
|
-
}, () => Promise.resolve(processes.kill(input.processId, input.force)))));
|
|
56
|
+
action: (input) => (input.force ? "kill" : "terminate"),
|
|
57
|
+
}, (input) => processes.kill(input.processId, input.force));
|
|
69
58
|
}
|
|
@@ -6,23 +6,20 @@ import { ProcessSupervisor } from "./process-supervisor.js";
|
|
|
6
6
|
export const ProcessService = createServiceToken("process");
|
|
7
7
|
export function createProcessPlugin() {
|
|
8
8
|
let service = null;
|
|
9
|
-
let operations = null;
|
|
10
9
|
return {
|
|
11
10
|
id: "process",
|
|
12
11
|
activate(context) {
|
|
13
12
|
service = new ProcessSupervisor({ pipe: new PipeProcessBackend(), pty: new PtyProcessBackend() }, context.operations, context.events, context.config.process.maxOutputBytes, context.config.process.defaultTimeoutMs, context.config.process.maxCompletedProcesses);
|
|
14
|
-
operations = context.operations;
|
|
15
13
|
context.services.provide(ProcessService, service);
|
|
16
14
|
},
|
|
17
|
-
registerMcp(
|
|
18
|
-
if (!service
|
|
15
|
+
registerMcp(mcp) {
|
|
16
|
+
if (!service)
|
|
19
17
|
throw new Error("Process plugin is not active");
|
|
20
|
-
registerProcessTools(
|
|
18
|
+
registerProcessTools(mcp, service);
|
|
21
19
|
},
|
|
22
20
|
async deactivate() {
|
|
23
21
|
await service?.shutdown();
|
|
24
22
|
service = null;
|
|
25
|
-
operations = null;
|
|
26
23
|
},
|
|
27
24
|
};
|
|
28
25
|
}
|
|
@@ -28,20 +28,24 @@ export class ProcessSupervisor {
|
|
|
28
28
|
throw new ChatRoomError("CONFLICT", "Process supervisor is shutting down");
|
|
29
29
|
const processId = `proc_${randomUUID()}`;
|
|
30
30
|
const args = request.args ?? [];
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
31
|
+
const adoptedOperation = operationContext.operationId !== undefined;
|
|
32
|
+
const operationId = operationContext.operationId ??
|
|
33
|
+
this.operations.start({
|
|
34
|
+
pluginId: "process",
|
|
35
|
+
source: operationContext.source,
|
|
36
|
+
action: operationContext.action ?? "start",
|
|
37
|
+
processId,
|
|
38
|
+
input: {
|
|
39
|
+
command: request.command,
|
|
40
|
+
args,
|
|
41
|
+
cwd: request.cwd,
|
|
42
|
+
pty: request.pty ?? false,
|
|
43
|
+
timeoutMs: request.timeoutMs ?? this.defaultTimeoutMs,
|
|
44
|
+
env: request.env ?? {},
|
|
45
|
+
},
|
|
46
|
+
}).operationId;
|
|
47
|
+
if (adoptedOperation)
|
|
48
|
+
this.operations.associate(operationId, { processId });
|
|
45
49
|
let backend;
|
|
46
50
|
try {
|
|
47
51
|
// Child environments inherit only the runtime allowlist, then apply explicit request overrides.
|
|
@@ -51,7 +55,8 @@ export class ProcessSupervisor {
|
|
|
51
55
|
});
|
|
52
56
|
}
|
|
53
57
|
catch (error) {
|
|
54
|
-
|
|
58
|
+
if (!adoptedOperation)
|
|
59
|
+
this.operations.finish(operationId, "error", null, error);
|
|
55
60
|
throw new ChatRoomError("PROCESS_FAILED", `Failed to start process: ${request.command}`, undefined, { cause: error });
|
|
56
61
|
}
|
|
57
62
|
let resolveSettled;
|
|
@@ -71,7 +76,7 @@ export class ProcessSupervisor {
|
|
|
71
76
|
timeout: null,
|
|
72
77
|
forceTimeout: null,
|
|
73
78
|
timedOut: false,
|
|
74
|
-
operationId
|
|
79
|
+
operationId,
|
|
75
80
|
settled,
|
|
76
81
|
resolveSettled,
|
|
77
82
|
};
|
package/dist/plugins/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { McpServer } from "@modelcontextprotocol/server";
|
|
2
1
|
import type { ChatRoomConfig } from "../config/types.js";
|
|
3
2
|
import type { AppDatabase } from "../infrastructure/database/app-database.js";
|
|
3
|
+
import type { PluginMcpRegistrar } from "../mcp/server/plugin-mcp-registrar.js";
|
|
4
4
|
import type { OperationLog } from "../operations/operation-log.js";
|
|
5
5
|
import type { RuntimeEventBus } from "../app/event-bus.js";
|
|
6
6
|
import type { ExternalAccessRegistry } from "../app/external-access-registry.js";
|
|
@@ -27,6 +27,6 @@ export interface PluginContext {
|
|
|
27
27
|
export interface InternalPlugin {
|
|
28
28
|
id: string;
|
|
29
29
|
activate(context: PluginContext): Promise<void> | void;
|
|
30
|
-
registerMcp?(
|
|
30
|
+
registerMcp?(mcp: PluginMcpRegistrar): void;
|
|
31
31
|
deactivate?(): Promise<void> | void;
|
|
32
32
|
}
|
|
@@ -15,18 +15,6 @@ export function createCloudApiRouter(controller, operations) {
|
|
|
15
15
|
res.json(await operations.run({ pluginId: "cloud", source: "gui", action: "restore", input: body }, () => controller.restore(body.recoveryKey)));
|
|
16
16
|
}));
|
|
17
17
|
router.post("/cloud/recovery-key", asyncRoute(async (_req, res) => res.json(await operations.run({ pluginId: "cloud", source: "gui", action: "recovery-key.replace" }, async () => ({ recoveryKey: await controller.replaceRecoveryKey() })))));
|
|
18
|
-
router.post("/cloud/prefix", asyncRoute(async (req, res) => {
|
|
19
|
-
const body = z
|
|
20
|
-
.object({ prefix: z.string().min(1).max(64) })
|
|
21
|
-
.strict()
|
|
22
|
-
.parse(req.body);
|
|
23
|
-
res.json(await operations.run({
|
|
24
|
-
pluginId: "cloud",
|
|
25
|
-
source: "gui",
|
|
26
|
-
action: "prefix.set",
|
|
27
|
-
input: body,
|
|
28
|
-
}, () => controller.setPrefix(body.prefix)));
|
|
29
|
-
}));
|
|
30
18
|
router.post("/cloud/services/:service", asyncRoute(async (req, res) => {
|
|
31
19
|
const service = CLOUD_SERVICE_SCHEMA.parse(req.params.service);
|
|
32
20
|
const body = z.object({ enabled: z.boolean() }).strict().parse(req.body);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { OperationLog } from "../../operations/operation-log.js";
|
|
1
|
+
import type { PluginMcpRegistrar } from "../../mcp/server/plugin-mcp-registrar.js";
|
|
3
2
|
import type { WorkspaceService } from "./workspace-service.js";
|
|
4
|
-
export declare function registerWorkspaceTools(
|
|
3
|
+
export declare function registerWorkspaceTools(mcp: PluginMcpRegistrar, workspaces: WorkspaceService): void;
|