@geminixiang/mama 0.2.0-beta.0 → 0.2.0-beta.10
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.
- package/README.md +171 -334
- package/dist/adapter.d.ts +36 -10
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/adapters/discord/bot.d.ts +10 -5
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +349 -114
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/discord/context.d.ts +1 -1
- package/dist/adapters/discord/context.d.ts.map +1 -1
- package/dist/adapters/discord/context.js +102 -31
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/shared.d.ts +71 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/adapters/shared.js +168 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/adapters/slack/bot.d.ts +29 -22
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +620 -186
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/branch-manager.d.ts +22 -0
- package/dist/adapters/slack/branch-manager.d.ts.map +1 -0
- package/dist/adapters/slack/branch-manager.js +97 -0
- package/dist/adapters/slack/branch-manager.js.map +1 -0
- package/dist/adapters/slack/context.d.ts +1 -1
- package/dist/adapters/slack/context.d.ts.map +1 -1
- package/dist/adapters/slack/context.js +136 -71
- package/dist/adapters/slack/context.js.map +1 -1
- package/dist/adapters/slack/session.d.ts +3 -0
- package/dist/adapters/slack/session.d.ts.map +1 -0
- package/dist/adapters/slack/session.js +16 -0
- package/dist/adapters/slack/session.js.map +1 -0
- package/dist/adapters/slack/tools/attach.d.ts +1 -1
- package/dist/adapters/slack/tools/attach.d.ts.map +1 -1
- package/dist/adapters/slack/tools/attach.js.map +1 -1
- package/dist/adapters/telegram/bot.d.ts +2 -0
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +190 -123
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/adapters/telegram/context.d.ts.map +1 -1
- package/dist/adapters/telegram/context.js +57 -59
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/adapters/telegram/html.d.ts +3 -0
- package/dist/adapters/telegram/html.d.ts.map +1 -0
- package/dist/adapters/telegram/html.js +98 -0
- package/dist/adapters/telegram/html.js.map +1 -0
- package/dist/agent.d.ts +9 -10
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +645 -555
- package/dist/agent.js.map +1 -1
- package/dist/commands/auto-reply.d.ts +16 -0
- package/dist/commands/auto-reply.d.ts.map +1 -0
- package/dist/commands/auto-reply.js +69 -0
- package/dist/commands/auto-reply.js.map +1 -0
- package/dist/commands/index.d.ts +5 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +19 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/login.d.ts +5 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +76 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/model.d.ts +14 -0
- package/dist/commands/model.d.ts.map +1 -0
- package/dist/commands/model.js +112 -0
- package/dist/commands/model.js.map +1 -0
- package/dist/commands/new.d.ts +9 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +28 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/registry.d.ts +7 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +14 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/commands/sandbox.d.ts +10 -0
- package/dist/commands/sandbox.d.ts.map +1 -0
- package/dist/commands/sandbox.js +88 -0
- package/dist/commands/sandbox.js.map +1 -0
- package/dist/commands/session-view.d.ts +5 -0
- package/dist/commands/session-view.d.ts.map +1 -0
- package/dist/commands/session-view.js +62 -0
- package/dist/commands/session-view.js.map +1 -0
- package/dist/commands/types.d.ts +41 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/commands/utils.d.ts +8 -0
- package/dist/commands/utils.d.ts.map +1 -0
- package/dist/commands/utils.js +14 -0
- package/dist/commands/utils.js.map +1 -0
- package/dist/config.d.ts +53 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +320 -55
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +10 -42
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +15 -128
- package/dist/context.js.map +1 -1
- package/dist/events.d.ts +16 -5
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +127 -58
- package/dist/events.js.map +1 -1
- package/dist/execution-resolver.d.ts +24 -0
- package/dist/execution-resolver.d.ts.map +1 -0
- package/dist/execution-resolver.js +115 -0
- package/dist/execution-resolver.js.map +1 -0
- package/dist/file-guards.d.ts +6 -0
- package/dist/file-guards.d.ts.map +1 -0
- package/dist/file-guards.js +48 -0
- package/dist/file-guards.js.map +1 -0
- package/dist/fs-atomic.d.ts +10 -0
- package/dist/fs-atomic.d.ts.map +1 -0
- package/dist/fs-atomic.js +45 -0
- package/dist/fs-atomic.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/instrument.d.ts.map +1 -1
- package/dist/instrument.js +3 -3
- package/dist/instrument.js.map +1 -1
- package/dist/log.d.ts +3 -7
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +20 -45
- package/dist/log.js.map +1 -1
- package/dist/login/index.d.ts +41 -0
- package/dist/login/index.d.ts.map +1 -0
- package/dist/login/index.js +202 -0
- package/dist/login/index.js.map +1 -0
- package/dist/login/portal.d.ts +19 -0
- package/dist/login/portal.d.ts.map +1 -0
- package/dist/login/portal.js +1453 -0
- package/dist/login/portal.js.map +1 -0
- package/dist/login/session.d.ts +33 -0
- package/dist/login/session.d.ts.map +1 -0
- package/dist/login/session.js +68 -0
- package/dist/login/session.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +229 -264
- package/dist/main.js.map +1 -1
- package/dist/provisioner.d.ts +79 -0
- package/dist/provisioner.d.ts.map +1 -0
- package/dist/provisioner.js +437 -0
- package/dist/provisioner.js.map +1 -0
- package/dist/runtime/conversation-orchestrator.d.ts +42 -0
- package/dist/runtime/conversation-orchestrator.d.ts.map +1 -0
- package/dist/runtime/conversation-orchestrator.js +150 -0
- package/dist/runtime/conversation-orchestrator.js.map +1 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/session-runtime.d.ts +27 -0
- package/dist/runtime/session-runtime.d.ts.map +1 -0
- package/dist/runtime/session-runtime.js +211 -0
- package/dist/runtime/session-runtime.js.map +1 -0
- package/dist/sandbox/cloudflare.d.ts +15 -0
- package/dist/sandbox/cloudflare.d.ts.map +1 -0
- package/dist/sandbox/cloudflare.js +137 -0
- package/dist/sandbox/cloudflare.js.map +1 -0
- package/dist/sandbox/container.d.ts +16 -0
- package/dist/sandbox/container.d.ts.map +1 -0
- package/dist/sandbox/container.js +126 -0
- package/dist/sandbox/container.js.map +1 -0
- package/dist/sandbox/errors.d.ts +6 -0
- package/dist/sandbox/errors.d.ts.map +1 -0
- package/dist/sandbox/errors.js +11 -0
- package/dist/sandbox/errors.js.map +1 -0
- package/dist/sandbox/firecracker.d.ts +17 -0
- package/dist/sandbox/firecracker.d.ts.map +1 -0
- package/dist/sandbox/firecracker.js +212 -0
- package/dist/sandbox/firecracker.js.map +1 -0
- package/dist/sandbox/host.d.ts +11 -0
- package/dist/sandbox/host.d.ts.map +1 -0
- package/dist/sandbox/host.js +89 -0
- package/dist/sandbox/host.js.map +1 -0
- package/dist/sandbox/image.d.ts +5 -0
- package/dist/sandbox/image.d.ts.map +1 -0
- package/dist/sandbox/image.js +30 -0
- package/dist/sandbox/image.js.map +1 -0
- package/dist/sandbox/index.d.ts +22 -0
- package/dist/sandbox/index.d.ts.map +1 -0
- package/dist/sandbox/index.js +54 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/sandbox/path-context.d.ts +4 -0
- package/dist/sandbox/path-context.d.ts.map +1 -0
- package/dist/sandbox/path-context.js +20 -0
- package/dist/sandbox/path-context.js.map +1 -0
- package/dist/sandbox/types.d.ts +67 -0
- package/dist/sandbox/types.d.ts.map +1 -0
- package/dist/sandbox/types.js +2 -0
- package/dist/sandbox/types.js.map +1 -0
- package/dist/sandbox/utils.d.ts +4 -0
- package/dist/sandbox/utils.d.ts.map +1 -0
- package/dist/sandbox/utils.js +51 -0
- package/dist/sandbox/utils.js.map +1 -0
- package/dist/sandbox.d.ts +1 -39
- package/dist/sandbox.d.ts.map +1 -1
- package/dist/sandbox.js +1 -286
- package/dist/sandbox.js.map +1 -1
- package/dist/sentry.d.ts +2 -2
- package/dist/sentry.d.ts.map +1 -1
- package/dist/sentry.js +6 -4
- package/dist/sentry.js.map +1 -1
- package/dist/session-policy.d.ts +13 -0
- package/dist/session-policy.d.ts.map +1 -0
- package/dist/session-policy.js +23 -0
- package/dist/session-policy.js.map +1 -0
- package/dist/session-store.d.ts +35 -8
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +182 -23
- package/dist/session-store.js.map +1 -1
- package/dist/session-view/command.d.ts +5 -0
- package/dist/session-view/command.d.ts.map +1 -0
- package/dist/session-view/command.js +11 -0
- package/dist/session-view/command.js.map +1 -0
- package/dist/session-view/portal.d.ts +16 -0
- package/dist/session-view/portal.d.ts.map +1 -0
- package/dist/session-view/portal.js +1742 -0
- package/dist/session-view/portal.js.map +1 -0
- package/dist/session-view/service.d.ts +34 -0
- package/dist/session-view/service.d.ts.map +1 -0
- package/dist/session-view/service.js +427 -0
- package/dist/session-view/service.js.map +1 -0
- package/dist/session-view/store.d.ts +18 -0
- package/dist/session-view/store.d.ts.map +1 -0
- package/dist/session-view/store.js +39 -0
- package/dist/session-view/store.js.map +1 -0
- package/dist/store.d.ts +4 -7
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +26 -52
- package/dist/store.js.map +1 -1
- package/dist/tool-diagnostics.d.ts +2 -0
- package/dist/tool-diagnostics.d.ts.map +1 -0
- package/dist/tool-diagnostics.js +7 -0
- package/dist/tool-diagnostics.js.map +1 -0
- package/dist/tools/bash.d.ts +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/event.d.ts +62 -0
- package/dist/tools/event.d.ts.map +1 -0
- package/dist/tools/event.js +138 -0
- package/dist/tools/event.js.map +1 -0
- package/dist/tools/index.d.ts +8 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +5 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/read.d.ts +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/write.d.ts +1 -1
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js.map +1 -1
- package/dist/trigger.d.ts +31 -0
- package/dist/trigger.d.ts.map +1 -0
- package/dist/trigger.js +98 -0
- package/dist/trigger.js.map +1 -0
- package/dist/ui-copy.d.ts +12 -0
- package/dist/ui-copy.d.ts.map +1 -0
- package/dist/ui-copy.js +36 -0
- package/dist/ui-copy.js.map +1 -0
- package/dist/vault-routing.d.ts +4 -0
- package/dist/vault-routing.d.ts.map +1 -0
- package/dist/vault-routing.js +16 -0
- package/dist/vault-routing.js.map +1 -0
- package/dist/vault.d.ts +72 -0
- package/dist/vault.d.ts.map +1 -0
- package/dist/vault.js +264 -0
- package/dist/vault.js.map +1 -0
- package/package.json +16 -13
package/dist/main.js
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./instrument.js";
|
|
3
3
|
import { join, resolve } from "path";
|
|
4
|
-
import {
|
|
4
|
+
import { mkdirSync, statSync, writeFileSync } from "fs";
|
|
5
|
+
import { homedir } from "os";
|
|
5
6
|
import { fileURLToPath } from "url";
|
|
6
7
|
import { dirname, join as pathJoin } from "path";
|
|
7
8
|
import { DiscordBot } from "./adapters/discord/index.js";
|
|
8
9
|
import { TelegramBot } from "./adapters/telegram/index.js";
|
|
9
10
|
import { SlackBot as SlackBotClass } from "./adapters/slack/index.js";
|
|
10
|
-
import { createRunner } from "./agent.js";
|
|
11
|
-
import { createManagedSessionFile, createManagedSessionFileAtPath, getSessionDir, getThreadSessionFile, } from "./session-store.js";
|
|
12
11
|
import { downloadChannel } from "./download.js";
|
|
13
12
|
import { createEventsWatcher } from "./events.js";
|
|
14
13
|
import * as log from "./log.js";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
14
|
+
import { startLinkServer } from "./login/portal.js";
|
|
15
|
+
import { InMemoryLinkTokenStore } from "./login/session.js";
|
|
16
|
+
import { InMemorySessionViewTokenStore } from "./session-view/store.js";
|
|
17
|
+
import { DockerContainerManager } from "./provisioner.js";
|
|
18
|
+
import { createGlobalSettingsFile, loadAgentConfig, MissingGlobalSettingsError } from "./config.js";
|
|
19
|
+
import { ensureDirExists, isRecord, readJsonFileIfExists } from "./file-guards.js";
|
|
20
|
+
import { SandboxError, parseSandboxArg, validateSandbox } from "./sandbox.js";
|
|
21
|
+
import { FileVaultManager } from "./vault.js";
|
|
22
|
+
import { createSessionRuntime } from "./runtime/index.js";
|
|
17
23
|
import { ChannelStore } from "./store.js";
|
|
18
24
|
import * as Sentry from "@sentry/node";
|
|
19
25
|
// ============================================================================
|
|
@@ -28,38 +34,50 @@ function getVersion() {
|
|
|
28
34
|
pathJoin(process.cwd(), "package.json"),
|
|
29
35
|
];
|
|
30
36
|
for (const pkgPath of possiblePaths) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return pkg.version;
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
// Continue to next path
|
|
38
|
-
}
|
|
37
|
+
const pkg = readJsonFileIfExists(pkgPath, (value) => isRecord(value), () => "Ignoring package.json while resolving version");
|
|
38
|
+
if (typeof pkg?.version === "string" && pkg.version)
|
|
39
|
+
return pkg.version;
|
|
39
40
|
}
|
|
40
41
|
return "unknown";
|
|
41
42
|
}
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const
|
|
43
|
+
const MAMA_SLACK_APP_TOKEN = process.env.MAMA_SLACK_APP_TOKEN;
|
|
44
|
+
const MAMA_SLACK_BOT_TOKEN = process.env.MAMA_SLACK_BOT_TOKEN;
|
|
45
|
+
const MAMA_TELEGRAM_BOT_TOKEN = process.env.MAMA_TELEGRAM_BOT_TOKEN;
|
|
46
|
+
const MAMA_DISCORD_BOT_TOKEN = process.env.MAMA_DISCORD_BOT_TOKEN;
|
|
47
|
+
const MAMA_LINK_URL = process.env.MAMA_LINK_URL;
|
|
48
|
+
const MAMA_LINK_PORT = process.env.MAMA_LINK_PORT
|
|
49
|
+
? parseInt(process.env.MAMA_LINK_PORT, 10)
|
|
50
|
+
: MAMA_LINK_URL
|
|
51
|
+
? 8181
|
|
52
|
+
: undefined;
|
|
46
53
|
function parseArgs() {
|
|
47
54
|
const args = process.argv.slice(2);
|
|
48
55
|
let sandbox = { type: "host" };
|
|
49
56
|
let workingDir;
|
|
57
|
+
let stateDirArg;
|
|
50
58
|
let downloadChannelId;
|
|
59
|
+
let showOnboard = false;
|
|
51
60
|
let showVersion = false;
|
|
52
61
|
for (let i = 0; i < args.length; i++) {
|
|
53
62
|
const arg = args[i];
|
|
54
63
|
if (arg === "--version" || arg === "-v" || arg === "-V") {
|
|
55
64
|
showVersion = true;
|
|
56
65
|
}
|
|
66
|
+
else if (arg === "--onboard") {
|
|
67
|
+
showOnboard = true;
|
|
68
|
+
}
|
|
57
69
|
else if (arg.startsWith("--sandbox=")) {
|
|
58
70
|
sandbox = parseSandboxArg(arg.slice("--sandbox=".length));
|
|
59
71
|
}
|
|
60
72
|
else if (arg === "--sandbox") {
|
|
61
73
|
sandbox = parseSandboxArg(args[++i] || "");
|
|
62
74
|
}
|
|
75
|
+
else if (arg.startsWith("--state-dir=")) {
|
|
76
|
+
stateDirArg = arg.slice("--state-dir=".length);
|
|
77
|
+
}
|
|
78
|
+
else if (arg === "--state-dir") {
|
|
79
|
+
stateDirArg = args[++i];
|
|
80
|
+
}
|
|
63
81
|
else if (arg.startsWith("--download=")) {
|
|
64
82
|
downloadChannelId = arg.slice("--download=".length);
|
|
65
83
|
}
|
|
@@ -72,283 +90,225 @@ function parseArgs() {
|
|
|
72
90
|
}
|
|
73
91
|
return {
|
|
74
92
|
workingDir: workingDir ? resolve(workingDir) : undefined,
|
|
93
|
+
stateDir: stateDirArg ? resolve(stateDirArg) : undefined,
|
|
75
94
|
sandbox,
|
|
76
95
|
downloadChannel: downloadChannelId,
|
|
96
|
+
showOnboard,
|
|
77
97
|
showVersion,
|
|
78
98
|
};
|
|
79
99
|
}
|
|
80
|
-
const
|
|
100
|
+
const WORLD_WRITABLE_MODE = 0o002;
|
|
101
|
+
function ensureSecureStateDir(path) {
|
|
102
|
+
let stat;
|
|
103
|
+
try {
|
|
104
|
+
stat = statSync(path);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
const code = err.code;
|
|
108
|
+
if (code === "ENOENT") {
|
|
109
|
+
mkdirSync(path, { recursive: true, mode: 0o700 });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
console.error(`Error: cannot access --state-dir ${path}: ${err.message}`);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
if (!stat.isDirectory()) {
|
|
116
|
+
console.error(`Error: --state-dir ${path} exists but is not a directory`);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
if (stat.mode & WORLD_WRITABLE_MODE) {
|
|
120
|
+
console.error(`Error: --state-dir ${path} is world-writable (mode ${(stat.mode & 0o777).toString(8)}). ` +
|
|
121
|
+
`Credentials stored there would be exposed to other local users. ` +
|
|
122
|
+
`Fix with: chmod 0700 ${path}`);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
const euid = typeof process.geteuid === "function" ? process.geteuid() : undefined;
|
|
126
|
+
if (euid !== undefined && stat.uid !== euid) {
|
|
127
|
+
console.error(`Error: --state-dir ${path} is owned by uid ${stat.uid} but mama is running as uid ${euid}. ` +
|
|
128
|
+
`Run mama as the directory owner or point --state-dir at a directory you own.`);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function handleStartupError(error) {
|
|
133
|
+
if (error instanceof SandboxError) {
|
|
134
|
+
for (const line of error.formatForCli()) {
|
|
135
|
+
console.error(line);
|
|
136
|
+
}
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
if (error instanceof MissingGlobalSettingsError) {
|
|
140
|
+
console.error(`Missing global settings: ${error.settingsPath}`);
|
|
141
|
+
console.error("");
|
|
142
|
+
console.error("Run onboarding to create it:");
|
|
143
|
+
console.error(` mama --onboard --state-dir ${stateDir}`);
|
|
144
|
+
console.error("");
|
|
145
|
+
console.error("Then review the generated settings.json and start mama again.");
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
if (error instanceof Error) {
|
|
149
|
+
console.error(`Error: ${error.message}`);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
console.error(String(error));
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
let parsedArgs;
|
|
156
|
+
try {
|
|
157
|
+
parsedArgs = parseArgs();
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
handleStartupError(error);
|
|
161
|
+
}
|
|
81
162
|
// Handle --version
|
|
82
163
|
if (parsedArgs.showVersion) {
|
|
83
164
|
console.log(getVersion());
|
|
84
165
|
process.exit(0);
|
|
85
166
|
}
|
|
167
|
+
// Handle --onboard mode
|
|
168
|
+
if (parsedArgs.showOnboard) {
|
|
169
|
+
const stateDir = parsedArgs.stateDir ?? join(homedir(), ".mama");
|
|
170
|
+
process.env.MAMA_STATE_DIR = stateDir;
|
|
171
|
+
ensureSecureStateDir(stateDir);
|
|
172
|
+
try {
|
|
173
|
+
const settingsPath = createGlobalSettingsFile(stateDir);
|
|
174
|
+
console.log(`Created global settings at ${settingsPath}`);
|
|
175
|
+
console.log("Review the file, then start mama with your working directory.");
|
|
176
|
+
process.exit(0);
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
86
183
|
// Handle --download mode (Slack only)
|
|
87
184
|
if (parsedArgs.downloadChannel) {
|
|
88
|
-
if (!
|
|
89
|
-
console.error("Missing env:
|
|
185
|
+
if (!MAMA_SLACK_BOT_TOKEN) {
|
|
186
|
+
console.error("Missing env: MAMA_SLACK_BOT_TOKEN");
|
|
90
187
|
process.exit(1);
|
|
91
188
|
}
|
|
92
|
-
await downloadChannel(parsedArgs.downloadChannel,
|
|
189
|
+
await downloadChannel(parsedArgs.downloadChannel, MAMA_SLACK_BOT_TOKEN);
|
|
93
190
|
process.exit(0);
|
|
94
191
|
}
|
|
95
192
|
// Normal bot mode - require working dir
|
|
96
193
|
if (!parsedArgs.workingDir) {
|
|
97
|
-
console.error("Usage: mama [--sandbox=host|
|
|
194
|
+
console.error("Usage: mama [--state-dir=<dir>] [--sandbox=host|container:<name>|image:<image>|firecracker:<vm-id>:<host-path>|cloudflare:<sandbox-id>] <working-directory>");
|
|
195
|
+
console.error(" mama --onboard [--state-dir=<dir>]");
|
|
98
196
|
console.error(" mama --download <channel-id>");
|
|
99
197
|
process.exit(1);
|
|
100
198
|
}
|
|
101
199
|
const { workingDir, sandbox } = { workingDir: parsedArgs.workingDir, sandbox: parsedArgs.sandbox };
|
|
200
|
+
const stateDir = parsedArgs.stateDir ?? join(homedir(), ".mama");
|
|
201
|
+
process.env.MAMA_STATE_DIR = stateDir;
|
|
202
|
+
ensureSecureStateDir(stateDir);
|
|
102
203
|
// Validate platform tokens
|
|
103
|
-
const hasSlack = !!(
|
|
104
|
-
const hasTelegram = !!
|
|
105
|
-
const hasDiscord = !!
|
|
204
|
+
const hasSlack = !!(MAMA_SLACK_APP_TOKEN && MAMA_SLACK_BOT_TOKEN);
|
|
205
|
+
const hasTelegram = !!MAMA_TELEGRAM_BOT_TOKEN;
|
|
206
|
+
const hasDiscord = !!MAMA_DISCORD_BOT_TOKEN;
|
|
106
207
|
if (!hasSlack && !hasTelegram && !hasDiscord) {
|
|
107
208
|
console.error("No platform tokens found. Set one of:\n" +
|
|
108
|
-
" Slack:
|
|
109
|
-
" Telegram:
|
|
110
|
-
" Discord:
|
|
209
|
+
" Slack: MAMA_SLACK_APP_TOKEN + MAMA_SLACK_BOT_TOKEN\n" +
|
|
210
|
+
" Telegram: MAMA_TELEGRAM_BOT_TOKEN\n" +
|
|
211
|
+
" Discord: MAMA_DISCORD_BOT_TOKEN");
|
|
111
212
|
process.exit(1);
|
|
112
213
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
runner: await createRunner(sandbox, key, channelId, channelDir, workingDir),
|
|
131
|
-
stopRequested: false,
|
|
132
|
-
lastAccessedAt: Date.now(),
|
|
133
|
-
};
|
|
134
|
-
channelStates.set(key, state);
|
|
214
|
+
try {
|
|
215
|
+
await validateSandbox(sandbox);
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
handleStartupError(error);
|
|
219
|
+
}
|
|
220
|
+
const vaultManager = new FileVaultManager(stateDir);
|
|
221
|
+
if (vaultManager.isEnabled()) {
|
|
222
|
+
console.log(sandbox.type === "container"
|
|
223
|
+
? " Vault system enabled. Container vault active."
|
|
224
|
+
: sandbox.type === "image" || sandbox.type === "firecracker" || sandbox.type === "cloudflare"
|
|
225
|
+
? " Vault system enabled. Conversation-scoped credential routing active."
|
|
226
|
+
: " Vault system enabled. Host mode will not inject vault env.");
|
|
227
|
+
}
|
|
228
|
+
const startupConfig = (() => {
|
|
229
|
+
try {
|
|
230
|
+
return loadAgentConfig();
|
|
135
231
|
}
|
|
136
|
-
|
|
137
|
-
|
|
232
|
+
catch (error) {
|
|
233
|
+
handleStartupError(error);
|
|
138
234
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
235
|
+
})();
|
|
236
|
+
const sandboxLimits = startupConfig.sandboxCpus || startupConfig.sandboxMemory
|
|
237
|
+
? { cpus: startupConfig.sandboxCpus, memory: startupConfig.sandboxMemory }
|
|
238
|
+
: undefined;
|
|
239
|
+
const sandboxBoostLimits = startupConfig.sandboxBoostCpus || startupConfig.sandboxBoostMemory
|
|
240
|
+
? { cpus: startupConfig.sandboxBoostCpus, memory: startupConfig.sandboxBoostMemory }
|
|
241
|
+
: undefined;
|
|
242
|
+
const provisioner = sandbox.type === "image"
|
|
243
|
+
? new DockerContainerManager(sandbox.image, {
|
|
244
|
+
limits: sandboxLimits,
|
|
245
|
+
boostLimits: sandboxBoostLimits,
|
|
246
|
+
})
|
|
247
|
+
: undefined;
|
|
248
|
+
if (sandbox.type === "image") {
|
|
249
|
+
ensureDirExists(join(workingDir, "skills"));
|
|
250
|
+
ensureDirExists(join(workingDir, "events"));
|
|
251
|
+
try {
|
|
252
|
+
writeFileSync(join(workingDir, "MEMORY.md"), "", { flag: "wx" });
|
|
151
253
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (!state.running) {
|
|
156
|
-
idleSessions.push({ key, lastAccessedAt: state.lastAccessedAt });
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
idleSessions.sort((a, b) => a.lastAccessedAt - b.lastAccessedAt);
|
|
160
|
-
const toEvict = channelStates.size - MAX_SESSIONS;
|
|
161
|
-
for (let i = 0; i < toEvict && i < idleSessions.length; i++) {
|
|
162
|
-
channelStates.delete(idleSessions[i].key);
|
|
163
|
-
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
if (err.code !== "EEXIST")
|
|
256
|
+
throw err;
|
|
164
257
|
}
|
|
165
258
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
return
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
state.stopRequested = true;
|
|
194
|
-
state.runner.abort();
|
|
195
|
-
const ts = await bot.postMessage(channelId, "_Stopping..._");
|
|
196
|
-
state.stopMessageTs = ts;
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
await bot.postMessage(channelId, "_Nothing running_");
|
|
200
|
-
}
|
|
201
|
-
},
|
|
202
|
-
forceStop(sessionKey) {
|
|
203
|
-
const state = channelStates.get(sessionKey);
|
|
204
|
-
if (state?.running) {
|
|
205
|
-
log.logInfo(`[Force Stop] Force stopping session: ${sessionKey}`);
|
|
206
|
-
state.stopRequested = true;
|
|
207
|
-
state.runner.abort();
|
|
208
|
-
state.running = false;
|
|
209
|
-
}
|
|
210
|
-
},
|
|
211
|
-
async handleNew(sessionKey, channelId, bot) {
|
|
212
|
-
const state = channelStates.get(sessionKey);
|
|
213
|
-
if (state?.running) {
|
|
214
|
-
state.stopRequested = true;
|
|
215
|
-
state.runner.abort();
|
|
216
|
-
}
|
|
217
|
-
// Channel sessions rotate via current pointer. Thread sessions reset in place.
|
|
218
|
-
const channelDir = join(workingDir, channelId);
|
|
219
|
-
if (sessionKey.includes(":")) {
|
|
220
|
-
createManagedSessionFileAtPath(getThreadSessionFile(channelDir, sessionKey), channelDir);
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
createManagedSessionFile(getSessionDir(channelDir, sessionKey), channelDir);
|
|
224
|
-
}
|
|
225
|
-
// Remove from in-memory cache
|
|
226
|
-
channelStates.delete(sessionKey);
|
|
227
|
-
log.logInfo(`[${channelId}] Session reset: ${sessionKey}`);
|
|
228
|
-
await bot.postMessage(channelId, "Conversation reset. Send a new message to start fresh.");
|
|
229
|
-
},
|
|
230
|
-
async handleEvent(event, bot, adapters, _isEvent) {
|
|
231
|
-
// Don't accept new events during shutdown
|
|
232
|
-
if (isShuttingDown) {
|
|
233
|
-
log.logInfo(`[${event.channel}] Rejected event during shutdown: ${event.text.substring(0, 50)}`);
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
const sessionKey = event.sessionKey ?? `${event.channel}:${event.thread_ts ?? event.ts}`;
|
|
237
|
-
const state = await getState(event.channel, sessionKey);
|
|
238
|
-
// Start run
|
|
239
|
-
state.running = true;
|
|
240
|
-
state.stopRequested = false;
|
|
241
|
-
state.startedAt = Date.now();
|
|
242
|
-
state.lastActivityAt = Date.now();
|
|
243
|
-
log.logInfo(`[${event.channel}] Starting run: ${event.text.substring(0, 50)}`);
|
|
244
|
-
// Wrap in-flight run tracking
|
|
245
|
-
Sentry.metrics.count("agent.run.started", 1, {
|
|
246
|
-
attributes: { channel: event.channel },
|
|
247
|
-
});
|
|
248
|
-
Sentry.metrics.gauge("agent.sessions.active", inFlightRuns.size + 1);
|
|
249
|
-
const runPromise = Sentry.startSpan({ name: "agent.run", op: "agent", attributes: { channelId: event.channel, sessionKey } }, async () => {
|
|
250
|
-
return Sentry.withScope(async (scope) => {
|
|
251
|
-
const { message, responseCtx, platform } = adapters;
|
|
252
|
-
applyRunScope(scope, {
|
|
253
|
-
channelId: event.channel,
|
|
254
|
-
sessionKey,
|
|
255
|
-
messageId: message.id,
|
|
256
|
-
platform: platform.name,
|
|
257
|
-
userId: message.userId,
|
|
258
|
-
userName: message.userName,
|
|
259
|
-
threadTs: message.threadTs,
|
|
260
|
-
isEvent: _isEvent,
|
|
261
|
-
});
|
|
262
|
-
addLifecycleBreadcrumb("agent.run.started", {
|
|
263
|
-
channel_id: event.channel,
|
|
264
|
-
platform: platform.name,
|
|
265
|
-
has_attachments: (message.attachments?.length ?? 0) > 0,
|
|
266
|
-
});
|
|
267
|
-
try {
|
|
268
|
-
await responseCtx.setTyping(true);
|
|
269
|
-
await responseCtx.setWorking(true);
|
|
270
|
-
const result = await state.runner.run(message, responseCtx, platform);
|
|
271
|
-
await responseCtx.setWorking(false);
|
|
272
|
-
const durationMs = Date.now() - state.startedAt;
|
|
273
|
-
Sentry.metrics.distribution("agent.run.duration", durationMs, {
|
|
274
|
-
unit: "millisecond",
|
|
275
|
-
attributes: {
|
|
276
|
-
channel: event.channel,
|
|
277
|
-
platform: platform.name,
|
|
278
|
-
stop_reason: result.stopReason,
|
|
279
|
-
},
|
|
280
|
-
});
|
|
281
|
-
Sentry.metrics.count("agent.run.completed", 1, {
|
|
282
|
-
attributes: {
|
|
283
|
-
channel: event.channel,
|
|
284
|
-
platform: platform.name,
|
|
285
|
-
stop_reason: result.stopReason,
|
|
286
|
-
},
|
|
287
|
-
});
|
|
288
|
-
addLifecycleBreadcrumb("agent.run.completed", {
|
|
289
|
-
channel_id: event.channel,
|
|
290
|
-
platform: platform.name,
|
|
291
|
-
stop_reason: result.stopReason,
|
|
292
|
-
duration_ms: durationMs,
|
|
293
|
-
});
|
|
294
|
-
if (result.stopReason === "aborted" && state.stopRequested) {
|
|
295
|
-
if (state.stopMessageTs) {
|
|
296
|
-
await bot.updateMessage(event.channel, state.stopMessageTs, "_Stopped_");
|
|
297
|
-
state.stopMessageTs = undefined;
|
|
298
|
-
}
|
|
299
|
-
else {
|
|
300
|
-
await bot.postMessage(event.channel, "_Stopped_");
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
catch (err) {
|
|
305
|
-
scope.setContext("agent_run_error", {
|
|
306
|
-
channelId: event.channel,
|
|
307
|
-
sessionKey,
|
|
308
|
-
platform: adapters.platform.name,
|
|
309
|
-
messageId: adapters.message.id,
|
|
310
|
-
threadTs: adapters.message.threadTs,
|
|
311
|
-
});
|
|
312
|
-
Sentry.captureException(err);
|
|
313
|
-
Sentry.metrics.count("agent.run.errors", 1, {
|
|
314
|
-
attributes: { channel: event.channel, platform: adapters.platform.name },
|
|
315
|
-
});
|
|
316
|
-
log.logWarning(`[${event.channel}] Run error`, err instanceof Error ? err.message : String(err));
|
|
317
|
-
}
|
|
318
|
-
finally {
|
|
319
|
-
state.running = false;
|
|
320
|
-
state.lastAccessedAt = Date.now();
|
|
321
|
-
Sentry.metrics.gauge("agent.sessions.active", inFlightRuns.size - 1);
|
|
322
|
-
evictIdleSessions();
|
|
323
|
-
}
|
|
324
|
-
});
|
|
325
|
-
});
|
|
326
|
-
inFlightRuns.add(runPromise);
|
|
327
|
-
try {
|
|
328
|
-
await runPromise;
|
|
329
|
-
}
|
|
330
|
-
finally {
|
|
331
|
-
inFlightRuns.delete(runPromise);
|
|
332
|
-
}
|
|
333
|
-
},
|
|
334
|
-
};
|
|
259
|
+
const linkTokenStore = new InMemoryLinkTokenStore();
|
|
260
|
+
const sessionViewTokenStore = new InMemorySessionViewTokenStore();
|
|
261
|
+
setInterval(() => linkTokenStore.purge(), 5 * 60 * 1000).unref();
|
|
262
|
+
setInterval(() => sessionViewTokenStore.purge(), 5 * 60 * 1000).unref();
|
|
263
|
+
function portalBaseUrl() {
|
|
264
|
+
if (MAMA_LINK_URL)
|
|
265
|
+
return MAMA_LINK_URL.replace(/\/+$/, "");
|
|
266
|
+
if (MAMA_LINK_PORT)
|
|
267
|
+
return `http://localhost:${MAMA_LINK_PORT}`;
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
/** Idle timeout for managed image containers (10 minutes) */
|
|
271
|
+
const IMAGE_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
272
|
+
if (provisioner) {
|
|
273
|
+
await provisioner.reconcile();
|
|
274
|
+
await provisioner.stopIdle(IMAGE_IDLE_TIMEOUT_MS);
|
|
275
|
+
setInterval(() => provisioner.stopIdle(IMAGE_IDLE_TIMEOUT_MS), IMAGE_IDLE_TIMEOUT_MS).unref();
|
|
276
|
+
}
|
|
277
|
+
const handler = createSessionRuntime({
|
|
278
|
+
workingDir,
|
|
279
|
+
sandbox,
|
|
280
|
+
vaultManager,
|
|
281
|
+
provisioner,
|
|
282
|
+
linkTokenStore,
|
|
283
|
+
sessionViewTokenStore,
|
|
284
|
+
portalBaseUrl: portalBaseUrl(),
|
|
285
|
+
});
|
|
335
286
|
// ============================================================================
|
|
336
287
|
// Start
|
|
337
288
|
// ============================================================================
|
|
338
289
|
const sandboxDesc = sandbox.type === "host"
|
|
339
290
|
? "host"
|
|
340
|
-
: sandbox.type === "
|
|
341
|
-
? `
|
|
342
|
-
:
|
|
291
|
+
: sandbox.type === "container"
|
|
292
|
+
? `container:${sandbox.container}`
|
|
293
|
+
: sandbox.type === "image"
|
|
294
|
+
? `image:${sandbox.image}`
|
|
295
|
+
: sandbox.type === "firecracker"
|
|
296
|
+
? `firecracker:${sandbox.vmId}`
|
|
297
|
+
: `cloudflare:${sandbox.sandboxId}`;
|
|
343
298
|
log.logStartup(workingDir, sandboxDesc);
|
|
344
299
|
// Create platform bots
|
|
345
300
|
const bots = [];
|
|
346
301
|
const botsByPlatform = {};
|
|
347
302
|
if (hasSlack) {
|
|
348
|
-
const
|
|
303
|
+
const slackBotToken = MAMA_SLACK_BOT_TOKEN;
|
|
304
|
+
const slackAppToken = MAMA_SLACK_APP_TOKEN;
|
|
305
|
+
if (!slackBotToken || !slackAppToken) {
|
|
306
|
+
throw new Error("Slack startup requires both MAMA_SLACK_APP_TOKEN and MAMA_SLACK_BOT_TOKEN");
|
|
307
|
+
}
|
|
308
|
+
const sharedStore = new ChannelStore({ workingDir, botToken: slackBotToken });
|
|
349
309
|
const slackBot = new SlackBotClass(handler, {
|
|
350
|
-
appToken:
|
|
351
|
-
botToken:
|
|
310
|
+
appToken: slackAppToken,
|
|
311
|
+
botToken: slackBotToken,
|
|
352
312
|
workingDir,
|
|
353
313
|
store: sharedStore,
|
|
354
314
|
});
|
|
@@ -357,8 +317,12 @@ if (hasSlack) {
|
|
|
357
317
|
log.logInfo("Platform: Slack");
|
|
358
318
|
}
|
|
359
319
|
if (hasTelegram) {
|
|
320
|
+
const telegramToken = MAMA_TELEGRAM_BOT_TOKEN;
|
|
321
|
+
if (!telegramToken) {
|
|
322
|
+
throw new Error("Telegram startup requires MAMA_TELEGRAM_BOT_TOKEN");
|
|
323
|
+
}
|
|
360
324
|
const telegramBot = new TelegramBot(handler, {
|
|
361
|
-
token:
|
|
325
|
+
token: telegramToken,
|
|
362
326
|
workingDir,
|
|
363
327
|
});
|
|
364
328
|
bots.push(telegramBot);
|
|
@@ -366,14 +330,25 @@ if (hasTelegram) {
|
|
|
366
330
|
log.logInfo("Platform: Telegram");
|
|
367
331
|
}
|
|
368
332
|
if (hasDiscord) {
|
|
333
|
+
const discordToken = MAMA_DISCORD_BOT_TOKEN;
|
|
334
|
+
if (!discordToken) {
|
|
335
|
+
throw new Error("Discord startup requires MAMA_DISCORD_BOT_TOKEN");
|
|
336
|
+
}
|
|
369
337
|
const discordBot = new DiscordBot(handler, {
|
|
370
|
-
token:
|
|
338
|
+
token: discordToken,
|
|
371
339
|
workingDir,
|
|
372
340
|
});
|
|
373
341
|
bots.push(discordBot);
|
|
374
342
|
botsByPlatform.discord = discordBot;
|
|
375
343
|
log.logInfo("Platform: Discord");
|
|
376
344
|
}
|
|
345
|
+
if (MAMA_LINK_PORT) {
|
|
346
|
+
startLinkServer(MAMA_LINK_PORT, linkTokenStore, vaultManager, async (platform, conversationId, message) => {
|
|
347
|
+
const bot = botsByPlatform[platform];
|
|
348
|
+
if (bot)
|
|
349
|
+
await bot.postMessage(conversationId, message);
|
|
350
|
+
}, sessionViewTokenStore, { handler, botsByPlatform });
|
|
351
|
+
}
|
|
377
352
|
// Start events watcher with explicit platform routing
|
|
378
353
|
const eventsWatcher = createEventsWatcher(workingDir, botsByPlatform);
|
|
379
354
|
const slackBot = botsByPlatform.slack;
|
|
@@ -383,17 +358,7 @@ if (slackBot) {
|
|
|
383
358
|
eventsWatcher.start();
|
|
384
359
|
// Handle shutdown
|
|
385
360
|
async function shutdown() {
|
|
386
|
-
|
|
387
|
-
return;
|
|
388
|
-
isShuttingDown = true;
|
|
389
|
-
log.logInfo("Shutting down gracefully...");
|
|
390
|
-
const timeout = Date.now() + 30000;
|
|
391
|
-
while (inFlightRuns.size > 0 && Date.now() < timeout) {
|
|
392
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
393
|
-
}
|
|
394
|
-
if (inFlightRuns.size > 0) {
|
|
395
|
-
log.logWarning(`Forcing exit with ${inFlightRuns.size} runs still in progress`);
|
|
396
|
-
}
|
|
361
|
+
await handler.shutdown();
|
|
397
362
|
eventsWatcher.stop();
|
|
398
363
|
await Sentry.close(5000);
|
|
399
364
|
process.exit(0);
|