mono-pilot 0.2.9 → 0.2.12

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 (158) hide show
  1. package/README.md +270 -7
  2. package/dist/src/agents-paths.js +36 -0
  3. package/dist/src/brief/blocks.js +83 -0
  4. package/dist/src/brief/defaults.js +60 -0
  5. package/dist/src/brief/frontmatter.js +53 -0
  6. package/dist/src/brief/paths.js +10 -0
  7. package/dist/src/brief/reflection.js +27 -0
  8. package/dist/src/cli.js +62 -5
  9. package/dist/src/cluster/bus.js +102 -0
  10. package/dist/src/cluster/follower.js +137 -0
  11. package/dist/src/cluster/init.js +182 -0
  12. package/dist/src/cluster/leader.js +97 -0
  13. package/dist/src/cluster/log.js +49 -0
  14. package/dist/src/cluster/protocol.js +34 -0
  15. package/dist/src/cluster/services/bus.js +243 -0
  16. package/dist/src/cluster/services/embedding.js +12 -0
  17. package/dist/src/cluster/socket.js +86 -0
  18. package/dist/src/cluster/test-bus.js +175 -0
  19. package/dist/src/cluster_v2/connection-lifecycle.js +31 -0
  20. package/dist/src/cluster_v2/connection-lifecycle.test.js +24 -0
  21. package/dist/src/cluster_v2/connection.js +159 -0
  22. package/dist/src/cluster_v2/connection.test.js +55 -0
  23. package/dist/src/cluster_v2/events.js +102 -0
  24. package/dist/src/cluster_v2/index.js +2 -0
  25. package/dist/src/cluster_v2/observability.js +99 -0
  26. package/dist/src/cluster_v2/observability.test.js +46 -0
  27. package/dist/src/cluster_v2/rpc.js +389 -0
  28. package/dist/src/cluster_v2/rpc.test.js +110 -0
  29. package/dist/src/cluster_v2/runtime.failover.integration.test.js +156 -0
  30. package/dist/src/cluster_v2/runtime.js +531 -0
  31. package/dist/src/cluster_v2/runtime.lease-compromise.integration.test.js +91 -0
  32. package/dist/src/cluster_v2/runtime.lifecycle.integration.test.js +225 -0
  33. package/dist/src/cluster_v2/services/bus.integration.test.js +140 -0
  34. package/dist/src/cluster_v2/services/bus.js +450 -0
  35. package/dist/src/cluster_v2/services/discord/auth-store.js +82 -0
  36. package/dist/src/cluster_v2/services/discord/collector.js +569 -0
  37. package/dist/src/cluster_v2/services/discord/index.js +1 -0
  38. package/dist/src/cluster_v2/services/discord/oauth.js +87 -0
  39. package/dist/src/cluster_v2/services/discord/rpc-client.js +325 -0
  40. package/dist/src/cluster_v2/services/embedding.js +66 -0
  41. package/dist/src/cluster_v2/services/registry-cache.js +107 -0
  42. package/dist/src/cluster_v2/services/registry-cache.test.js +66 -0
  43. package/dist/src/cluster_v2/services/registry.js +36 -0
  44. package/dist/src/cluster_v2/services/twitter/collector.js +1055 -0
  45. package/dist/src/cluster_v2/services/twitter/index.js +1 -0
  46. package/dist/src/config/digest.js +78 -0
  47. package/dist/src/config/discord.js +143 -0
  48. package/dist/src/config/image-gen.js +48 -0
  49. package/dist/src/config/mono-pilot.js +31 -0
  50. package/dist/src/config/twitter.js +100 -0
  51. package/dist/src/extensions/cluster.js +311 -0
  52. package/dist/src/extensions/commands/build-memory.js +76 -0
  53. package/dist/src/extensions/commands/digest/backfill.js +779 -0
  54. package/dist/src/extensions/commands/digest/index.js +1133 -0
  55. package/dist/src/extensions/commands/image-model.js +214 -0
  56. package/dist/src/extensions/game/bus-injection.js +47 -0
  57. package/dist/src/extensions/game/identity.js +83 -0
  58. package/dist/src/extensions/game/mailbox.js +61 -0
  59. package/dist/src/extensions/game/system-prompt.js +134 -0
  60. package/dist/src/extensions/game/tools.js +28 -0
  61. package/dist/src/extensions/lifecycle.js +337 -0
  62. package/dist/src/extensions/mode-runtime.js +26 -2
  63. package/dist/src/extensions/mono-game.js +66 -0
  64. package/dist/src/extensions/mono-pilot.js +100 -18
  65. package/dist/src/extensions/nvim.js +47 -0
  66. package/dist/src/extensions/session-hints.js +60 -35
  67. package/dist/src/extensions/sftp.js +897 -0
  68. package/dist/src/extensions/status.js +676 -0
  69. package/dist/src/extensions/system-events.js +478 -0
  70. package/dist/src/extensions/system-prompt.js +24 -14
  71. package/dist/src/extensions/user-message.js +94 -50
  72. package/dist/src/lsp/client.js +235 -0
  73. package/dist/src/lsp/index.js +165 -0
  74. package/dist/src/lsp/runtime.js +67 -0
  75. package/dist/src/lsp/server.js +242 -0
  76. package/dist/src/mcp/config.js +112 -0
  77. package/dist/src/{utils/mcp-client.js → mcp/protocol.js} +1 -100
  78. package/dist/src/mcp/servers.js +90 -0
  79. package/dist/src/memory/build-memory.js +103 -0
  80. package/dist/src/memory/config/defaults.js +55 -0
  81. package/dist/src/memory/config/loader.js +29 -0
  82. package/dist/src/memory/config/paths.js +9 -0
  83. package/dist/src/memory/config/resolve.js +90 -0
  84. package/dist/src/memory/config/types.js +1 -0
  85. package/dist/src/memory/embeddings/batch-runner.js +39 -0
  86. package/dist/src/memory/embeddings/cache.js +47 -0
  87. package/dist/src/memory/embeddings/chunk-limits.js +26 -0
  88. package/dist/src/memory/embeddings/input-limits.js +48 -0
  89. package/dist/src/memory/embeddings/local.js +108 -0
  90. package/dist/src/memory/embeddings/types.js +1 -0
  91. package/dist/src/memory/index-manager.js +552 -0
  92. package/dist/src/memory/indexing/embeddings.js +67 -0
  93. package/dist/src/memory/indexing/files.js +180 -0
  94. package/dist/src/memory/indexing/index-file.js +105 -0
  95. package/dist/src/memory/log.js +38 -0
  96. package/dist/src/memory/paths.js +15 -0
  97. package/dist/src/memory/runtime/index.js +299 -0
  98. package/dist/src/memory/runtime/thread.js +116 -0
  99. package/dist/src/memory/search/fts.js +57 -0
  100. package/dist/src/memory/search/hybrid.js +50 -0
  101. package/dist/src/memory/search/text.js +30 -0
  102. package/dist/src/memory/search/vector.js +43 -0
  103. package/dist/src/memory/session/content-hash.js +7 -0
  104. package/dist/src/memory/session/entry.js +33 -0
  105. package/dist/src/memory/session/flush-policy.js +34 -0
  106. package/dist/src/memory/session/hook.js +191 -0
  107. package/dist/src/memory/session/paths.js +15 -0
  108. package/dist/src/memory/session/session-reader.js +88 -0
  109. package/dist/src/memory/session/transcript/content-hash.js +7 -0
  110. package/dist/src/memory/session/transcript/entry.js +28 -0
  111. package/dist/src/memory/session/transcript/flush.js +56 -0
  112. package/dist/src/memory/session/transcript/paths.js +28 -0
  113. package/dist/src/memory/session/transcript/reader.js +112 -0
  114. package/dist/src/memory/session/transcript/state.js +31 -0
  115. package/dist/src/memory/store/schema.js +89 -0
  116. package/dist/src/memory/store/sqlite.js +89 -0
  117. package/dist/src/memory/types.js +1 -0
  118. package/dist/src/memory/warm.js +25 -0
  119. package/dist/src/rules/discovery.js +41 -0
  120. package/dist/{tools → src/tools}/README.md +29 -3
  121. package/dist/{tools → src/tools}/apply-patch-description.md +8 -2
  122. package/dist/{tools → src/tools}/apply-patch.js +174 -104
  123. package/dist/{tools → src/tools}/apply-patch.test.js +52 -1
  124. package/dist/{tools/ask-question.js → src/tools/ask-user-question.js} +3 -3
  125. package/dist/src/tools/ast-grep.js +357 -0
  126. package/dist/src/tools/brief-write.js +122 -0
  127. package/dist/src/tools/bus-send.js +100 -0
  128. package/dist/{tools → src/tools}/call-mcp-tool.js +40 -124
  129. package/dist/src/tools/codex-apply-patch-description.md +52 -0
  130. package/dist/src/tools/codex-apply-patch.js +540 -0
  131. package/dist/{tools → src/tools}/delete.js +24 -0
  132. package/dist/src/tools/exit-plan-mode.js +83 -0
  133. package/dist/{tools → src/tools}/fetch-mcp-resource.js +56 -100
  134. package/dist/src/tools/generate-image.js +567 -0
  135. package/dist/{tools → src/tools}/glob.js +55 -1
  136. package/dist/{tools → src/tools}/list-mcp-resources.js +46 -57
  137. package/dist/{tools → src/tools}/list-mcp-tools.js +52 -63
  138. package/dist/src/tools/ls.js +48 -0
  139. package/dist/src/tools/lsp-diagnostics.js +67 -0
  140. package/dist/src/tools/lsp-symbols.js +54 -0
  141. package/dist/src/tools/mailbox.js +85 -0
  142. package/dist/src/tools/memory-get.js +90 -0
  143. package/dist/src/tools/memory-search.js +180 -0
  144. package/dist/{tools → src/tools}/plan-mode-reminder.md +3 -4
  145. package/dist/{tools → src/tools}/read-file.js +8 -19
  146. package/dist/{tools → src/tools}/rg.js +10 -20
  147. package/dist/{tools → src/tools}/shell.js +19 -42
  148. package/dist/{tools → src/tools}/subagent.js +255 -6
  149. package/dist/{tools → src/tools}/switch-mode.js +37 -6
  150. package/dist/{tools → src/tools}/web-fetch.js +105 -7
  151. package/dist/{tools → src/tools}/web-search.js +29 -1
  152. package/package.json +21 -9
  153. /package/dist/{tools → src/tools}/ask-mode-reminder.md +0 -0
  154. /package/dist/{tools → src/tools}/rg.test.js +0 -0
  155. /package/dist/{tools → src/tools}/semantic-search-description.md +0 -0
  156. /package/dist/{tools → src/tools}/semantic-search.js +0 -0
  157. /package/dist/{tools → src/tools}/shell-description.md +0 -0
  158. /package/dist/{tools → src/tools}/subagent-description.md +0 -0
@@ -0,0 +1,242 @@
1
+ import { spawn, execSync } from "node:child_process";
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ import os from "node:os";
5
+ import fs from "node:fs/promises";
6
+ import { Filesystem, Log, LspState } from "./runtime.js";
7
+ export var LSPServer;
8
+ (function (LSPServer) {
9
+ const log = Log.create({ service: "lsp.server" });
10
+ // Directory for npm-installed LSP servers
11
+ const binDir = path.join(os.homedir(), ".mono-pilot", "lsp");
12
+ function which(cmd) {
13
+ const w = process.platform === "win32" ? "where" : "which";
14
+ try {
15
+ return execSync(`${w} ${cmd}`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
16
+ .trim()
17
+ .split("\n")[0] || undefined;
18
+ }
19
+ catch {
20
+ return undefined;
21
+ }
22
+ }
23
+ function xcrunFind(cmd) {
24
+ if (process.platform !== "darwin")
25
+ return undefined;
26
+ try {
27
+ return execSync(`xcrun --find ${cmd}`, {
28
+ encoding: "utf8",
29
+ stdio: ["pipe", "pipe", "pipe"],
30
+ })
31
+ .trim()
32
+ .split("\n")[0] || undefined;
33
+ }
34
+ catch {
35
+ return undefined;
36
+ }
37
+ }
38
+ async function npmInstall(...pkgs) {
39
+ await fs.mkdir(binDir, { recursive: true });
40
+ await new Promise((resolve, reject) => {
41
+ const npm = process.platform === "win32" ? "npm.cmd" : "npm";
42
+ const proc = spawn(npm, ["install", "--prefix", binDir, ...pkgs], { stdio: "pipe" });
43
+ proc.on("exit", (code) => code === 0 ? resolve() : reject(new Error(`npm install ${pkgs.join(" ")} failed (exit ${code})`)));
44
+ proc.on("error", reject);
45
+ });
46
+ }
47
+ const NearestRoot = (includePatterns, excludePatterns) => {
48
+ return async (file) => {
49
+ const dir = path.dirname(file);
50
+ const stop = LspState.directory;
51
+ if (excludePatterns) {
52
+ const gen = Filesystem.up({ targets: excludePatterns, start: dir, stop });
53
+ const first = await gen.next();
54
+ await gen.return(undefined);
55
+ if (first.value)
56
+ return undefined;
57
+ }
58
+ const gen = Filesystem.up({ targets: includePatterns, start: dir, stop });
59
+ const first = await gen.next();
60
+ await gen.return(undefined);
61
+ if (!first.value)
62
+ return stop;
63
+ return path.dirname(first.value);
64
+ };
65
+ };
66
+ LSPServer.Typescript = {
67
+ id: "typescript",
68
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
69
+ root: NearestRoot(["package-lock.json", "package.json", "yarn.lock", "pnpm-lock.yaml"], ["deno.json", "deno.jsonc"]),
70
+ async spawn(root) {
71
+ // Try to find the project's tsserver.js for initialization
72
+ const req = createRequire(path.join(root, "_"));
73
+ let tsserver;
74
+ try {
75
+ tsserver = req.resolve("typescript/lib/tsserver.js");
76
+ }
77
+ catch {
78
+ // TypeScript not in project node_modules; server will use its own
79
+ }
80
+ let bin = which("typescript-language-server");
81
+ if (!bin) {
82
+ const local = path.join(binDir, "node_modules", ".bin", "typescript-language-server");
83
+ if (!(await Filesystem.exists(local))) {
84
+ log.info("installing typescript-language-server");
85
+ try {
86
+ await npmInstall("typescript-language-server", "typescript");
87
+ }
88
+ catch (e) {
89
+ log.error("failed to install typescript-language-server", { error: String(e) });
90
+ return;
91
+ }
92
+ }
93
+ if (await Filesystem.exists(local))
94
+ bin = local;
95
+ }
96
+ if (!bin) {
97
+ log.error("typescript-language-server not found");
98
+ return;
99
+ }
100
+ return {
101
+ process: spawn(bin, ["--stdio"], { cwd: root }),
102
+ initialization: tsserver ? { tsserver: { path: tsserver } } : undefined,
103
+ };
104
+ },
105
+ };
106
+ LSPServer.Pyright = {
107
+ id: "pyright",
108
+ extensions: [".py", ".pyi"],
109
+ root: NearestRoot([
110
+ "pyproject.toml",
111
+ "setup.py",
112
+ "setup.cfg",
113
+ "requirements.txt",
114
+ "Pipfile",
115
+ "pyrightconfig.json",
116
+ ]),
117
+ async spawn(root) {
118
+ let bin = which("pyright-langserver");
119
+ if (!bin) {
120
+ const local = path.join(binDir, "node_modules", ".bin", "pyright-langserver");
121
+ if (!(await Filesystem.exists(local))) {
122
+ log.info("installing pyright");
123
+ try {
124
+ await npmInstall("pyright");
125
+ }
126
+ catch (e) {
127
+ log.error("failed to install pyright", { error: String(e) });
128
+ return;
129
+ }
130
+ }
131
+ if (await Filesystem.exists(local))
132
+ bin = local;
133
+ }
134
+ if (!bin) {
135
+ log.error("pyright-langserver not found");
136
+ return;
137
+ }
138
+ // Detect virtualenv for python path initialization
139
+ const initialization = {};
140
+ const venvPaths = [
141
+ process.env["VIRTUAL_ENV"],
142
+ path.join(root, ".venv"),
143
+ path.join(root, "venv"),
144
+ ].filter((p) => p !== undefined);
145
+ for (const venv of venvPaths) {
146
+ const python = process.platform === "win32"
147
+ ? path.join(venv, "Scripts", "python.exe")
148
+ : path.join(venv, "bin", "python");
149
+ if (await Filesystem.exists(python)) {
150
+ initialization["pythonPath"] = python;
151
+ break;
152
+ }
153
+ }
154
+ return {
155
+ process: spawn(bin, ["--stdio"], { cwd: root }),
156
+ initialization,
157
+ };
158
+ },
159
+ };
160
+ LSPServer.Gopls = {
161
+ id: "gopls",
162
+ extensions: [".go"],
163
+ root: async (file) => {
164
+ const work = await NearestRoot(["go.work"])(file);
165
+ if (work)
166
+ return work;
167
+ return NearestRoot(["go.mod", "go.sum"])(file);
168
+ },
169
+ async spawn(root) {
170
+ const bin = which("gopls");
171
+ if (!bin) {
172
+ log.error("gopls not found. Install with: go install golang.org/x/tools/gopls@latest");
173
+ return;
174
+ }
175
+ return { process: spawn(bin, { cwd: root }) };
176
+ },
177
+ };
178
+ LSPServer.RustAnalyzer = {
179
+ id: "rust",
180
+ extensions: [".rs"],
181
+ root: async (file) => {
182
+ const crate = await NearestRoot(["Cargo.toml", "Cargo.lock"])(file);
183
+ if (!crate)
184
+ return undefined;
185
+ // Walk up to find workspace-level Cargo.toml
186
+ let current = crate;
187
+ while (true) {
188
+ const content = await fs.readFile(path.join(current, "Cargo.toml"), "utf8").catch(() => "");
189
+ if (content.includes("[workspace]"))
190
+ return current;
191
+ const parent = path.dirname(current);
192
+ if (parent === current || !parent.startsWith(LspState.directory))
193
+ break;
194
+ current = parent;
195
+ }
196
+ return crate;
197
+ },
198
+ async spawn(root) {
199
+ const bin = which("rust-analyzer");
200
+ if (!bin) {
201
+ log.error("rust-analyzer not found. Install with: rustup component add rust-analyzer");
202
+ return;
203
+ }
204
+ return { process: spawn(bin, { cwd: root }) };
205
+ },
206
+ };
207
+ LSPServer.SourcekitLsp = {
208
+ id: "sourcekit-lsp",
209
+ extensions: [".swift"],
210
+ root: NearestRoot(["Package.swift"]),
211
+ async spawn(root) {
212
+ const bin = which("sourcekit-lsp") ?? xcrunFind("sourcekit-lsp");
213
+ if (!bin) {
214
+ log.error("sourcekit-lsp not found. Install with Xcode or a Swift toolchain.");
215
+ return;
216
+ }
217
+ return { process: spawn(bin, { cwd: root }) };
218
+ },
219
+ };
220
+ LSPServer.Clangd = {
221
+ id: "clangd",
222
+ extensions: [".c", ".cpp", ".cxx", ".cc", ".c++", ".h", ".hpp", ".hxx", ".hh"],
223
+ // compile_commands.json is the gold standard; fall back to CMake/Makefile markers
224
+ root: NearestRoot([
225
+ "compile_commands.json",
226
+ "compile_flags.txt",
227
+ ".clangd",
228
+ "CMakeLists.txt",
229
+ "Makefile",
230
+ ]),
231
+ async spawn(root) {
232
+ const bin = which("clangd");
233
+ if (!bin) {
234
+ log.error("clangd not found. Install with: brew install llvm, or apt install clangd");
235
+ return;
236
+ }
237
+ return {
238
+ process: spawn(bin, ["--background-index", "--clang-tidy"], { cwd: root }),
239
+ };
240
+ },
241
+ };
242
+ })(LSPServer || (LSPServer = {}));
@@ -0,0 +1,112 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { join, resolve } from "node:path";
5
+ export const MCP_CONFIG_RELATIVE_PATH = join(".pi", "mcp.json");
6
+ export function isRecord(value) {
7
+ return typeof value === "object" && value !== null;
8
+ }
9
+ export function toNonEmptyString(value) {
10
+ if (typeof value !== "string")
11
+ return undefined;
12
+ const trimmed = value.trim();
13
+ if (trimmed.length === 0)
14
+ return undefined;
15
+ return trimmed;
16
+ }
17
+ export function toBoolean(value) {
18
+ if (typeof value === "boolean")
19
+ return value;
20
+ return undefined;
21
+ }
22
+ export function formatErrorMessage(error) {
23
+ if (error instanceof Error)
24
+ return error.message;
25
+ return String(error);
26
+ }
27
+ export function getMcpConfigCandidates(workspaceCwd) {
28
+ return [resolve(workspaceCwd, MCP_CONFIG_RELATIVE_PATH), resolve(homedir(), MCP_CONFIG_RELATIVE_PATH)];
29
+ }
30
+ export function resolveMcpConfigSources(workspaceCwd) {
31
+ const projectPath = resolve(workspaceCwd, MCP_CONFIG_RELATIVE_PATH);
32
+ const userPath = resolve(homedir(), MCP_CONFIG_RELATIVE_PATH);
33
+ const sources = [];
34
+ if (existsSync(projectPath))
35
+ sources.push({ scope: "project", path: projectPath });
36
+ if (existsSync(userPath))
37
+ sources.push({ scope: "user", path: userPath });
38
+ return sources;
39
+ }
40
+ export async function loadMcpConfig(workspaceCwd) {
41
+ const sources = resolveMcpConfigSources(workspaceCwd);
42
+ if (sources.length === 0)
43
+ return undefined;
44
+ const servers = {};
45
+ const sourceByServer = {};
46
+ for (const source of sources) {
47
+ const parsed = await parseMcpConfig(source.path);
48
+ for (const [serverName, serverConfig] of Object.entries(parsed)) {
49
+ if (sourceByServer[serverName])
50
+ continue;
51
+ servers[serverName] = serverConfig;
52
+ sourceByServer[serverName] = source;
53
+ }
54
+ }
55
+ return { servers, sources, sourceByServer };
56
+ }
57
+ async function parseMcpConfig(configPath) {
58
+ const rawText = await readFile(configPath, "utf-8");
59
+ let parsed;
60
+ try {
61
+ parsed = JSON.parse(rawText);
62
+ }
63
+ catch (error) {
64
+ throw new Error(`Invalid JSON in MCP config: ${formatErrorMessage(error)}`);
65
+ }
66
+ if (!isRecord(parsed)) {
67
+ throw new Error("MCP config root must be a JSON object.");
68
+ }
69
+ const serversValue = parsed.mcpServers;
70
+ if (!isRecord(serversValue))
71
+ return {};
72
+ const result = {};
73
+ for (const [serverName, serverConfig] of Object.entries(serversValue)) {
74
+ if (!isRecord(serverConfig))
75
+ continue;
76
+ result[serverName] = serverConfig;
77
+ }
78
+ return result;
79
+ }
80
+ export function isServerEnabled(config) {
81
+ const disabled = toBoolean(config.disabled);
82
+ if (disabled === true)
83
+ return false;
84
+ const enabled = toBoolean(config.enabled);
85
+ if (enabled === false)
86
+ return false;
87
+ return true;
88
+ }
89
+ export function inferTransport(config) {
90
+ if (toNonEmptyString(config.url))
91
+ return "remote";
92
+ if (toNonEmptyString(config.command))
93
+ return "stdio";
94
+ return "unknown";
95
+ }
96
+ export function extractStringHeaders(rawHeaders) {
97
+ if (!isRecord(rawHeaders))
98
+ return {};
99
+ const headers = {};
100
+ for (const [key, value] of Object.entries(rawHeaders)) {
101
+ const headerName = key.trim();
102
+ if (!headerName)
103
+ continue;
104
+ if (typeof value === "string")
105
+ headers[headerName] = value;
106
+ }
107
+ return headers;
108
+ }
109
+ export function getHeaderKeys(headers) {
110
+ const keys = Object.keys(headers).sort((a, b) => a.localeCompare(b));
111
+ return keys.length > 0 ? keys : undefined;
112
+ }
@@ -1,107 +1,8 @@
1
- import { existsSync } from "node:fs";
2
- import { readFile } from "node:fs/promises";
3
- import { homedir } from "node:os";
4
- import { isAbsolute, join, resolve } from "node:path";
5
- import process from "node:process";
6
- export const MCP_CONFIG_RELATIVE_PATH = join(".pi", "mcp.json");
1
+ import { isRecord, toNonEmptyString } from "./config.js";
7
2
  export const MCP_PROTOCOL_VERSION = "2025-03-26";
8
3
  export const MCP_CLIENT_NAME = "mono-pilot";
9
4
  export const MCP_CLIENT_VERSION = "0.1.0";
10
5
  export const MCP_REQUEST_TIMEOUT_MS = 20_000;
11
- export function isRecord(value) {
12
- return typeof value === "object" && value !== null;
13
- }
14
- export function toNonEmptyString(value) {
15
- if (typeof value !== "string")
16
- return undefined;
17
- const trimmed = value.trim();
18
- if (trimmed.length === 0)
19
- return undefined;
20
- return trimmed;
21
- }
22
- export function toBoolean(value) {
23
- if (typeof value === "boolean")
24
- return value;
25
- return undefined;
26
- }
27
- export function formatErrorMessage(error) {
28
- if (error instanceof Error)
29
- return error.message;
30
- return String(error);
31
- }
32
- export function resolveMcpConfigPath(workspaceCwd) {
33
- const envOverride = toNonEmptyString(process.env.MONOPILOT_MCP_CONFIG);
34
- const candidates = [];
35
- if (envOverride) {
36
- candidates.push(isAbsolute(envOverride) ? resolve(envOverride) : resolve(workspaceCwd, envOverride));
37
- }
38
- candidates.push(resolve(workspaceCwd, MCP_CONFIG_RELATIVE_PATH));
39
- candidates.push(resolve(homedir(), MCP_CONFIG_RELATIVE_PATH));
40
- for (const candidate of candidates) {
41
- if (existsSync(candidate))
42
- return candidate;
43
- }
44
- return undefined;
45
- }
46
- export async function parseMcpConfig(configPath) {
47
- const rawText = await readFile(configPath, "utf-8");
48
- return _parseMcpConfigContent(rawText, configPath);
49
- }
50
- function _parseMcpConfigContent(rawText, configPath) {
51
- let parsed;
52
- try {
53
- parsed = JSON.parse(rawText);
54
- }
55
- catch (error) {
56
- throw new Error(`Invalid JSON in MCP config: ${formatErrorMessage(error)}`);
57
- }
58
- if (!isRecord(parsed)) {
59
- throw new Error("MCP config root must be a JSON object.");
60
- }
61
- const serversValue = parsed.mcpServers;
62
- if (!isRecord(serversValue))
63
- return {};
64
- const result = {};
65
- for (const [serverName, serverConfig] of Object.entries(serversValue)) {
66
- if (!isRecord(serverConfig))
67
- continue;
68
- result[serverName] = serverConfig;
69
- }
70
- return result;
71
- }
72
- export function isServerEnabled(config) {
73
- const disabled = toBoolean(config.disabled);
74
- if (disabled === true)
75
- return false;
76
- const enabled = toBoolean(config.enabled);
77
- if (enabled === false)
78
- return false;
79
- return true;
80
- }
81
- export function inferTransport(config) {
82
- if (toNonEmptyString(config.url))
83
- return "remote";
84
- if (toNonEmptyString(config.command))
85
- return "stdio";
86
- return "unknown";
87
- }
88
- export function extractStringHeaders(rawHeaders) {
89
- if (!isRecord(rawHeaders))
90
- return {};
91
- const headers = {};
92
- for (const [key, value] of Object.entries(rawHeaders)) {
93
- const headerName = key.trim();
94
- if (!headerName)
95
- continue;
96
- if (typeof value === "string")
97
- headers[headerName] = value;
98
- }
99
- return headers;
100
- }
101
- export function getHeaderKeys(headers) {
102
- const keys = Object.keys(headers).sort((a, b) => a.localeCompare(b));
103
- return keys.length > 0 ? keys : undefined;
104
- }
105
6
  export function parseSseJsonPayload(rawBody) {
106
7
  const lines = rawBody.split(/\r?\n/);
107
8
  const events = [];
@@ -0,0 +1,90 @@
1
+ import { extractStringHeaders, formatErrorMessage, getMcpConfigCandidates, inferTransport, isServerEnabled, loadMcpConfig, resolveMcpConfigSources, toNonEmptyString, } from "./config.js";
2
+ /** Thrown when no MCP config files are found or config fails to parse. */
3
+ export class McpConfigError extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = "McpConfigError";
7
+ }
8
+ }
9
+ /** Thrown when a specific server can't be used (not found, disabled, wrong transport, etc.). */
10
+ export class McpServerError extends Error {
11
+ transport;
12
+ configPaths;
13
+ constructor(message, transport, configPaths) {
14
+ super(message);
15
+ this.transport = transport;
16
+ this.configPaths = configPaths;
17
+ this.name = "McpServerError";
18
+ }
19
+ }
20
+ async function loadRequiredMcpConfig(cwd) {
21
+ const sources = resolveMcpConfigSources(cwd);
22
+ if (sources.length === 0) {
23
+ const candidates = getMcpConfigCandidates(cwd);
24
+ throw new McpConfigError(`MCP config not found. Checked:\n- ${candidates.join("\n- ")}`);
25
+ }
26
+ let config;
27
+ try {
28
+ config = await loadMcpConfig(cwd);
29
+ }
30
+ catch (error) {
31
+ throw new McpConfigError(formatErrorMessage(error));
32
+ }
33
+ if (!config) {
34
+ const candidates = getMcpConfigCandidates(cwd);
35
+ throw new McpConfigError(`MCP config not found. Checked:\n- ${candidates.join("\n- ")}`);
36
+ }
37
+ return config;
38
+ }
39
+ /**
40
+ * For list-style tools (ListMcpTools, ListMcpResources).
41
+ * Returns all enabled remote servers matching the optional name filter.
42
+ * Throws McpConfigError on config load failure.
43
+ */
44
+ export async function resolveTargetServers(cwd, serverFilter) {
45
+ const config = await loadRequiredMcpConfig(cwd);
46
+ const configPaths = config.sources.map((source) => source.path);
47
+ const servers = [];
48
+ for (const [serverName, serverConfig] of Object.entries(config.servers)) {
49
+ if (serverFilter && serverName !== serverFilter)
50
+ continue;
51
+ if (!isServerEnabled(serverConfig))
52
+ continue;
53
+ if (inferTransport(serverConfig) !== "remote")
54
+ continue;
55
+ const serverUrl = toNonEmptyString(serverConfig.url);
56
+ if (!serverUrl)
57
+ continue;
58
+ servers.push({ name: serverName, url: serverUrl, headers: extractStringHeaders(serverConfig.headers) });
59
+ }
60
+ return { servers, sources: config.sources, configPaths };
61
+ }
62
+ /**
63
+ * For single-server tools (CallMcpTool, FetchMcpResource).
64
+ * Returns the resolved remote server or throws McpConfigError / McpServerError.
65
+ */
66
+ export async function resolveTargetServer(cwd, serverName) {
67
+ const config = await loadRequiredMcpConfig(cwd);
68
+ const configPaths = config.sources.map((source) => source.path);
69
+ const serverConfig = config.servers[serverName];
70
+ if (!serverConfig) {
71
+ throw new McpServerError(`MCP server '${serverName}' not found in configured MCP sources.`, undefined, configPaths);
72
+ }
73
+ if (!isServerEnabled(serverConfig)) {
74
+ throw new McpServerError(`MCP server '${serverName}' is disabled in config.`, inferTransport(serverConfig), configPaths);
75
+ }
76
+ const transport = inferTransport(serverConfig);
77
+ if (transport === "stdio") {
78
+ const command = toNonEmptyString(serverConfig.command);
79
+ const message = `MCP stdio transport is not supported yet.` + (command ? ` Configured command: ${command}` : "");
80
+ throw new McpServerError(message, transport, configPaths);
81
+ }
82
+ const serverUrl = toNonEmptyString(serverConfig.url);
83
+ if (!serverUrl) {
84
+ throw new McpServerError(`MCP server '${serverName}' is missing a remote URL.`, transport, configPaths);
85
+ }
86
+ return {
87
+ server: { name: serverName, url: serverUrl, headers: extractStringHeaders(serverConfig.headers) },
88
+ configPaths,
89
+ };
90
+ }
@@ -0,0 +1,103 @@
1
+ import { loadResolvedMemorySearchConfig } from "./config/loader.js";
2
+ import { closeMemorySearchManagers, peekMemorySearchManager, getDefaultEmbeddingProvider } from "./runtime/index.js";
3
+ import { getMemoryIndexPath } from "./paths.js";
4
+ import { MemoryIndexManager } from "./index-manager.js";
5
+ import { deriveAgentId } from "../agents-paths.js";
6
+ import { openSqliteDatabase } from "./store/sqlite.js";
7
+ import { ensureMemoryIndexSchema, CHUNKS_TABLE, FILES_TABLE, FTS_TABLE, VECTOR_TABLE } from "./store/schema.js";
8
+ import { memoryLog } from "./log.js";
9
+ export async function buildMemoryIndex(params) {
10
+ const settings = await loadResolvedMemorySearchConfig();
11
+ if (!settings.enabled) {
12
+ return { ok: false, message: "Memory search is disabled in config.", agents: [] };
13
+ }
14
+ if (!settings.sources.includes("memory") && !settings.sources.includes("sessions")) {
15
+ return { ok: false, message: "Config sources do not include 'memory' or 'sessions'.", agents: [] };
16
+ }
17
+ if (params.mode === "full") {
18
+ return await buildFull(params, settings);
19
+ }
20
+ return await buildDirty(params);
21
+ }
22
+ async function buildFull(params, settings) {
23
+ // Release cached managers so DB files are not locked
24
+ await closeMemorySearchManagers();
25
+ const agentId = deriveAgentId(params.workspaceDir);
26
+ const provider = getDefaultEmbeddingProvider();
27
+ memoryLog.info("build full start", { agentId });
28
+ await clearAgentPartition(agentId, settings);
29
+ const manager = new MemoryIndexManager({
30
+ agentId,
31
+ workspaceDir: params.workspaceDir,
32
+ settings,
33
+ embedFn: provider?.embedBatch,
34
+ embedModel: provider?.model,
35
+ });
36
+ try {
37
+ await manager.sync({ reason: "build-full", force: true });
38
+ }
39
+ finally {
40
+ await manager.close();
41
+ }
42
+ memoryLog.info("build full complete", { agentId });
43
+ return {
44
+ ok: true,
45
+ message: `Full rebuild completed for agent ${agentId}.`,
46
+ agents: [agentId],
47
+ };
48
+ }
49
+ async function buildDirty(params) {
50
+ const agentId = deriveAgentId(params.workspaceDir);
51
+ const manager = peekMemorySearchManager({ agentId });
52
+ memoryLog.info("build dirty start", { agentId });
53
+ if (!manager) {
54
+ memoryLog.info("build dirty skipped", { agentId, reason: "no manager" });
55
+ return { ok: true, message: "No active memory manager found. Nothing to sync.", agents: [] };
56
+ }
57
+ if (!manager.isDirty?.()) {
58
+ memoryLog.info("build dirty skipped", { agentId, reason: "not dirty" });
59
+ return { ok: true, message: "Memory index is up to date (not dirty).", agents: [] };
60
+ }
61
+ if (manager.syncDirty) {
62
+ const synced = await manager.syncDirty();
63
+ memoryLog.info("build dirty complete", { agentId, syncedCount: synced.length });
64
+ return {
65
+ ok: true,
66
+ message: synced.length > 0
67
+ ? `Dirty sync completed for ${synced.length} agent(s).`
68
+ : "No dirty agents found.",
69
+ agents: synced,
70
+ };
71
+ }
72
+ if (manager.sync) {
73
+ await manager.sync({ reason: "build-dirty" });
74
+ }
75
+ memoryLog.info("build dirty complete", { agentId });
76
+ return {
77
+ ok: true,
78
+ message: "Dirty sync completed.",
79
+ agents: [agentId],
80
+ };
81
+ }
82
+ async function clearAgentPartition(agentId, settings) {
83
+ const indexPath = getMemoryIndexPath();
84
+ const db = openSqliteDatabase(indexPath, true);
85
+ try {
86
+ ensureMemoryIndexSchema({ db, ftsEnabled: true });
87
+ if (settings.store.vector.enabled) {
88
+ try {
89
+ db.prepare(`DELETE FROM ${VECTOR_TABLE} WHERE id IN (SELECT id FROM ${CHUNKS_TABLE} WHERE agent_id = ?)`).run(agentId);
90
+ }
91
+ catch { }
92
+ }
93
+ try {
94
+ db.prepare(`DELETE FROM ${FTS_TABLE} WHERE id IN (SELECT id FROM ${CHUNKS_TABLE} WHERE agent_id = ?)`).run(agentId);
95
+ }
96
+ catch { }
97
+ db.prepare(`DELETE FROM ${CHUNKS_TABLE} WHERE agent_id = ?`).run(agentId);
98
+ db.prepare(`DELETE FROM ${FILES_TABLE} WHERE agent_id = ?`).run(agentId);
99
+ }
100
+ finally {
101
+ db.close();
102
+ }
103
+ }