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,28 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ function formatMessages(messages) {
4
+ return messages.map((message) => `${message.role}: ${message.text}`).join("\n");
5
+ }
6
+ export function buildSessionTranscriptEntry(input) {
7
+ const iso = input.timestamp.toISOString();
8
+ const [datePart, timePart] = iso.split("T");
9
+ const time = (timePart ?? "").split(".")[0] ?? "";
10
+ const lines = [
11
+ `# Session Transcript Delta: ${datePart ?? ""} ${time} UTC`,
12
+ "",
13
+ `- **Reason**: ${input.reason}`,
14
+ `- **Session ID**: ${input.sessionId ?? "unknown"}`,
15
+ `- **Session File**: ${input.sessionFile}`,
16
+ `- **Range**: lines ${input.fromLineExclusive + 1}-${input.toLineInclusive}`,
17
+ "",
18
+ "## Conversation Excerpt",
19
+ "",
20
+ formatMessages(input.messages),
21
+ "",
22
+ ];
23
+ return lines.join("\n");
24
+ }
25
+ export async function writeSessionTranscriptFile(filePath, content) {
26
+ await mkdir(dirname(filePath), { recursive: true });
27
+ await writeFile(filePath, content, "utf-8");
28
+ }
@@ -0,0 +1,56 @@
1
+ import { join } from "node:path";
2
+ import { buildTranscriptContentHashSlug } from "./content-hash.js";
3
+ import { formatSessionTimestampParts } from "../paths.js";
4
+ import { buildSessionTranscriptEntry, writeSessionTranscriptFile } from "./entry.js";
5
+ import { readSessionTranscriptDelta } from "./reader.js";
6
+ import { buildSessionTranscriptFilename, getSessionTranscriptDir, getSessionTranscriptStatePath, normalizeSessionId, } from "./paths.js";
7
+ import { loadSessionTranscriptState, saveSessionTranscriptState } from "./state.js";
8
+ function toIsoMinuteSlug(time) {
9
+ return time.replace(/:/g, "").slice(0, 4);
10
+ }
11
+ export async function flushSessionTranscript(params) {
12
+ const initialSessionId = normalizeSessionId(undefined, params.sessionFile);
13
+ const initialStatePath = getSessionTranscriptStatePath(params.agentId, initialSessionId);
14
+ const initialState = await loadSessionTranscriptState(initialStatePath);
15
+ const delta = await readSessionTranscriptDelta(params.sessionFile, initialState.lastLine);
16
+ const finalSessionId = normalizeSessionId(delta.sessionId, params.sessionFile);
17
+ const statePath = getSessionTranscriptStatePath(params.agentId, finalSessionId);
18
+ const state = finalSessionId === initialSessionId ? initialState : await loadSessionTranscriptState(statePath);
19
+ const finalDelta = finalSessionId === initialSessionId
20
+ ? delta
21
+ : await readSessionTranscriptDelta(params.sessionFile, state.lastLine);
22
+ if (finalDelta.messages.length === 0) {
23
+ await saveSessionTranscriptState(statePath, {
24
+ lastLine: finalDelta.lastLine,
25
+ updatedAt: new Date().toISOString(),
26
+ });
27
+ return { written: false, lastLine: finalDelta.lastLine };
28
+ }
29
+ const now = new Date();
30
+ const { date, time } = formatSessionTimestampParts(now);
31
+ const timeSlug = toIsoMinuteSlug(time);
32
+ const textForHash = finalDelta.messages.map((msg) => `${msg.role}:${msg.text}`).join("\n");
33
+ const hashSlug = buildTranscriptContentHashSlug(`${finalDelta.sessionId ?? finalSessionId}\n${textForHash}`);
34
+ const filename = buildSessionTranscriptFilename(date, timeSlug, hashSlug);
35
+ const transcriptDir = getSessionTranscriptDir(params.agentId, finalSessionId);
36
+ const transcriptPath = join(transcriptDir, filename);
37
+ const content = buildSessionTranscriptEntry({
38
+ timestamp: now,
39
+ reason: params.reason,
40
+ sessionId: finalDelta.sessionId,
41
+ sessionFile: params.sessionFile,
42
+ fromLineExclusive: state.lastLine,
43
+ toLineInclusive: finalDelta.lastLine,
44
+ messages: finalDelta.messages,
45
+ });
46
+ await writeSessionTranscriptFile(transcriptPath, content);
47
+ await saveSessionTranscriptState(statePath, {
48
+ lastLine: finalDelta.lastLine,
49
+ updatedAt: now.toISOString(),
50
+ });
51
+ return {
52
+ written: true,
53
+ filePath: transcriptPath,
54
+ lastLine: finalDelta.lastLine,
55
+ };
56
+ }
@@ -0,0 +1,28 @@
1
+ import { basename, extname, join } from "node:path";
2
+ import { getAgentDir } from "../../../agents-paths.js";
3
+ const MAX_SESSION_SLUG_CHARS = 80;
4
+ function sanitizeSessionId(raw) {
5
+ const normalized = raw
6
+ .trim()
7
+ .replace(/[^a-z0-9._-]+/g, "-")
8
+ .replace(/^-+|-+$/g, "");
9
+ return normalized.slice(0, MAX_SESSION_SLUG_CHARS);
10
+ }
11
+ export function normalizeSessionId(sessionId, sessionFile) {
12
+ const fallback = basename(sessionFile, extname(sessionFile));
13
+ const candidate = sessionId && sessionId.trim().length > 0 ? sessionId : fallback;
14
+ const sanitized = sanitizeSessionId(candidate);
15
+ return sanitized || "unknown-session";
16
+ }
17
+ export function getSessionTranscriptsRootDir(agentId) {
18
+ return join(getAgentDir(agentId), "session-transcripts");
19
+ }
20
+ export function getSessionTranscriptDir(agentId, sessionSlug) {
21
+ return join(getSessionTranscriptsRootDir(agentId), sessionSlug);
22
+ }
23
+ export function getSessionTranscriptStatePath(agentId, sessionSlug) {
24
+ return join(getSessionTranscriptsRootDir(agentId), ".state", `${sessionSlug}.json`);
25
+ }
26
+ export function buildSessionTranscriptFilename(datePart, timePart, hashSlug) {
27
+ return `${datePart}-${timePart}-${hashSlug}.md`;
28
+ }
@@ -0,0 +1,112 @@
1
+ import { readFile } from "node:fs/promises";
2
+ const USER_QUERY_OPEN = "<user_query>";
3
+ const USER_QUERY_CLOSE = "</user_query>";
4
+ function parseJsonLine(line) {
5
+ try {
6
+ const parsed = JSON.parse(line);
7
+ return parsed && typeof parsed === "object" ? parsed : undefined;
8
+ }
9
+ catch {
10
+ return undefined;
11
+ }
12
+ }
13
+ function extractTextContent(content) {
14
+ if (typeof content === "string") {
15
+ return content;
16
+ }
17
+ if (!Array.isArray(content)) {
18
+ return undefined;
19
+ }
20
+ const texts = content
21
+ .map((item) => {
22
+ if (!item || typeof item !== "object")
23
+ return undefined;
24
+ const entry = item;
25
+ return entry.type === "text" && typeof entry.text === "string" ? entry.text : undefined;
26
+ })
27
+ .filter((text) => typeof text === "string" && text.trim().length > 0);
28
+ if (texts.length === 0)
29
+ return undefined;
30
+ return texts.join("\n");
31
+ }
32
+ function shouldSkipUserText(text) {
33
+ return text.trim().startsWith("/");
34
+ }
35
+ function extractUserQuery(text) {
36
+ const start = text.indexOf(USER_QUERY_OPEN);
37
+ const end = text.lastIndexOf(USER_QUERY_CLOSE);
38
+ if (start === -1 || end === -1 || end <= start)
39
+ return undefined;
40
+ const extracted = text.slice(start + USER_QUERY_OPEN.length, end).trim();
41
+ return extracted.length > 0 ? extracted : undefined;
42
+ }
43
+ export async function readSessionTranscriptDelta(sessionFile, fromLineExclusive) {
44
+ const result = {
45
+ messages: [],
46
+ lastLine: fromLineExclusive,
47
+ deltaBytes: 0,
48
+ };
49
+ let raw;
50
+ try {
51
+ raw = await readFile(sessionFile, "utf-8");
52
+ }
53
+ catch {
54
+ return result;
55
+ }
56
+ const lines = raw.split(/\r?\n/);
57
+ for (let index = 0; index < lines.length; index += 1) {
58
+ const lineNo = index + 1;
59
+ const line = lines[index] ?? "";
60
+ if (!line.trim())
61
+ continue;
62
+ if (lineNo <= fromLineExclusive) {
63
+ result.lastLine = lineNo;
64
+ continue;
65
+ }
66
+ result.deltaBytes += Buffer.byteLength(line, "utf-8") + 1;
67
+ const entry = parseJsonLine(line);
68
+ if (!entry) {
69
+ result.lastLine = lineNo;
70
+ continue;
71
+ }
72
+ if (entry.type === "session" && typeof entry.id === "string" && !result.sessionId) {
73
+ result.sessionId = entry.id;
74
+ result.lastLine = lineNo;
75
+ continue;
76
+ }
77
+ if (entry.type !== "message" || !entry.message) {
78
+ result.lastLine = lineNo;
79
+ continue;
80
+ }
81
+ const role = entry.message.role;
82
+ if (role !== "user" && role !== "assistant") {
83
+ result.lastLine = lineNo;
84
+ continue;
85
+ }
86
+ const text = extractTextContent(entry.message.content ?? "");
87
+ if (!text) {
88
+ result.lastLine = lineNo;
89
+ continue;
90
+ }
91
+ let normalized = text.trim();
92
+ if (!normalized) {
93
+ result.lastLine = lineNo;
94
+ continue;
95
+ }
96
+ if (role === "user") {
97
+ const extracted = extractUserQuery(normalized);
98
+ if (!extracted) {
99
+ result.lastLine = lineNo;
100
+ continue;
101
+ }
102
+ if (shouldSkipUserText(extracted)) {
103
+ result.lastLine = lineNo;
104
+ continue;
105
+ }
106
+ normalized = extracted;
107
+ }
108
+ result.messages.push({ role, text: normalized, line: lineNo });
109
+ result.lastLine = lineNo;
110
+ }
111
+ return result;
112
+ }
@@ -0,0 +1,31 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ const INITIAL_STATE = {
4
+ lastLine: 0,
5
+ updatedAt: new Date(0).toISOString(),
6
+ };
7
+ function isRecord(value) {
8
+ return typeof value === "object" && value !== null;
9
+ }
10
+ export async function loadSessionTranscriptState(statePath) {
11
+ try {
12
+ const raw = await readFile(statePath, "utf-8");
13
+ const parsed = JSON.parse(raw);
14
+ if (!isRecord(parsed))
15
+ return INITIAL_STATE;
16
+ const lastLineRaw = parsed.lastLine;
17
+ const updatedAtRaw = parsed.updatedAt;
18
+ const lastLine = typeof lastLineRaw === "number" && Number.isFinite(lastLineRaw) && lastLineRaw > 0
19
+ ? Math.trunc(lastLineRaw)
20
+ : 0;
21
+ const updatedAt = typeof updatedAtRaw === "string" && updatedAtRaw.trim() ? updatedAtRaw : INITIAL_STATE.updatedAt;
22
+ return { lastLine, updatedAt };
23
+ }
24
+ catch {
25
+ return INITIAL_STATE;
26
+ }
27
+ }
28
+ export async function saveSessionTranscriptState(statePath, state) {
29
+ await mkdir(dirname(statePath), { recursive: true });
30
+ await writeFile(statePath, JSON.stringify(state, null, 2), "utf-8");
31
+ }
@@ -0,0 +1,89 @@
1
+ export const FILES_TABLE = "files";
2
+ export const CHUNKS_TABLE = "chunks";
3
+ export const FTS_TABLE = "chunks_fts";
4
+ export const EMBEDDING_CACHE_TABLE = "embedding_cache";
5
+ export const VECTOR_TABLE = "chunks_vec";
6
+ export function ensureMemoryIndexSchema(params) {
7
+ params.db.exec(`
8
+ CREATE TABLE IF NOT EXISTS meta (
9
+ key TEXT PRIMARY KEY,
10
+ value TEXT NOT NULL
11
+ );
12
+ `);
13
+ params.db.exec(`
14
+ CREATE TABLE IF NOT EXISTS ${FILES_TABLE} (
15
+ path TEXT NOT NULL,
16
+ agent_id TEXT NOT NULL,
17
+ source TEXT NOT NULL DEFAULT 'memory',
18
+ hash TEXT NOT NULL,
19
+ mtime INTEGER NOT NULL,
20
+ size INTEGER NOT NULL,
21
+ PRIMARY KEY (path, agent_id)
22
+ );
23
+ `);
24
+ params.db.exec(`
25
+ CREATE TABLE IF NOT EXISTS ${CHUNKS_TABLE} (
26
+ id TEXT PRIMARY KEY,
27
+ path TEXT NOT NULL,
28
+ agent_id TEXT NOT NULL,
29
+ source TEXT NOT NULL DEFAULT 'memory',
30
+ start_line INTEGER NOT NULL,
31
+ end_line INTEGER NOT NULL,
32
+ hash TEXT NOT NULL,
33
+ model TEXT NOT NULL,
34
+ text TEXT NOT NULL,
35
+ embedding TEXT NOT NULL,
36
+ updated_at INTEGER NOT NULL
37
+ );
38
+ `);
39
+ params.db.exec(`
40
+ CREATE TABLE IF NOT EXISTS ${EMBEDDING_CACHE_TABLE} (
41
+ provider TEXT NOT NULL,
42
+ model TEXT NOT NULL,
43
+ provider_key TEXT NOT NULL,
44
+ hash TEXT NOT NULL,
45
+ embedding TEXT NOT NULL,
46
+ dims INTEGER,
47
+ updated_at INTEGER NOT NULL,
48
+ PRIMARY KEY (provider, model, provider_key, hash)
49
+ );
50
+ `);
51
+ params.db.exec(`CREATE INDEX IF NOT EXISTS idx_embedding_cache_updated_at ON ${EMBEDDING_CACHE_TABLE}(updated_at);`);
52
+ let ftsAvailable = false;
53
+ let ftsError;
54
+ if (params.ftsEnabled) {
55
+ try {
56
+ params.db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS ${FTS_TABLE} USING fts5(
57
+ text,
58
+ id UNINDEXED,
59
+ path UNINDEXED,
60
+ source UNINDEXED,
61
+ model UNINDEXED,
62
+ start_line UNINDEXED,
63
+ end_line UNINDEXED
64
+ );`);
65
+ ftsAvailable = true;
66
+ }
67
+ catch (err) {
68
+ const message = err instanceof Error ? err.message : String(err);
69
+ ftsAvailable = false;
70
+ ftsError = message;
71
+ }
72
+ }
73
+ ensureColumn(params.db, FILES_TABLE, "source", "TEXT NOT NULL DEFAULT 'memory'");
74
+ ensureColumn(params.db, FILES_TABLE, "agent_id", "TEXT NOT NULL DEFAULT ''");
75
+ ensureColumn(params.db, CHUNKS_TABLE, "source", "TEXT NOT NULL DEFAULT 'memory'");
76
+ ensureColumn(params.db, CHUNKS_TABLE, "agent_id", "TEXT NOT NULL DEFAULT ''");
77
+ params.db.exec(`CREATE INDEX IF NOT EXISTS idx_chunks_path ON ${CHUNKS_TABLE}(path);`);
78
+ params.db.exec(`CREATE INDEX IF NOT EXISTS idx_chunks_source ON ${CHUNKS_TABLE}(source);`);
79
+ params.db.exec(`CREATE INDEX IF NOT EXISTS idx_chunks_agent_id ON ${CHUNKS_TABLE}(agent_id);`);
80
+ params.db.exec(`CREATE INDEX IF NOT EXISTS idx_files_agent_id ON ${FILES_TABLE}(agent_id);`);
81
+ return { ftsAvailable, ...(ftsError ? { ftsError } : {}) };
82
+ }
83
+ function ensureColumn(db, table, column, definition) {
84
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
85
+ if (rows.some((row) => row.name === column)) {
86
+ return;
87
+ }
88
+ db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
89
+ }
@@ -0,0 +1,89 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { dirname } from "node:path";
4
+ const require = createRequire(import.meta.url);
5
+ let sqliteWarningPatched = false;
6
+ export function requireNodeSqlite() {
7
+ try {
8
+ suppressSqliteExperimentalWarning();
9
+ return require("node:sqlite");
10
+ }
11
+ catch (err) {
12
+ const message = err instanceof Error ? err.message : String(err);
13
+ throw new Error(`SQLite support is unavailable in this Node runtime (missing node:sqlite). ${message}`, { cause: err });
14
+ }
15
+ }
16
+ export function openSqliteDatabase(path, allowExtension = false) {
17
+ ensureDir(dirname(path));
18
+ const { DatabaseSync } = requireNodeSqlite();
19
+ const db = new DatabaseSync(path, { allowExtension });
20
+ configureSqliteConnection(db);
21
+ return db;
22
+ }
23
+ export async function loadSqliteVecExtension(params) {
24
+ try {
25
+ const sqliteVec = await import("sqlite-vec");
26
+ const resolvedPath = params.extensionPath?.trim() ? params.extensionPath.trim() : undefined;
27
+ const extensionPath = resolvedPath ?? sqliteVec.getLoadablePath();
28
+ params.db.enableLoadExtension(true);
29
+ if (resolvedPath) {
30
+ params.db.loadExtension(extensionPath);
31
+ }
32
+ else {
33
+ sqliteVec.load(params.db);
34
+ }
35
+ return { ok: true, extensionPath };
36
+ }
37
+ catch (err) {
38
+ const message = err instanceof Error ? err.message : String(err);
39
+ return { ok: false, error: message };
40
+ }
41
+ }
42
+ function ensureDir(path) {
43
+ try {
44
+ mkdirSync(path, { recursive: true });
45
+ }
46
+ catch {
47
+ // Ignore directory creation errors; caller handles downstream failures.
48
+ }
49
+ }
50
+ function configureSqliteConnection(db) {
51
+ try {
52
+ db.exec("PRAGMA journal_mode=WAL;");
53
+ }
54
+ catch { }
55
+ try {
56
+ db.exec("PRAGMA busy_timeout=5000;");
57
+ }
58
+ catch { }
59
+ }
60
+ function suppressSqliteExperimentalWarning() {
61
+ if (sqliteWarningPatched)
62
+ return;
63
+ sqliteWarningPatched = true;
64
+ const originalEmitWarning = process.emitWarning.bind(process);
65
+ process.emitWarning = ((warning, ...args) => {
66
+ if (isSqliteExperimentalWarning(warning, args))
67
+ return;
68
+ return originalEmitWarning(warning, ...args);
69
+ });
70
+ }
71
+ function isSqliteExperimentalWarning(warning, args) {
72
+ const message = typeof warning === "string"
73
+ ? warning
74
+ : warning instanceof Error
75
+ ? warning.message
76
+ : warning?.message;
77
+ const name = warning instanceof Error
78
+ ? warning.name
79
+ : warning?.name;
80
+ const type = typeof args[0] === "string"
81
+ ? args[0]
82
+ : args[0]?.type;
83
+ const warningType = name ?? type;
84
+ if (warningType !== "ExperimentalWarning")
85
+ return false;
86
+ if (!message)
87
+ return false;
88
+ return message.includes("SQLite is an experimental feature");
89
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ import { loadResolvedMemorySearchConfig } from "./config/loader.js";
2
+ import { getMemorySearchManager } from "./runtime/index.js";
3
+ import { memoryLog } from "./log.js";
4
+ export async function warmMemorySearch(params) {
5
+ try {
6
+ const settings = await loadResolvedMemorySearchConfig();
7
+ if (!settings.enabled || !settings.sync.onSessionStart) {
8
+ return { attempted: false, succeeded: true };
9
+ }
10
+ memoryLog.info("warm start", { agentId: params.agentId });
11
+ const manager = await getMemorySearchManager(params);
12
+ if (!manager?.sync) {
13
+ return { attempted: false, succeeded: true };
14
+ }
15
+ await manager.sync({ reason: "session-start", onWorkDetected: params.onWorkDetected });
16
+ memoryLog.info("warm complete", { agentId: params.agentId });
17
+ return { attempted: true, succeeded: true };
18
+ }
19
+ catch (err) {
20
+ const message = err instanceof Error ? err.message : String(err);
21
+ console.warn(`[memory] warm failed: ${message}`);
22
+ memoryLog.error("warm failed", { agentId: params.agentId, error: message });
23
+ return { attempted: true, succeeded: false, error: message };
24
+ }
25
+ }
@@ -0,0 +1,41 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readdir } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { basename, join, resolve } from "node:path";
5
+ export const RULES_RELATIVE_DIR = join(".pi", "rules");
6
+ /** List *.rule.txt full paths from a directory, sorted. Returns empty array if directory is missing. */
7
+ export async function listRuleFiles(dirPath) {
8
+ if (!existsSync(dirPath))
9
+ return [];
10
+ try {
11
+ const entries = await readdir(dirPath, { withFileTypes: true, encoding: "utf8" });
12
+ return entries
13
+ .filter((e) => e.isFile() && e.name.endsWith(".rule.txt"))
14
+ .map((e) => resolve(dirPath, e.name))
15
+ .sort((a, b) => a.localeCompare(b));
16
+ }
17
+ catch {
18
+ return [];
19
+ }
20
+ }
21
+ /**
22
+ * Discover rule files from project (.pi/rules/) and user (~/.pi/rules/) directories.
23
+ * Project rules take priority: a user rule with the same basename is excluded.
24
+ */
25
+ export async function discoverRules(cwd) {
26
+ const projectDir = resolve(cwd, RULES_RELATIVE_DIR);
27
+ const userDir = resolve(homedir(), RULES_RELATIVE_DIR);
28
+ const [projectFiles, userFiles] = await Promise.all([listRuleFiles(projectDir), listRuleFiles(userDir)]);
29
+ // Project rules are processed first so they claim seenNames → user duplicates are dropped
30
+ const seenNames = new Set();
31
+ const dedupeByName = (files) => files.filter((filePath) => {
32
+ const name = basename(filePath, ".rule.txt");
33
+ if (seenNames.has(name))
34
+ return false;
35
+ seenNames.add(name);
36
+ return true;
37
+ });
38
+ const projectRules = dedupeByName(projectFiles);
39
+ const userRules = dedupeByName(userFiles);
40
+ return { userRules, projectRules };
41
+ }
@@ -21,22 +21,35 @@ Tool descriptions are now loaded by the tool implementation and exposed via the
21
21
  - Find paths by glob pattern.
22
22
  - `rg.ts` (`rg`)
23
23
  - Search file content with ripgrep.
24
+ - `ast-grep.ts` (`AstGrep`)
25
+ - Search code by AST structure using ast-grep (`run` and `scan`, read-only).
24
26
  - `read-file.ts` (`ReadFile`)
25
27
  - Read file content with truncation and pagination behavior.
26
28
  - `delete.ts` (`Delete`)
27
29
  - Delete files or directories.
28
30
  - `semantic-search.ts` / `semantic-search-description.md` (`SemanticSearch`)
29
31
  - Search code semantically by intent and meaning.
32
+ - `memory-search.ts` (`memory_search`)
33
+ - Search indexed memory files for relevant snippets.
34
+ - `memory-get.ts` (`memory_get`)
35
+ - Read a snippet from a memory file.
30
36
  - `web-search.ts` (`WebSearch`)
31
37
  - Search the web for current information with result snippets.
32
38
  - `web-fetch.ts` (`WebFetch`)
33
39
  - Fetch web page content and return readable markdown.
40
+ - Detects common anti-bot/captcha interstitial pages (for example WeChat risk-control pages) and returns an explicit block reason instead of noisy converted HTML.
41
+ - `generate-image.ts` (`GenerateImage`)
42
+ - Generate images with Gemini API or OpenRouter.
34
43
  - `ask-question.ts` (`AskQuestion`)
35
44
  - Collect structured multiple-choice answers from the user.
36
45
  - `subagent.ts` / `subagent-description.md` (`Subagent`)
37
46
  - Launch delegated subagent subprocesses with foreground/background and parallel orchestration.
47
+ - `bus-send.ts` (`BusSend`)
48
+ - Send messages to a displayName/agentId or broadcast to a channel over the cluster bus.
49
+ - `mailbox.ts` (`MailBox`)
50
+ - Read queued bus messages that are not injected into the conversation.
38
51
  - `list-mcp-resources.ts` (`ListMcpResources`)
39
- - Read `.pi/mcp.json` config and list MCP resources with server metadata.
52
+ - Read `.pi/mcp.json` (project) and `~/.pi/mcp.json` (user) configs to list MCP resources with server metadata.
40
53
  - `fetch-mcp-resource.ts` (`FetchMcpResource`)
41
54
  - Fetch a specific MCP resource by server + URI, optionally writing to workspace.
42
55
  - `list-mcp-tools.ts` (`ListMcpTools`)
@@ -45,6 +58,19 @@ Tool descriptions are now loaded by the tool implementation and exposed via the
45
58
  - `call-mcp-tool.ts` (`CallMcpTool`)
46
59
  - Call a remote MCP tool by server + tool name + JSON arguments.
47
60
  - `switch-mode.ts` (`SwitchMode`)
48
- - Switch runtime interaction mode (tool supports `plan`; `/plan` toggles Plan/Agent).
61
+ - Switch runtime interaction mode (tool supports `plan`; optional `plan_file` binds a plan document path used by Plan-mode reminders).
62
+ - `exit-plan-mode.ts` / `exit-plan-mode-description.md` (`ExitPlanMode`)
63
+ - Exit Plan mode and switch runtime mode back to Agent.
49
64
  - `apply-patch.ts` / `apply-patch-description.md` (`ApplyPatch`)
50
- - Apply single-file patches in `*** Begin Patch` format.
65
+ - Apply single-file patches in `*** Begin Patch` format (SFTP auto-upload is attached by the SFTP extension hook).
66
+ - `codex-apply-patch.ts` / `codex-apply-patch-description.md` (`CodexApplyPatch`)
67
+ - Apply codex-style multi-file patches (`Add/Delete/Update/Move`) with relative paths.
68
+
69
+ ### Brief tools
70
+
71
+ - `brief-write.ts` (`brief_write`)
72
+ - Write content to a brief file with frontmatter preservation and line-limit validation.
73
+
74
+ Brief files are stored in `~/.mono-pilot/agents/<agent-id>/brief/`.
75
+ Core briefs are auto-injected into the system prompt via `src/brief/blocks.ts`.
76
+ Reflection reminders are injected every 25 turns via `src/brief/reflection.ts`.
@@ -1,4 +1,4 @@
1
- Use this tool to edit files.
1
+ Use this tool to edit files. Patch path must be absolute.
2
2
  Your patch language is a stripped-down, file-oriented diff format designed to be easy to parse and safe to apply. You can think of it as a high-level envelope:
3
3
 
4
4
  *** Begin Patch
@@ -33,6 +33,12 @@ For instructions on [context_before] and [context_after]:
33
33
  + [new_code]
34
34
  [3 lines of post-context]
35
35
 
36
+ You can optionally anchor a hunk to a line number (1-based) to reduce ambiguity. Supported forms:
37
+ @@ -42,5 +42,6 @@
38
+ @@ line 42
39
+ @@ line: 42-60
40
+ The line hint is a search hint; context headers still apply if provided.
41
+
36
42
  The full grammar definition is below:
37
43
  Patch := Begin { FileOp } End
38
44
  Begin := "*** Begin Patch" NEWLINE
@@ -89,4 +95,4 @@ eof_line: "*** End of File" LF
89
95
 
90
96
  %import common.LF
91
97
  . You must reason carefully about the input and make sure it obeys the grammar.
92
- IMPORTANT: Do NOT call this tool in parallel with other tools.
98
+ IMPORTANT: Do NOT call this tool in parallel with other tools.