workspacecord 1.1.0 → 1.1.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.
Files changed (35) hide show
  1. package/README.md +40 -0
  2. package/README.zh-CN.md +40 -0
  3. package/dist/{archive-manager-KMNAWQIN.js → archive-manager-XM3UPOY2.js} +5 -7
  4. package/dist/{attachment-cli-QEF7IZBI.js → attachment-cli-IGQBB6YJ.js} +3 -3
  5. package/dist/{chunk-7KESUGJP.js → chunk-54DP53ZK.js} +2 -2
  6. package/dist/chunk-COXPTYH5.js +165 -0
  7. package/dist/{chunk-PUKMHDXS.js → chunk-GMYN4SYT.js} +175 -9
  8. package/dist/chunk-I6EOCCQV.js +13415 -0
  9. package/dist/{chunk-Q2AJ4UEK.js → chunk-L2ZJXV6H.js} +32 -7
  10. package/dist/chunk-MO4EEYFW.js +38 -0
  11. package/dist/chunk-QWPKAUSV.js +18177 -0
  12. package/dist/chunk-RK6EIZOL.js +589 -0
  13. package/dist/chunk-UEX7U2KW.js +6528 -0
  14. package/dist/chunk-ZAQV2RZS.js +2284 -0
  15. package/dist/cli-framework-SQM2465A.js +18 -0
  16. package/dist/cli.js +15 -11
  17. package/dist/{codex-launcher-OOF2WYQF.js → codex-launcher-VDQ5VZPT.js} +3 -3
  18. package/dist/{codex-provider-P7TDYK6B.js → codex-provider-NYI7KBGO.js} +62 -24
  19. package/dist/{config-cli-YSKOXADF.js → config-cli-RQR2ZRQ5.js} +3 -3
  20. package/dist/{daemon-NW4WRMQK.js → daemon-JGPVKLK3.js} +1 -1
  21. package/dist/panel-adapter-QTDL3S6O.js +47 -0
  22. package/dist/{project-cli-QW5QYT6A.js → project-cli-6P6ZWDR6.js} +3 -3
  23. package/dist/{project-registry-LL75XEUV.js → project-registry-OEVPECMS.js} +3 -3
  24. package/dist/sdk-V7A7IF7F.js +43 -0
  25. package/dist/session-local-registration-RIO5EPZ5.js +14 -0
  26. package/dist/{setup-QJ4HVVCU.js → setup-KOS7SRSL.js} +3 -3
  27. package/package.json +10 -38
  28. package/dist/chunk-52OOARML.js +0 -284
  29. package/dist/chunk-CBNENUW6.js +0 -55
  30. package/dist/chunk-D6J3X35H.js +0 -96
  31. package/dist/chunk-G6CCOBUY.js +0 -7310
  32. package/dist/chunk-IFSOU4XI.js +0 -1477
  33. package/dist/chunk-K3NQKI34.js +0 -10
  34. package/dist/cli-framework-KMQXM2PO.js +0 -17
  35. package/dist/thread-manager-DRWNFXXY.js +0 -88
package/README.md CHANGED
@@ -35,7 +35,11 @@ Discord Server
35
35
  - Discord-side project binding with `/project setup`
36
36
  - Main session channels and subagent threads
37
37
  - Session archiving into `#history`
38
+ - Project-level personality, reusable skills, and MCP registry management
38
39
  - Support for both Claude and Codex providers
40
+ - `monitor` mode with continued steering until task completion
41
+ - Remote human approval / gate handling from Discord for managed sessions
42
+ - Managed local `Codex` launch plus automatic local session discovery/sync
39
43
  - Global config storage without requiring a project-local `.env`
40
44
  - Optional daemon install and background management
41
45
 
@@ -153,6 +157,42 @@ workspacecord codex [options] # Launch managed Codex session with remote appro
153
157
  - `/subagent list` — list subagents for the current session
154
158
  - `/shell run` / `/shell processes` / `/shell kill`
155
159
 
160
+
161
+ ## Capability Matrix
162
+
163
+ | Capability | Status | Entry Points |
164
+ |---|---|---|
165
+ | Global configuration | Stable | `workspacecord config *` |
166
+ | Explicit local project mounting | Stable | `workspacecord project *` |
167
+ | Discord category binding | Stable | `/project setup`, `/project info` |
168
+ | Project-level personality | Available | `/project personality`, `/project personality-clear` |
169
+ | Project-level reusable skills | Available | `/project skill-add`, `/project skill-list`, `/project skill-run` |
170
+ | Project-level MCP registry | Available | `/project mcp-add`, `/project mcp-list`, `/project mcp-remove` |
171
+ | Main agent sessions | Stable | `/agent spawn`, `/agent list`, `/agent archive`, `/agent cleanup` |
172
+ | Subagent threads | Stable | `/subagent run`, `/subagent list` |
173
+ | Session execution modes | Stable | `/agent mode`, `/agent goal`, `/agent verbose` |
174
+ | Remote human approval / gates | Available | interaction cards, `monitor` mode, Claude permission handling |
175
+ | Managed local Codex launch | Available | `workspacecord codex` |
176
+ | Local session discovery / sync | Available | hook / codex-log / sync recovery paths |
177
+ | Shell execution from Discord | Available | `/shell run`, `/shell processes`, `/shell kill` |
178
+ | History archive | Stable | `#history`, `/agent archive` |
179
+ | Daemon installation / background run | Available | `workspacecord daemon *` |
180
+
181
+ ## Runtime Architecture
182
+
183
+ Current main runtime path:
184
+
185
+ ```text
186
+ bot.ts
187
+ -> BotEventRouter
188
+ -> command handlers / button handler / message handler
189
+ -> thread-manager façade
190
+ -> session-registry / session runtime / state machine / panel adapter
191
+ -> Discord delivery + status cards + summaries
192
+ ```
193
+
194
+ This repository intentionally keeps Discord as the control plane, while local project state, provider sessions, approvals, and output rendering stay on the machine that runs `workspacecord`.
195
+
156
196
  ## Development
157
197
 
158
198
  Run the standard verification flow:
package/README.zh-CN.md CHANGED
@@ -35,7 +35,11 @@ Discord Server
35
35
  - 使用 `/project setup` 在 Discord 中绑定项目
36
36
  - 主会话频道与子代理线程模型
37
37
  - 会话自动归档到 `#history`
38
+ - 项目级人格、可复用技能和 MCP 服务注册管理
38
39
  - 同时支持 Claude 与 Codex 提供方
40
+ - `monitor` 模式可持续推进直到任务完成
41
+ - 托管会话支持 Discord 远程人工审批 / 门控处理
42
+ - 支持本地托管 `Codex` 启动与自动会话发现 / 同步
39
43
  - 使用全局配置存储,不依赖项目内 `.env`
40
44
  - 支持守护进程安装与后台管理
41
45
 
@@ -153,6 +157,42 @@ workspacecord codex [options] # 启动托管的 Codex 会话(支持远程审
153
157
  - `/subagent list`:查看当前会话的子代理
154
158
  - `/shell run` / `/shell processes` / `/shell kill`
155
159
 
160
+
161
+ ## 能力矩阵
162
+
163
+ | 能力 | 状态 | 入口 |
164
+ |---|---|---|
165
+ | 全局配置 | 稳定 | `workspacecord config *` |
166
+ | 显式本地项目挂载 | 稳定 | `workspacecord project *` |
167
+ | Discord 分类绑定 | 稳定 | `/project setup`, `/project info` |
168
+ | 项目级人格 | 可用 | `/project personality`, `/project personality-clear` |
169
+ | 项目级可复用技能 | 可用 | `/project skill-add`, `/project skill-list`, `/project skill-run` |
170
+ | 项目级 MCP 注册表 | 可用 | `/project mcp-add`, `/project mcp-list`, `/project mcp-remove` |
171
+ | 主代理会话 | 稳定 | `/agent spawn`, `/agent list`, `/agent archive`, `/agent cleanup` |
172
+ | 子代理线程 | 稳定 | `/subagent run`, `/subagent list` |
173
+ | 会话执行模式 | 稳定 | `/agent mode`, `/agent goal`, `/agent verbose` |
174
+ | 远程人工审批 / 门控 | 可用 | 交互卡、`monitor` 模式、Claude 权限处理 |
175
+ | 本地托管 Codex 启动 | 可用 | `workspacecord codex` |
176
+ | 本地会话发现 / 同步 | 可用 | hook / codex-log / sync 恢复路径 |
177
+ | 从 Discord 执行 shell | 可用 | `/shell run`, `/shell processes`, `/shell kill` |
178
+ | 历史归档 | 稳定 | `#history`, `/agent archive` |
179
+ | 守护进程安装 / 后台运行 | 可用 | `workspacecord daemon *` |
180
+
181
+ ## 运行时架构
182
+
183
+ 当前主运行路径:
184
+
185
+ ```text
186
+ bot.ts
187
+ -> BotEventRouter
188
+ -> command handlers / button handler / message handler
189
+ -> thread-manager façade
190
+ -> session-registry / session runtime / state machine / panel adapter
191
+ -> Discord delivery + status cards + summaries
192
+ ```
193
+
194
+ 这意味着 Discord 只是控制平面;真正的项目状态、provider 会话、审批、输出渲染都发生在运行 `workspacecord` 的本机上。
195
+
156
196
  ## 开发与验证
157
197
 
158
198
  建议先运行基础验证:
@@ -5,13 +5,11 @@ import {
5
5
  getArchivedSessions,
6
6
  isArchivedProviderSession,
7
7
  loadArchived
8
- } from "./chunk-PUKMHDXS.js";
9
- import "./chunk-IFSOU4XI.js";
10
- import "./chunk-D6J3X35H.js";
11
- import "./chunk-7KESUGJP.js";
12
- import "./chunk-CBNENUW6.js";
13
- import "./chunk-52OOARML.js";
14
- import "./chunk-K3NQKI34.js";
8
+ } from "./chunk-GMYN4SYT.js";
9
+ import "./chunk-54DP53ZK.js";
10
+ import "./chunk-RK6EIZOL.js";
11
+ import "./chunk-UEX7U2KW.js";
12
+ import "./chunk-MO4EEYFW.js";
15
13
  export {
16
14
  archiveSession,
17
15
  checkAutoArchive,
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  fetchRegisteredAttachments
4
- } from "./chunk-Q2AJ4UEK.js";
5
- import "./chunk-CBNENUW6.js";
6
- import "./chunk-K3NQKI34.js";
4
+ } from "./chunk-L2ZJXV6H.js";
5
+ import "./chunk-UEX7U2KW.js";
6
+ import "./chunk-MO4EEYFW.js";
7
7
 
8
8
  // src/attachment-cli.ts
9
9
  function printHelp() {
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  Store
4
- } from "./chunk-CBNENUW6.js";
4
+ } from "./chunk-UEX7U2KW.js";
5
5
 
6
- // src/project-registry.ts
6
+ // ../engine/src/project-registry.ts
7
7
  import { randomUUID } from "crypto";
8
8
  import { resolve } from "path";
9
9
  var store = new Store("projects.json");
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createSession,
4
+ getSessionByProviderSession,
5
+ updateSession
6
+ } from "./chunk-RK6EIZOL.js";
7
+ import {
8
+ resolvePath
9
+ } from "./chunk-UEX7U2KW.js";
10
+
11
+ // ../bot/src/session-local-registration.ts
12
+ import { sep } from "path";
13
+ function buildClaudeSubagentProviderSessionId(parentProviderSessionId, agentId) {
14
+ return `subagent:${parentProviderSessionId}:${agentId}`;
15
+ }
16
+ function updateLocalObservation(sessionId, patch) {
17
+ updateSession(sessionId, {
18
+ discoverySource: patch.discoverySource,
19
+ lastObservedAt: Date.now(),
20
+ lastObservedCwd: resolvePath(patch.cwd),
21
+ ...patch.remoteHumanControl !== void 0 ? { remoteHumanControl: patch.remoteHumanControl } : {}
22
+ });
23
+ }
24
+ async function registerLocalSession(params, guild) {
25
+ const {
26
+ provider,
27
+ providerSessionId,
28
+ cwd,
29
+ discoverySource,
30
+ labelHint,
31
+ remoteHumanControl,
32
+ subagent
33
+ } = params;
34
+ const effectiveProviderSessionId = provider === "claude" && subagent?.parentProviderSessionId && subagent.agentId ? buildClaudeSubagentProviderSessionId(subagent.parentProviderSessionId, subagent.agentId) : providerSessionId;
35
+ const effectiveAgentLabel = subagent?.agentType || labelHint || effectiveProviderSessionId.slice(0, 12);
36
+ const { isArchivedProviderSession } = await import("./archive-manager-XM3UPOY2.js");
37
+ if (isArchivedProviderSession(provider, effectiveProviderSessionId)) {
38
+ console.log(
39
+ `[registerLocalSession] Skip archived ${provider} session ${effectiveProviderSessionId} (source: ${discoverySource})`
40
+ );
41
+ return null;
42
+ }
43
+ const existing = getSessionByProviderSession(provider, effectiveProviderSessionId);
44
+ if (existing) {
45
+ updateLocalObservation(existing.id, { discoverySource, cwd, remoteHumanControl });
46
+ return { session: existing, isNewlyCreated: false };
47
+ }
48
+ const { getProjectByPath, getAllRegisteredProjects } = await import("./project-registry-OEVPECMS.js");
49
+ const { ChannelType, ThreadAutoArchiveDuration } = await import("discord.js");
50
+ const normalizedCwd = resolvePath(cwd);
51
+ let project = getProjectByPath(normalizedCwd);
52
+ if (!project) {
53
+ const allProjects = getAllRegisteredProjects();
54
+ let bestMatch;
55
+ let bestMatchPathLength = -1;
56
+ for (const p of allProjects) {
57
+ const projectPath = resolvePath(p.path);
58
+ if (normalizedCwd.startsWith(projectPath + sep) && projectPath.length > bestMatchPathLength) {
59
+ bestMatch = p;
60
+ bestMatchPathLength = projectPath.length;
61
+ }
62
+ }
63
+ project = bestMatch;
64
+ }
65
+ if (!project || !project.discordCategoryId) {
66
+ console.warn(
67
+ `[registerLocalSession] Cannot register ${provider} session ${providerSessionId}: cwd "${cwd}" does not belong to any mounted project`
68
+ );
69
+ return null;
70
+ }
71
+ const category = guild.channels.cache.get(project.discordCategoryId);
72
+ if (!category || category.type !== ChannelType.GuildCategory) {
73
+ console.warn(
74
+ `[registerLocalSession] Cannot register ${provider} session ${providerSessionId}: category ${project.discordCategoryId} not found`
75
+ );
76
+ return null;
77
+ }
78
+ if (subagent?.parentProviderSessionId) {
79
+ const parentSession = getSessionByProviderSession(provider, subagent.parentProviderSessionId);
80
+ if (!parentSession) {
81
+ console.warn(
82
+ `[registerLocalSession] Delaying subagent ${provider} session ${providerSessionId}: parent provider session ${subagent.parentProviderSessionId} not registered yet`
83
+ );
84
+ return null;
85
+ }
86
+ const parentChannel = guild.channels.cache.get(parentSession.channelId);
87
+ const threadHostChannel = parentChannel?.type === ChannelType.GuildText ? parentChannel : parentChannel?.isThread?.() || parentChannel?.type === ChannelType.PublicThread ? parentChannel.parent : void 0;
88
+ if (threadHostChannel?.type !== ChannelType.GuildText) {
89
+ console.warn(
90
+ `[registerLocalSession] Delaying subagent ${provider} session ${providerSessionId}: parent channel ${parentSession.channelId} is unavailable`
91
+ );
92
+ return null;
93
+ }
94
+ const normalizedThreadName = `[sub:${provider}] ${effectiveAgentLabel}`.slice(0, 100);
95
+ const thread = await threadHostChannel.threads.create({
96
+ name: normalizedThreadName,
97
+ type: ChannelType.PublicThread,
98
+ autoArchiveDuration: ThreadAutoArchiveDuration.OneHour,
99
+ reason: `Auto-registered subagent session ${effectiveProviderSessionId}`
100
+ });
101
+ const session2 = await createSession({
102
+ channelId: thread.id,
103
+ categoryId: parentSession.categoryId,
104
+ projectName: parentSession.projectName,
105
+ agentLabel: effectiveAgentLabel,
106
+ provider,
107
+ providerSessionId: effectiveProviderSessionId,
108
+ directory: normalizedCwd,
109
+ type: "subagent",
110
+ parentChannelId: parentSession.type === "subagent" ? parentSession.parentChannelId ?? threadHostChannel.id : parentSession.channelId,
111
+ subagentDepth: Math.max(1, subagent.depth ?? parentSession.subagentDepth + 1),
112
+ discoverySource,
113
+ remoteHumanControl: remoteHumanControl ?? false
114
+ });
115
+ updateLocalObservation(session2.id, {
116
+ discoverySource,
117
+ cwd: normalizedCwd,
118
+ remoteHumanControl: remoteHumanControl ?? false
119
+ });
120
+ console.log(
121
+ `[registerLocalSession] Registered subagent ${provider} session ${effectiveProviderSessionId} (source: ${discoverySource}, parent: ${parentSession.channelId}, thread: ${thread.id})`
122
+ );
123
+ return { session: session2, isNewlyCreated: true };
124
+ }
125
+ const base = labelHint ? labelHint.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 60) : effectiveProviderSessionId.slice(0, 12);
126
+ const channelName = `${provider}-${base}`.slice(0, 100);
127
+ let channel = category.children.cache.find(
128
+ (ch) => ch.type === ChannelType.GuildText && typeof ch.topic === "string" && ch.topic.includes(`Provider Session: ${effectiveProviderSessionId}`)
129
+ );
130
+ if (!channel) {
131
+ channel = await guild.channels.create({
132
+ name: channelName,
133
+ type: ChannelType.GuildText,
134
+ parent: category.id,
135
+ topic: `${provider} session (local) | Provider Session: ${effectiveProviderSessionId}`
136
+ });
137
+ }
138
+ const session = await createSession({
139
+ channelId: channel.id,
140
+ categoryId: project.discordCategoryId,
141
+ projectName: project.name,
142
+ agentLabel: effectiveAgentLabel,
143
+ provider,
144
+ providerSessionId: effectiveProviderSessionId,
145
+ directory: normalizedCwd,
146
+ type: "persistent",
147
+ discoverySource,
148
+ remoteHumanControl: remoteHumanControl ?? false
149
+ });
150
+ updateLocalObservation(session.id, {
151
+ discoverySource,
152
+ cwd: normalizedCwd,
153
+ remoteHumanControl: remoteHumanControl ?? false
154
+ });
155
+ console.log(
156
+ `[registerLocalSession] Registered ${provider} session ${effectiveProviderSessionId} (source: ${discoverySource}, channel: ${channel.id})`
157
+ );
158
+ return { session, isNewlyCreated: true };
159
+ }
160
+
161
+ export {
162
+ buildClaudeSubagentProviderSessionId,
163
+ updateLocalObservation,
164
+ registerLocalSession
165
+ };
@@ -1,19 +1,170 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ bindProjectCategory,
4
+ getProjectByCategoryId,
5
+ getProjectByName,
6
+ loadRegistry,
7
+ setProjectControlChannel,
8
+ setProjectHistoryChannel,
9
+ updateProject
10
+ } from "./chunk-54DP53ZK.js";
2
11
  import {
3
12
  endSession,
4
- getAllSessions,
5
- getProject,
6
- setHistoryChannelId
7
- } from "./chunk-IFSOU4XI.js";
13
+ getAllSessions
14
+ } from "./chunk-RK6EIZOL.js";
8
15
  import {
16
+ Store,
9
17
  config
10
- } from "./chunk-D6J3X35H.js";
11
- import {
12
- Store
13
- } from "./chunk-CBNENUW6.js";
18
+ } from "./chunk-UEX7U2KW.js";
14
19
 
15
- // src/archive-manager.ts
20
+ // ../bot/src/archive-manager.ts
16
21
  import { ChannelType, EmbedBuilder } from "discord.js";
22
+
23
+ // ../engine/src/project-manager.ts
24
+ import { readFile, writeFile } from "fs/promises";
25
+ import { existsSync } from "fs";
26
+ import { join } from "path";
27
+ async function loadProjects() {
28
+ await loadRegistry();
29
+ }
30
+ function getProject(categoryId) {
31
+ const project = getProjectByCategoryId(categoryId);
32
+ if (!project) return void 0;
33
+ return {
34
+ categoryId: project.discordCategoryId ?? categoryId,
35
+ historyChannelId: project.historyChannelId,
36
+ controlChannelId: project.controlChannelId,
37
+ name: project.name,
38
+ directory: project.path,
39
+ personality: project.personality,
40
+ skills: Object.entries(project.skills).map(([name, prompt]) => ({ name, prompt })),
41
+ mcpServers: project.mcpServers,
42
+ createdAt: project.createdAt
43
+ };
44
+ }
45
+ async function bindMountedProjectToCategory(projectName, categoryId, categoryName) {
46
+ const project = getProjectByName(projectName);
47
+ if (!project) throw new Error(`Mounted project not found: ${projectName}`);
48
+ if (project.discordCategoryId && project.discordCategoryId !== categoryId) {
49
+ throw new Error(`Project "${projectName}" is already bound to another Discord category`);
50
+ }
51
+ await bindProjectCategory(projectName, categoryId, categoryName);
52
+ return getProject(categoryId);
53
+ }
54
+ function setHistoryChannelId(categoryId, channelId) {
55
+ const project = getProjectByCategoryId(categoryId);
56
+ if (!project) return;
57
+ setProjectHistoryChannel(project.name, channelId).catch(
58
+ (err) => console.error(`Failed to set history channel for project "${project.name}": ${err.message}`)
59
+ );
60
+ }
61
+ function setControlChannelId(categoryId, channelId) {
62
+ const project = getProjectByCategoryId(categoryId);
63
+ if (!project) return;
64
+ setProjectControlChannel(project.name, channelId).catch(
65
+ (err) => console.error(`Failed to set control channel for project "${project.name}": ${err.message}`)
66
+ );
67
+ }
68
+ function setPersonality(categoryId, personality) {
69
+ const project = getProjectByCategoryId(categoryId);
70
+ if (!project) return;
71
+ project.personality = personality;
72
+ updateProject(project).catch(
73
+ (err) => console.error(`Failed to update personality for project "${project.name}": ${err.message}`)
74
+ );
75
+ }
76
+ function getPersonality(categoryId) {
77
+ return getProjectByCategoryId(categoryId)?.personality;
78
+ }
79
+ function clearPersonality(categoryId) {
80
+ const project = getProjectByCategoryId(categoryId);
81
+ if (!project) return;
82
+ delete project.personality;
83
+ updateProject(project).catch(
84
+ (err) => console.error(`Failed to clear personality for project "${project.name}": ${err.message}`)
85
+ );
86
+ }
87
+ function addSkill(categoryId, name, prompt) {
88
+ const project = getProjectByCategoryId(categoryId);
89
+ if (!project) return;
90
+ project.skills[name] = prompt;
91
+ updateProject(project).catch(
92
+ (err) => console.error(`Failed to add skill "${name}" for project "${project.name}": ${err.message}`)
93
+ );
94
+ }
95
+ function removeSkill(categoryId, name) {
96
+ const project = getProjectByCategoryId(categoryId);
97
+ if (!project || !project.skills[name]) return false;
98
+ delete project.skills[name];
99
+ updateProject(project).catch(
100
+ (err) => console.error(`Failed to remove skill "${name}" for project "${project.name}": ${err.message}`)
101
+ );
102
+ return true;
103
+ }
104
+ function getSkills(categoryId) {
105
+ const project = getProjectByCategoryId(categoryId);
106
+ if (!project) return [];
107
+ return Object.entries(project.skills).map(([name, prompt]) => ({ name, prompt }));
108
+ }
109
+ function executeSkill(categoryId, name, input) {
110
+ const project = getProjectByCategoryId(categoryId);
111
+ if (!project) return null;
112
+ const template = project.skills[name];
113
+ if (!template) return null;
114
+ return input ? template.replace(/\{input\}/g, input) : template.replace(/\{input\}/g, "");
115
+ }
116
+ async function addMcpServer(categoryId, serverName, command, args) {
117
+ const project = getProjectByCategoryId(categoryId);
118
+ if (!project) return;
119
+ const existing = project.mcpServers.findIndex((server2) => server2.name === serverName);
120
+ const server = {
121
+ name: serverName,
122
+ command,
123
+ ...args?.length ? { args } : {}
124
+ };
125
+ if (existing >= 0) {
126
+ project.mcpServers[existing] = server;
127
+ } else {
128
+ project.mcpServers.push(server);
129
+ }
130
+ await updateProject(project);
131
+ await writeMcpJson(project.path, project.mcpServers);
132
+ }
133
+ async function removeMcpServer(categoryId, serverName) {
134
+ const project = getProjectByCategoryId(categoryId);
135
+ if (!project) return false;
136
+ const index = project.mcpServers.findIndex((server) => server.name === serverName);
137
+ if (index < 0) return false;
138
+ project.mcpServers.splice(index, 1);
139
+ await updateProject(project);
140
+ await writeMcpJson(project.path, project.mcpServers);
141
+ return true;
142
+ }
143
+ function getMcpServers(categoryId) {
144
+ return getProjectByCategoryId(categoryId)?.mcpServers || [];
145
+ }
146
+ async function writeMcpJson(projectDir, servers) {
147
+ const mcpPath = join(projectDir, ".mcp.json");
148
+ let mcpConfig = {};
149
+ try {
150
+ if (existsSync(mcpPath)) {
151
+ const existing = await readFile(mcpPath, "utf-8");
152
+ mcpConfig = JSON.parse(existing);
153
+ }
154
+ } catch {
155
+ }
156
+ const mcpServers = {};
157
+ for (const server of servers) {
158
+ mcpServers[server.name] = {
159
+ command: server.command,
160
+ ...server.args?.length ? { args: server.args } : {}
161
+ };
162
+ }
163
+ mcpConfig.mcpServers = mcpServers;
164
+ await writeFile(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
165
+ }
166
+
167
+ // ../bot/src/archive-manager.ts
17
168
  var archiveStore = new Store("archived.json");
18
169
  var archived = [];
19
170
  async function loadArchived() {
@@ -188,6 +339,21 @@ function formatDuration(ms) {
188
339
  }
189
340
 
190
341
  export {
342
+ loadProjects,
343
+ getProject,
344
+ bindMountedProjectToCategory,
345
+ setHistoryChannelId,
346
+ setControlChannelId,
347
+ setPersonality,
348
+ getPersonality,
349
+ clearPersonality,
350
+ addSkill,
351
+ removeSkill,
352
+ getSkills,
353
+ executeSkill,
354
+ addMcpServer,
355
+ removeMcpServer,
356
+ getMcpServers,
191
357
  loadArchived,
192
358
  getArchivedSessions,
193
359
  isArchivedProviderSession,