mono-pilot 0.2.10 → 0.2.13

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 (155) hide show
  1. package/README.md +260 -2
  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 +1 -2
  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 +70 -1
  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/memory/build-memory.js +103 -0
  77. package/dist/src/memory/config/defaults.js +55 -0
  78. package/dist/src/memory/config/loader.js +29 -0
  79. package/dist/src/memory/config/paths.js +9 -0
  80. package/dist/src/memory/config/resolve.js +90 -0
  81. package/dist/src/memory/config/types.js +1 -0
  82. package/dist/src/memory/embeddings/batch-runner.js +39 -0
  83. package/dist/src/memory/embeddings/cache.js +47 -0
  84. package/dist/src/memory/embeddings/chunk-limits.js +26 -0
  85. package/dist/src/memory/embeddings/input-limits.js +48 -0
  86. package/dist/src/memory/embeddings/local.js +108 -0
  87. package/dist/src/memory/embeddings/types.js +1 -0
  88. package/dist/src/memory/index-manager.js +552 -0
  89. package/dist/src/memory/indexing/embeddings.js +67 -0
  90. package/dist/src/memory/indexing/files.js +180 -0
  91. package/dist/src/memory/indexing/index-file.js +105 -0
  92. package/dist/src/memory/log.js +38 -0
  93. package/dist/src/memory/paths.js +15 -0
  94. package/dist/src/memory/runtime/index.js +299 -0
  95. package/dist/src/memory/runtime/thread.js +116 -0
  96. package/dist/src/memory/search/fts.js +57 -0
  97. package/dist/src/memory/search/hybrid.js +50 -0
  98. package/dist/src/memory/search/text.js +30 -0
  99. package/dist/src/memory/search/vector.js +43 -0
  100. package/dist/src/memory/session/content-hash.js +7 -0
  101. package/dist/src/memory/session/entry.js +33 -0
  102. package/dist/src/memory/session/flush-policy.js +34 -0
  103. package/dist/src/memory/session/hook.js +191 -0
  104. package/dist/src/memory/session/paths.js +15 -0
  105. package/dist/src/memory/session/session-reader.js +88 -0
  106. package/dist/src/memory/session/transcript/content-hash.js +7 -0
  107. package/dist/src/memory/session/transcript/entry.js +28 -0
  108. package/dist/src/memory/session/transcript/flush.js +56 -0
  109. package/dist/src/memory/session/transcript/paths.js +28 -0
  110. package/dist/src/memory/session/transcript/reader.js +112 -0
  111. package/dist/src/memory/session/transcript/state.js +31 -0
  112. package/dist/src/memory/store/schema.js +89 -0
  113. package/dist/src/memory/store/sqlite.js +89 -0
  114. package/dist/src/memory/types.js +1 -0
  115. package/dist/src/memory/warm.js +25 -0
  116. package/dist/{tools → src/tools}/README.md +28 -2
  117. package/dist/{tools → src/tools}/apply-patch-description.md +8 -2
  118. package/dist/{tools → src/tools}/apply-patch.js +174 -104
  119. package/dist/{tools → src/tools}/apply-patch.test.js +52 -1
  120. package/dist/{tools/ask-question.js → src/tools/ask-user-question.js} +3 -3
  121. package/dist/src/tools/ast-grep.js +357 -0
  122. package/dist/src/tools/brief-write.js +122 -0
  123. package/dist/src/tools/bus-send.js +100 -0
  124. package/dist/{tools → src/tools}/call-mcp-tool.js +20 -24
  125. package/dist/src/tools/codex-apply-patch-description.md +52 -0
  126. package/dist/src/tools/codex-apply-patch.js +540 -0
  127. package/dist/{tools → src/tools}/delete.js +24 -0
  128. package/dist/src/tools/exit-plan-mode.js +83 -0
  129. package/dist/{tools → src/tools}/fetch-mcp-resource.js +31 -3
  130. package/dist/src/tools/generate-image.js +567 -0
  131. package/dist/{tools → src/tools}/glob.js +55 -1
  132. package/dist/{tools → src/tools}/list-mcp-resources.js +32 -3
  133. package/dist/{tools → src/tools}/list-mcp-tools.js +38 -3
  134. package/dist/src/tools/ls.js +48 -0
  135. package/dist/src/tools/lsp-diagnostics.js +67 -0
  136. package/dist/src/tools/lsp-symbols.js +54 -0
  137. package/dist/src/tools/mailbox.js +85 -0
  138. package/dist/src/tools/memory-get.js +90 -0
  139. package/dist/src/tools/memory-search.js +180 -0
  140. package/dist/{tools → src/tools}/plan-mode-reminder.md +3 -4
  141. package/dist/{tools → src/tools}/read-file.js +8 -19
  142. package/dist/{tools → src/tools}/rg.js +10 -20
  143. package/dist/{tools → src/tools}/shell.js +19 -42
  144. package/dist/{tools → src/tools}/subagent.js +255 -6
  145. package/dist/{tools → src/tools}/switch-mode.js +37 -6
  146. package/dist/{tools → src/tools}/web-fetch.js +105 -7
  147. package/dist/{tools → src/tools}/web-search.js +29 -1
  148. package/package.json +21 -9
  149. package/dist/src/utils/mcp-client.js +0 -282
  150. /package/dist/{tools → src/tools}/ask-mode-reminder.md +0 -0
  151. /package/dist/{tools → src/tools}/rg.test.js +0 -0
  152. /package/dist/{tools → src/tools}/semantic-search-description.md +0 -0
  153. /package/dist/{tools → src/tools}/semantic-search.js +0 -0
  154. /package/dist/{tools → src/tools}/shell-description.md +0 -0
  155. /package/dist/{tools → src/tools}/subagent-description.md +0 -0
@@ -0,0 +1,214 @@
1
+ import { DynamicBorder } from "@mariozechner/pi-coding-agent";
2
+ import { Container, SelectList, Text } from "@mariozechner/pi-tui";
3
+ import { loadMonoPilotConfigObject, saveMonoPilotConfigObject } from "../../config/mono-pilot.js";
4
+ import { applyImageGenSelection, extractImageGenConfig } from "../../config/image-gen.js";
5
+ const USAGE = [
6
+ "Usage:",
7
+ " /image-model (open interactive picker)",
8
+ " /image-model list",
9
+ " /image-model use <provider> [modelId]",
10
+ " /image-model <provider> [modelId]",
11
+ ].join("\n");
12
+ export function registerImageModelCommands(pi) {
13
+ pi.registerCommand("image-model", {
14
+ description: "Switch image generation model/provider from config.json",
15
+ handler: async (args, ctx) => {
16
+ let config;
17
+ try {
18
+ config = await loadMonoPilotConfigObject();
19
+ }
20
+ catch (error) {
21
+ notify(ctx, error.message, "error");
22
+ return;
23
+ }
24
+ const { providers, selection } = extractImageGenConfig(config);
25
+ const providerNames = Object.keys(providers);
26
+ if (providerNames.length === 0) {
27
+ notify(ctx, "No imageGenProviders configured in ~/.mono-pilot/config.json.", "warning");
28
+ return;
29
+ }
30
+ const trimmedArgs = args.trim();
31
+ if (!trimmedArgs) {
32
+ if (!ctx.hasUI) {
33
+ notify(ctx, `Interactive picker unavailable.\n${USAGE}`, "warning");
34
+ return;
35
+ }
36
+ const entries = buildEntries(providers);
37
+ if (entries.length === 0) {
38
+ notify(ctx, "No image models configured.", "warning");
39
+ return;
40
+ }
41
+ const selected = await pickImageModel(ctx, entries, selection.provider, selection.model);
42
+ if (!selected) {
43
+ notify(ctx, "Image model selection cancelled.", "info");
44
+ return;
45
+ }
46
+ applyImageGenSelection(config, {
47
+ provider: selected.provider,
48
+ model: selected.model,
49
+ });
50
+ try {
51
+ await saveMonoPilotConfigObject(config);
52
+ }
53
+ catch (error) {
54
+ notify(ctx, error.message, "error");
55
+ return;
56
+ }
57
+ const modelSuffix = selected.model ? ` (${selected.model})` : "";
58
+ notify(ctx, `Image model set to ${selected.provider}${modelSuffix}.`, "info");
59
+ return;
60
+ }
61
+ const command = parseCommand(args);
62
+ switch (command.cmd) {
63
+ case "list": {
64
+ notify(ctx, formatProviders(providers, selection.provider, selection.model), "info");
65
+ return;
66
+ }
67
+ case "use": {
68
+ if (!command.provider) {
69
+ notify(ctx, `Missing provider.\n${USAGE}`, "warning");
70
+ return;
71
+ }
72
+ const providerConfig = providers[command.provider];
73
+ if (!providerConfig) {
74
+ notify(ctx, `Unknown provider: ${command.provider}.\n${formatProviders(providers)}`, "warning");
75
+ return;
76
+ }
77
+ const nextModel = resolveModelSelection(providerConfig, command.model, command.provider === selection.provider ? selection.model : undefined);
78
+ if (command.model && providerConfig.models?.length && nextModel !== command.model) {
79
+ notify(ctx, `Unknown model for ${command.provider}: ${command.model}.\n${formatModels(providerConfig)}`, "warning");
80
+ return;
81
+ }
82
+ applyImageGenSelection(config, {
83
+ provider: command.provider,
84
+ model: nextModel,
85
+ });
86
+ try {
87
+ await saveMonoPilotConfigObject(config);
88
+ }
89
+ catch (error) {
90
+ notify(ctx, error.message, "error");
91
+ return;
92
+ }
93
+ const modelSuffix = nextModel ? ` (${nextModel})` : "";
94
+ notify(ctx, `Image model set to ${command.provider}${modelSuffix}.`, "info");
95
+ return;
96
+ }
97
+ case "current":
98
+ default: {
99
+ notify(ctx, formatProviders(providers, selection.provider, selection.model, true), "info");
100
+ return;
101
+ }
102
+ }
103
+ },
104
+ });
105
+ }
106
+ function parseCommand(raw) {
107
+ const trimmed = raw.trim();
108
+ if (!trimmed)
109
+ return { cmd: "current" };
110
+ const parts = trimmed.split(/\s+/);
111
+ const [first, second, third] = parts;
112
+ if (first === "list") {
113
+ return { cmd: "list" };
114
+ }
115
+ if (first === "use") {
116
+ return { cmd: "use", provider: second, model: third };
117
+ }
118
+ return { cmd: "use", provider: first, model: second };
119
+ }
120
+ function resolveModelSelection(provider, modelOverride, currentModel) {
121
+ if (modelOverride)
122
+ return modelOverride;
123
+ if (currentModel)
124
+ return currentModel;
125
+ return provider.models?.[0]?.id;
126
+ }
127
+ function buildEntries(providers) {
128
+ const entries = [];
129
+ for (const [provider, config] of Object.entries(providers)) {
130
+ const models = config.models ?? [];
131
+ if (models.length === 0) {
132
+ entries.push({ provider, label: provider });
133
+ continue;
134
+ }
135
+ for (const model of models) {
136
+ const label = model.name ? `${provider} · ${model.name}` : `${provider} · ${model.id}`;
137
+ entries.push({ provider, model: model.id, label });
138
+ }
139
+ }
140
+ return entries;
141
+ }
142
+ async function pickImageModel(ctx, entries, currentProvider, currentModel) {
143
+ const items = entries.map((entry, index) => {
144
+ const isCurrent = entry.provider === currentProvider && entry.model === currentModel;
145
+ return {
146
+ value: String(index),
147
+ label: isCurrent ? `${entry.label} (current)` : entry.label,
148
+ description: entry.model ? entry.model : "(provider default)",
149
+ };
150
+ });
151
+ const currentIndex = entries.findIndex((entry) => entry.provider === currentProvider && entry.model === currentModel);
152
+ const result = await ctx.ui.custom((tui, theme, _kb, done) => {
153
+ const container = new Container();
154
+ container.addChild(new DynamicBorder((s) => theme.fg("accent", s)));
155
+ container.addChild(new Text(theme.fg("accent", theme.bold("Select Image Model")), 1, 0));
156
+ const selectList = new SelectList(items, Math.min(items.length, 10), {
157
+ selectedPrefix: (text) => theme.fg("accent", text),
158
+ selectedText: (text) => theme.fg("accent", text),
159
+ description: (text) => theme.fg("muted", text),
160
+ scrollInfo: (text) => theme.fg("dim", text),
161
+ noMatch: (text) => theme.fg("warning", text),
162
+ });
163
+ if (currentIndex >= 0) {
164
+ selectList.setSelectedIndex(currentIndex);
165
+ }
166
+ selectList.onSelect = (item) => done(item.value);
167
+ selectList.onCancel = () => done(null);
168
+ container.addChild(selectList);
169
+ container.addChild(new Text(theme.fg("dim", "↑↓ navigate • enter select • esc cancel"), 1, 0));
170
+ container.addChild(new DynamicBorder((s) => theme.fg("accent", s)));
171
+ return {
172
+ render: (width) => container.render(width),
173
+ invalidate: () => container.invalidate(),
174
+ handleInput: (data) => {
175
+ selectList.handleInput(data);
176
+ tui.requestRender();
177
+ },
178
+ };
179
+ });
180
+ if (!result)
181
+ return undefined;
182
+ const index = Number(result);
183
+ return Number.isFinite(index) ? entries[index] : undefined;
184
+ }
185
+ function formatModels(provider) {
186
+ const models = provider.models ?? [];
187
+ if (models.length === 0) {
188
+ return "No models configured for this provider.";
189
+ }
190
+ const lines = models.map((model) => {
191
+ const label = model.name ? `${model.id} (${model.name})` : model.id;
192
+ return ` - ${label}`;
193
+ });
194
+ return `Available models:\n${lines.join("\n")}`;
195
+ }
196
+ function formatProviders(providers, currentProvider, currentModel, includeUsage = false) {
197
+ const lines = Object.entries(providers).map(([name, provider]) => {
198
+ const label = currentProvider === name ? "*" : "-";
199
+ const modelLabel = currentProvider === name && currentModel ? ` (${currentModel})` : "";
200
+ const models = provider.models?.map((model) => model.id).join(", ") ?? "(no models)";
201
+ return `${label} ${name}${modelLabel}: ${models}`;
202
+ });
203
+ const header = includeUsage ? `${USAGE}\n\nConfigured providers:` : "Configured providers:";
204
+ return `${header}\n${lines.join("\n")}`;
205
+ }
206
+ function notify(ctx, message, level) {
207
+ if (ctx.hasUI && ctx.ui?.notify) {
208
+ ctx.ui.notify(message, level);
209
+ }
210
+ else {
211
+ const prefix = level === "error" ? "[error]" : level === "warning" ? "[warn]" : "[info]";
212
+ console.log(`${prefix} ${message}`);
213
+ }
214
+ }
@@ -0,0 +1,47 @@
1
+ export function createGameBusMessageInjector(pi, options) {
2
+ let pending = [];
3
+ let timer = null;
4
+ const seenSeq = new Set();
5
+ const seenQueue = [];
6
+ function markSeen(seq) {
7
+ if (seenSeq.has(seq))
8
+ return false;
9
+ seenSeq.add(seq);
10
+ seenQueue.push(seq);
11
+ if (seenQueue.length > 200) {
12
+ const dropped = seenQueue.splice(0, seenQueue.length - 200);
13
+ for (const id of dropped)
14
+ seenSeq.delete(id);
15
+ }
16
+ return true;
17
+ }
18
+ function flush() {
19
+ if (pending.length === 0)
20
+ return;
21
+ const msgs = pending;
22
+ pending = [];
23
+ timer = null;
24
+ const lines = msgs.map((m) => {
25
+ const text = typeof m.payload === "object" && m.payload !== null && "text" in m.payload
26
+ ? m.payload.text
27
+ : JSON.stringify(m.payload);
28
+ const sender = m.fromName?.trim() ? m.fromName.trim() : "GM";
29
+ const ch = m.channel && m.channel !== "public" ? ` [${m.channel}]` : "";
30
+ return `[from ${sender}${ch}] ${text}`;
31
+ });
32
+ const envelope = "<bus_messages>\n" + lines.join("\n") + "\n</bus_messages>\n\n" +
33
+ "You received the above in-world messages from GM. " +
34
+ "Stay in character and reply using BusSend when needed.";
35
+ pi.sendUserMessage(envelope, { deliverAs: "followUp" });
36
+ }
37
+ return (msg) => {
38
+ if (msg.channel && msg.channel !== options.gmChannel)
39
+ return;
40
+ if (!markSeen(msg.seq))
41
+ return;
42
+ pending.push(msg);
43
+ if (timer)
44
+ clearTimeout(timer);
45
+ timer = setTimeout(flush, 300);
46
+ };
47
+ }
@@ -0,0 +1,83 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { basename, resolve } from "node:path";
4
+ const PROFILE_FILE = "profile.json";
5
+ const IDENTITY_FILE = "identity.md";
6
+ export function getGameChannel(workspaceCwd, channelOverride) {
7
+ const override = channelOverride?.trim();
8
+ if (override)
9
+ return override;
10
+ const hash = createHash("sha1").update(workspaceCwd).digest("hex").slice(0, 10);
11
+ return `game:${hash}`;
12
+ }
13
+ export function getGameGmChannel(gameChannel) {
14
+ return `${gameChannel}:gm`;
15
+ }
16
+ function parseProfile(path) {
17
+ if (!existsSync(path))
18
+ return {};
19
+ try {
20
+ const raw = readFileSync(path, "utf-8");
21
+ const parsed = JSON.parse(raw);
22
+ return parsed && typeof parsed === "object" ? parsed : {};
23
+ }
24
+ catch {
25
+ return {};
26
+ }
27
+ }
28
+ function readIdentityPrompt(path) {
29
+ if (!existsSync(path))
30
+ return undefined;
31
+ try {
32
+ const raw = readFileSync(path, "utf-8").trim();
33
+ return raw.length > 0 ? raw : undefined;
34
+ }
35
+ catch {
36
+ return undefined;
37
+ }
38
+ }
39
+ function extractDisplayNameFromIdentity(identityPrompt) {
40
+ if (!identityPrompt)
41
+ return undefined;
42
+ for (const line of identityPrompt.split(/\r?\n/)) {
43
+ const trimmed = line.trim();
44
+ if (trimmed.length === 0)
45
+ continue;
46
+ const match = trimmed.match(/^角色\s*[::]\s*(.+)$/);
47
+ if (match?.[1])
48
+ return match[1].trim();
49
+ }
50
+ return undefined;
51
+ }
52
+ export function loadGameIdentity(workspaceCwd) {
53
+ const profilePath = resolve(workspaceCwd, PROFILE_FILE);
54
+ const identityPath = resolve(workspaceCwd, IDENTITY_FILE);
55
+ const profile = parseProfile(profilePath);
56
+ const identityPrompt = readIdentityPrompt(identityPath);
57
+ const fromProfile = typeof profile.displayName === "string" ? profile.displayName.trim() : "";
58
+ const fromIdentity = extractDisplayNameFromIdentity(identityPrompt);
59
+ const fallback = basename(workspaceCwd);
60
+ const tools = resolveToolList(profile);
61
+ const displayName = fromProfile || fromIdentity || fallback;
62
+ return {
63
+ displayName,
64
+ identityPrompt,
65
+ profilePath,
66
+ identityPath,
67
+ tools,
68
+ };
69
+ }
70
+ function resolveToolList(profile) {
71
+ const hasTools = typeof profile === "object" && profile !== null && "tools" in profile;
72
+ if (!hasTools)
73
+ return undefined;
74
+ const raw = profile.tools;
75
+ if (!Array.isArray(raw)) {
76
+ console.warn("[mono-game] profile.tools must be an array of strings");
77
+ return [];
78
+ }
79
+ return raw
80
+ .filter((value) => typeof value === "string")
81
+ .map((value) => value.trim())
82
+ .filter(Boolean);
83
+ }
@@ -0,0 +1,61 @@
1
+ let activeBus = null;
2
+ let gmChannel;
3
+ let listenerInstalled = false;
4
+ const mailbox = [];
5
+ const seenSeq = new Set();
6
+ let roundGateSeq = null;
7
+ export function setMailBoxHandle(bus, options) {
8
+ activeBus = bus;
9
+ gmChannel = options?.gmChannel;
10
+ if (!bus) {
11
+ listenerInstalled = false;
12
+ mailbox.length = 0;
13
+ roundGateSeq = null;
14
+ return;
15
+ }
16
+ if (listenerInstalled)
17
+ return;
18
+ listenerInstalled = true;
19
+ bus.onMessage((msg) => {
20
+ if (gmChannel && msg.channel === gmChannel) {
21
+ roundGateSeq = msg.seq;
22
+ return;
23
+ }
24
+ if (seenSeq.has(msg.seq))
25
+ return;
26
+ seenSeq.add(msg.seq);
27
+ mailbox.push(msg);
28
+ });
29
+ }
30
+ export function isMailBoxConnected() {
31
+ return Boolean(activeBus);
32
+ }
33
+ export function readMailBox(options) {
34
+ const limit = options?.limit ?? mailbox.length;
35
+ const clear = options?.clear ?? true;
36
+ const gateSeq = roundGateSeq;
37
+ const items = [];
38
+ const indices = [];
39
+ for (let i = 0; i < mailbox.length && items.length < limit; i++) {
40
+ const msg = mailbox[i];
41
+ if (gateSeq !== null && msg.seq > gateSeq)
42
+ continue;
43
+ items.push(msg);
44
+ indices.push(i);
45
+ }
46
+ if (clear && indices.length > 0) {
47
+ for (let i = indices.length - 1; i >= 0; i--) {
48
+ const idx = indices[i];
49
+ const [removed] = mailbox.splice(idx, 1);
50
+ if (removed)
51
+ seenSeq.delete(removed.seq);
52
+ }
53
+ }
54
+ return items;
55
+ }
56
+ export function getMailBoxCount() {
57
+ const gateSeq = roundGateSeq;
58
+ if (gateSeq === null)
59
+ return mailbox.length;
60
+ return mailbox.reduce((count, msg) => (msg.seq <= gateSeq ? count + 1 : count), 0);
61
+ }
@@ -0,0 +1,134 @@
1
+ import { getGameChannel, getGameGmChannel, loadGameIdentity } from "./identity.js";
2
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { dirname, resolve } from "node:path";
4
+ const NO_DESCRIPTION_PLACEHOLDER = "No short description provided.";
5
+ const AVAILABLE_TOOLS_TOKEN = "{{AVAILABLE_TOOLS_BULLETS}}";
6
+ const DISPLAY_NAME_TOKEN = "{{DISPLAY_NAME}}";
7
+ const IDENTITY_PROMPT_TOKEN = "{{IDENTITY_PROMPT}}";
8
+ const GAME_CHANNEL_TOKEN = "{{GAME_CHANNEL}}";
9
+ const GM_CHANNEL_TOKEN = "{{GM_CHANNEL}}";
10
+ const GAME_RULES_TOKEN = "{{GAME_RULES}}";
11
+ const ROLE_MANUAL_TOKEN = "{{ROLE_MANUAL}}";
12
+ const GAME_RULES_FILENAME = "游戏说明.md";
13
+ const ROLE_MANUAL_FILENAME = "角色手册.md";
14
+ const GAME_SYSTEM_PROMPT_TEMPLATE = `You are a role-play agent in a multi-agent murder mystery simulation.
15
+
16
+ Agent identity:
17
+ - Display name: ${DISPLAY_NAME_TOKEN}
18
+
19
+ Core goal:
20
+ - Stay in character and advance the story coherently.
21
+ - Keep private knowledge private unless role rules allow disclosure.
22
+ - Use communication tools to coordinate with other agents in-world.
23
+
24
+ Behavior rules:
25
+ - Default to in-character responses.
26
+ - If the user gives an explicit out-of-character instruction, follow it directly.
27
+ - Prefer concise responses unless the user asks for long-form narration.
28
+ - Treat <bus_messages> as incoming in-world messages from other agents.
29
+ - Use BusSend to reply to other agents when needed.
30
+ - Check MailBox once for unread messages before taking action; avoid repeated checks.
31
+ - Strictly follow guidance received from the GM channel.
32
+ - The GM channel is receive-only; never send messages to it.
33
+
34
+ Channel subscriptions (explicit):
35
+ - \`${GAME_CHANNEL_TOKEN}\`: public message board (check MailBox).
36
+ - \`${GM_CHANNEL_TOKEN}\`: GM instructions (auto-injected into user message, receive-only).
37
+
38
+ Game rules:
39
+ ${GAME_RULES_TOKEN}
40
+
41
+ Role manual:
42
+ ${ROLE_MANUAL_TOKEN}
43
+
44
+ Character sheet:
45
+ ${IDENTITY_PROMPT_TOKEN}
46
+
47
+ Available tools:
48
+ ${AVAILABLE_TOOLS_TOKEN}`;
49
+ function getFirstDescriptionLine(description) {
50
+ if (!description)
51
+ return NO_DESCRIPTION_PLACEHOLDER;
52
+ for (const line of description.split(/\r?\n/)) {
53
+ const trimmed = line.trim();
54
+ if (trimmed.length > 0)
55
+ return trimmed;
56
+ }
57
+ return NO_DESCRIPTION_PLACEHOLDER;
58
+ }
59
+ function buildActiveToolBullets(activeToolNames, allTools) {
60
+ const activeSet = new Set(activeToolNames);
61
+ const lines = allTools
62
+ .filter((tool) => activeSet.has(tool.name))
63
+ .map((tool) => `- ${tool.name}: ${getFirstDescriptionLine(tool.description)}`);
64
+ return lines.length > 0 ? lines.join("\n") : "- (none)";
65
+ }
66
+ function loadGameDocs(workspaceCwd, displayName) {
67
+ const gameRoot = findGameRoot(workspaceCwd);
68
+ if (!gameRoot)
69
+ return {};
70
+ return {
71
+ gameRules: readText(resolve(gameRoot, GAME_RULES_FILENAME)),
72
+ roleManual: readRoleManual(workspaceCwd),
73
+ };
74
+ }
75
+ function findGameRoot(startDir) {
76
+ let current = resolve(startDir);
77
+ for (;;) {
78
+ if (existsSync(resolve(current, GAME_RULES_FILENAME)))
79
+ return current;
80
+ const parent = dirname(current);
81
+ if (parent === current)
82
+ return undefined;
83
+ current = parent;
84
+ }
85
+ }
86
+ function readRoleManual(roleDir) {
87
+ return readText(resolve(roleDir, ROLE_MANUAL_FILENAME));
88
+ }
89
+ function readText(path) {
90
+ if (!existsSync(path))
91
+ return undefined;
92
+ try {
93
+ const raw = readFileSync(path, "utf-8").trim();
94
+ return raw.length > 0 ? raw : undefined;
95
+ }
96
+ catch {
97
+ return undefined;
98
+ }
99
+ }
100
+ export default function gameSystemPromptExtension(pi) {
101
+ pi.on("before_agent_start", (_event, ctx) => {
102
+ const tools = buildActiveToolBullets(pi.getActiveTools(), pi.getAllTools());
103
+ const identity = loadGameIdentity(ctx.cwd);
104
+ const identityPrompt = identity.identityPrompt ?? "(missing .mono-game/identity.md)";
105
+ const channelOverride = pi.getFlag("game-channel");
106
+ const gameChannel = getGameChannel(ctx.cwd, typeof channelOverride === "string" ? channelOverride : undefined);
107
+ const gmChannel = getGameGmChannel(gameChannel);
108
+ const docs = loadGameDocs(ctx.cwd, identity.displayName);
109
+ const gameRules = docs.gameRules ?? "(missing 游戏说明.md)";
110
+ const roleManual = docs.roleManual ?? "(missing 角色手册)";
111
+ const gamePrompt = GAME_SYSTEM_PROMPT_TEMPLATE
112
+ .split(AVAILABLE_TOOLS_TOKEN)
113
+ .join(tools)
114
+ .split(DISPLAY_NAME_TOKEN)
115
+ .join(identity.displayName)
116
+ .split(IDENTITY_PROMPT_TOKEN)
117
+ .join(identityPrompt)
118
+ .split(GAME_CHANNEL_TOKEN)
119
+ .join(gameChannel)
120
+ .split(GM_CHANNEL_TOKEN)
121
+ .join(gmChannel)
122
+ .split(GAME_RULES_TOKEN)
123
+ .join(gameRules)
124
+ .split(ROLE_MANUAL_TOKEN)
125
+ .join(roleManual);
126
+ try {
127
+ writeFileSync(".mono-game/system-prompt.txt", gamePrompt, "utf-8");
128
+ }
129
+ catch (err) {
130
+ console.warn("[mono-game] failed to write system prompt", String(err));
131
+ }
132
+ return { systemPrompt: gamePrompt };
133
+ });
134
+ }
@@ -0,0 +1,28 @@
1
+ import busSendExtension from "../../tools/bus-send.js";
2
+ import mailboxExtension from "../../tools/mailbox.js";
3
+ import readFileExtension from "../../tools/read-file.js";
4
+ const TOOL_REGISTRY = [
5
+ { key: "bussend", name: "BusSend", extension: busSendExtension },
6
+ { key: "mailbox", name: "MailBox", extension: mailboxExtension },
7
+ { key: "readfile", name: "ReadFile", extension: readFileExtension },
8
+ ];
9
+ export function resolveGameToolExtensions(allowlist) {
10
+ if (!allowlist) {
11
+ return TOOL_REGISTRY.map((tool) => tool.extension);
12
+ }
13
+ const normalized = allowlist.map((value) => normalizeToolName(value));
14
+ const allowed = new Set(normalized.filter(Boolean));
15
+ const selected = TOOL_REGISTRY.filter((tool) => allowed.has(tool.key) || allowed.has(tool.name.toLowerCase()));
16
+ const known = new Set([
17
+ ...TOOL_REGISTRY.map((tool) => tool.key),
18
+ ...TOOL_REGISTRY.map((tool) => tool.name.toLowerCase()),
19
+ ]);
20
+ const unknown = normalized.filter((value) => value && !known.has(value));
21
+ if (unknown.length > 0) {
22
+ console.warn(`[mono-game] unknown tools in profile: ${unknown.join(", ")}`);
23
+ }
24
+ return selected.map((tool) => tool.extension);
25
+ }
26
+ function normalizeToolName(value) {
27
+ return value.trim().toLowerCase();
28
+ }