@geminixiang/mama 0.2.0-beta.1 → 0.2.0-beta.11

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 (271) hide show
  1. package/README.md +168 -371
  2. package/dist/adapter.d.ts +36 -12
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/adapter.js.map +1 -1
  5. package/dist/adapters/discord/bot.d.ts +12 -7
  6. package/dist/adapters/discord/bot.d.ts.map +1 -1
  7. package/dist/adapters/discord/bot.js +358 -135
  8. package/dist/adapters/discord/bot.js.map +1 -1
  9. package/dist/adapters/discord/context.d.ts +1 -1
  10. package/dist/adapters/discord/context.d.ts.map +1 -1
  11. package/dist/adapters/discord/context.js +100 -36
  12. package/dist/adapters/discord/context.js.map +1 -1
  13. package/dist/adapters/shared.d.ts +71 -0
  14. package/dist/adapters/shared.d.ts.map +1 -0
  15. package/dist/adapters/shared.js +168 -0
  16. package/dist/adapters/shared.js.map +1 -0
  17. package/dist/adapters/slack/bot.d.ts +30 -24
  18. package/dist/adapters/slack/bot.d.ts.map +1 -1
  19. package/dist/adapters/slack/bot.js +613 -224
  20. package/dist/adapters/slack/bot.js.map +1 -1
  21. package/dist/adapters/slack/branch-manager.d.ts +22 -0
  22. package/dist/adapters/slack/branch-manager.d.ts.map +1 -0
  23. package/dist/adapters/slack/branch-manager.js +97 -0
  24. package/dist/adapters/slack/branch-manager.js.map +1 -0
  25. package/dist/adapters/slack/context.d.ts +1 -1
  26. package/dist/adapters/slack/context.d.ts.map +1 -1
  27. package/dist/adapters/slack/context.js +127 -72
  28. package/dist/adapters/slack/context.js.map +1 -1
  29. package/dist/adapters/slack/session.d.ts +3 -0
  30. package/dist/adapters/slack/session.d.ts.map +1 -0
  31. package/dist/adapters/slack/session.js +16 -0
  32. package/dist/adapters/slack/session.js.map +1 -0
  33. package/dist/adapters/slack/tools/attach.d.ts +1 -1
  34. package/dist/adapters/slack/tools/attach.d.ts.map +1 -1
  35. package/dist/adapters/slack/tools/attach.js.map +1 -1
  36. package/dist/adapters/telegram/bot.d.ts +4 -2
  37. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  38. package/dist/adapters/telegram/bot.js +193 -147
  39. package/dist/adapters/telegram/bot.js.map +1 -1
  40. package/dist/adapters/telegram/context.d.ts.map +1 -1
  41. package/dist/adapters/telegram/context.js +58 -111
  42. package/dist/adapters/telegram/context.js.map +1 -1
  43. package/dist/adapters/telegram/html.d.ts +3 -0
  44. package/dist/adapters/telegram/html.d.ts.map +1 -0
  45. package/dist/adapters/telegram/html.js +98 -0
  46. package/dist/adapters/telegram/html.js.map +1 -0
  47. package/dist/agent.d.ts +9 -13
  48. package/dist/agent.d.ts.map +1 -1
  49. package/dist/agent.js +601 -567
  50. package/dist/agent.js.map +1 -1
  51. package/dist/commands/auto-reply.d.ts +16 -0
  52. package/dist/commands/auto-reply.d.ts.map +1 -0
  53. package/dist/commands/auto-reply.js +69 -0
  54. package/dist/commands/auto-reply.js.map +1 -0
  55. package/dist/commands/index.d.ts +5 -0
  56. package/dist/commands/index.d.ts.map +1 -0
  57. package/dist/commands/index.js +19 -0
  58. package/dist/commands/index.js.map +1 -0
  59. package/dist/commands/login.d.ts +5 -0
  60. package/dist/commands/login.d.ts.map +1 -0
  61. package/dist/commands/login.js +76 -0
  62. package/dist/commands/login.js.map +1 -0
  63. package/dist/commands/model.d.ts +14 -0
  64. package/dist/commands/model.d.ts.map +1 -0
  65. package/dist/commands/model.js +112 -0
  66. package/dist/commands/model.js.map +1 -0
  67. package/dist/commands/new.d.ts +9 -0
  68. package/dist/commands/new.d.ts.map +1 -0
  69. package/dist/commands/new.js +28 -0
  70. package/dist/commands/new.js.map +1 -0
  71. package/dist/commands/registry.d.ts +7 -0
  72. package/dist/commands/registry.d.ts.map +1 -0
  73. package/dist/commands/registry.js +14 -0
  74. package/dist/commands/registry.js.map +1 -0
  75. package/dist/commands/sandbox.d.ts +10 -0
  76. package/dist/commands/sandbox.d.ts.map +1 -0
  77. package/dist/commands/sandbox.js +88 -0
  78. package/dist/commands/sandbox.js.map +1 -0
  79. package/dist/commands/session-view.d.ts +5 -0
  80. package/dist/commands/session-view.d.ts.map +1 -0
  81. package/dist/commands/session-view.js +62 -0
  82. package/dist/commands/session-view.js.map +1 -0
  83. package/dist/commands/types.d.ts +41 -0
  84. package/dist/commands/types.d.ts.map +1 -0
  85. package/dist/commands/types.js +2 -0
  86. package/dist/commands/types.js.map +1 -0
  87. package/dist/commands/utils.d.ts +8 -0
  88. package/dist/commands/utils.d.ts.map +1 -0
  89. package/dist/commands/utils.js +14 -0
  90. package/dist/commands/utils.js.map +1 -0
  91. package/dist/config.d.ts +49 -30
  92. package/dist/config.d.ts.map +1 -1
  93. package/dist/config.js +313 -75
  94. package/dist/config.js.map +1 -1
  95. package/dist/context.d.ts +10 -42
  96. package/dist/context.d.ts.map +1 -1
  97. package/dist/context.js +14 -127
  98. package/dist/context.js.map +1 -1
  99. package/dist/events.d.ts +13 -6
  100. package/dist/events.d.ts.map +1 -1
  101. package/dist/events.js +118 -64
  102. package/dist/events.js.map +1 -1
  103. package/dist/execution-resolver.d.ts +9 -5
  104. package/dist/execution-resolver.d.ts.map +1 -1
  105. package/dist/execution-resolver.js +82 -18
  106. package/dist/execution-resolver.js.map +1 -1
  107. package/dist/file-guards.d.ts +6 -0
  108. package/dist/file-guards.d.ts.map +1 -0
  109. package/dist/file-guards.js +48 -0
  110. package/dist/file-guards.js.map +1 -0
  111. package/dist/fs-atomic.d.ts +10 -0
  112. package/dist/fs-atomic.d.ts.map +1 -0
  113. package/dist/fs-atomic.js +45 -0
  114. package/dist/fs-atomic.js.map +1 -0
  115. package/dist/index.d.ts +7 -0
  116. package/dist/index.d.ts.map +1 -0
  117. package/dist/index.js +4 -0
  118. package/dist/index.js.map +1 -0
  119. package/dist/instrument.d.ts.map +1 -1
  120. package/dist/instrument.js +4 -11
  121. package/dist/instrument.js.map +1 -1
  122. package/dist/log.d.ts +1 -5
  123. package/dist/log.d.ts.map +1 -1
  124. package/dist/log.js +13 -38
  125. package/dist/log.js.map +1 -1
  126. package/dist/{login.d.ts → login/index.d.ts} +16 -4
  127. package/dist/login/index.d.ts.map +1 -0
  128. package/dist/{login.js → login/index.js} +55 -17
  129. package/dist/login/index.js.map +1 -0
  130. package/dist/{link-server.d.ts → login/portal.d.ts} +7 -4
  131. package/dist/login/portal.d.ts.map +1 -0
  132. package/dist/login/portal.js +1453 -0
  133. package/dist/login/portal.js.map +1 -0
  134. package/dist/{link-token.d.ts → login/session.d.ts} +4 -3
  135. package/dist/login/session.d.ts.map +1 -0
  136. package/dist/{link-token.js → login/session.js} +1 -1
  137. package/dist/login/session.js.map +1 -0
  138. package/dist/main.d.ts.map +1 -1
  139. package/dist/main.js +151 -373
  140. package/dist/main.js.map +1 -1
  141. package/dist/provisioner.d.ts +42 -52
  142. package/dist/provisioner.d.ts.map +1 -1
  143. package/dist/provisioner.js +256 -111
  144. package/dist/provisioner.js.map +1 -1
  145. package/dist/runtime/conversation-orchestrator.d.ts +42 -0
  146. package/dist/runtime/conversation-orchestrator.d.ts.map +1 -0
  147. package/dist/runtime/conversation-orchestrator.js +150 -0
  148. package/dist/runtime/conversation-orchestrator.js.map +1 -0
  149. package/dist/runtime/index.d.ts +2 -0
  150. package/dist/runtime/index.d.ts.map +1 -0
  151. package/dist/runtime/index.js +2 -0
  152. package/dist/runtime/index.js.map +1 -0
  153. package/dist/runtime/session-runtime.d.ts +27 -0
  154. package/dist/runtime/session-runtime.d.ts.map +1 -0
  155. package/dist/runtime/session-runtime.js +211 -0
  156. package/dist/runtime/session-runtime.js.map +1 -0
  157. package/dist/sandbox/cloudflare.d.ts +15 -0
  158. package/dist/sandbox/cloudflare.d.ts.map +1 -0
  159. package/dist/sandbox/cloudflare.js +137 -0
  160. package/dist/sandbox/cloudflare.js.map +1 -0
  161. package/dist/sandbox/container.d.ts +2 -1
  162. package/dist/sandbox/container.d.ts.map +1 -1
  163. package/dist/sandbox/container.js +5 -1
  164. package/dist/sandbox/container.js.map +1 -1
  165. package/dist/sandbox/firecracker.d.ts +2 -1
  166. package/dist/sandbox/firecracker.d.ts.map +1 -1
  167. package/dist/sandbox/firecracker.js +6 -0
  168. package/dist/sandbox/firecracker.js.map +1 -1
  169. package/dist/sandbox/host.d.ts +2 -3
  170. package/dist/sandbox/host.d.ts.map +1 -1
  171. package/dist/sandbox/host.js +5 -5
  172. package/dist/sandbox/host.js.map +1 -1
  173. package/dist/sandbox/index.d.ts +6 -4
  174. package/dist/sandbox/index.d.ts.map +1 -1
  175. package/dist/sandbox/index.js +9 -6
  176. package/dist/sandbox/index.js.map +1 -1
  177. package/dist/sandbox/path-context.d.ts +4 -0
  178. package/dist/sandbox/path-context.d.ts.map +1 -0
  179. package/dist/sandbox/path-context.js +20 -0
  180. package/dist/sandbox/path-context.js.map +1 -0
  181. package/dist/sandbox/types.d.ts +17 -1
  182. package/dist/sandbox/types.d.ts.map +1 -1
  183. package/dist/sandbox/types.js.map +1 -1
  184. package/dist/sentry.d.ts +1 -1
  185. package/dist/sentry.d.ts.map +1 -1
  186. package/dist/sentry.js +4 -2
  187. package/dist/sentry.js.map +1 -1
  188. package/dist/session-policy.d.ts +13 -0
  189. package/dist/session-policy.d.ts.map +1 -0
  190. package/dist/session-policy.js +23 -0
  191. package/dist/session-policy.js.map +1 -0
  192. package/dist/session-store.d.ts +34 -3
  193. package/dist/session-store.d.ts.map +1 -1
  194. package/dist/session-store.js +184 -22
  195. package/dist/session-store.js.map +1 -1
  196. package/dist/session-view/command.d.ts +5 -0
  197. package/dist/session-view/command.d.ts.map +1 -0
  198. package/dist/session-view/command.js +11 -0
  199. package/dist/session-view/command.js.map +1 -0
  200. package/dist/session-view/portal.d.ts +16 -0
  201. package/dist/session-view/portal.d.ts.map +1 -0
  202. package/dist/session-view/portal.js +1742 -0
  203. package/dist/session-view/portal.js.map +1 -0
  204. package/dist/session-view/service.d.ts +34 -0
  205. package/dist/session-view/service.d.ts.map +1 -0
  206. package/dist/session-view/service.js +427 -0
  207. package/dist/session-view/service.js.map +1 -0
  208. package/dist/session-view/store.d.ts +18 -0
  209. package/dist/session-view/store.d.ts.map +1 -0
  210. package/dist/session-view/store.js +39 -0
  211. package/dist/session-view/store.js.map +1 -0
  212. package/dist/store.d.ts +3 -6
  213. package/dist/store.d.ts.map +1 -1
  214. package/dist/store.js +22 -48
  215. package/dist/store.js.map +1 -1
  216. package/dist/tool-diagnostics.d.ts +2 -0
  217. package/dist/tool-diagnostics.d.ts.map +1 -0
  218. package/dist/tool-diagnostics.js +7 -0
  219. package/dist/tool-diagnostics.js.map +1 -0
  220. package/dist/tools/bash.d.ts +1 -1
  221. package/dist/tools/bash.d.ts.map +1 -1
  222. package/dist/tools/bash.js.map +1 -1
  223. package/dist/tools/edit.d.ts +1 -1
  224. package/dist/tools/edit.d.ts.map +1 -1
  225. package/dist/tools/edit.js.map +1 -1
  226. package/dist/tools/event.d.ts +43 -2
  227. package/dist/tools/event.d.ts.map +1 -1
  228. package/dist/tools/event.js +48 -13
  229. package/dist/tools/event.js.map +1 -1
  230. package/dist/tools/index.d.ts +2 -1
  231. package/dist/tools/index.d.ts.map +1 -1
  232. package/dist/tools/index.js +3 -3
  233. package/dist/tools/index.js.map +1 -1
  234. package/dist/tools/read.d.ts +1 -1
  235. package/dist/tools/read.d.ts.map +1 -1
  236. package/dist/tools/read.js.map +1 -1
  237. package/dist/tools/write.d.ts +1 -1
  238. package/dist/tools/write.d.ts.map +1 -1
  239. package/dist/tools/write.js.map +1 -1
  240. package/dist/trigger.d.ts +31 -0
  241. package/dist/trigger.d.ts.map +1 -0
  242. package/dist/trigger.js +98 -0
  243. package/dist/trigger.js.map +1 -0
  244. package/dist/ui-copy.d.ts +1 -0
  245. package/dist/ui-copy.d.ts.map +1 -1
  246. package/dist/ui-copy.js +3 -0
  247. package/dist/ui-copy.js.map +1 -1
  248. package/dist/vault-routing.d.ts +1 -7
  249. package/dist/vault-routing.d.ts.map +1 -1
  250. package/dist/vault-routing.js +6 -48
  251. package/dist/vault-routing.js.map +1 -1
  252. package/dist/vault.d.ts +21 -55
  253. package/dist/vault.d.ts.map +1 -1
  254. package/dist/vault.js +144 -263
  255. package/dist/vault.js.map +1 -1
  256. package/package.json +12 -10
  257. package/dist/bindings.d.ts +0 -63
  258. package/dist/bindings.d.ts.map +0 -1
  259. package/dist/bindings.js +0 -94
  260. package/dist/bindings.js.map +0 -1
  261. package/dist/link-server.d.ts.map +0 -1
  262. package/dist/link-server.js +0 -839
  263. package/dist/link-server.js.map +0 -1
  264. package/dist/link-token.d.ts.map +0 -1
  265. package/dist/link-token.js.map +0 -1
  266. package/dist/login.d.ts.map +0 -1
  267. package/dist/login.js.map +0 -1
  268. package/dist/vault.test.d.ts +0 -2
  269. package/dist/vault.test.d.ts.map +0 -1
  270. package/dist/vault.test.js +0 -67
  271. package/dist/vault.test.js.map +0 -1
package/dist/config.js CHANGED
@@ -1,60 +1,238 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2
- import { dirname, join } from "path";
3
- const SETTINGS_TEMPLATE = {
4
- provider: "anthropic",
5
- model: "claude-sonnet-4-6",
6
- thinkingLevel: "off",
7
- sessionScope: "thread",
8
- logFormat: "console",
9
- logLevel: "info",
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { dirname, join, resolve } from "path";
4
+ import { ensureDirExists, isRecord, readJsonFileIfExists } from "./file-guards.js";
5
+ import { atomicWritePrivateFile } from "./fs-atomic.js";
6
+ export class MissingGlobalSettingsError extends Error {
7
+ constructor(settingsPath) {
8
+ super(`Missing global settings file at ${settingsPath}`);
9
+ this.settingsPath = settingsPath;
10
+ this.name = "MissingGlobalSettingsError";
11
+ }
12
+ }
13
+ const ONBOARD_SETTINGS = {
14
+ llm: {
15
+ provider: "anthropic",
16
+ model: "claude-sonnet-4-6",
17
+ thinkingLevel: "off",
18
+ autoReply: {
19
+ provider: "anthropic",
20
+ model: "claude-haiku-4-5",
21
+ },
22
+ },
23
+ log: {
24
+ format: "console",
25
+ level: "info",
26
+ },
27
+ sandbox: {
28
+ cpus: "0.5",
29
+ memory: "1g",
30
+ boost: {
31
+ cpus: "2",
32
+ memory: "4g",
33
+ },
34
+ image: {
35
+ workspaceMount: "private",
36
+ },
37
+ },
10
38
  };
11
- export function loadAgentConfig(stateDir) {
12
- const settingsPath = join(stateDir, "settings.json");
13
- let fromFile = {};
14
- try {
15
- const raw = readFileSync(settingsPath, "utf-8");
16
- const parsed = JSON.parse(raw);
17
- if (parsed && typeof parsed === "object") {
18
- fromFile = parsed;
19
- }
39
+ function loadSettingsFile(settingsPath) {
40
+ return readJsonFileIfExists(settingsPath, (value) => isRecord(value), (detail) => detail === "unexpected JSON shape"
41
+ ? `Malformed settings file at ${settingsPath}: expected a JSON object at the top level`
42
+ : `Malformed settings file at ${settingsPath}: ${detail}`);
43
+ }
44
+ function getStateDir() {
45
+ const raw = process.env.MAMA_STATE_DIR?.trim();
46
+ return raw ? resolve(raw) : join(homedir(), ".mama");
47
+ }
48
+ function normalizeSettingsConfig(config) {
49
+ return {
50
+ ...(config.llm?.provider !== undefined ? { provider: config.llm.provider } : {}),
51
+ ...(config.llm?.model !== undefined ? { model: config.llm.model } : {}),
52
+ ...(config.llm?.thinkingLevel !== undefined ? { thinkingLevel: config.llm.thinkingLevel } : {}),
53
+ ...(config.log?.format !== undefined ? { logFormat: config.log.format } : {}),
54
+ ...(config.log?.level !== undefined ? { logLevel: config.log.level } : {}),
55
+ ...(config.sentry?.dsn !== undefined ? { sentryDsn: config.sentry.dsn } : {}),
56
+ ...(config.sandbox?.cpus !== undefined ? { sandboxCpus: config.sandbox.cpus } : {}),
57
+ ...(config.sandbox?.memory !== undefined ? { sandboxMemory: config.sandbox.memory } : {}),
58
+ ...(config.sandbox?.boost?.cpus !== undefined
59
+ ? { sandboxBoostCpus: config.sandbox.boost.cpus }
60
+ : {}),
61
+ ...(config.sandbox?.boost?.memory !== undefined
62
+ ? { sandboxBoostMemory: config.sandbox.boost.memory }
63
+ : {}),
64
+ ...(config.sandbox?.image?.workspaceMount !== undefined
65
+ ? { sandboxImageWorkspaceMount: config.sandbox.image.workspaceMount }
66
+ : {}),
67
+ };
68
+ }
69
+ function getSettingsPath() {
70
+ return join(getStateDir(), "settings.json");
71
+ }
72
+ function requireGlobalSettings() {
73
+ const settingsPath = getSettingsPath();
74
+ const config = loadSettingsFile(settingsPath);
75
+ if (!config) {
76
+ throw new MissingGlobalSettingsError(settingsPath);
20
77
  }
21
- catch {
22
- // File missing or malformed
78
+ return config;
79
+ }
80
+ function requireString(value, path) {
81
+ if (!value) {
82
+ throw new Error(`Missing required global setting: ${path}. Run \`mama --onboard\` to create settings.json.`);
23
83
  }
24
- return applyEnvOverrides(fromFile);
84
+ return value;
85
+ }
86
+ function requireThinkingLevel(value) {
87
+ return requireString(value, "llm.thinkingLevel");
25
88
  }
26
- function applyEnvOverrides(config) {
89
+ function requireLogFormat(value) {
90
+ if (value !== "console" && value !== "json") {
91
+ throw new Error("Missing or invalid required global setting: log.format");
92
+ }
93
+ return value;
94
+ }
95
+ function requireLogLevel(value) {
96
+ const allowed = ["trace", "debug", "info", "warn", "error"];
97
+ if (!value || !allowed.includes(value)) {
98
+ throw new Error("Missing or invalid required global setting: log.level");
99
+ }
100
+ return value;
101
+ }
102
+ function toAgentConfig(fromFile) {
103
+ const provider = requireString(fromFile.provider, "llm.provider");
104
+ const model = requireString(fromFile.model, "llm.model");
105
+ const thinkingLevel = requireThinkingLevel(fromFile.thinkingLevel);
106
+ const logFormat = requireLogFormat(fromFile.logFormat);
107
+ const logLevel = requireLogLevel(fromFile.logLevel);
108
+ const sentryDsn = fromFile.sentryDsn ?? process.env.SENTRY_DSN;
109
+ const sandboxCpus = fromFile.sandboxCpus;
110
+ const sandboxMemory = fromFile.sandboxMemory;
111
+ const sandboxBoostCpus = fromFile.sandboxBoostCpus;
112
+ const sandboxBoostMemory = fromFile.sandboxBoostMemory;
113
+ const sandboxImageWorkspaceMount = fromFile.sandboxImageWorkspaceMount;
27
114
  return {
28
- ...config,
29
- provider: process.env.MOM_AI_PROVIDER ?? config.provider,
30
- model: process.env.MOM_AI_MODEL ?? config.model,
115
+ provider,
116
+ model,
117
+ thinkingLevel,
118
+ logFormat,
119
+ logLevel,
120
+ sentryDsn,
121
+ sandboxCpus,
122
+ sandboxMemory,
123
+ sandboxBoostCpus,
124
+ sandboxBoostMemory,
125
+ sandboxImageWorkspaceMount,
126
+ };
127
+ }
128
+ function loadRawAgentConfig() {
129
+ return normalizeSettingsConfig(requireGlobalSettings());
130
+ }
131
+ export function loadAgentConfig() {
132
+ return toAgentConfig(loadRawAgentConfig());
133
+ }
134
+ export function loadAgentConfigForConversation(conversationDir) {
135
+ const globalConfig = loadRawAgentConfig();
136
+ const conversationConfig = normalizeSettingsConfig(loadSettingsFile(join(conversationDir, "settings.json")) ?? {});
137
+ return toAgentConfig({ ...globalConfig, ...conversationConfig });
138
+ }
139
+ export function saveConversationModelConfig(conversationDir, config) {
140
+ if (!existsSync(conversationDir)) {
141
+ ensureDirExists(conversationDir);
142
+ }
143
+ const settingsPath = join(conversationDir, "settings.json");
144
+ const existing = loadSettingsFile(settingsPath) ?? {};
145
+ const scopedConfig = {
146
+ ...existing,
147
+ llm: { ...existing.llm, ...config },
31
148
  };
149
+ atomicWritePrivateFile(settingsPath, JSON.stringify(scopedConfig, null, 2));
150
+ }
151
+ export function saveConversationSandboxConfig(conversationDir, config) {
152
+ if (!existsSync(conversationDir)) {
153
+ ensureDirExists(conversationDir);
154
+ }
155
+ const settingsPath = join(conversationDir, "settings.json");
156
+ const existing = loadSettingsFile(settingsPath) ?? {};
157
+ const scopedConfig = {
158
+ ...existing,
159
+ sandbox: {
160
+ ...existing.sandbox,
161
+ image: {
162
+ ...existing.sandbox?.image,
163
+ workspaceMount: config.imageWorkspaceMount,
164
+ },
165
+ },
166
+ };
167
+ atomicWritePrivateFile(settingsPath, JSON.stringify(scopedConfig, null, 2));
32
168
  }
33
169
  /**
34
- * Ensure settings.json exists in stateDir.
35
- * If missing, writes a default template and returns it.
36
- * If it already exists, loads and returns the current config.
170
+ * Resolve the model used to judge auto-reply rules. Falls back to the main
171
+ * llm.{provider,model} when llm.autoReply is not set, so a missing override
172
+ * keeps current behavior.
37
173
  */
38
- export function ensureSettingsFile(stateDir) {
39
- const settingsPath = join(stateDir, "settings.json");
40
- if (!existsSync(settingsPath)) {
41
- mkdirSync(stateDir, { recursive: true });
42
- writeFileSync(settingsPath, JSON.stringify(SETTINGS_TEMPLATE, null, 2) + "\n", "utf-8");
43
- return { created: true, config: applyEnvOverrides(SETTINGS_TEMPLATE) };
174
+ export function loadAutoReplyJudgeModel(conversationDir) {
175
+ const global = requireGlobalSettings();
176
+ const local = conversationDir
177
+ ? (loadSettingsFile(join(conversationDir, "settings.json")) ?? {})
178
+ : {};
179
+ const merged = { ...global.llm, ...local.llm };
180
+ const judge = { ...global.llm?.autoReply, ...local.llm?.autoReply };
181
+ const provider = requireString(judge.provider ?? merged?.provider, "llm.autoReply.provider");
182
+ const model = requireString(judge.model ?? merged?.model, "llm.autoReply.model");
183
+ return { provider, model };
184
+ }
185
+ const AUTO_REPLY_FILE = "auto-reply";
186
+ const AUTO_REPLY_DISABLED_FILE = "auto-reply.disabled";
187
+ function readAutoReplyRulesFile(path) {
188
+ const text = readFileSync(path, "utf-8").trim();
189
+ return text ? [text] : [];
190
+ }
191
+ /**
192
+ * Load the mom-compatible auto-reply marker file state for a conversation.
193
+ *
194
+ * - `auto-reply` exists: enabled; empty file means reply to any top-level message.
195
+ * - `auto-reply.disabled` exists: disabled, preserving any rules text for re-enable.
196
+ * - neither exists: disabled.
197
+ */
198
+ export function loadConversationAutoReplyConfig(conversationDir) {
199
+ const enabledPath = join(conversationDir, AUTO_REPLY_FILE);
200
+ if (existsSync(enabledPath)) {
201
+ return { enabled: true, rules: readAutoReplyRulesFile(enabledPath) };
202
+ }
203
+ const disabledPath = join(conversationDir, AUTO_REPLY_DISABLED_FILE);
204
+ if (existsSync(disabledPath)) {
205
+ return { enabled: false, rules: readAutoReplyRulesFile(disabledPath) };
206
+ }
207
+ return { enabled: false, rules: [] };
208
+ }
209
+ /** Save auto-reply state using mom-compatible marker files. */
210
+ export function saveConversationAutoReplyConfig(conversationDir, config) {
211
+ if (!existsSync(conversationDir)) {
212
+ mkdirSync(conversationDir, { recursive: true });
213
+ }
214
+ const enabledPath = join(conversationDir, AUTO_REPLY_FILE);
215
+ const disabledPath = join(conversationDir, AUTO_REPLY_DISABLED_FILE);
216
+ const targetPath = config.enabled ? enabledPath : disabledPath;
217
+ const otherPath = config.enabled ? disabledPath : enabledPath;
218
+ if (existsSync(otherPath)) {
219
+ renameSync(otherPath, targetPath);
44
220
  }
45
- return { created: false, config: loadAgentConfig(stateDir) };
221
+ writeFileSync(targetPath, config.rules.join("\n"), "utf-8");
46
222
  }
47
223
  export function resolveWorkspaceDirFromArgv(args = process.argv.slice(2)) {
48
224
  for (let i = 0; i < args.length; i++) {
49
225
  const arg = args[i];
50
- if (arg === "--sandbox" || arg === "--download") {
226
+ if (arg === "--sandbox" || arg === "--download" || arg === "--state-dir") {
51
227
  i += 1;
52
228
  continue;
53
229
  }
54
- if (arg === "--version" || arg === "-v" || arg === "-V") {
230
+ if (arg === "--version" || arg === "-v" || arg === "-V" || arg === "--onboard") {
55
231
  continue;
56
232
  }
57
- if (arg.startsWith("--sandbox=") || arg.startsWith("--download=")) {
233
+ if (arg.startsWith("--sandbox=") ||
234
+ arg.startsWith("--download=") ||
235
+ arg.startsWith("--state-dir=")) {
58
236
  continue;
59
237
  }
60
238
  if (!arg.startsWith("-")) {
@@ -63,54 +241,114 @@ export function resolveWorkspaceDirFromArgv(args = process.argv.slice(2)) {
63
241
  }
64
242
  return undefined;
65
243
  }
66
- export function resolveSentryDsn(stateDir) {
67
- return loadAgentConfig(stateDir).sentryDsn;
244
+ export function resolveStateDirFromArgv(args = process.argv.slice(2)) {
245
+ for (let i = 0; i < args.length; i++) {
246
+ const arg = args[i];
247
+ if (arg.startsWith("--state-dir=")) {
248
+ return resolve(arg.slice("--state-dir=".length));
249
+ }
250
+ if (arg === "--state-dir") {
251
+ return resolve(args[++i] || "");
252
+ }
253
+ }
254
+ return join(homedir(), ".mama");
255
+ }
256
+ export function resolveSentryDsn() {
257
+ const fromFile = normalizeSettingsConfig(loadSettingsFile(getSettingsPath()) ?? {});
258
+ if (fromFile.sentryDsn) {
259
+ return fromFile.sentryDsn;
260
+ }
261
+ return process.env.SENTRY_DSN;
262
+ }
263
+ export function createGlobalSettingsFile(stateDir) {
264
+ const settingsPath = join(stateDir, "settings.json");
265
+ if (existsSync(settingsPath)) {
266
+ throw new Error(`Global settings already exists at ${settingsPath}`);
267
+ }
268
+ if (!existsSync(stateDir)) {
269
+ ensureDirExists(stateDir);
270
+ }
271
+ atomicWritePrivateFile(settingsPath, JSON.stringify(ONBOARD_SETTINGS, null, 2));
272
+ return settingsPath;
68
273
  }
69
274
  /**
70
275
  * Externally-visible base URL of the link/OAuth server, e.g.
71
- * `https://mama.example.com` (no trailing slash). Read from `MOM_LINK_URL`,
72
- * the same env var the bot uses to build `/link?token=...` invitations.
73
- *
74
- * Used by the link server to build OAuth `redirect_uri` values that must
75
- * match the registered callback URL at the identity provider. When unset,
76
- * the link server falls back to deriving the base from request headers
77
- * (Host / X-Forwarded-*), which is insecure in production because those
78
- * headers are client-controlled.
276
+ * `https://mama.example.com` (no trailing slash). Read from `MAMA_LINK_URL`,
277
+ * the same env var the bot uses to build credential onboarding links.
79
278
  */
80
279
  export function resolveLinkBaseUrl() {
81
- const raw = process.env.MOM_LINK_URL?.trim();
280
+ const raw = process.env.MAMA_LINK_URL?.trim();
82
281
  if (!raw)
83
282
  return undefined;
84
283
  return raw.replace(/\/+$/, "");
85
284
  }
86
- /**
87
- * Resolve Sentry DSN from settings.json, checking both the new stateDir location
88
- * (default: ~/.mama/settings.json) and the legacy workspace location for backwards compatibility.
89
- * Returns undefined if not found in either location.
90
- */
91
- export function resolveSentryDsnFromConfig(stateDir, workingDir) {
92
- // First check the new stateDir location
93
- const stateDirDsn = resolveSentryDsn(stateDir);
94
- if (stateDirDsn) {
95
- return stateDirDsn;
96
- }
97
- // Fall back to legacy workspace location for backwards compatibility
98
- return workingDir ? resolveSentryDsn(workingDir) : undefined;
285
+ function hasDefinedValue(values) {
286
+ return values !== undefined && Object.values(values).some((value) => value !== undefined);
99
287
  }
100
- export function saveAgentConfig(stateDir, config) {
101
- const settingsPath = join(stateDir, "settings.json");
102
- let existing = {};
103
- try {
104
- const raw = readFileSync(settingsPath, "utf-8");
105
- const parsed = JSON.parse(raw);
106
- if (parsed && typeof parsed === "object") {
107
- existing = parsed;
288
+ function compactSettingsConfig(config) {
289
+ return {
290
+ ...(hasDefinedValue(config.llm) ? { llm: config.llm } : {}),
291
+ ...(hasDefinedValue(config.log) ? { log: config.log } : {}),
292
+ ...(hasDefinedValue(config.sentry) ? { sentry: config.sentry } : {}),
293
+ ...(hasDefinedValue(config.sandbox) ? { sandbox: config.sandbox } : {}),
294
+ ...(hasDefinedValue(config.autoReply) ? { autoReply: config.autoReply } : {}),
295
+ };
296
+ }
297
+ function patchSettingsConfig(existing, config) {
298
+ const patched = {
299
+ ...existing,
300
+ llm: {
301
+ ...existing.llm,
302
+ ...(config.provider !== undefined ? { provider: config.provider } : {}),
303
+ ...(config.model !== undefined ? { model: config.model } : {}),
304
+ ...(config.thinkingLevel !== undefined ? { thinkingLevel: config.thinkingLevel } : {}),
305
+ },
306
+ log: {
307
+ ...existing.log,
308
+ ...(config.logFormat !== undefined ? { format: config.logFormat } : {}),
309
+ ...(config.logLevel !== undefined ? { level: config.logLevel } : {}),
310
+ },
311
+ sentry: {
312
+ ...existing.sentry,
313
+ ...(config.sentryDsn !== undefined ? { dsn: config.sentryDsn } : {}),
314
+ },
315
+ sandbox: {
316
+ ...existing.sandbox,
317
+ ...(config.sandboxCpus !== undefined ? { cpus: config.sandboxCpus } : {}),
318
+ ...(config.sandboxMemory !== undefined ? { memory: config.sandboxMemory } : {}),
319
+ ...(config.sandboxBoostCpus !== undefined || config.sandboxBoostMemory !== undefined
320
+ ? {
321
+ boost: {
322
+ ...existing.sandbox?.boost,
323
+ ...(config.sandboxBoostCpus !== undefined ? { cpus: config.sandboxBoostCpus } : {}),
324
+ ...(config.sandboxBoostMemory !== undefined
325
+ ? { memory: config.sandboxBoostMemory }
326
+ : {}),
327
+ },
328
+ }
329
+ : {}),
330
+ },
331
+ };
332
+ return compactSettingsConfig(patched);
333
+ }
334
+ export function saveAgentConfig(config) {
335
+ const settingsPath = join(getStateDir(), "settings.json");
336
+ let existing = ONBOARD_SETTINGS;
337
+ if (existsSync(settingsPath)) {
338
+ try {
339
+ existing = loadSettingsFile(settingsPath) ?? {};
340
+ }
341
+ catch (err) {
342
+ const detail = err instanceof Error ? err.message : String(err);
343
+ const message = detail.startsWith("Malformed settings file")
344
+ ? detail.replace("Malformed settings file", "Refusing to overwrite malformed settings file")
345
+ : detail;
346
+ throw new Error(message, { cause: err });
108
347
  }
109
348
  }
110
- catch {
111
- // Start fresh if file is missing or malformed
112
- }
113
- mkdirSync(dirname(settingsPath), { recursive: true });
114
- writeFileSync(settingsPath, JSON.stringify({ ...existing, ...config }, null, 2), "utf-8");
349
+ const merged = patchSettingsConfig(existing, config);
350
+ const dir = dirname(settingsPath);
351
+ ensureDirExists(dir);
352
+ atomicWritePrivateFile(settingsPath, JSON.stringify(merged, null, 2));
115
353
  }
116
354
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\n\nexport interface AgentConfig {\n provider?: string;\n model?: string;\n thinkingLevel?: string;\n sessionScope?: \"thread\" | \"channel\";\n logFormat?: \"console\" | \"json\";\n logLevel?: \"trace\" | \"debug\" | \"info\" | \"warn\" | \"error\";\n sentryDsn?: string;\n}\n\nconst SETTINGS_TEMPLATE: AgentConfig = {\n provider: \"anthropic\",\n model: \"claude-sonnet-4-6\",\n thinkingLevel: \"off\",\n sessionScope: \"thread\",\n logFormat: \"console\",\n logLevel: \"info\",\n};\n\nexport function loadAgentConfig(stateDir: string): AgentConfig {\n const settingsPath = join(stateDir, \"settings.json\");\n let fromFile: AgentConfig = {};\n try {\n const raw = readFileSync(settingsPath, \"utf-8\");\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\") {\n fromFile = parsed as AgentConfig;\n }\n } catch {\n // File missing or malformed\n }\n return applyEnvOverrides(fromFile);\n}\n\nfunction applyEnvOverrides(config: AgentConfig): AgentConfig {\n return {\n ...config,\n provider: process.env.MOM_AI_PROVIDER ?? config.provider,\n model: process.env.MOM_AI_MODEL ?? config.model,\n };\n}\n\n/**\n * Ensure settings.json exists in stateDir.\n * If missing, writes a default template and returns it.\n * If it already exists, loads and returns the current config.\n */\nexport function ensureSettingsFile(stateDir: string): { created: boolean; config: AgentConfig } {\n const settingsPath = join(stateDir, \"settings.json\");\n if (!existsSync(settingsPath)) {\n mkdirSync(stateDir, { recursive: true });\n writeFileSync(settingsPath, JSON.stringify(SETTINGS_TEMPLATE, null, 2) + \"\\n\", \"utf-8\");\n return { created: true, config: applyEnvOverrides(SETTINGS_TEMPLATE) };\n }\n return { created: false, config: loadAgentConfig(stateDir) };\n}\n\nexport function resolveWorkspaceDirFromArgv(args = process.argv.slice(2)): string | undefined {\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n\n if (arg === \"--sandbox\" || arg === \"--download\") {\n i += 1;\n continue;\n }\n\n if (arg === \"--version\" || arg === \"-v\" || arg === \"-V\") {\n continue;\n }\n\n if (arg.startsWith(\"--sandbox=\") || arg.startsWith(\"--download=\")) {\n continue;\n }\n\n if (!arg.startsWith(\"-\")) {\n return arg;\n }\n }\n\n return undefined;\n}\n\nexport function resolveSentryDsn(stateDir: string): string | undefined {\n return loadAgentConfig(stateDir).sentryDsn;\n}\n\n/**\n * Externally-visible base URL of the link/OAuth server, e.g.\n * `https://mama.example.com` (no trailing slash). Read from `MOM_LINK_URL`,\n * the same env var the bot uses to build `/link?token=...` invitations.\n *\n * Used by the link server to build OAuth `redirect_uri` values that must\n * match the registered callback URL at the identity provider. When unset,\n * the link server falls back to deriving the base from request headers\n * (Host / X-Forwarded-*), which is insecure in production because those\n * headers are client-controlled.\n */\nexport function resolveLinkBaseUrl(): string | undefined {\n const raw = process.env.MOM_LINK_URL?.trim();\n if (!raw) return undefined;\n return raw.replace(/\\/+$/, \"\");\n}\n\n/**\n * Resolve Sentry DSN from settings.json, checking both the new stateDir location\n * (default: ~/.mama/settings.json) and the legacy workspace location for backwards compatibility.\n * Returns undefined if not found in either location.\n */\nexport function resolveSentryDsnFromConfig(\n stateDir: string,\n workingDir?: string,\n): string | undefined {\n // First check the new stateDir location\n const stateDirDsn = resolveSentryDsn(stateDir);\n if (stateDirDsn) {\n return stateDirDsn;\n }\n // Fall back to legacy workspace location for backwards compatibility\n return workingDir ? resolveSentryDsn(workingDir) : undefined;\n}\n\nexport function saveAgentConfig(stateDir: string, config: AgentConfig): void {\n const settingsPath = join(stateDir, \"settings.json\");\n\n let existing: AgentConfig = {};\n try {\n const raw = readFileSync(settingsPath, \"utf-8\");\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\") {\n existing = parsed as AgentConfig;\n }\n } catch {\n // Start fresh if file is missing or malformed\n }\n\n mkdirSync(dirname(settingsPath), { recursive: true });\n writeFileSync(settingsPath, JSON.stringify({ ...existing, ...config }, null, 2), \"utf-8\");\n}\n"]}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { dirname, join, resolve } from \"path\";\nimport { ensureDirExists, isRecord, readJsonFileIfExists } from \"./file-guards.js\";\nimport { atomicWritePrivateFile } from \"./fs-atomic.js\";\n\nexport class MissingGlobalSettingsError extends Error {\n constructor(public readonly settingsPath: string) {\n super(`Missing global settings file at ${settingsPath}`);\n this.name = \"MissingGlobalSettingsError\";\n }\n}\n\nexport interface AgentConfig {\n provider: string;\n model: string;\n thinkingLevel: ThinkingLevel;\n logFormat: \"console\" | \"json\";\n logLevel: \"trace\" | \"debug\" | \"info\" | \"warn\" | \"error\";\n sentryDsn?: string;\n sandboxCpus?: string;\n sandboxMemory?: string;\n sandboxBoostCpus?: string;\n sandboxBoostMemory?: string;\n sandboxImageWorkspaceMount?: \"private\" | \"full\";\n}\n\nexport interface AutoReplyConfig {\n enabled: boolean;\n rules: string[];\n}\n\nexport interface JudgeModelConfig {\n provider: string;\n model: string;\n}\n\nconst ONBOARD_SETTINGS: SettingsFileConfig = {\n llm: {\n provider: \"anthropic\",\n model: \"claude-sonnet-4-6\",\n thinkingLevel: \"off\",\n autoReply: {\n provider: \"anthropic\",\n model: \"claude-haiku-4-5\",\n },\n },\n log: {\n format: \"console\",\n level: \"info\",\n },\n sandbox: {\n cpus: \"0.5\",\n memory: \"1g\",\n boost: {\n cpus: \"2\",\n memory: \"4g\",\n },\n image: {\n workspaceMount: \"private\",\n },\n },\n};\n\ninterface SettingsFileConfig {\n llm?: Partial<Pick<AgentConfig, \"provider\" | \"model\" | \"thinkingLevel\">> & {\n autoReply?: { provider?: string; model?: string };\n };\n log?: { format?: AgentConfig[\"logFormat\"]; level?: AgentConfig[\"logLevel\"] };\n sentry?: { dsn?: string };\n sandbox?: {\n cpus?: string;\n memory?: string;\n boost?: { cpus?: string; memory?: string };\n image?: { workspaceMount?: AgentConfig[\"sandboxImageWorkspaceMount\"] };\n };\n autoReply?: {\n enabled?: boolean;\n rules?: string[];\n };\n}\n\nfunction loadSettingsFile(settingsPath: string): SettingsFileConfig | undefined {\n return readJsonFileIfExists(\n settingsPath,\n (value): value is SettingsFileConfig => isRecord(value),\n (detail) =>\n detail === \"unexpected JSON shape\"\n ? `Malformed settings file at ${settingsPath}: expected a JSON object at the top level`\n : `Malformed settings file at ${settingsPath}: ${detail}`,\n );\n}\n\nfunction getStateDir(): string {\n const raw = process.env.MAMA_STATE_DIR?.trim();\n return raw ? resolve(raw) : join(homedir(), \".mama\");\n}\n\nfunction normalizeSettingsConfig(config: SettingsFileConfig): Partial<AgentConfig> {\n return {\n ...(config.llm?.provider !== undefined ? { provider: config.llm.provider } : {}),\n ...(config.llm?.model !== undefined ? { model: config.llm.model } : {}),\n ...(config.llm?.thinkingLevel !== undefined ? { thinkingLevel: config.llm.thinkingLevel } : {}),\n ...(config.log?.format !== undefined ? { logFormat: config.log.format } : {}),\n ...(config.log?.level !== undefined ? { logLevel: config.log.level } : {}),\n ...(config.sentry?.dsn !== undefined ? { sentryDsn: config.sentry.dsn } : {}),\n ...(config.sandbox?.cpus !== undefined ? { sandboxCpus: config.sandbox.cpus } : {}),\n ...(config.sandbox?.memory !== undefined ? { sandboxMemory: config.sandbox.memory } : {}),\n ...(config.sandbox?.boost?.cpus !== undefined\n ? { sandboxBoostCpus: config.sandbox.boost.cpus }\n : {}),\n ...(config.sandbox?.boost?.memory !== undefined\n ? { sandboxBoostMemory: config.sandbox.boost.memory }\n : {}),\n ...(config.sandbox?.image?.workspaceMount !== undefined\n ? { sandboxImageWorkspaceMount: config.sandbox.image.workspaceMount }\n : {}),\n };\n}\n\nfunction getSettingsPath(): string {\n return join(getStateDir(), \"settings.json\");\n}\n\nfunction requireGlobalSettings(): SettingsFileConfig {\n const settingsPath = getSettingsPath();\n const config = loadSettingsFile(settingsPath);\n if (!config) {\n throw new MissingGlobalSettingsError(settingsPath);\n }\n return config;\n}\n\nfunction requireString(value: string | undefined, path: string): string {\n if (!value) {\n throw new Error(\n `Missing required global setting: ${path}. Run \\`mama --onboard\\` to create settings.json.`,\n );\n }\n return value;\n}\n\nfunction requireThinkingLevel(value: ThinkingLevel | undefined): ThinkingLevel {\n return requireString(value, \"llm.thinkingLevel\") as ThinkingLevel;\n}\n\nfunction requireLogFormat(value: AgentConfig[\"logFormat\"] | undefined): AgentConfig[\"logFormat\"] {\n if (value !== \"console\" && value !== \"json\") {\n throw new Error(\"Missing or invalid required global setting: log.format\");\n }\n return value;\n}\n\nfunction requireLogLevel(value: AgentConfig[\"logLevel\"] | undefined): AgentConfig[\"logLevel\"] {\n const allowed = [\"trace\", \"debug\", \"info\", \"warn\", \"error\"];\n if (!value || !allowed.includes(value)) {\n throw new Error(\"Missing or invalid required global setting: log.level\");\n }\n return value;\n}\n\nfunction toAgentConfig(fromFile: Partial<AgentConfig>): AgentConfig {\n const provider = requireString(fromFile.provider, \"llm.provider\");\n const model = requireString(fromFile.model, \"llm.model\");\n const thinkingLevel = requireThinkingLevel(fromFile.thinkingLevel);\n const logFormat = requireLogFormat(fromFile.logFormat);\n const logLevel = requireLogLevel(fromFile.logLevel);\n const sentryDsn = fromFile.sentryDsn ?? process.env.SENTRY_DSN;\n const sandboxCpus = fromFile.sandboxCpus;\n const sandboxMemory = fromFile.sandboxMemory;\n const sandboxBoostCpus = fromFile.sandboxBoostCpus;\n const sandboxBoostMemory = fromFile.sandboxBoostMemory;\n const sandboxImageWorkspaceMount = fromFile.sandboxImageWorkspaceMount;\n\n return {\n provider,\n model,\n thinkingLevel,\n logFormat,\n logLevel,\n sentryDsn,\n sandboxCpus,\n sandboxMemory,\n sandboxBoostCpus,\n sandboxBoostMemory,\n sandboxImageWorkspaceMount,\n };\n}\n\nfunction loadRawAgentConfig(): Partial<AgentConfig> {\n return normalizeSettingsConfig(requireGlobalSettings());\n}\n\nexport function loadAgentConfig(): AgentConfig {\n return toAgentConfig(loadRawAgentConfig());\n}\n\nexport function loadAgentConfigForConversation(conversationDir: string): AgentConfig {\n const globalConfig = loadRawAgentConfig();\n const conversationConfig = normalizeSettingsConfig(\n loadSettingsFile(join(conversationDir, \"settings.json\")) ?? {},\n );\n return toAgentConfig({ ...globalConfig, ...conversationConfig });\n}\n\nexport function saveConversationModelConfig(\n conversationDir: string,\n config: Pick<AgentConfig, \"provider\" | \"model\"> & Partial<Pick<AgentConfig, \"thinkingLevel\">>,\n): void {\n if (!existsSync(conversationDir)) {\n ensureDirExists(conversationDir);\n }\n const settingsPath = join(conversationDir, \"settings.json\");\n const existing = loadSettingsFile(settingsPath) ?? {};\n const scopedConfig: SettingsFileConfig = {\n ...existing,\n llm: { ...existing.llm, ...config },\n };\n atomicWritePrivateFile(settingsPath, JSON.stringify(scopedConfig, null, 2));\n}\n\nexport function saveConversationSandboxConfig(\n conversationDir: string,\n config: { imageWorkspaceMount: AgentConfig[\"sandboxImageWorkspaceMount\"] },\n): void {\n if (!existsSync(conversationDir)) {\n ensureDirExists(conversationDir);\n }\n const settingsPath = join(conversationDir, \"settings.json\");\n const existing = loadSettingsFile(settingsPath) ?? {};\n const scopedConfig: SettingsFileConfig = {\n ...existing,\n sandbox: {\n ...existing.sandbox,\n image: {\n ...existing.sandbox?.image,\n workspaceMount: config.imageWorkspaceMount,\n },\n },\n };\n atomicWritePrivateFile(settingsPath, JSON.stringify(scopedConfig, null, 2));\n}\n\n/**\n * Resolve the model used to judge auto-reply rules. Falls back to the main\n * llm.{provider,model} when llm.autoReply is not set, so a missing override\n * keeps current behavior.\n */\nexport function loadAutoReplyJudgeModel(conversationDir?: string): JudgeModelConfig {\n const global = requireGlobalSettings();\n const local = conversationDir\n ? (loadSettingsFile(join(conversationDir, \"settings.json\")) ?? {})\n : {};\n const merged: SettingsFileConfig[\"llm\"] = { ...global.llm, ...local.llm };\n const judge = { ...global.llm?.autoReply, ...local.llm?.autoReply };\n const provider = requireString(judge.provider ?? merged?.provider, \"llm.autoReply.provider\");\n const model = requireString(judge.model ?? merged?.model, \"llm.autoReply.model\");\n return { provider, model };\n}\n\nconst AUTO_REPLY_FILE = \"auto-reply\";\nconst AUTO_REPLY_DISABLED_FILE = \"auto-reply.disabled\";\n\nfunction readAutoReplyRulesFile(path: string): string[] {\n const text = readFileSync(path, \"utf-8\").trim();\n return text ? [text] : [];\n}\n\n/**\n * Load the mom-compatible auto-reply marker file state for a conversation.\n *\n * - `auto-reply` exists: enabled; empty file means reply to any top-level message.\n * - `auto-reply.disabled` exists: disabled, preserving any rules text for re-enable.\n * - neither exists: disabled.\n */\nexport function loadConversationAutoReplyConfig(conversationDir: string): AutoReplyConfig {\n const enabledPath = join(conversationDir, AUTO_REPLY_FILE);\n if (existsSync(enabledPath)) {\n return { enabled: true, rules: readAutoReplyRulesFile(enabledPath) };\n }\n\n const disabledPath = join(conversationDir, AUTO_REPLY_DISABLED_FILE);\n if (existsSync(disabledPath)) {\n return { enabled: false, rules: readAutoReplyRulesFile(disabledPath) };\n }\n\n return { enabled: false, rules: [] };\n}\n\n/** Save auto-reply state using mom-compatible marker files. */\nexport function saveConversationAutoReplyConfig(\n conversationDir: string,\n config: AutoReplyConfig,\n): void {\n if (!existsSync(conversationDir)) {\n mkdirSync(conversationDir, { recursive: true });\n }\n\n const enabledPath = join(conversationDir, AUTO_REPLY_FILE);\n const disabledPath = join(conversationDir, AUTO_REPLY_DISABLED_FILE);\n const targetPath = config.enabled ? enabledPath : disabledPath;\n const otherPath = config.enabled ? disabledPath : enabledPath;\n\n if (existsSync(otherPath)) {\n renameSync(otherPath, targetPath);\n }\n\n writeFileSync(targetPath, config.rules.join(\"\\n\"), \"utf-8\");\n}\n\nexport function resolveWorkspaceDirFromArgv(args = process.argv.slice(2)): string | undefined {\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n\n if (arg === \"--sandbox\" || arg === \"--download\" || arg === \"--state-dir\") {\n i += 1;\n continue;\n }\n\n if (arg === \"--version\" || arg === \"-v\" || arg === \"-V\" || arg === \"--onboard\") {\n continue;\n }\n\n if (\n arg.startsWith(\"--sandbox=\") ||\n arg.startsWith(\"--download=\") ||\n arg.startsWith(\"--state-dir=\")\n ) {\n continue;\n }\n\n if (!arg.startsWith(\"-\")) {\n return arg;\n }\n }\n\n return undefined;\n}\n\nexport function resolveStateDirFromArgv(args = process.argv.slice(2)): string {\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg.startsWith(\"--state-dir=\")) {\n return resolve(arg.slice(\"--state-dir=\".length));\n }\n if (arg === \"--state-dir\") {\n return resolve(args[++i] || \"\");\n }\n }\n\n return join(homedir(), \".mama\");\n}\n\nexport function resolveSentryDsn(): string | undefined {\n const fromFile = normalizeSettingsConfig(loadSettingsFile(getSettingsPath()) ?? {});\n if (fromFile.sentryDsn) {\n return fromFile.sentryDsn;\n }\n\n return process.env.SENTRY_DSN;\n}\n\nexport function createGlobalSettingsFile(stateDir: string): string {\n const settingsPath = join(stateDir, \"settings.json\");\n if (existsSync(settingsPath)) {\n throw new Error(`Global settings already exists at ${settingsPath}`);\n }\n if (!existsSync(stateDir)) {\n ensureDirExists(stateDir);\n }\n atomicWritePrivateFile(settingsPath, JSON.stringify(ONBOARD_SETTINGS, null, 2));\n return settingsPath;\n}\n\n/**\n * Externally-visible base URL of the link/OAuth server, e.g.\n * `https://mama.example.com` (no trailing slash). Read from `MAMA_LINK_URL`,\n * the same env var the bot uses to build credential onboarding links.\n */\nexport function resolveLinkBaseUrl(): string | undefined {\n const raw = process.env.MAMA_LINK_URL?.trim();\n if (!raw) return undefined;\n return raw.replace(/\\/+$/, \"\");\n}\n\nfunction hasDefinedValue(values: Record<string, unknown> | undefined): boolean {\n return values !== undefined && Object.values(values).some((value) => value !== undefined);\n}\n\nfunction compactSettingsConfig(config: SettingsFileConfig): SettingsFileConfig {\n return {\n ...(hasDefinedValue(config.llm) ? { llm: config.llm } : {}),\n ...(hasDefinedValue(config.log) ? { log: config.log } : {}),\n ...(hasDefinedValue(config.sentry) ? { sentry: config.sentry } : {}),\n ...(hasDefinedValue(config.sandbox) ? { sandbox: config.sandbox } : {}),\n ...(hasDefinedValue(config.autoReply) ? { autoReply: config.autoReply } : {}),\n };\n}\n\nfunction patchSettingsConfig(\n existing: SettingsFileConfig,\n config: Partial<AgentConfig>,\n): SettingsFileConfig {\n const patched: SettingsFileConfig = {\n ...existing,\n llm: {\n ...existing.llm,\n ...(config.provider !== undefined ? { provider: config.provider } : {}),\n ...(config.model !== undefined ? { model: config.model } : {}),\n ...(config.thinkingLevel !== undefined ? { thinkingLevel: config.thinkingLevel } : {}),\n },\n log: {\n ...existing.log,\n ...(config.logFormat !== undefined ? { format: config.logFormat } : {}),\n ...(config.logLevel !== undefined ? { level: config.logLevel } : {}),\n },\n sentry: {\n ...existing.sentry,\n ...(config.sentryDsn !== undefined ? { dsn: config.sentryDsn } : {}),\n },\n sandbox: {\n ...existing.sandbox,\n ...(config.sandboxCpus !== undefined ? { cpus: config.sandboxCpus } : {}),\n ...(config.sandboxMemory !== undefined ? { memory: config.sandboxMemory } : {}),\n ...(config.sandboxBoostCpus !== undefined || config.sandboxBoostMemory !== undefined\n ? {\n boost: {\n ...existing.sandbox?.boost,\n ...(config.sandboxBoostCpus !== undefined ? { cpus: config.sandboxBoostCpus } : {}),\n ...(config.sandboxBoostMemory !== undefined\n ? { memory: config.sandboxBoostMemory }\n : {}),\n },\n }\n : {}),\n },\n };\n return compactSettingsConfig(patched);\n}\n\nexport function saveAgentConfig(config: Partial<AgentConfig>): void {\n const settingsPath = join(getStateDir(), \"settings.json\");\n\n let existing: SettingsFileConfig = ONBOARD_SETTINGS;\n if (existsSync(settingsPath)) {\n try {\n existing = loadSettingsFile(settingsPath) ?? {};\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n const message = detail.startsWith(\"Malformed settings file\")\n ? detail.replace(\"Malformed settings file\", \"Refusing to overwrite malformed settings file\")\n : detail;\n throw new Error(message, { cause: err });\n }\n }\n\n const merged = patchSettingsConfig(existing, config);\n\n const dir = dirname(settingsPath);\n ensureDirExists(dir);\n\n atomicWritePrivateFile(settingsPath, JSON.stringify(merged, null, 2));\n}\n"]}
package/dist/context.d.ts CHANGED
@@ -1,48 +1,16 @@
1
1
  /**
2
- * Context management for mama.
2
+ * Human-readable log.jsonl entry used for grep/debugging and a few metadata lookups.
3
3
  *
4
- * Mama uses two data sources per conversation:
5
- * - sessions/*.jsonl: Structured session history for the agent context
6
- * - log.jsonl: Human-readable conversation history for grep (no tool results)
7
- *
8
- * This module provides:
9
- * - syncLogToSessionManager: Syncs messages from log.jsonl to SessionManager
10
- * - createMamaSettingsManager: Creates a SettingsManager backed by workspace settings.json
11
- */
12
- import { type SessionManager, SettingsManager } from "@mariozechner/pi-coding-agent";
13
- /**
14
- * Time range for filtering log messages
15
- */
16
- export interface TimeRange {
17
- start: number;
18
- end: number;
19
- }
20
- /**
21
- * Thread filter for scoping log sync to a specific thread session.
22
- * When provided, only messages belonging to this thread are synced,
23
- * preventing cross-thread context contamination.
4
+ * Runtime conversation context comes from sessions/*.jsonl, not from this file.
24
5
  */
25
- export interface ThreadFilter {
26
- /** Filter mode: a specific thread, or top-level messages only for persistent channel/chat sessions */
27
- scope?: "thread" | "top-level";
28
- /** The root message timestamp (user's original message ts, derived from sessionKey) */
29
- rootTs: string;
30
- /** The thread anchor timestamp (bot's first reply ts, used as thread_ts by Slack replies) */
6
+ export interface ConversationLogMessage {
7
+ date?: string;
8
+ ts?: string;
31
9
  threadTs?: string;
10
+ user?: string;
11
+ userName?: string;
12
+ text?: string;
13
+ isBot?: boolean;
32
14
  }
33
- /**
34
- * Sync user messages from log.jsonl to SessionManager.
35
- *
36
- * This ensures that messages logged while mama wasn't running (conversation chatter,
37
- * backfilled messages, messages while busy) are added to the LLM context.
38
- *
39
- * @param sessionManager - The SessionManager to sync to
40
- * @param conversationDir - Path to the conversation directory containing log.jsonl
41
- * @param excludeSlackTs - Slack timestamp of current message (will be added via prompt(), not sync)
42
- * @param timeRange - Optional time range to filter log entries (defaults to last 10 days)
43
- * @param threadFilter - Optional thread filter to scope sync to a specific thread
44
- * @returns Number of messages synced
45
- */
46
- export declare function syncLogToSessionManager(sessionManager: SessionManager, conversationDir: string, excludeSlackTs?: string, timeRange?: TimeRange, threadFilter?: ThreadFilter): Promise<number>;
47
- export declare function createMamaSettingsManager(_workspaceDir: string): SettingsManager;
15
+ export declare function findLogMessageById(conversationDir: string, messageId: string): Promise<ConversationLogMessage | null>;
48
16
  //# sourceMappingURL=context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EACL,KAAK,cAAc,EAEnB,eAAe,EAChB,MAAM,+BAA+B,CAAC;AASvC;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAiBD;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,sGAAsG;IACtG,KAAK,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;IAC/B,uFAAuF;IACvF,MAAM,EAAE,MAAM,CAAC;IACf,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAC3C,cAAc,EAAE,cAAc,EAC9B,eAAe,EAAE,MAAM,EACvB,cAAc,CAAC,EAAE,MAAM,EACvB,SAAS,CAAC,EAAE,SAAS,EACrB,YAAY,CAAC,EAAE,YAAY,GAC1B,OAAO,CAAC,MAAM,CAAC,CAyGjB;AASD,wBAAgB,yBAAyB,CAAC,aAAa,EAAE,MAAM,GAAG,eAAe,CAEhF","sourcesContent":["/**\n * Context management for mama.\n *\n * Mama uses two data sources per conversation:\n * - sessions/*.jsonl: Structured session history for the agent context\n * - log.jsonl: Human-readable conversation history for grep (no tool results)\n *\n * This module provides:\n * - syncLogToSessionManager: Syncs messages from log.jsonl to SessionManager\n * - createMamaSettingsManager: Creates a SettingsManager backed by workspace settings.json\n */\n\nimport type { UserMessage } from \"@mariozechner/pi-ai\";\nimport {\n type SessionManager,\n type SessionMessageEntry,\n SettingsManager,\n} from \"@mariozechner/pi-coding-agent\";\nimport { existsSync } from \"fs\";\nimport { readFile } from \"fs/promises\";\nimport { join } from \"path\";\n\n// ============================================================================\n// Sync log.jsonl to SessionManager\n// ============================================================================\n\n/**\n * Time range for filtering log messages\n */\nexport interface TimeRange {\n start: number; // Unix timestamp in ms\n end: number;\n}\n\n/**\n * Default number of days to sync when no time range is specified\n */\nconst DEFAULT_SYNC_DAYS = 10;\n\ninterface LogMessage {\n date?: string;\n ts?: string;\n threadTs?: string;\n user?: string;\n userName?: string;\n text?: string;\n isBot?: boolean;\n}\n\n/**\n * Thread filter for scoping log sync to a specific thread session.\n * When provided, only messages belonging to this thread are synced,\n * preventing cross-thread context contamination.\n */\nexport interface ThreadFilter {\n /** Filter mode: a specific thread, or top-level messages only for persistent channel/chat sessions */\n scope?: \"thread\" | \"top-level\";\n /** The root message timestamp (user's original message ts, derived from sessionKey) */\n rootTs: string;\n /** The thread anchor timestamp (bot's first reply ts, used as thread_ts by Slack replies) */\n threadTs?: string;\n}\n\n/**\n * Sync user messages from log.jsonl to SessionManager.\n *\n * This ensures that messages logged while mama wasn't running (conversation chatter,\n * backfilled messages, messages while busy) are added to the LLM context.\n *\n * @param sessionManager - The SessionManager to sync to\n * @param conversationDir - Path to the conversation directory containing log.jsonl\n * @param excludeSlackTs - Slack timestamp of current message (will be added via prompt(), not sync)\n * @param timeRange - Optional time range to filter log entries (defaults to last 10 days)\n * @param threadFilter - Optional thread filter to scope sync to a specific thread\n * @returns Number of messages synced\n */\nexport async function syncLogToSessionManager(\n sessionManager: SessionManager,\n conversationDir: string,\n excludeSlackTs?: string,\n timeRange?: TimeRange,\n threadFilter?: ThreadFilter,\n): Promise<number> {\n // Calculate default time range (last 10 days) if not provided\n const now = Date.now();\n const defaultStart = now - DEFAULT_SYNC_DAYS * 24 * 60 * 60 * 1000;\n const range = timeRange ?? { start: defaultStart, end: now };\n const logFile = join(conversationDir, \"log.jsonl\");\n\n if (!existsSync(logFile)) return 0;\n\n // Build set of existing timestamps from session entries\n // We use ts (Slack timestamp) as the unique key instead of message content\n const existingTimestamps = new Set<string>();\n for (const entry of sessionManager.getEntries()) {\n if (entry.type === \"message\") {\n const msgEntry = entry as SessionMessageEntry;\n // SessionMessageEntry has a timestamp field (number, Unix ms)\n if (msgEntry.timestamp) {\n existingTimestamps.add(msgEntry.timestamp.toString());\n }\n }\n }\n\n // Read log.jsonl and find user messages not in context\n const logContent = await readFile(logFile, \"utf-8\");\n const logLines = logContent.trim().split(\"\\n\").filter(Boolean);\n\n const newMessages: Array<{ timestamp: number; message: UserMessage }> = [];\n\n for (const line of logLines) {\n try {\n const logMsg: LogMessage = JSON.parse(line);\n\n const slackTs = logMsg.ts;\n const date = logMsg.date;\n if (!slackTs || !date) continue;\n\n // Skip the current message being processed (will be added via prompt())\n if (excludeSlackTs && slackTs === excludeSlackTs) continue;\n\n // Skip bot messages - added through agent flow\n if (logMsg.isBot) continue;\n\n // Thread filtering: only sync messages belonging to this session's thread\n if (threadFilter) {\n if (threadFilter.scope === \"top-level\") {\n // Persistent top-level sessions should only ingest top-level messages.\n // This avoids pulling in unrelated replies from other threads.\n if (logMsg.threadTs) {\n continue;\n }\n } else {\n if (logMsg.threadTs) {\n // Thread reply: only include if threadTs matches our thread anchor or rootTs\n if (\n logMsg.threadTs !== threadFilter.threadTs &&\n logMsg.threadTs !== threadFilter.rootTs\n ) {\n continue;\n }\n } else {\n // Top-level message: only include if it's this session's root message\n if (slackTs !== threadFilter.rootTs) {\n continue;\n }\n }\n }\n }\n\n // Skip if this Slack timestamp is already in the session (dedupe by ts, not content)\n // Convert Slack ts (e.g., \"1234567890.123456\") to Unix ms for comparison\n const slackTsMs = Math.floor(parseFloat(slackTs) * 1000).toString();\n if (existingTimestamps.has(slackTsMs)) continue;\n\n // Build the message text as it would appear in context\n const threadContext = logMsg.threadTs ? ` [in-thread:${logMsg.threadTs}]` : \"\";\n const messageText = `[${logMsg.userName || logMsg.user || \"unknown\"}]${threadContext}: ${logMsg.text || \"\"}`;\n\n const msgTime = new Date(date).getTime() || Date.now();\n\n // Skip messages outside the time range\n if (msgTime < range.start || msgTime > range.end) continue;\n\n const userMessage: UserMessage = {\n role: \"user\",\n content: [{ type: \"text\", text: messageText }],\n timestamp: msgTime,\n };\n\n newMessages.push({ timestamp: msgTime, message: userMessage });\n existingTimestamps.add(slackTsMs); // Track to avoid duplicates within this sync\n } catch {\n // Skip malformed lines\n }\n }\n\n if (newMessages.length === 0) return 0;\n\n // Sort by timestamp and add to session\n newMessages.sort((a, b) => a.timestamp - b.timestamp);\n\n for (const { message } of newMessages) {\n sessionManager.appendMessage(message);\n }\n\n return newMessages.length;\n}\n\n// ============================================================================\n// Settings manager for mama\n// ============================================================================\n\n// Mama manages model/provider config through its own config.ts / settings.json.\n// We use an in-memory SettingsManager so AgentSession has valid defaults\n// without interfering with coding-agent's global settings files.\nexport function createMamaSettingsManager(_workspaceDir: string): SettingsManager {\n return SettingsManager.inMemory();\n}\n"]}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAsB,kBAAkB,CACtC,eAAe,EAAE,MAAM,EACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CA0BxC","sourcesContent":["import { join } from \"path\";\nimport { isRecord, parseJsonValue, readTextFileIfExists } from \"./file-guards.js\";\nimport * as log from \"./log.js\";\n\n/**\n * Human-readable log.jsonl entry used for grep/debugging and a few metadata lookups.\n *\n * Runtime conversation context comes from sessions/*.jsonl, not from this file.\n */\nexport interface ConversationLogMessage {\n date?: string;\n ts?: string;\n threadTs?: string;\n user?: string;\n userName?: string;\n text?: string;\n isBot?: boolean;\n}\n\nexport async function findLogMessageById(\n conversationDir: string,\n messageId: string,\n): Promise<ConversationLogMessage | null> {\n const logFile = join(conversationDir, \"log.jsonl\");\n const logContent = readTextFileIfExists(logFile);\n if (logContent === undefined) return null;\n const logLines = logContent.trim().split(\"\\n\").filter(Boolean);\n\n for (let i = logLines.length - 1; i >= 0; i--) {\n try {\n const entry = parseJsonValue(\n logLines[i],\n (value): value is ConversationLogMessage => isRecord(value),\n (detail) => (detail === \"unexpected JSON shape\" ? \"expected a JSON object\" : detail),\n );\n if (entry.ts === messageId) {\n return entry;\n }\n } catch (err) {\n log.logWarning(\n `Skipping malformed log entry at ${logFile}:${i + 1}`,\n err instanceof Error ? err.message : String(err),\n );\n continue;\n }\n }\n\n return null;\n}\n"]}