@zhanguym/chatroom 1.2.0 → 1.3.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/app/application.d.ts +5 -0
- package/dist/app/application.js +55 -4
- package/dist/app/lifecycle.js +12 -0
- package/dist/app/workspace-access.d.ts +17 -4
- package/dist/core/operations/types.d.ts +1 -1
- package/dist/core/operations/types.js +1 -1
- package/dist/infrastructure/database/agent-mcp-settings-repository.d.ts +12 -0
- package/dist/infrastructure/database/agent-mcp-settings-repository.js +55 -0
- package/dist/infrastructure/database/app-database.js +3 -3
- package/dist/infrastructure/database/migrate.d.ts +2 -0
- package/dist/infrastructure/database/migrate.js +68 -0
- package/dist/infrastructure/database/schema.d.ts +3 -1
- package/dist/infrastructure/database/schema.js +35 -6
- package/dist/infrastructure/database/workspace-repository.d.ts +28 -0
- package/dist/infrastructure/database/workspace-repository.js +126 -0
- package/dist/mcp/custom/manager.d.ts +3 -1
- package/dist/mcp/custom/manager.js +37 -2
- package/dist/mcp/server/plugin-mcp-registrar.d.ts +4 -1
- package/dist/mcp/server/plugin-mcp-registrar.js +7 -2
- package/dist/mcp/server/tool-control.d.ts +6 -1
- package/dist/mcp/server/tool-control.js +11 -2
- package/dist/native/macos/ChatRoomComputerHelper.app/Contents/MacOS/chatroom-computer-helper +0 -0
- package/dist/native/windows/chatroom-computer-helper.exe +0 -0
- package/dist/plugins/agent/custom-mcp-store.d.ts +17 -0
- package/dist/plugins/agent/custom-mcp-store.js +38 -0
- package/dist/plugins/agent/mcp-gateway.d.ts +16 -0
- package/dist/plugins/agent/mcp-gateway.js +110 -0
- package/dist/plugins/agent/mcp.js +3 -2
- package/dist/plugins/agent/plugin.js +1 -1
- package/dist/plugins/agent/provider.d.ts +1 -0
- package/dist/plugins/agent/providers/codex/launch.d.ts +17 -0
- package/dist/plugins/agent/providers/codex/launch.js +24 -0
- package/dist/plugins/agent/providers/codex/provider.d.ts +3 -1
- package/dist/plugins/agent/providers/codex/provider.js +8 -2
- package/dist/plugins/agent/providers/codex/rpc.d.ts +7 -1
- package/dist/plugins/agent/providers/codex/rpc.js +28 -3
- package/dist/plugins/agent/providers/index.d.ts +2 -1
- package/dist/plugins/agent/providers/index.js +5 -2
- package/dist/plugins/agent/repository.js +10 -7
- package/dist/plugins/agent/runtime-files.d.ts +4 -4
- package/dist/plugins/agent/runtime-files.js +18 -14
- package/dist/plugins/agent/runtime.d.ts +2 -0
- package/dist/plugins/agent/runtime.js +52 -13
- package/dist/plugins/agent/types.d.ts +3 -2
- package/dist/plugins/cloud/v2/tunnel-client.js +1 -7
- package/dist/plugins/plugin-manager.d.ts +6 -1
- package/dist/plugins/plugin-manager.js +9 -2
- package/dist/plugins/types.d.ts +4 -0
- package/dist/plugins/web/http/agent-api-router.js +15 -1
- package/dist/plugins/web/http/git-api-router.js +13 -13
- package/dist/plugins/web/http/rtc-api-router.js +3 -0
- package/dist/plugins/web/http/runtime-api-router.js +22 -0
- package/dist/plugins/web/http/workspace-api-router.js +63 -12
- package/dist/plugins/web/plugin.js +1 -1
- package/dist/plugins/web/runtime.d.ts +3 -1
- package/dist/plugins/web/runtime.js +5 -1
- package/dist/plugins/workspace/mcp.js +20 -15
- package/dist/plugins/workspace/plugin.js +1 -1
- package/dist/plugins/workspace/workspace-root-policy.d.ts +0 -1
- package/dist/plugins/workspace/workspace-root-policy.js +25 -19
- package/dist/plugins/workspace/workspace-service.d.ts +20 -8
- package/dist/plugins/workspace/workspace-service.js +270 -64
- package/dist/web/assets/{AgentView-CMxIn3KB.css → AgentView-BqAfmgc4.css} +1 -1
- package/dist/web/assets/AgentView-tr6COjRg.js +4 -0
- package/dist/web/assets/{CloudView-CXuTK7Of.js → CloudView-D-fYLw_O.js} +1 -1
- package/dist/web/assets/{CodeViewer.vue_vue_type_script_setup_true_lang-DK6xGH2G.js → CodeViewer.vue_vue_type_script_setup_true_lang-BtA5aWrS.js} +1 -1
- package/dist/web/assets/ComputerView-B25C_C_h.js +1 -0
- package/dist/web/assets/ComputerView-vSSsexBY.css +1 -0
- package/dist/web/assets/{MarkdownContent-D4KYOn-f.js → MarkdownContent-BuDRrZ6c.js} +1 -1
- package/dist/web/assets/McpToolsView-D0G_HACK.css +1 -0
- package/dist/web/assets/McpToolsView-s0-Qvg2g.js +2 -0
- package/dist/web/assets/OperationsView-B_0SN40Y.js +4 -0
- package/dist/web/assets/{ProcessesView-NUAplTg4.js → ProcessesView-B2i-O0AL.js} +3 -3
- package/dist/web/assets/StateChip.vue_vue_type_script_setup_true_lang-DwnqcvSQ.js +1 -0
- package/dist/web/assets/{SystemLogsView-DGW_4nS0.js → SystemLogsView-BR4FZoAh.js} +1 -1
- package/dist/web/assets/{TerminalView-DCqHCfb5.js → TerminalView-2Z1blITD.js} +1 -1
- package/dist/web/assets/VSelect-CHI_FOMl.js +1 -0
- package/dist/web/assets/VSheet-CKEhsY-7.js +1 -0
- package/dist/web/assets/VSpacer-FbdDPUGx.js +1 -0
- package/dist/web/assets/{VSwitch-o-WPjZs0.js → VSwitch-o9DuRTKF.js} +1 -1
- package/dist/web/assets/{VTabs-D2y6M66D.js → VTabs-BSfSKBtT.js} +1 -1
- package/dist/web/assets/{VTextarea-DFSNyIwH.js → VTextarea-D_kPhUVU.js} +1 -1
- package/dist/web/assets/WorkspaceDownloadPreview-BoALqL1M.js +1 -0
- package/dist/web/assets/WorkspaceDownloadPreview-dFslZB0W.css +1 -0
- package/dist/web/assets/{WorkspaceMediaPreview-tkpcPK82.js → WorkspaceMediaPreview-qjt04eze.js} +3 -3
- package/dist/web/assets/{WorkspacePdfPreview-PqIFAx2J.js → WorkspacePdfPreview-BrUJpmNs.js} +1 -1
- package/dist/web/assets/WorkspacesView-CHXmDTlX.js +2 -0
- package/dist/web/assets/WorkspacesView-Dt24eGQ4.css +1 -0
- package/dist/web/assets/index-9DLDvF8M.js +19 -0
- package/dist/web/assets/{useMasterDetailFocus-DgNjKho5.js → useMasterDetailFocus-Chh0cM6g.js} +1 -1
- package/dist/web/assets/useOperationLabels-DF67nIF0.js +1 -0
- package/dist/web/index.html +1 -1
- package/dist/web/transport-sw.js +43 -15
- package/package.json +1 -1
- package/dist/web/assets/AgentView-XEf1AUcH.js +0 -4
- package/dist/web/assets/ComputerView-Bz2Wsz2X.css +0 -1
- package/dist/web/assets/ComputerView-Cxn7zhrw.js +0 -1
- package/dist/web/assets/McpToolsView-DdYOoY9_.css +0 -1
- package/dist/web/assets/McpToolsView-XLtFPY_M.js +0 -2
- package/dist/web/assets/OperationsView-DRx2Qq8W.js +0 -4
- package/dist/web/assets/StateChip.vue_vue_type_script_setup_true_lang-BYoHRX9h.js +0 -1
- package/dist/web/assets/VSelect-Dla-KtWu.js +0 -1
- package/dist/web/assets/VSheet-B9d-Gdzq.js +0 -1
- package/dist/web/assets/VSpacer--LXbKJE2.js +0 -1
- package/dist/web/assets/WorkspaceDownloadPreview-DJKSNaZ_.css +0 -1
- package/dist/web/assets/WorkspaceDownloadPreview-DaFQwUIa.js +0 -1
- package/dist/web/assets/WorkspacesView-ClA6xcjn.css +0 -1
- package/dist/web/assets/WorkspacesView-DkGvIFCb.js +0 -2
- package/dist/web/assets/index-CacDJSDn.js +0 -19
- package/dist/web/assets/useOperationLabels-BNiE-bRq.js +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
export class AgentRepository {
|
|
2
3
|
database;
|
|
3
4
|
constructor(database) {
|
|
@@ -27,23 +28,23 @@ export class AgentRepository {
|
|
|
27
28
|
}
|
|
28
29
|
insertSession(session) {
|
|
29
30
|
this.database.raw
|
|
30
|
-
.prepare("INSERT INTO agent_sessions(id,
|
|
31
|
-
.run(session.id, session.
|
|
31
|
+
.prepare("INSERT INTO agent_sessions(id, workspace_id, provider, status, created_at, updated_at) VALUES(?,?,?,?,?,?)")
|
|
32
|
+
.run(session.id, session.workspaceId, session.provider, session.status, session.createdAt, session.updatedAt);
|
|
32
33
|
}
|
|
33
34
|
updateSession(session) {
|
|
34
35
|
this.database.raw
|
|
35
|
-
.prepare("UPDATE agent_sessions SET
|
|
36
|
-
.run(session.
|
|
36
|
+
.prepare("UPDATE agent_sessions SET workspace_id=?, provider=?, status=?, created_at=?, updated_at=? WHERE id=?")
|
|
37
|
+
.run(session.workspaceId, session.provider, session.status, session.createdAt, session.updatedAt, session.id);
|
|
37
38
|
}
|
|
38
39
|
getSession(id) {
|
|
39
40
|
const row = this.database.raw
|
|
40
|
-
.prepare("SELECT s.*, p.model AS model, p.reasoning_effort, p.reasoning_summary, p.service_tier, p.approval_policy_json, p.approvals_reviewer, p.permission_mode, p.token_usage_json, CASE WHEN EXISTS (SELECT 1 FROM agent_items ai JOIN agent_turns at ON at.id=ai.turn_id WHERE at.session_id=s.id AND ai.type='interaction' AND ai.status='waiting' AND json_extract(ai.payload_json,'$.interaction.kind')='approval') THEN 'approval' WHEN EXISTS (SELECT 1 FROM agent_items ai JOIN agent_turns at ON at.id=ai.turn_id WHERE at.session_id=s.id AND ai.type='interaction' AND ai.status='waiting') THEN 'input' ELSE NULL END AS attention_kind, (SELECT COUNT(*) FROM agent_items ai JOIN agent_turns at ON at.id=ai.turn_id WHERE at.session_id=s.id AND ai.type='interaction' AND ai.status='waiting') AS attention_count FROM agent_sessions s LEFT JOIN agent_session_providers p ON p.session_id=s.id AND p.provider=s.provider WHERE s.id=?")
|
|
41
|
+
.prepare("SELECT s.*, w.name_override AS workspace_name, w.path AS workspace_path, p.model AS model, p.reasoning_effort, p.reasoning_summary, p.service_tier, p.approval_policy_json, p.approvals_reviewer, p.permission_mode, p.token_usage_json, CASE WHEN EXISTS (SELECT 1 FROM agent_items ai JOIN agent_turns at ON at.id=ai.turn_id WHERE at.session_id=s.id AND ai.type='interaction' AND ai.status='waiting' AND json_extract(ai.payload_json,'$.interaction.kind')='approval') THEN 'approval' WHEN EXISTS (SELECT 1 FROM agent_items ai JOIN agent_turns at ON at.id=ai.turn_id WHERE at.session_id=s.id AND ai.type='interaction' AND ai.status='waiting') THEN 'input' ELSE NULL END AS attention_kind, (SELECT COUNT(*) FROM agent_items ai JOIN agent_turns at ON at.id=ai.turn_id WHERE at.session_id=s.id AND ai.type='interaction' AND ai.status='waiting') AS attention_count FROM agent_sessions s LEFT JOIN workspaces w ON w.id=s.workspace_id LEFT JOIN agent_session_providers p ON p.session_id=s.id AND p.provider=s.provider WHERE s.id=?")
|
|
41
42
|
.get(id);
|
|
42
43
|
return row ? sessionFromRow(row) : null;
|
|
43
44
|
}
|
|
44
45
|
listSessions() {
|
|
45
46
|
return this.database.raw
|
|
46
|
-
.prepare("SELECT s.*, p.model AS model, p.reasoning_effort, p.reasoning_summary, p.service_tier, p.approval_policy_json, p.approvals_reviewer, p.permission_mode, p.token_usage_json, CASE WHEN EXISTS (SELECT 1 FROM agent_items ai JOIN agent_turns at ON at.id=ai.turn_id WHERE at.session_id=s.id AND ai.type='interaction' AND ai.status='waiting' AND json_extract(ai.payload_json,'$.interaction.kind')='approval') THEN 'approval' WHEN EXISTS (SELECT 1 FROM agent_items ai JOIN agent_turns at ON at.id=ai.turn_id WHERE at.session_id=s.id AND ai.type='interaction' AND ai.status='waiting') THEN 'input' ELSE NULL END AS attention_kind, (SELECT COUNT(*) FROM agent_items ai JOIN agent_turns at ON at.id=ai.turn_id WHERE at.session_id=s.id AND ai.type='interaction' AND ai.status='waiting') AS attention_count FROM agent_sessions s LEFT JOIN agent_session_providers p ON p.session_id=s.id AND p.provider=s.provider ORDER BY s.updated_at DESC")
|
|
47
|
+
.prepare("SELECT s.*, w.name_override AS workspace_name, w.path AS workspace_path, p.model AS model, p.reasoning_effort, p.reasoning_summary, p.service_tier, p.approval_policy_json, p.approvals_reviewer, p.permission_mode, p.token_usage_json, CASE WHEN EXISTS (SELECT 1 FROM agent_items ai JOIN agent_turns at ON at.id=ai.turn_id WHERE at.session_id=s.id AND ai.type='interaction' AND ai.status='waiting' AND json_extract(ai.payload_json,'$.interaction.kind')='approval') THEN 'approval' WHEN EXISTS (SELECT 1 FROM agent_items ai JOIN agent_turns at ON at.id=ai.turn_id WHERE at.session_id=s.id AND ai.type='interaction' AND ai.status='waiting') THEN 'input' ELSE NULL END AS attention_kind, (SELECT COUNT(*) FROM agent_items ai JOIN agent_turns at ON at.id=ai.turn_id WHERE at.session_id=s.id AND ai.type='interaction' AND ai.status='waiting') AS attention_count FROM agent_sessions s LEFT JOIN workspaces w ON w.id=s.workspace_id LEFT JOIN agent_session_providers p ON p.session_id=s.id AND p.provider=s.provider ORDER BY s.updated_at DESC")
|
|
47
48
|
.all().map(sessionFromRow);
|
|
48
49
|
}
|
|
49
50
|
deleteSession(id) {
|
|
@@ -131,7 +132,9 @@ function selectByIds(database, prefix, sessionId, ids) {
|
|
|
131
132
|
function sessionFromRow(row) {
|
|
132
133
|
return {
|
|
133
134
|
id: row.id,
|
|
134
|
-
|
|
135
|
+
workspaceId: row.workspace_id,
|
|
136
|
+
workspaceName: row.workspace_name ??
|
|
137
|
+
(row.workspace_path ? path.basename(row.workspace_path) : ""),
|
|
135
138
|
tokenUsage: parseJson(row.token_usage_json),
|
|
136
139
|
provider: row.provider,
|
|
137
140
|
model: row.model,
|
|
@@ -2,7 +2,7 @@ import type { ProviderItem } from "./provider.js";
|
|
|
2
2
|
import type { AgentStore } from "./store.js";
|
|
3
3
|
import type { AgentHistory, AgentSession, AgentTurnInput } from "./types.js";
|
|
4
4
|
export declare function sessionAttachmentPrefix(sessionId: string): string;
|
|
5
|
-
export declare function cleanupSessionFiles(store: AgentStore, history: AgentHistory): Promise<void>;
|
|
6
|
-
export declare function cleanupOrphanAttachments(store: AgentStore, maxAgeMs: number): Promise<void>;
|
|
7
|
-
export declare function prepareProviderItem(
|
|
8
|
-
export declare function validateAttachments(session: AgentSession, attachments: AgentTurnInput["attachments"]): Promise<AgentTurnInput["attachments"]>;
|
|
5
|
+
export declare function cleanupSessionFiles(store: AgentStore, history: AgentHistory, workspaceRoot: string | null): Promise<void>;
|
|
6
|
+
export declare function cleanupOrphanAttachments(store: AgentStore, resolveWorkspace: (workspaceId: string) => Promise<string>, maxAgeMs: number): Promise<void>;
|
|
7
|
+
export declare function prepareProviderItem(workspaceRoot: string, item: ProviderItem): Promise<ProviderItem>;
|
|
8
|
+
export declare function validateAttachments(session: AgentSession, workspaceRoot: string, attachments: AgentTurnInput["attachments"]): Promise<AgentTurnInput["attachments"]>;
|
|
@@ -7,9 +7,10 @@ import { runtimeErrorMessage } from "./runtime-state.js";
|
|
|
7
7
|
export function sessionAttachmentPrefix(sessionId) {
|
|
8
8
|
return ".chatroom/agent-attachments/" + sessionId + "/";
|
|
9
9
|
}
|
|
10
|
-
export async function cleanupSessionFiles(store, history) {
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
export async function cleanupSessionFiles(store, history, workspaceRoot) {
|
|
11
|
+
if (!workspaceRoot)
|
|
12
|
+
return;
|
|
13
|
+
const retained = referencedManagedFiles(store, history.session.workspaceId, history.session.id);
|
|
13
14
|
const attachmentPrefix = sessionAttachmentPrefix(history.session.id);
|
|
14
15
|
const attachmentDirectory = path.join(workspaceRoot, attachmentPrefix);
|
|
15
16
|
try {
|
|
@@ -29,14 +30,17 @@ export async function cleanupSessionFiles(store, history) {
|
|
|
29
30
|
await removeFileWithin(workspaceRoot, filePath).catch(() => undefined);
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
|
-
export async function cleanupOrphanAttachments(store, maxAgeMs) {
|
|
33
|
+
export async function cleanupOrphanAttachments(store, resolveWorkspace, maxAgeMs) {
|
|
33
34
|
const now = Date.now();
|
|
34
|
-
const
|
|
35
|
-
for (const
|
|
36
|
-
|
|
35
|
+
const workspaceIds = new Set(store.listSessions().map((session) => session.workspaceId));
|
|
36
|
+
for (const workspaceId of workspaceIds) {
|
|
37
|
+
const workspaceRoot = await resolveWorkspace(workspaceId).catch(() => null);
|
|
38
|
+
if (!workspaceRoot)
|
|
39
|
+
continue;
|
|
40
|
+
await removeUnreferencedAttachmentFiles(workspaceRoot, referencedManagedFiles(store, workspaceId), now, maxAgeMs);
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
|
-
export async function prepareProviderItem(
|
|
43
|
+
export async function prepareProviderItem(workspaceRoot, item) {
|
|
40
44
|
if (item.type !== "artifact")
|
|
41
45
|
return item;
|
|
42
46
|
try {
|
|
@@ -45,7 +49,7 @@ export async function prepareProviderItem(session, item) {
|
|
|
45
49
|
...item,
|
|
46
50
|
artifact: {
|
|
47
51
|
...item.artifact,
|
|
48
|
-
path: (await resolveReadableFileWithin(
|
|
52
|
+
path: (await resolveReadableFileWithin(workspaceRoot, item.artifact.path)).relative,
|
|
49
53
|
},
|
|
50
54
|
};
|
|
51
55
|
}
|
|
@@ -61,7 +65,7 @@ export async function prepareProviderItem(session, item) {
|
|
|
61
65
|
const content = await readFile(item.artifact.path);
|
|
62
66
|
if (content.byteLength > 32 * 1024 * 1024)
|
|
63
67
|
throw new ChatRoomError("INVALID_INPUT", "Agent artifact is too large");
|
|
64
|
-
await writeBytesAtomicWithin(
|
|
68
|
+
await writeBytesAtomicWithin(workspaceRoot, target, content);
|
|
65
69
|
return {
|
|
66
70
|
...item,
|
|
67
71
|
artifact: {
|
|
@@ -78,9 +82,9 @@ export async function prepareProviderItem(session, item) {
|
|
|
78
82
|
};
|
|
79
83
|
}
|
|
80
84
|
}
|
|
81
|
-
export async function validateAttachments(session, attachments) {
|
|
85
|
+
export async function validateAttachments(session, workspaceRoot, attachments) {
|
|
82
86
|
return Promise.all(attachments.map(async (attachment) => {
|
|
83
|
-
const resolved = await resolveReadableFileWithin(
|
|
87
|
+
const resolved = await resolveReadableFileWithin(workspaceRoot, attachment.path);
|
|
84
88
|
if (resolved.relative.startsWith(".chatroom/agent-attachments/") &&
|
|
85
89
|
!resolved.relative.startsWith(sessionAttachmentPrefix(session.id)))
|
|
86
90
|
throw new ChatRoomError("FORBIDDEN", "Agent attachment belongs to another session");
|
|
@@ -90,12 +94,12 @@ export async function validateAttachments(session, attachments) {
|
|
|
90
94
|
};
|
|
91
95
|
}));
|
|
92
96
|
}
|
|
93
|
-
function referencedManagedFiles(store,
|
|
97
|
+
function referencedManagedFiles(store, workspaceId, excludedSessionId) {
|
|
94
98
|
const paths = new Set();
|
|
95
99
|
for (const session of store.listSessions()) {
|
|
96
100
|
if (session.id === excludedSessionId)
|
|
97
101
|
continue;
|
|
98
|
-
if (session.
|
|
102
|
+
if (session.workspaceId !== workspaceId)
|
|
99
103
|
continue;
|
|
100
104
|
const history = store.history(session.id);
|
|
101
105
|
if (!history)
|
|
@@ -14,6 +14,7 @@ export declare class AgentRuntime {
|
|
|
14
14
|
private closed;
|
|
15
15
|
private readonly providers;
|
|
16
16
|
constructor(store: AgentStore, workspaces: WorkspaceAccess, events: RuntimeEventBus, providers: AgentProvider[]);
|
|
17
|
+
refreshMcpServers(): Promise<void>;
|
|
17
18
|
providerStatuses(): Promise<AgentProviderStatus[]>;
|
|
18
19
|
providerDetails(providerId: AgentProviderId): Promise<AgentProviderDetails>;
|
|
19
20
|
models(providerId: AgentProviderId): Promise<AgentModel[]>;
|
|
@@ -33,6 +34,7 @@ export declare class AgentRuntime {
|
|
|
33
34
|
interrupt(sessionId: string): Promise<void>;
|
|
34
35
|
respondInteraction(sessionId: string, itemId: string, response: AgentInteractionResponse): Promise<AgentItem>;
|
|
35
36
|
deleteSession(sessionId: string): Promise<void>;
|
|
37
|
+
deleteSessions(sessionIds: readonly string[]): Promise<void>;
|
|
36
38
|
private deleteSessionUnlocked;
|
|
37
39
|
switchProvider(sessionId: string, targetProvider: AgentProviderId, patch?: Partial<AgentSessionConfig>): Promise<AgentSession>;
|
|
38
40
|
private switchProviderUnlocked;
|
|
@@ -30,6 +30,12 @@ export class AgentRuntime {
|
|
|
30
30
|
}
|
|
31
31
|
this.store.reconcileInterrupted(new Date().toISOString());
|
|
32
32
|
}
|
|
33
|
+
async refreshMcpServers() {
|
|
34
|
+
const results = await Promise.allSettled([...this.providers.values()].map((provider) => provider.refreshMcpServers?.()));
|
|
35
|
+
const failures = results.filter((result) => result.status === "rejected");
|
|
36
|
+
if (failures.length)
|
|
37
|
+
throw new AggregateError(failures.map((failure) => failure.reason), "Failed to refresh MCP servers for one or more Agent providers");
|
|
38
|
+
}
|
|
33
39
|
async providerStatuses() {
|
|
34
40
|
return Promise.all([...this.providers.values()].map(async (provider) => ({
|
|
35
41
|
id: provider.id,
|
|
@@ -67,8 +73,8 @@ export class AgentRuntime {
|
|
|
67
73
|
return update;
|
|
68
74
|
}
|
|
69
75
|
async createSession(input) {
|
|
70
|
-
const workspace = await this.workspaces.info(input.
|
|
71
|
-
const workspaceRoot = workspace.
|
|
76
|
+
const workspace = await this.workspaces.info(input.workspaceId);
|
|
77
|
+
const workspaceRoot = workspace.path;
|
|
72
78
|
const provider = this.requireProvider(input.provider);
|
|
73
79
|
const providerStatus = await provider.probe();
|
|
74
80
|
if (!providerStatus.installed)
|
|
@@ -87,7 +93,8 @@ export class AgentRuntime {
|
|
|
87
93
|
const now = new Date().toISOString();
|
|
88
94
|
const session = {
|
|
89
95
|
id: "agent_" + randomUUID(),
|
|
90
|
-
|
|
96
|
+
workspaceId: workspace.id,
|
|
97
|
+
workspaceName: workspace.name,
|
|
91
98
|
provider: input.provider,
|
|
92
99
|
...config,
|
|
93
100
|
tokenUsage: null,
|
|
@@ -122,7 +129,8 @@ export class AgentRuntime {
|
|
|
122
129
|
const text = input.text.trim();
|
|
123
130
|
if (!text && input.attachments.length === 0)
|
|
124
131
|
throw new ChatRoomError("INVALID_INPUT", "Agent message is empty");
|
|
125
|
-
const
|
|
132
|
+
const workspaceRoot = await this.workspaces.resolve(session.workspaceId);
|
|
133
|
+
const attachments = await validateAttachments(session, workspaceRoot, input.attachments);
|
|
126
134
|
await this.validateInputModalities(session, text, attachments);
|
|
127
135
|
const providerSession = await this.providerSession(session);
|
|
128
136
|
const active = this.beginTurn(session, providerSession, source, session.serviceTier, { text, attachments });
|
|
@@ -160,7 +168,8 @@ export class AgentRuntime {
|
|
|
160
168
|
const text = input.text.trim();
|
|
161
169
|
if (!text && input.attachments.length === 0)
|
|
162
170
|
throw new ChatRoomError("INVALID_INPUT", "Agent message is empty");
|
|
163
|
-
const
|
|
171
|
+
const workspaceRoot = await this.workspaces.resolve(session.workspaceId);
|
|
172
|
+
const attachments = await validateAttachments(session, workspaceRoot, input.attachments);
|
|
164
173
|
await this.validateInputModalities(session, text, attachments);
|
|
165
174
|
if (this.activeTurns.get(sessionId) !== active)
|
|
166
175
|
throw new ChatRoomError("CONFLICT", "Agent turn ended before steering");
|
|
@@ -412,6 +421,34 @@ export class AgentRuntime {
|
|
|
412
421
|
async deleteSession(sessionId) {
|
|
413
422
|
return this.withSessionMutation(sessionId, () => this.deleteSessionUnlocked(sessionId));
|
|
414
423
|
}
|
|
424
|
+
async deleteSessions(sessionIds) {
|
|
425
|
+
const ids = [...new Set(sessionIds)];
|
|
426
|
+
if (ids.length === 0)
|
|
427
|
+
throw new ChatRoomError("INVALID_INPUT", "Select at least one Agent session");
|
|
428
|
+
if (ids.length > 100)
|
|
429
|
+
throw new ChatRoomError("INVALID_INPUT", "Cannot delete more than 100 Agent sessions at once");
|
|
430
|
+
// Validate every target before changing anything, so active/unknown sessions
|
|
431
|
+
// cannot cause an avoidable partial batch deletion.
|
|
432
|
+
for (const id of ids) {
|
|
433
|
+
this.requireSession(id);
|
|
434
|
+
if (this.sessionMutations.has(id))
|
|
435
|
+
throw new ChatRoomError("CONFLICT", "An Agent session is already being modified");
|
|
436
|
+
if (this.activeTurns.has(id))
|
|
437
|
+
throw new ChatRoomError("CONFLICT", "Cannot delete a running Agent session");
|
|
438
|
+
}
|
|
439
|
+
// Acquire all locks synchronously before the first await, preventing a turn
|
|
440
|
+
// or another mutation from starting for any member of this batch.
|
|
441
|
+
for (const id of ids)
|
|
442
|
+
this.sessionMutations.add(id);
|
|
443
|
+
try {
|
|
444
|
+
for (const id of ids)
|
|
445
|
+
await this.deleteSessionUnlocked(id);
|
|
446
|
+
}
|
|
447
|
+
finally {
|
|
448
|
+
for (const id of ids)
|
|
449
|
+
this.sessionMutations.delete(id);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
415
452
|
async deleteSessionUnlocked(sessionId) {
|
|
416
453
|
if (this.activeTurns.has(sessionId))
|
|
417
454
|
throw new ChatRoomError("CONFLICT", "Cannot delete a running Agent session");
|
|
@@ -421,7 +458,9 @@ export class AgentRuntime {
|
|
|
421
458
|
await providerSession.close().catch(() => undefined);
|
|
422
459
|
this.providerSessions.delete(sessionId);
|
|
423
460
|
}
|
|
424
|
-
await cleanupSessionFiles(this.store, history
|
|
461
|
+
await cleanupSessionFiles(this.store, history, await this.workspaces
|
|
462
|
+
.resolve(history.session.workspaceId)
|
|
463
|
+
.catch(() => null));
|
|
425
464
|
this.store.deleteSession(sessionId);
|
|
426
465
|
this.invalidateSession(sessionId, { deleted: true });
|
|
427
466
|
}
|
|
@@ -434,8 +473,8 @@ export class AgentRuntime {
|
|
|
434
473
|
throw new ChatRoomError("CONFLICT", "Wait for the current turn to finish before switching agents");
|
|
435
474
|
if (session.provider === targetProvider)
|
|
436
475
|
return this.configureSessionUnlocked(sessionId, patch);
|
|
437
|
-
const workspace = await this.workspaces.info(session.
|
|
438
|
-
const workspaceRoot = workspace.
|
|
476
|
+
const workspace = await this.workspaces.info(session.workspaceId);
|
|
477
|
+
const workspaceRoot = workspace.path;
|
|
439
478
|
const provider = this.requireProvider(targetProvider);
|
|
440
479
|
const providerStatus = await provider.probe();
|
|
441
480
|
if (!providerStatus.installed)
|
|
@@ -569,7 +608,7 @@ export class AgentRuntime {
|
|
|
569
608
|
}
|
|
570
609
|
if (event.type === "item.updated") {
|
|
571
610
|
const existing = active.items.get(event.providerItemId);
|
|
572
|
-
const payload = await prepareProviderItem(session, event.item);
|
|
611
|
+
const payload = await prepareProviderItem(await this.workspaces.resolve(session.workspaceId), event.item);
|
|
573
612
|
const updated = materializeProviderItem(active.turn.id, event.providerItemId, existing?.sequence ?? active.nextSequence++, existing?.status ?? "running", payload, existing);
|
|
574
613
|
active.items.set(event.providerItemId, updated);
|
|
575
614
|
const bounded = this.boundCommandOutput(active, event.providerItemId, updated);
|
|
@@ -624,7 +663,7 @@ export class AgentRuntime {
|
|
|
624
663
|
}
|
|
625
664
|
if (event.type === "item.completed") {
|
|
626
665
|
const existing = active.items.get(event.providerItemId);
|
|
627
|
-
const providerItem = await prepareProviderItem(session, event.item);
|
|
666
|
+
const providerItem = await prepareProviderItem(await this.workspaces.resolve(session.workspaceId), event.item);
|
|
628
667
|
const completed = materializeProviderItem(active.turn.id, event.providerItemId, existing?.sequence ?? active.nextSequence++, event.failed ? "failed" : "completed", providerItem, existing);
|
|
629
668
|
active.items.set(event.providerItemId, completed);
|
|
630
669
|
const bounded = this.boundCommandOutput(active, event.providerItemId, completed);
|
|
@@ -713,8 +752,8 @@ export class AgentRuntime {
|
|
|
713
752
|
});
|
|
714
753
|
}
|
|
715
754
|
async providerSession(session) {
|
|
716
|
-
const workspace = await this.workspaces.info(session.
|
|
717
|
-
const workspaceRoot = workspace.
|
|
755
|
+
const workspace = await this.workspaces.info(session.workspaceId);
|
|
756
|
+
const workspaceRoot = workspace.path;
|
|
718
757
|
const existing = this.providerSessions.get(session.id);
|
|
719
758
|
if (existing)
|
|
720
759
|
return existing;
|
|
@@ -767,7 +806,7 @@ export class AgentRuntime {
|
|
|
767
806
|
return session;
|
|
768
807
|
}
|
|
769
808
|
async cleanupOrphanAttachments(maxAgeMs = 7 * 24 * 60 * 60 * 1000) {
|
|
770
|
-
await cleanupOrphanAttachmentFiles(this.store, maxAgeMs);
|
|
809
|
+
await cleanupOrphanAttachmentFiles(this.store, (workspaceId) => this.workspaces.resolve(workspaceId), maxAgeMs);
|
|
771
810
|
}
|
|
772
811
|
async withSessionMutation(sessionId, action) {
|
|
773
812
|
if (this.sessionMutations.has(sessionId))
|
|
@@ -155,7 +155,8 @@ export interface AgentSessionAttention {
|
|
|
155
155
|
}
|
|
156
156
|
export interface AgentSession extends AgentSessionConfig {
|
|
157
157
|
id: string;
|
|
158
|
-
|
|
158
|
+
workspaceId: string;
|
|
159
|
+
workspaceName: string;
|
|
159
160
|
provider: AgentProviderId;
|
|
160
161
|
status: AgentSessionStatus;
|
|
161
162
|
attention: AgentSessionAttention | null;
|
|
@@ -314,7 +315,7 @@ export interface AgentTurnInput {
|
|
|
314
315
|
attachments: AgentAttachment[];
|
|
315
316
|
}
|
|
316
317
|
export interface CreateAgentSessionInput extends Partial<AgentSessionConfig> {
|
|
317
|
-
|
|
318
|
+
workspaceId: string;
|
|
318
319
|
provider: AgentProviderId;
|
|
319
320
|
model: string | null;
|
|
320
321
|
}
|
|
@@ -199,18 +199,13 @@ export class V2TunnelClient {
|
|
|
199
199
|
const sender = new CreditSender(frame.id, (buffer) => this.send(buffer), () => response.pause(), () => response.resume(), () => {
|
|
200
200
|
state.responseEnded = true;
|
|
201
201
|
this.finishIfIdle(frame.id);
|
|
202
|
-
});
|
|
202
|
+
}, () => (this.socket?.bufferedAmount ?? 0) < 1024 * 1024);
|
|
203
203
|
state.sender = sender;
|
|
204
204
|
this.send(metaFrame(2 /* Op.META */, frame.id, {
|
|
205
205
|
status: response.statusCode ?? 502,
|
|
206
206
|
headers: responseHeaders(response.headers),
|
|
207
207
|
}));
|
|
208
208
|
response.on("data", (chunk) => {
|
|
209
|
-
state.responseBytes += chunk.length;
|
|
210
|
-
if (state.responseBytes > 128 * 1024 * 1024) {
|
|
211
|
-
this.abort(frame.id);
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
209
|
try {
|
|
215
210
|
sender.enqueue(chunk);
|
|
216
211
|
}
|
|
@@ -232,7 +227,6 @@ export class V2TunnelClient {
|
|
|
232
227
|
pendingCredit: 0,
|
|
233
228
|
drainArmed: false,
|
|
234
229
|
requestBytes: 0,
|
|
235
|
-
responseBytes: 0,
|
|
236
230
|
requestEnded: false,
|
|
237
231
|
responseEnded: false,
|
|
238
232
|
};
|
|
@@ -6,6 +6,11 @@ export declare class PluginManager {
|
|
|
6
6
|
private readonly active;
|
|
7
7
|
constructor(context: PluginContext, plugins: InternalPlugin[]);
|
|
8
8
|
start(): Promise<void>;
|
|
9
|
-
|
|
9
|
+
defineMcp(toolControl: import("#mcp/server/tool-control").McpToolControl, options?: {
|
|
10
|
+
agentMode?: boolean;
|
|
11
|
+
}): void;
|
|
12
|
+
registerMcp(server: McpServer, toolControl?: import("#mcp/server/tool-control").McpToolControl, options?: {
|
|
13
|
+
agentMode?: boolean;
|
|
14
|
+
}): void;
|
|
10
15
|
stop(): Promise<void>;
|
|
11
16
|
}
|
|
@@ -36,11 +36,18 @@ export class PluginManager {
|
|
|
36
36
|
throw error;
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
|
|
39
|
+
defineMcp(toolControl, options = {}) {
|
|
40
40
|
for (const plugin of this.active) {
|
|
41
41
|
if (!plugin.registerMcp)
|
|
42
42
|
continue;
|
|
43
|
-
plugin.registerMcp(new PluginMcpRegistrar(
|
|
43
|
+
plugin.registerMcp(new PluginMcpRegistrar(null, this.context.operations, plugin.id, toolControl, options));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
registerMcp(server, toolControl = this.context.mcpTools, options = {}) {
|
|
47
|
+
for (const plugin of this.active) {
|
|
48
|
+
if (!plugin.registerMcp)
|
|
49
|
+
continue;
|
|
50
|
+
plugin.registerMcp(new PluginMcpRegistrar(server, this.context.operations, plugin.id, toolControl, options));
|
|
44
51
|
}
|
|
45
52
|
}
|
|
46
53
|
async stop() {
|
package/dist/plugins/types.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { CustomMcpManager } from "#mcp/custom/manager";
|
|
|
7
7
|
import type { RuntimeEventBus } from "#app/event-bus";
|
|
8
8
|
import type { ExternalAccessRegistry } from "#app/external-access-registry";
|
|
9
9
|
import type { LogWriter } from "#core/logging/types";
|
|
10
|
+
import type { AgentMcpConnection } from "#plugins/agent/mcp-gateway";
|
|
10
11
|
import type { ServiceRegistry } from "#app/service-registry";
|
|
11
12
|
export interface PluginContext {
|
|
12
13
|
config: ChatRoomConfig;
|
|
@@ -14,6 +15,9 @@ export interface PluginContext {
|
|
|
14
15
|
operations: OperationLog;
|
|
15
16
|
mcpTools: McpToolControl;
|
|
16
17
|
customMcp: CustomMcpManager;
|
|
18
|
+
agentMcp: AgentMcpConnection;
|
|
19
|
+
agentMcpTools: import("#mcp/server/tool-control").McpToolControl;
|
|
20
|
+
agentCustomMcp: CustomMcpManager;
|
|
17
21
|
events: RuntimeEventBus;
|
|
18
22
|
externalAccess: ExternalAccessRegistry;
|
|
19
23
|
services: ServiceRegistry;
|
|
@@ -19,7 +19,7 @@ export function createAgentApiRouter(agents) {
|
|
|
19
19
|
router.post("/agents/sessions", asyncRoute(async (req, res) => {
|
|
20
20
|
const body = bodyRecord(req.body);
|
|
21
21
|
res.json(await agents.createSession({
|
|
22
|
-
|
|
22
|
+
workspaceId: requireString(body.workspaceId, "workspaceId"),
|
|
23
23
|
provider: requireProvider(requireString(body.provider, "provider")),
|
|
24
24
|
model: null,
|
|
25
25
|
reasoningEffort: null,
|
|
@@ -76,6 +76,20 @@ export function createAgentApiRouter(agents) {
|
|
|
76
76
|
const body = bodyRecord(req.body);
|
|
77
77
|
res.json(await agents.switchProvider(requireString(req.params.sessionId, "sessionId"), requireProvider(requireString(body.provider, "provider")), parseConfig(body)));
|
|
78
78
|
}));
|
|
79
|
+
const bulkDeleteSessions = asyncRoute(async (req, res) => {
|
|
80
|
+
const body = bodyRecord(req.body);
|
|
81
|
+
if (!Array.isArray(body.sessionIds) || body.sessionIds.length === 0)
|
|
82
|
+
throw new ChatRoomError("INVALID_INPUT", "sessionIds must be a non-empty array");
|
|
83
|
+
if (body.sessionIds.length > 100)
|
|
84
|
+
throw new ChatRoomError("INVALID_INPUT", "Cannot delete more than 100 Agent sessions at once");
|
|
85
|
+
const sessionIds = body.sessionIds.map((id) => requireString(id, "sessionIds[]"));
|
|
86
|
+
await agents.deleteSessions(sessionIds);
|
|
87
|
+
res.status(204).end();
|
|
88
|
+
});
|
|
89
|
+
// POST is the primary batch action for maximum compatibility with clients
|
|
90
|
+
// and intermediaries that mishandle bodies on DELETE requests.
|
|
91
|
+
router.post("/agents/sessions/bulk-delete", bulkDeleteSessions);
|
|
92
|
+
router.delete("/agents/sessions", bulkDeleteSessions);
|
|
79
93
|
router.delete("/agents/sessions/:sessionId", asyncRoute(async (req, res) => {
|
|
80
94
|
await agents.deleteSession(requireString(req.params.sessionId, "sessionId"));
|
|
81
95
|
res.status(204).end();
|
|
@@ -3,77 +3,77 @@ import { asyncRoute, bodyRecord, requireString, requireStringArray, } from "#pre
|
|
|
3
3
|
export function createGitApiRouter(application) {
|
|
4
4
|
const router = Router();
|
|
5
5
|
router.get("/git/status", asyncRoute(async (req, res) => {
|
|
6
|
-
const root = await resolveRoot(application, req.query.
|
|
6
|
+
const root = await resolveRoot(application, req.query.workspaceId);
|
|
7
7
|
res.json(await application.git.status(root));
|
|
8
8
|
}));
|
|
9
9
|
router.get("/git/diff", asyncRoute(async (req, res) => {
|
|
10
|
-
const root = await resolveRoot(application, req.query.
|
|
10
|
+
const root = await resolveRoot(application, req.query.workspaceId);
|
|
11
11
|
const filePath = typeof req.query.path === "string" ? req.query.path : undefined;
|
|
12
12
|
res.json(await application.git.diff(root, filePath));
|
|
13
13
|
}));
|
|
14
14
|
router.get("/git/branches", asyncRoute(async (req, res) => {
|
|
15
|
-
const root = await resolveRoot(application, req.query.
|
|
15
|
+
const root = await resolveRoot(application, req.query.workspaceId);
|
|
16
16
|
res.json(await application.git.branches(root));
|
|
17
17
|
}));
|
|
18
18
|
router.get("/git/log", asyncRoute(async (req, res) => {
|
|
19
|
-
const root = await resolveRoot(application, req.query.
|
|
19
|
+
const root = await resolveRoot(application, req.query.workspaceId);
|
|
20
20
|
const requested = Number(req.query.limit ?? 30);
|
|
21
21
|
const limit = Number.isFinite(requested) ? requested : 30;
|
|
22
22
|
res.json(await application.git.log(root, limit));
|
|
23
23
|
}));
|
|
24
24
|
router.post("/git/stage", asyncRoute(async (req, res) => {
|
|
25
25
|
const body = bodyRecord(req.body);
|
|
26
|
-
const root = await resolveRoot(application, body.
|
|
26
|
+
const root = await resolveRoot(application, body.workspaceId);
|
|
27
27
|
const paths = requireStringArray(body.paths, "paths");
|
|
28
28
|
res.json(await mutate(application, "git.stage", root, { paths }, () => application.git.stage(root, paths)));
|
|
29
29
|
}));
|
|
30
30
|
router.post("/git/unstage", asyncRoute(async (req, res) => {
|
|
31
31
|
const body = bodyRecord(req.body);
|
|
32
|
-
const root = await resolveRoot(application, body.
|
|
32
|
+
const root = await resolveRoot(application, body.workspaceId);
|
|
33
33
|
const paths = requireStringArray(body.paths, "paths");
|
|
34
34
|
res.json(await mutate(application, "git.unstage", root, { paths }, () => application.git.unstage(root, paths)));
|
|
35
35
|
}));
|
|
36
36
|
router.post("/git/restore", asyncRoute(async (req, res) => {
|
|
37
37
|
const body = bodyRecord(req.body);
|
|
38
|
-
const root = await resolveRoot(application, body.
|
|
38
|
+
const root = await resolveRoot(application, body.workspaceId);
|
|
39
39
|
const filePath = requireString(body.path, "path");
|
|
40
40
|
res.json(await mutate(application, "git.restore", root, { path: filePath }, () => application.git.restore(root, filePath)));
|
|
41
41
|
}));
|
|
42
42
|
router.post("/git/commit", asyncRoute(async (req, res) => {
|
|
43
43
|
const body = bodyRecord(req.body);
|
|
44
|
-
const root = await resolveRoot(application, body.
|
|
44
|
+
const root = await resolveRoot(application, body.workspaceId);
|
|
45
45
|
const message = requireString(body.message, "message");
|
|
46
46
|
res.json(await mutate(application, "git.commit", root, { message }, () => application.git.commit(root, message)));
|
|
47
47
|
}));
|
|
48
48
|
router.post("/git/branches", asyncRoute(async (req, res) => {
|
|
49
49
|
const body = bodyRecord(req.body);
|
|
50
|
-
const root = await resolveRoot(application, body.
|
|
50
|
+
const root = await resolveRoot(application, body.workspaceId);
|
|
51
51
|
const name = requireString(body.name, "name");
|
|
52
52
|
res.json(await mutate(application, "git.branch.create", root, { name }, () => application.git.createBranch(root, name)));
|
|
53
53
|
}));
|
|
54
54
|
router.post("/git/switch", asyncRoute(async (req, res) => {
|
|
55
55
|
const body = bodyRecord(req.body);
|
|
56
|
-
const root = await resolveRoot(application, body.
|
|
56
|
+
const root = await resolveRoot(application, body.workspaceId);
|
|
57
57
|
const name = requireString(body.name, "name");
|
|
58
58
|
res.json(await mutate(application, "git.branch.switch", root, { name }, () => application.git.switchBranch(root, name)));
|
|
59
59
|
}));
|
|
60
60
|
router.delete("/git/branches", asyncRoute(async (req, res) => {
|
|
61
61
|
const body = bodyRecord(req.body);
|
|
62
|
-
const root = await resolveRoot(application, body.
|
|
62
|
+
const root = await resolveRoot(application, body.workspaceId);
|
|
63
63
|
const name = requireString(body.name, "name");
|
|
64
64
|
res.json(await mutate(application, "git.branch.delete", root, { name }, () => application.git.deleteBranch(root, name)));
|
|
65
65
|
}));
|
|
66
66
|
for (const action of ["fetch", "pull", "push"]) {
|
|
67
67
|
router.post(`/git/${action}`, asyncRoute(async (req, res) => {
|
|
68
68
|
const body = bodyRecord(req.body);
|
|
69
|
-
const root = await resolveRoot(application, body.
|
|
69
|
+
const root = await resolveRoot(application, body.workspaceId);
|
|
70
70
|
res.json(await mutate(application, `git.${action}`, root, {}, () => application.git[action](root)));
|
|
71
71
|
}));
|
|
72
72
|
}
|
|
73
73
|
return router;
|
|
74
74
|
}
|
|
75
75
|
async function resolveRoot(application, value) {
|
|
76
|
-
return application.workspaces.resolve(requireString(value, "
|
|
76
|
+
return application.workspaces.resolve(requireString(value, "workspaceId"));
|
|
77
77
|
}
|
|
78
78
|
function mutate(application, action, root, input, operation) {
|
|
79
79
|
return application.operations.run({
|
|
@@ -28,6 +28,7 @@ const HEADER_NAMES = new Set([
|
|
|
28
28
|
"if-none-match",
|
|
29
29
|
"if-modified-since",
|
|
30
30
|
"x-chatroom-mime-type",
|
|
31
|
+
"x-chatroom-upload-size",
|
|
31
32
|
]);
|
|
32
33
|
const RESPONSE_HEADERS = new Set([
|
|
33
34
|
"content-type",
|
|
@@ -36,6 +37,8 @@ const RESPONSE_HEADERS = new Set([
|
|
|
36
37
|
"content-range",
|
|
37
38
|
"accept-ranges",
|
|
38
39
|
"cache-control",
|
|
40
|
+
"content-security-policy",
|
|
41
|
+
"x-content-type-options",
|
|
39
42
|
"etag",
|
|
40
43
|
"last-modified",
|
|
41
44
|
]);
|
|
@@ -13,6 +13,28 @@ export function createRuntimeApiRouter(application, eventBus, runtimeStatus) {
|
|
|
13
13
|
throw new ChatRoomError("INVALID_INPUT", "enabled must be a boolean");
|
|
14
14
|
res.json(application.mcpTools.setEnabled(requireString(req.params.toolName, "toolName"), body.enabled));
|
|
15
15
|
});
|
|
16
|
+
router.get("/agent-mcp/tools", (_req, res) => {
|
|
17
|
+
res.json(application.agentMcpTools.list());
|
|
18
|
+
});
|
|
19
|
+
router.patch("/agent-mcp/tools/:toolName", async (req, res) => {
|
|
20
|
+
const body = bodyRecord(req.body);
|
|
21
|
+
if (typeof body.enabled !== "boolean")
|
|
22
|
+
throw new ChatRoomError("INVALID_INPUT", "enabled must be a boolean");
|
|
23
|
+
const updated = application.agentMcpTools.setEnabled(requireString(req.params.toolName, "toolName"), body.enabled);
|
|
24
|
+
await application.agents.refreshMcpServers();
|
|
25
|
+
res.json(updated);
|
|
26
|
+
});
|
|
27
|
+
router.get("/agent-mcp/servers", (_req, res) => {
|
|
28
|
+
res.json(application.agentCustomMcp.list());
|
|
29
|
+
});
|
|
30
|
+
router.patch("/agent-mcp/servers/:id", async (req, res) => {
|
|
31
|
+
const body = bodyRecord(req.body);
|
|
32
|
+
if (typeof body.enabled !== "boolean")
|
|
33
|
+
throw new ChatRoomError("INVALID_INPUT", "enabled must be a boolean");
|
|
34
|
+
const updated = await application.agentCustomMcp.update(requireString(req.params.id, "id"), { enabled: body.enabled });
|
|
35
|
+
await application.agents.refreshMcpServers();
|
|
36
|
+
res.json(updated);
|
|
37
|
+
});
|
|
16
38
|
router.get("/events", (_req, res) => {
|
|
17
39
|
res.status(200);
|
|
18
40
|
res.setHeader("Content-Type", "text/event-stream");
|