@geminixiang/mama 0.2.0-beta.1 → 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 +168 -371
- package/dist/adapter.d.ts +36 -12
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/adapters/discord/bot.d.ts +12 -7
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +358 -135
- 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 +100 -36
- 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 +30 -24
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +620 -224
- 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 +127 -72
- 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 +4 -2
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +193 -147
- 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 +58 -111
- 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 -13
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +601 -567
- 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 +49 -30
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +313 -75
- 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 +14 -127
- package/dist/context.js.map +1 -1
- package/dist/events.d.ts +13 -6
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +118 -64
- package/dist/events.js.map +1 -1
- package/dist/execution-resolver.d.ts +9 -5
- package/dist/execution-resolver.d.ts.map +1 -1
- package/dist/execution-resolver.js +82 -18
- package/dist/execution-resolver.js.map +1 -1
- 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 +4 -11
- package/dist/instrument.js.map +1 -1
- package/dist/log.d.ts +1 -5
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +13 -38
- package/dist/log.js.map +1 -1
- package/dist/{login.d.ts → login/index.d.ts} +16 -4
- package/dist/login/index.d.ts.map +1 -0
- package/dist/{login.js → login/index.js} +55 -17
- package/dist/login/index.js.map +1 -0
- package/dist/{link-server.d.ts → login/portal.d.ts} +7 -4
- 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/{link-token.d.ts → login/session.d.ts} +4 -3
- package/dist/login/session.d.ts.map +1 -0
- package/dist/{link-token.js → login/session.js} +1 -1
- package/dist/login/session.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +151 -373
- package/dist/main.js.map +1 -1
- package/dist/provisioner.d.ts +38 -52
- package/dist/provisioner.d.ts.map +1 -1
- package/dist/provisioner.js +212 -111
- package/dist/provisioner.js.map +1 -1
- 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 +2 -1
- package/dist/sandbox/container.d.ts.map +1 -1
- package/dist/sandbox/container.js +5 -1
- package/dist/sandbox/container.js.map +1 -1
- package/dist/sandbox/firecracker.d.ts +2 -1
- package/dist/sandbox/firecracker.d.ts.map +1 -1
- package/dist/sandbox/firecracker.js +6 -0
- package/dist/sandbox/firecracker.js.map +1 -1
- package/dist/sandbox/host.d.ts +2 -3
- package/dist/sandbox/host.d.ts.map +1 -1
- package/dist/sandbox/host.js +5 -5
- package/dist/sandbox/host.js.map +1 -1
- package/dist/sandbox/index.d.ts +6 -4
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/index.js +9 -6
- package/dist/sandbox/index.js.map +1 -1
- 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 +17 -1
- package/dist/sandbox/types.d.ts.map +1 -1
- package/dist/sandbox/types.js.map +1 -1
- package/dist/sentry.d.ts +1 -1
- package/dist/sentry.d.ts.map +1 -1
- package/dist/sentry.js +4 -2
- 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 +34 -3
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +184 -22
- 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 +3 -6
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +22 -48
- 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 +43 -2
- package/dist/tools/event.d.ts.map +1 -1
- package/dist/tools/event.js +48 -13
- package/dist/tools/event.js.map +1 -1
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +3 -3
- 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 +1 -0
- package/dist/ui-copy.d.ts.map +1 -1
- package/dist/ui-copy.js +3 -0
- package/dist/ui-copy.js.map +1 -1
- package/dist/vault-routing.d.ts +1 -7
- package/dist/vault-routing.d.ts.map +1 -1
- package/dist/vault-routing.js +6 -48
- package/dist/vault-routing.js.map +1 -1
- package/dist/vault.d.ts +21 -55
- package/dist/vault.d.ts.map +1 -1
- package/dist/vault.js +138 -263
- package/dist/vault.js.map +1 -1
- package/package.json +12 -10
- package/dist/bindings.d.ts +0 -63
- package/dist/bindings.d.ts.map +0 -1
- package/dist/bindings.js +0 -94
- package/dist/bindings.js.map +0 -1
- package/dist/link-server.d.ts.map +0 -1
- package/dist/link-server.js +0 -839
- package/dist/link-server.js.map +0 -1
- package/dist/link-token.d.ts.map +0 -1
- package/dist/link-token.js.map +0 -1
- package/dist/login.d.ts.map +0 -1
- package/dist/login.js.map +0 -1
- package/dist/vault.test.d.ts +0 -2
- package/dist/vault.test.d.ts.map +0 -1
- package/dist/vault.test.js +0 -67
- package/dist/vault.test.js.map +0 -1
package/dist/main.js
CHANGED
|
@@ -1,29 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./instrument.js";
|
|
3
3
|
import { join, resolve } from "path";
|
|
4
|
+
import { mkdirSync, statSync, writeFileSync } from "fs";
|
|
4
5
|
import { homedir } from "os";
|
|
5
|
-
import { mkdirSync, readFileSync, statSync } from "fs";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
import { dirname, join as pathJoin } from "path";
|
|
8
8
|
import { DiscordBot } from "./adapters/discord/index.js";
|
|
9
9
|
import { TelegramBot } from "./adapters/telegram/index.js";
|
|
10
10
|
import { SlackBot as SlackBotClass } from "./adapters/slack/index.js";
|
|
11
|
-
import { createRunner } from "./agent.js";
|
|
12
|
-
import { createManagedSessionFile, createManagedSessionFileAtPath, getChannelSessionDir, getThreadSessionFile, } from "./session-store.js";
|
|
13
11
|
import { downloadChannel } from "./download.js";
|
|
14
12
|
import { createEventsWatcher } from "./events.js";
|
|
15
13
|
import * as log from "./log.js";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import { InMemoryLinkTokenStore } from "./link-token.js";
|
|
14
|
+
import { startLinkServer } from "./login/portal.js";
|
|
15
|
+
import { InMemoryLinkTokenStore } from "./login/session.js";
|
|
16
|
+
import { InMemorySessionViewTokenStore } from "./session-view/store.js";
|
|
20
17
|
import { DockerContainerManager } from "./provisioner.js";
|
|
18
|
+
import { createGlobalSettingsFile, loadAgentConfig, MissingGlobalSettingsError } from "./config.js";
|
|
19
|
+
import { ensureDirExists, isRecord, readJsonFileIfExists } from "./file-guards.js";
|
|
21
20
|
import { SandboxError, parseSandboxArg, validateSandbox } from "./sandbox.js";
|
|
22
|
-
import { formatNothingRunning, formatStopping } from "./ui-copy.js";
|
|
23
21
|
import { FileVaultManager } from "./vault.js";
|
|
24
|
-
import {
|
|
25
|
-
import { createManagedVaultEntry, ensureSandboxVaultEntry, resolveActorVaultKey, } from "./vault-routing.js";
|
|
26
|
-
import { addLifecycleBreadcrumb, applyRunScope } from "./sentry.js";
|
|
22
|
+
import { createSessionRuntime } from "./runtime/index.js";
|
|
27
23
|
import { ChannelStore } from "./store.js";
|
|
28
24
|
import * as Sentry from "@sentry/node";
|
|
29
25
|
// ============================================================================
|
|
@@ -38,31 +34,20 @@ function getVersion() {
|
|
|
38
34
|
pathJoin(process.cwd(), "package.json"),
|
|
39
35
|
];
|
|
40
36
|
for (const pkgPath of possiblePaths) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return pkg.version;
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
// Continue to next path
|
|
48
|
-
}
|
|
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;
|
|
49
40
|
}
|
|
50
41
|
return "unknown";
|
|
51
42
|
}
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
* Defaults to 8181 when MOM_LINK_URL is set (behind a reverse proxy).
|
|
61
|
-
* If neither is set, the server is not started.
|
|
62
|
-
*/
|
|
63
|
-
const MOM_LINK_PORT = process.env.MOM_LINK_PORT
|
|
64
|
-
? parseInt(process.env.MOM_LINK_PORT, 10)
|
|
65
|
-
: MOM_LINK_URL
|
|
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
|
|
66
51
|
? 8181
|
|
67
52
|
: undefined;
|
|
68
53
|
function parseArgs() {
|
|
@@ -71,12 +56,16 @@ function parseArgs() {
|
|
|
71
56
|
let workingDir;
|
|
72
57
|
let stateDirArg;
|
|
73
58
|
let downloadChannelId;
|
|
59
|
+
let showOnboard = false;
|
|
74
60
|
let showVersion = false;
|
|
75
61
|
for (let i = 0; i < args.length; i++) {
|
|
76
62
|
const arg = args[i];
|
|
77
63
|
if (arg === "--version" || arg === "-v" || arg === "-V") {
|
|
78
64
|
showVersion = true;
|
|
79
65
|
}
|
|
66
|
+
else if (arg === "--onboard") {
|
|
67
|
+
showOnboard = true;
|
|
68
|
+
}
|
|
80
69
|
else if (arg.startsWith("--sandbox=")) {
|
|
81
70
|
sandbox = parseSandboxArg(arg.slice("--sandbox=".length));
|
|
82
71
|
}
|
|
@@ -104,16 +93,11 @@ function parseArgs() {
|
|
|
104
93
|
stateDir: stateDirArg ? resolve(stateDirArg) : undefined,
|
|
105
94
|
sandbox,
|
|
106
95
|
downloadChannel: downloadChannelId,
|
|
96
|
+
showOnboard,
|
|
107
97
|
showVersion,
|
|
108
98
|
};
|
|
109
99
|
}
|
|
110
100
|
const WORLD_WRITABLE_MODE = 0o002;
|
|
111
|
-
/**
|
|
112
|
-
* Create stateDir if missing and refuse to use it if another local user could
|
|
113
|
-
* tamper with its contents. stateDir holds vaults, bindings, and settings —
|
|
114
|
-
* a world-writable or foreign-owned directory there would let a local attacker
|
|
115
|
-
* swap in credentials or change routing.
|
|
116
|
-
*/
|
|
117
101
|
function ensureSecureStateDir(path) {
|
|
118
102
|
let stat;
|
|
119
103
|
try {
|
|
@@ -152,7 +136,21 @@ function handleStartupError(error) {
|
|
|
152
136
|
}
|
|
153
137
|
process.exit(1);
|
|
154
138
|
}
|
|
155
|
-
|
|
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);
|
|
156
154
|
}
|
|
157
155
|
let parsedArgs;
|
|
158
156
|
try {
|
|
@@ -166,47 +164,51 @@ if (parsedArgs.showVersion) {
|
|
|
166
164
|
console.log(getVersion());
|
|
167
165
|
process.exit(0);
|
|
168
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
|
+
}
|
|
169
183
|
// Handle --download mode (Slack only)
|
|
170
184
|
if (parsedArgs.downloadChannel) {
|
|
171
|
-
if (!
|
|
172
|
-
console.error("Missing env:
|
|
185
|
+
if (!MAMA_SLACK_BOT_TOKEN) {
|
|
186
|
+
console.error("Missing env: MAMA_SLACK_BOT_TOKEN");
|
|
173
187
|
process.exit(1);
|
|
174
188
|
}
|
|
175
|
-
await downloadChannel(parsedArgs.downloadChannel,
|
|
189
|
+
await downloadChannel(parsedArgs.downloadChannel, MAMA_SLACK_BOT_TOKEN);
|
|
176
190
|
process.exit(0);
|
|
177
191
|
}
|
|
178
192
|
// Normal bot mode - require working dir
|
|
179
193
|
if (!parsedArgs.workingDir) {
|
|
180
|
-
console.error("Usage: mama [--sandbox=host|container:<name>|image:<image>|firecracker:<vm-id>:<host-path
|
|
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>]");
|
|
181
196
|
console.error(" mama --download <channel-id>");
|
|
182
197
|
process.exit(1);
|
|
183
198
|
}
|
|
184
199
|
const { workingDir, sandbox } = { workingDir: parsedArgs.workingDir, sandbox: parsedArgs.sandbox };
|
|
185
|
-
// stateDir holds operator-managed files (vaults, settings, bindings).
|
|
186
|
-
// Defaults to ~/.mama to keep secrets outside the project workspace mounted into sandboxes.
|
|
187
200
|
const stateDir = parsedArgs.stateDir ?? join(homedir(), ".mama");
|
|
188
|
-
ensureSecureStateDir(stateDir);
|
|
189
|
-
// Share stateDir with instrument.ts (for Sentry config loading)
|
|
190
201
|
process.env.MAMA_STATE_DIR = stateDir;
|
|
191
|
-
|
|
192
|
-
const { created: settingsCreated, config: agentSettings } = ensureSettingsFile(stateDir);
|
|
193
|
-
if (settingsCreated) {
|
|
194
|
-
console.log(`Created default settings: ${join(stateDir, "settings.json")}`);
|
|
195
|
-
console.log("Review and update provider/model before starting.");
|
|
196
|
-
}
|
|
197
|
-
if (!agentSettings.provider || !agentSettings.model) {
|
|
198
|
-
console.error(`Error: 'provider' and 'model' must be set in ${join(stateDir, "settings.json")}`);
|
|
199
|
-
process.exit(1);
|
|
200
|
-
}
|
|
202
|
+
ensureSecureStateDir(stateDir);
|
|
201
203
|
// Validate platform tokens
|
|
202
|
-
const hasSlack = !!(
|
|
203
|
-
const hasTelegram = !!
|
|
204
|
-
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;
|
|
205
207
|
if (!hasSlack && !hasTelegram && !hasDiscord) {
|
|
206
208
|
console.error("No platform tokens found. Set one of:\n" +
|
|
207
|
-
" Slack:
|
|
208
|
-
" Telegram:
|
|
209
|
-
" 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");
|
|
210
212
|
process.exit(1);
|
|
211
213
|
}
|
|
212
214
|
try {
|
|
@@ -218,297 +220,69 @@ catch (error) {
|
|
|
218
220
|
const vaultManager = new FileVaultManager(stateDir);
|
|
219
221
|
if (vaultManager.isEnabled()) {
|
|
220
222
|
console.log(sandbox.type === "container"
|
|
221
|
-
? " Vault system enabled.
|
|
222
|
-
: "
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (bindingStore.isEnabled()) {
|
|
226
|
-
console.log(sandbox.type === "container"
|
|
227
|
-
? " Binding store enabled. Shared container mode ignores per-user vault bindings."
|
|
228
|
-
: " Binding store enabled. Platform user → vault routing active.");
|
|
229
|
-
}
|
|
230
|
-
const provisioner = sandbox.type === "image" ? new DockerContainerManager(sandbox.image, workingDir) : undefined;
|
|
231
|
-
const linkTokenStore = new InMemoryLinkTokenStore();
|
|
232
|
-
// Purge expired link tokens every 5 minutes
|
|
233
|
-
setInterval(() => linkTokenStore.purge(), 5 * 60 * 1000).unref();
|
|
234
|
-
const conversationStates = new Map();
|
|
235
|
-
/** Track in-flight runs for graceful shutdown */
|
|
236
|
-
const inFlightRuns = new Set();
|
|
237
|
-
/** Flag to stop accepting new events during shutdown */
|
|
238
|
-
let isShuttingDown = false;
|
|
239
|
-
/** Maximum number of cached sessions */
|
|
240
|
-
const MAX_SESSIONS = 500;
|
|
241
|
-
/** Idle timeout before a non-running session can be evicted (10 minutes) */
|
|
242
|
-
const IDLE_TIMEOUT_MS = 600000;
|
|
243
|
-
if (provisioner) {
|
|
244
|
-
await provisioner.reconcile();
|
|
245
|
-
await provisioner.stopIdle(IDLE_TIMEOUT_MS);
|
|
246
|
-
}
|
|
247
|
-
// Stop idle containers every hour (same cadence as session eviction)
|
|
248
|
-
if (provisioner) {
|
|
249
|
-
setInterval(() => provisioner.stopIdle(IDLE_TIMEOUT_MS), IDLE_TIMEOUT_MS).unref();
|
|
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.");
|
|
250
227
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
return
|
|
254
|
-
}
|
|
255
|
-
if (MOM_LINK_PORT) {
|
|
256
|
-
return `http://localhost:${MOM_LINK_PORT}`;
|
|
228
|
+
const startupConfig = (() => {
|
|
229
|
+
try {
|
|
230
|
+
return loadAgentConfig();
|
|
257
231
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
function ensureLoginVault(platform, platformUserId) {
|
|
261
|
-
const vaultId = resolveActorVaultKey(sandbox, vaultManager, bindingStore, platform, platformUserId);
|
|
262
|
-
ensureSandboxVaultEntry(sandbox, vaultManager, platform, platformUserId, vaultId);
|
|
263
|
-
if (sandbox.type !== "image" && sandbox.type !== "container") {
|
|
264
|
-
vaultManager.addEntry(vaultId, createManagedVaultEntry(platform, platformUserId, vaultId, false));
|
|
232
|
+
catch (error) {
|
|
233
|
+
handleStartupError(error);
|
|
265
234
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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" });
|
|
280
253
|
}
|
|
281
|
-
|
|
282
|
-
|
|
254
|
+
catch (err) {
|
|
255
|
+
if (err.code !== "EEXIST")
|
|
256
|
+
throw err;
|
|
283
257
|
}
|
|
284
|
-
return state;
|
|
285
258
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
function
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
if (conversationStates.size > MAX_SESSIONS) {
|
|
298
|
-
const idleSessions = [];
|
|
299
|
-
for (const [key, state] of conversationStates) {
|
|
300
|
-
if (!state.running) {
|
|
301
|
-
idleSessions.push({ key, lastAccessedAt: state.lastAccessedAt });
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
idleSessions.sort((a, b) => a.lastAccessedAt - b.lastAccessedAt);
|
|
305
|
-
const toEvict = conversationStates.size - MAX_SESSIONS;
|
|
306
|
-
for (let i = 0; i < toEvict && i < idleSessions.length; i++) {
|
|
307
|
-
conversationStates.delete(idleSessions[i].key);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
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;
|
|
310
269
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
startedAt: state.startedAt,
|
|
328
|
-
lastActivityAt: state.lastActivityAt,
|
|
329
|
-
currentTool: currentStep?.label || currentStep?.toolName,
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
return sessions;
|
|
334
|
-
},
|
|
335
|
-
async handleStop(sessionKey, conversationId, bot) {
|
|
336
|
-
const state = conversationStates.get(sessionKey);
|
|
337
|
-
if (state?.running) {
|
|
338
|
-
state.stopRequested = true;
|
|
339
|
-
state.runner.abort();
|
|
340
|
-
const ts = await bot.postMessage(conversationId, formatStopping(bot));
|
|
341
|
-
state.stopMessageTs = ts;
|
|
342
|
-
}
|
|
343
|
-
else {
|
|
344
|
-
await bot.postMessage(conversationId, formatNothingRunning(bot));
|
|
345
|
-
}
|
|
346
|
-
},
|
|
347
|
-
forceStop(sessionKey) {
|
|
348
|
-
const state = conversationStates.get(sessionKey);
|
|
349
|
-
if (state?.running) {
|
|
350
|
-
log.logInfo(`[Force Stop] Force stopping session: ${sessionKey}`);
|
|
351
|
-
state.stopRequested = true;
|
|
352
|
-
state.runner.abort();
|
|
353
|
-
state.running = false;
|
|
354
|
-
}
|
|
355
|
-
},
|
|
356
|
-
async handleNew(sessionKey, conversationId, bot) {
|
|
357
|
-
const state = conversationStates.get(sessionKey);
|
|
358
|
-
if (state?.running) {
|
|
359
|
-
state.stopRequested = true;
|
|
360
|
-
state.runner.abort();
|
|
361
|
-
}
|
|
362
|
-
// Channel sessions rotate via current pointer. Thread sessions reset in place.
|
|
363
|
-
const conversationDir = join(workingDir, conversationId);
|
|
364
|
-
if (sessionKey.includes(":")) {
|
|
365
|
-
createManagedSessionFileAtPath(getThreadSessionFile(conversationDir, sessionKey), conversationDir);
|
|
366
|
-
}
|
|
367
|
-
else {
|
|
368
|
-
createManagedSessionFile(getChannelSessionDir(conversationDir), conversationDir);
|
|
369
|
-
}
|
|
370
|
-
// Remove from in-memory cache
|
|
371
|
-
conversationStates.delete(sessionKey);
|
|
372
|
-
log.logInfo(`[${conversationId}] Session reset: ${sessionKey}`);
|
|
373
|
-
await bot.postMessage(conversationId, "Conversation reset. Send a new message to start fresh.");
|
|
374
|
-
},
|
|
375
|
-
async handleLogin(platform, platformUserId, conversationId, bot, commandText, isPrivateConversation) {
|
|
376
|
-
const parsed = parseLoginCommand(commandText);
|
|
377
|
-
if (!parsed) {
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
if (!isPrivateConversation) {
|
|
381
|
-
await bot.postMessage(conversationId, "为了保护你的凭证,`/login` 只能在与机器人的私聊中使用。请先私信机器人,再重新执行 `/login`。");
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
const baseUrl = normalizeLoginBaseUrl();
|
|
385
|
-
if (!baseUrl) {
|
|
386
|
-
await bot.postMessage(conversationId, "Login is not configured. Set `MOM_LINK_URL` or `MOM_LINK_PORT` on the server.");
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
let vaultId;
|
|
390
|
-
try {
|
|
391
|
-
vaultId = ensureLoginVault(platform, platformUserId);
|
|
392
|
-
}
|
|
393
|
-
catch (error) {
|
|
394
|
-
log.logWarning(`[${conversationId}] Failed to prepare login vault for ${platform}/${platformUserId}`, error instanceof Error ? error.message : String(error));
|
|
395
|
-
await bot.postMessage(conversationId, "Login setup failed on the server. 请稍后重试,或联系管理员检查 vault 存储权限。");
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
const loginLabel = "credential";
|
|
399
|
-
const vaultLabel = sandbox.type === "container" ? "the shared container vault" : "your vault";
|
|
400
|
-
await bot.postMessage(conversationId, `Open this link to store ${loginLabel} in ${vaultLabel} ` +
|
|
401
|
-
`(expires in 15 minutes):\n${baseUrl}/link?token=${linkTokenStore.create(platform, platformUserId, conversationId, vaultId, "").token}`);
|
|
402
|
-
},
|
|
403
|
-
async handleEvent(event, bot, adapters, _isEvent) {
|
|
404
|
-
// Don't accept new events during shutdown
|
|
405
|
-
if (isShuttingDown) {
|
|
406
|
-
log.logInfo(`[${event.conversationId}] Rejected event during shutdown: ${event.text.substring(0, 50)}`);
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
const sessionKey = event.sessionKey ?? `${event.conversationId}:${event.thread_ts ?? event.ts}`;
|
|
410
|
-
const state = await getState(event.conversationId, sessionKey);
|
|
411
|
-
// Start run
|
|
412
|
-
state.running = true;
|
|
413
|
-
state.stopRequested = false;
|
|
414
|
-
state.startedAt = Date.now();
|
|
415
|
-
state.lastActivityAt = Date.now();
|
|
416
|
-
log.logInfo(`[${event.conversationId}] Starting run: ${event.text.substring(0, 50)}`);
|
|
417
|
-
// Wrap in-flight run tracking
|
|
418
|
-
Sentry.metrics.count("agent.run.started", 1, {
|
|
419
|
-
attributes: { channel: event.conversationId },
|
|
420
|
-
});
|
|
421
|
-
Sentry.metrics.gauge("agent.sessions.active", inFlightRuns.size + 1);
|
|
422
|
-
const runPromise = Sentry.startSpan({
|
|
423
|
-
name: "agent.run",
|
|
424
|
-
op: "agent",
|
|
425
|
-
attributes: { channelId: event.conversationId, sessionKey },
|
|
426
|
-
}, async () => {
|
|
427
|
-
return Sentry.withScope(async (scope) => {
|
|
428
|
-
const { message, responseCtx, platform } = adapters;
|
|
429
|
-
applyRunScope(scope, {
|
|
430
|
-
conversationId: event.conversationId,
|
|
431
|
-
sessionKey,
|
|
432
|
-
messageId: message.id,
|
|
433
|
-
platform: platform.name,
|
|
434
|
-
userId: message.userId,
|
|
435
|
-
userName: message.userName,
|
|
436
|
-
threadTs: message.threadTs,
|
|
437
|
-
isEvent: _isEvent,
|
|
438
|
-
});
|
|
439
|
-
addLifecycleBreadcrumb("agent.run.started", {
|
|
440
|
-
channel_id: event.conversationId,
|
|
441
|
-
platform: platform.name,
|
|
442
|
-
has_attachments: (message.attachments?.length ?? 0) > 0,
|
|
443
|
-
});
|
|
444
|
-
try {
|
|
445
|
-
await responseCtx.setTyping(true);
|
|
446
|
-
await responseCtx.setWorking(true);
|
|
447
|
-
const result = await state.runner.run(message, responseCtx, platform);
|
|
448
|
-
await responseCtx.setWorking(false);
|
|
449
|
-
const durationMs = Date.now() - state.startedAt;
|
|
450
|
-
Sentry.metrics.distribution("agent.run.duration", durationMs, {
|
|
451
|
-
unit: "millisecond",
|
|
452
|
-
attributes: {
|
|
453
|
-
channel: event.conversationId,
|
|
454
|
-
platform: platform.name,
|
|
455
|
-
stop_reason: result.stopReason,
|
|
456
|
-
},
|
|
457
|
-
});
|
|
458
|
-
Sentry.metrics.count("agent.run.completed", 1, {
|
|
459
|
-
attributes: {
|
|
460
|
-
channel: event.conversationId,
|
|
461
|
-
platform: platform.name,
|
|
462
|
-
stop_reason: result.stopReason,
|
|
463
|
-
},
|
|
464
|
-
});
|
|
465
|
-
addLifecycleBreadcrumb("agent.run.completed", {
|
|
466
|
-
channel_id: event.conversationId,
|
|
467
|
-
platform: platform.name,
|
|
468
|
-
stop_reason: result.stopReason,
|
|
469
|
-
duration_ms: durationMs,
|
|
470
|
-
});
|
|
471
|
-
if (result.stopReason === "aborted" && state.stopRequested) {
|
|
472
|
-
if (state.stopMessageTs) {
|
|
473
|
-
await bot.updateMessage(event.conversationId, state.stopMessageTs, "_Stopped_");
|
|
474
|
-
state.stopMessageTs = undefined;
|
|
475
|
-
}
|
|
476
|
-
else {
|
|
477
|
-
await bot.postMessage(event.conversationId, "_Stopped_");
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
catch (err) {
|
|
482
|
-
scope.setContext("agent_run_error", {
|
|
483
|
-
conversationId: event.conversationId,
|
|
484
|
-
sessionKey,
|
|
485
|
-
platform: adapters.platform.name,
|
|
486
|
-
messageId: adapters.message.id,
|
|
487
|
-
threadTs: adapters.message.threadTs,
|
|
488
|
-
});
|
|
489
|
-
Sentry.captureException(err);
|
|
490
|
-
Sentry.metrics.count("agent.run.errors", 1, {
|
|
491
|
-
attributes: { channel: event.conversationId, platform: adapters.platform.name },
|
|
492
|
-
});
|
|
493
|
-
log.logWarning(`[${event.conversationId}] Run error`, err instanceof Error ? err.message : String(err));
|
|
494
|
-
}
|
|
495
|
-
finally {
|
|
496
|
-
state.running = false;
|
|
497
|
-
state.lastAccessedAt = Date.now();
|
|
498
|
-
Sentry.metrics.gauge("agent.sessions.active", inFlightRuns.size - 1);
|
|
499
|
-
evictIdleSessions();
|
|
500
|
-
}
|
|
501
|
-
});
|
|
502
|
-
});
|
|
503
|
-
inFlightRuns.add(runPromise);
|
|
504
|
-
try {
|
|
505
|
-
await runPromise;
|
|
506
|
-
}
|
|
507
|
-
finally {
|
|
508
|
-
inFlightRuns.delete(runPromise);
|
|
509
|
-
}
|
|
510
|
-
},
|
|
511
|
-
};
|
|
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
|
+
});
|
|
512
286
|
// ============================================================================
|
|
513
287
|
// Start
|
|
514
288
|
// ============================================================================
|
|
@@ -518,24 +292,23 @@ const sandboxDesc = sandbox.type === "host"
|
|
|
518
292
|
? `container:${sandbox.container}`
|
|
519
293
|
: sandbox.type === "image"
|
|
520
294
|
? `image:${sandbox.image}`
|
|
521
|
-
:
|
|
295
|
+
: sandbox.type === "firecracker"
|
|
296
|
+
? `firecracker:${sandbox.vmId}`
|
|
297
|
+
: `cloudflare:${sandbox.sandboxId}`;
|
|
522
298
|
log.logStartup(workingDir, sandboxDesc);
|
|
523
|
-
// Start link callback server if port is configured
|
|
524
|
-
if (MOM_LINK_PORT) {
|
|
525
|
-
startLinkServer(MOM_LINK_PORT, linkTokenStore, vaultManager, async (platform, conversationId, msg) => {
|
|
526
|
-
const bot = botsByPlatform[platform];
|
|
527
|
-
if (bot)
|
|
528
|
-
await bot.postMessage(conversationId, msg);
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
299
|
// Create platform bots
|
|
532
300
|
const bots = [];
|
|
533
301
|
const botsByPlatform = {};
|
|
534
302
|
if (hasSlack) {
|
|
535
|
-
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 });
|
|
536
309
|
const slackBot = new SlackBotClass(handler, {
|
|
537
|
-
appToken:
|
|
538
|
-
botToken:
|
|
310
|
+
appToken: slackAppToken,
|
|
311
|
+
botToken: slackBotToken,
|
|
539
312
|
workingDir,
|
|
540
313
|
store: sharedStore,
|
|
541
314
|
});
|
|
@@ -544,8 +317,12 @@ if (hasSlack) {
|
|
|
544
317
|
log.logInfo("Platform: Slack");
|
|
545
318
|
}
|
|
546
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
|
+
}
|
|
547
324
|
const telegramBot = new TelegramBot(handler, {
|
|
548
|
-
token:
|
|
325
|
+
token: telegramToken,
|
|
549
326
|
workingDir,
|
|
550
327
|
});
|
|
551
328
|
bots.push(telegramBot);
|
|
@@ -553,14 +330,25 @@ if (hasTelegram) {
|
|
|
553
330
|
log.logInfo("Platform: Telegram");
|
|
554
331
|
}
|
|
555
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
|
+
}
|
|
556
337
|
const discordBot = new DiscordBot(handler, {
|
|
557
|
-
token:
|
|
338
|
+
token: discordToken,
|
|
558
339
|
workingDir,
|
|
559
340
|
});
|
|
560
341
|
bots.push(discordBot);
|
|
561
342
|
botsByPlatform.discord = discordBot;
|
|
562
343
|
log.logInfo("Platform: Discord");
|
|
563
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
|
+
}
|
|
564
352
|
// Start events watcher with explicit platform routing
|
|
565
353
|
const eventsWatcher = createEventsWatcher(workingDir, botsByPlatform);
|
|
566
354
|
const slackBot = botsByPlatform.slack;
|
|
@@ -570,17 +358,7 @@ if (slackBot) {
|
|
|
570
358
|
eventsWatcher.start();
|
|
571
359
|
// Handle shutdown
|
|
572
360
|
async function shutdown() {
|
|
573
|
-
|
|
574
|
-
return;
|
|
575
|
-
isShuttingDown = true;
|
|
576
|
-
log.logInfo("Shutting down gracefully...");
|
|
577
|
-
const timeout = Date.now() + 30000;
|
|
578
|
-
while (inFlightRuns.size > 0 && Date.now() < timeout) {
|
|
579
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
580
|
-
}
|
|
581
|
-
if (inFlightRuns.size > 0) {
|
|
582
|
-
log.logWarning(`Forcing exit with ${inFlightRuns.size} runs still in progress`);
|
|
583
|
-
}
|
|
361
|
+
await handler.shutdown();
|
|
584
362
|
eventsWatcher.stop();
|
|
585
363
|
await Sentry.close(5000);
|
|
586
364
|
process.exit(0);
|