@geminixiang/mama 0.2.0-beta.2 → 0.2.0-beta.20
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 +156 -392
- package/dist/adapter.d.ts +31 -7
- 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 +347 -115
- 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 +118 -25
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/shared.d.ts +91 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/adapters/shared.js +191 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/adapters/slack/bot.d.ts +21 -22
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +530 -221
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/branch-manager.d.ts +28 -0
- package/dist/adapters/slack/branch-manager.d.ts.map +1 -0
- package/dist/adapters/slack/branch-manager.js +107 -0
- package/dist/adapters/slack/branch-manager.js.map +1 -0
- package/dist/adapters/slack/context.d.ts +4 -1
- package/dist/adapters/slack/context.d.ts.map +1 -1
- package/dist/adapters/slack/context.js +193 -75
- package/dist/adapters/slack/context.js.map +1 -1
- package/dist/adapters/slack/session.d.ts +38 -0
- package/dist/adapters/slack/session.d.ts.map +1 -0
- package/dist/adapters/slack/session.js +66 -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.map +1 -1
- package/dist/adapters/telegram/bot.js +140 -153
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/adapters/telegram/context.d.ts +1 -1
- package/dist/adapters/telegram/context.d.ts.map +1 -1
- package/dist/adapters/telegram/context.js +74 -20
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/agent.d.ts +13 -3
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +677 -552
- 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 +72 -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 +18 -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 +91 -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 +4 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +9 -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 +45 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +299 -67
- 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 +2 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +148 -67
- package/dist/events.js.map +1 -1
- package/dist/execution-resolver.d.ts +10 -6
- package/dist/execution-resolver.d.ts.map +1 -1
- package/dist/execution-resolver.js +121 -21
- package/dist/execution-resolver.js.map +1 -1
- package/dist/file-guards.d.ts +9 -0
- package/dist/file-guards.d.ts.map +1 -0
- package/dist/file-guards.js +56 -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 +2 -3
- package/dist/instrument.js.map +1 -1
- package/dist/log.d.ts +1 -12
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +12 -143
- package/dist/log.js.map +1 -1
- package/dist/{login.d.ts → login/index.d.ts} +16 -3
- package/dist/login/index.d.ts.map +1 -0
- package/dist/{login.js → login/index.js} +94 -17
- package/dist/login/index.js.map +1 -0
- package/dist/{link-server.d.ts → login/portal.d.ts} +6 -4
- package/dist/login/portal.d.ts.map +1 -0
- package/dist/login/portal.js +1544 -0
- package/dist/login/portal.js.map +1 -0
- package/dist/login/session.d.ts +26 -0
- package/dist/login/session.d.ts.map +1 -0
- package/dist/{link-token.js → login/session.js} +10 -22
- package/dist/login/session.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +138 -352
- package/dist/main.js.map +1 -1
- package/dist/provisioner.d.ts +42 -11
- package/dist/provisioner.d.ts.map +1 -1
- package/dist/provisioner.js +273 -64
- package/dist/provisioner.js.map +1 -1
- package/dist/runtime/conversation-orchestrator.d.ts +40 -0
- package/dist/runtime/conversation-orchestrator.d.ts.map +1 -0
- package/dist/runtime/conversation-orchestrator.js +183 -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 +26 -0
- package/dist/runtime/session-runtime.d.ts.map +1 -0
- package/dist/runtime/session-runtime.js +221 -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 +18 -2
- 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 -1
- package/dist/sandbox/host.d.ts.map +1 -1
- package/dist/sandbox/host.js +4 -0
- 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 +20 -1
- package/dist/sentry.d.ts.map +1 -1
- package/dist/sentry.js +58 -8
- 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 +33 -2
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +179 -13
- 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 +1822 -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 +36 -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 +2 -2
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +2 -2
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/event.d.ts +42 -2
- package/dist/tools/event.d.ts.map +1 -1
- package/dist/tools/event.js +43 -9
- package/dist/tools/event.js.map +1 -1
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/read.d.ts +2 -2
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/write.d.ts +2 -2
- 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/vault-routing.d.ts +2 -7
- package/dist/vault-routing.d.ts.map +1 -1
- package/dist/vault-routing.js +6 -42
- package/dist/vault-routing.js.map +1 -1
- package/dist/vault.d.ts +22 -56
- package/dist/vault.d.ts.map +1 -1
- package/dist/vault.js +155 -263
- package/dist/vault.js.map +1 -1
- package/package.json +11 -11
- package/dist/bindings.d.ts +0 -44
- package/dist/bindings.d.ts.map +0 -1
- package/dist/bindings.js +0 -74
- package/dist/bindings.js.map +0 -1
- package/dist/link-server.d.ts.map +0 -1
- package/dist/link-server.js +0 -899
- package/dist/link-server.js.map +0 -1
- package/dist/link-token.d.ts +0 -32
- 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/sandbox.d.ts +0 -2
- package/dist/sandbox.d.ts.map +0 -1
- package/dist/sandbox.js +0 -2
- package/dist/sandbox.js.map +0 -1
package/dist/main.js
CHANGED
|
@@ -1,34 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./instrument.js";
|
|
3
3
|
import { join, resolve } from "path";
|
|
4
|
-
import { mkdirSync,
|
|
4
|
+
import { mkdirSync, statSync, writeFileSync } from "fs";
|
|
5
5
|
import { homedir } from "os";
|
|
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";
|
|
21
|
-
import {
|
|
18
|
+
import { createGlobalSettingsFile, loadAgentConfig, MissingGlobalSettingsError } from "./config.js";
|
|
19
|
+
import { ensureDirExists, isRecord, readJsonFileIfExists } from "./file-guards.js";
|
|
20
|
+
import { SandboxError, parseSandboxArg, validateSandbox, } from "./sandbox/index.js";
|
|
22
21
|
import { FileVaultManager } from "./vault.js";
|
|
23
|
-
import {
|
|
24
|
-
import { addLifecycleBreadcrumb, applyRunScope } from "./sentry.js";
|
|
22
|
+
import { createSessionRuntime } from "./runtime/index.js";
|
|
25
23
|
import { ChannelStore } from "./store.js";
|
|
26
|
-
import { formatNothingRunning, formatStopped, formatStopping } from "./ui-copy.js";
|
|
27
24
|
import * as Sentry from "@sentry/node";
|
|
28
|
-
// ============================================================================
|
|
29
|
-
// Config
|
|
30
|
-
// ============================================================================
|
|
31
|
-
// Get version from package.json
|
|
32
25
|
function getVersion() {
|
|
33
26
|
// Try to find package.json in the dist directory or parent
|
|
34
27
|
const possiblePaths = [
|
|
@@ -37,25 +30,20 @@ function getVersion() {
|
|
|
37
30
|
pathJoin(process.cwd(), "package.json"),
|
|
38
31
|
];
|
|
39
32
|
for (const pkgPath of possiblePaths) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return pkg.version;
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
// Continue to next path
|
|
47
|
-
}
|
|
33
|
+
const pkg = readJsonFileIfExists(pkgPath, (value) => isRecord(value), () => "Ignoring package.json while resolving version");
|
|
34
|
+
if (typeof pkg?.version === "string" && pkg.version)
|
|
35
|
+
return pkg.version;
|
|
48
36
|
}
|
|
49
37
|
return "unknown";
|
|
50
38
|
}
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
? parseInt(process.env.
|
|
58
|
-
:
|
|
39
|
+
const MAMA_SLACK_APP_TOKEN = process.env.MAMA_SLACK_APP_TOKEN;
|
|
40
|
+
const MAMA_SLACK_BOT_TOKEN = process.env.MAMA_SLACK_BOT_TOKEN;
|
|
41
|
+
const MAMA_TELEGRAM_BOT_TOKEN = process.env.MAMA_TELEGRAM_BOT_TOKEN;
|
|
42
|
+
const MAMA_DISCORD_BOT_TOKEN = process.env.MAMA_DISCORD_BOT_TOKEN;
|
|
43
|
+
const MAMA_LINK_URL = process.env.MAMA_LINK_URL;
|
|
44
|
+
const MAMA_LINK_PORT = process.env.MAMA_LINK_PORT
|
|
45
|
+
? parseInt(process.env.MAMA_LINK_PORT, 10)
|
|
46
|
+
: MAMA_LINK_URL
|
|
59
47
|
? 8181
|
|
60
48
|
: undefined;
|
|
61
49
|
function parseArgs() {
|
|
@@ -64,12 +52,16 @@ function parseArgs() {
|
|
|
64
52
|
let workingDir;
|
|
65
53
|
let stateDirArg;
|
|
66
54
|
let downloadChannelId;
|
|
55
|
+
let showOnboard = false;
|
|
67
56
|
let showVersion = false;
|
|
68
57
|
for (let i = 0; i < args.length; i++) {
|
|
69
58
|
const arg = args[i];
|
|
70
59
|
if (arg === "--version" || arg === "-v" || arg === "-V") {
|
|
71
60
|
showVersion = true;
|
|
72
61
|
}
|
|
62
|
+
else if (arg === "--onboard") {
|
|
63
|
+
showOnboard = true;
|
|
64
|
+
}
|
|
73
65
|
else if (arg.startsWith("--sandbox=")) {
|
|
74
66
|
sandbox = parseSandboxArg(arg.slice("--sandbox=".length));
|
|
75
67
|
}
|
|
@@ -97,6 +89,7 @@ function parseArgs() {
|
|
|
97
89
|
stateDir: stateDirArg ? resolve(stateDirArg) : undefined,
|
|
98
90
|
sandbox,
|
|
99
91
|
downloadChannel: downloadChannelId,
|
|
92
|
+
showOnboard,
|
|
100
93
|
showVersion,
|
|
101
94
|
};
|
|
102
95
|
}
|
|
@@ -139,7 +132,21 @@ function handleStartupError(error) {
|
|
|
139
132
|
}
|
|
140
133
|
process.exit(1);
|
|
141
134
|
}
|
|
142
|
-
|
|
135
|
+
if (error instanceof MissingGlobalSettingsError) {
|
|
136
|
+
console.error(`Missing global settings: ${error.settingsPath}`);
|
|
137
|
+
console.error("");
|
|
138
|
+
console.error("Run onboarding to create it:");
|
|
139
|
+
console.error(` mama --onboard --state-dir ${stateDir}`);
|
|
140
|
+
console.error("");
|
|
141
|
+
console.error("Then review the generated settings.json and start mama again.");
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
if (error instanceof Error) {
|
|
145
|
+
console.error(`Error: ${error.message}`);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
console.error(String(error));
|
|
149
|
+
process.exit(1);
|
|
143
150
|
}
|
|
144
151
|
let parsedArgs;
|
|
145
152
|
try {
|
|
@@ -153,18 +160,35 @@ if (parsedArgs.showVersion) {
|
|
|
153
160
|
console.log(getVersion());
|
|
154
161
|
process.exit(0);
|
|
155
162
|
}
|
|
163
|
+
// Handle --onboard mode
|
|
164
|
+
if (parsedArgs.showOnboard) {
|
|
165
|
+
const stateDir = parsedArgs.stateDir ?? join(homedir(), ".mama");
|
|
166
|
+
process.env.MAMA_STATE_DIR = stateDir;
|
|
167
|
+
ensureSecureStateDir(stateDir);
|
|
168
|
+
try {
|
|
169
|
+
const settingsPath = createGlobalSettingsFile(stateDir);
|
|
170
|
+
console.log(`Created global settings at ${settingsPath}`);
|
|
171
|
+
console.log("Review the file, then start mama with your working directory.");
|
|
172
|
+
process.exit(0);
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
156
179
|
// Handle --download mode (Slack only)
|
|
157
180
|
if (parsedArgs.downloadChannel) {
|
|
158
|
-
if (!
|
|
159
|
-
console.error("Missing env:
|
|
181
|
+
if (!MAMA_SLACK_BOT_TOKEN) {
|
|
182
|
+
console.error("Missing env: MAMA_SLACK_BOT_TOKEN");
|
|
160
183
|
process.exit(1);
|
|
161
184
|
}
|
|
162
|
-
await downloadChannel(parsedArgs.downloadChannel,
|
|
185
|
+
await downloadChannel(parsedArgs.downloadChannel, MAMA_SLACK_BOT_TOKEN);
|
|
163
186
|
process.exit(0);
|
|
164
187
|
}
|
|
165
188
|
// Normal bot mode - require working dir
|
|
166
189
|
if (!parsedArgs.workingDir) {
|
|
167
|
-
console.error("Usage: mama [--state-dir=<dir>] [--sandbox=host|container:<name>|image:<image>|firecracker:<vm-id>:<host-path>] <working-directory>");
|
|
190
|
+
console.error("Usage: mama [--state-dir=<dir>] [--sandbox=host|container:<name>|image:<image>|firecracker:<vm-id>:<host-path>|cloudflare:<sandbox-id>] <working-directory>");
|
|
191
|
+
console.error(" mama --onboard [--state-dir=<dir>]");
|
|
168
192
|
console.error(" mama --download <channel-id>");
|
|
169
193
|
process.exit(1);
|
|
170
194
|
}
|
|
@@ -173,14 +197,14 @@ const stateDir = parsedArgs.stateDir ?? join(homedir(), ".mama");
|
|
|
173
197
|
process.env.MAMA_STATE_DIR = stateDir;
|
|
174
198
|
ensureSecureStateDir(stateDir);
|
|
175
199
|
// Validate platform tokens
|
|
176
|
-
const hasSlack = !!(
|
|
177
|
-
const hasTelegram = !!
|
|
178
|
-
const hasDiscord = !!
|
|
200
|
+
const hasSlack = !!(MAMA_SLACK_APP_TOKEN && MAMA_SLACK_BOT_TOKEN);
|
|
201
|
+
const hasTelegram = !!MAMA_TELEGRAM_BOT_TOKEN;
|
|
202
|
+
const hasDiscord = !!MAMA_DISCORD_BOT_TOKEN;
|
|
179
203
|
if (!hasSlack && !hasTelegram && !hasDiscord) {
|
|
180
204
|
console.error("No platform tokens found. Set one of:\n" +
|
|
181
|
-
" Slack:
|
|
182
|
-
" Telegram:
|
|
183
|
-
" Discord:
|
|
205
|
+
" Slack: MAMA_SLACK_APP_TOKEN + MAMA_SLACK_BOT_TOKEN\n" +
|
|
206
|
+
" Telegram: MAMA_TELEGRAM_BOT_TOKEN\n" +
|
|
207
|
+
" Discord: MAMA_DISCORD_BOT_TOKEN");
|
|
184
208
|
process.exit(1);
|
|
185
209
|
}
|
|
186
210
|
try {
|
|
@@ -193,84 +217,52 @@ const vaultManager = new FileVaultManager(stateDir);
|
|
|
193
217
|
if (vaultManager.isEnabled()) {
|
|
194
218
|
console.log(sandbox.type === "container"
|
|
195
219
|
? " Vault system enabled. Container vault active."
|
|
196
|
-
: sandbox.type === "image" || sandbox.type === "firecracker"
|
|
197
|
-
? " Vault system enabled.
|
|
220
|
+
: sandbox.type === "image" || sandbox.type === "firecracker" || sandbox.type === "cloudflare"
|
|
221
|
+
? " Vault system enabled. Conversation-scoped credential routing active."
|
|
198
222
|
: " Vault system enabled. Host mode will not inject vault env.");
|
|
199
223
|
}
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
? " Binding store enabled. Container mode uses the container vault."
|
|
204
|
-
: sandbox.type === "image" || sandbox.type === "firecracker"
|
|
205
|
-
? " Binding store enabled. Platform user → vault routing active."
|
|
206
|
-
: " Binding store enabled. Host mode will not inject vault env.");
|
|
207
|
-
}
|
|
208
|
-
const provisioner = sandbox.type === "image" ? new DockerContainerManager(sandbox.image, workingDir) : undefined;
|
|
209
|
-
const linkTokenStore = new InMemoryLinkTokenStore();
|
|
210
|
-
setInterval(() => linkTokenStore.purge(), 5 * 60 * 1000).unref();
|
|
211
|
-
function normalizeLoginBaseUrl() {
|
|
212
|
-
if (MOM_LINK_URL) {
|
|
213
|
-
return MOM_LINK_URL.replace(/\/+$/, "");
|
|
214
|
-
}
|
|
215
|
-
if (MOM_LINK_PORT) {
|
|
216
|
-
return `http://localhost:${MOM_LINK_PORT}`;
|
|
217
|
-
}
|
|
218
|
-
return undefined;
|
|
219
|
-
}
|
|
220
|
-
function isPrivateConversation(event) {
|
|
221
|
-
return (event.conversationKind === "direct" ||
|
|
222
|
-
event.type === "dm" ||
|
|
223
|
-
event.sessionKey === event.conversationId);
|
|
224
|
-
}
|
|
225
|
-
function ensureLoginVault(platform, platformUserId) {
|
|
226
|
-
const vaultId = resolveActorVaultKey(sandbox, vaultManager, bindingStore, platform, platformUserId);
|
|
227
|
-
ensureSandboxVaultEntry(sandbox, vaultManager, platform, platformUserId, vaultId);
|
|
228
|
-
if (sandbox.type !== "container" && sandbox.type !== "image") {
|
|
229
|
-
vaultManager.addEntry(vaultId, createManagedVaultEntry(platform, platformUserId, vaultId));
|
|
230
|
-
}
|
|
231
|
-
return vaultId;
|
|
232
|
-
}
|
|
233
|
-
async function replyWithContext(responseCtx, text) {
|
|
234
|
-
await responseCtx.setTyping(false);
|
|
235
|
-
await responseCtx.setWorking(false);
|
|
236
|
-
await responseCtx.respond(text);
|
|
237
|
-
}
|
|
238
|
-
async function handleLoginCommand(platform, platformUserId, conversationId, responseCtx, commandText, privateConversation) {
|
|
239
|
-
const parsed = parseLoginCommand(commandText);
|
|
240
|
-
if (!parsed)
|
|
241
|
-
return false;
|
|
242
|
-
if (!privateConversation) {
|
|
243
|
-
await replyWithContext(responseCtx, "為了保護你的憑證,`/login` 只能在與機器人的私訊中使用。請先私訊機器人,再重新執行 `/login`。");
|
|
244
|
-
return true;
|
|
224
|
+
const startupConfig = (() => {
|
|
225
|
+
try {
|
|
226
|
+
return loadAgentConfig();
|
|
245
227
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
await replyWithContext(responseCtx, "Login is not configured. Set `MOM_LINK_URL` or `MOM_LINK_PORT` on the server.");
|
|
249
|
-
return true;
|
|
228
|
+
catch (error) {
|
|
229
|
+
handleStartupError(error);
|
|
250
230
|
}
|
|
251
|
-
|
|
231
|
+
})();
|
|
232
|
+
const sandboxLimits = startupConfig.sandboxCpus || startupConfig.sandboxMemory
|
|
233
|
+
? { cpus: startupConfig.sandboxCpus, memory: startupConfig.sandboxMemory }
|
|
234
|
+
: undefined;
|
|
235
|
+
const sandboxBoostLimits = startupConfig.sandboxBoostCpus || startupConfig.sandboxBoostMemory
|
|
236
|
+
? { cpus: startupConfig.sandboxBoostCpus, memory: startupConfig.sandboxBoostMemory }
|
|
237
|
+
: undefined;
|
|
238
|
+
const provisioner = sandbox.type === "image"
|
|
239
|
+
? new DockerContainerManager(sandbox.image, {
|
|
240
|
+
limits: sandboxLimits,
|
|
241
|
+
boostLimits: sandboxBoostLimits,
|
|
242
|
+
})
|
|
243
|
+
: undefined;
|
|
244
|
+
if (sandbox.type === "image") {
|
|
245
|
+
ensureDirExists(join(workingDir, "skills"));
|
|
246
|
+
ensureDirExists(join(workingDir, "events"));
|
|
252
247
|
try {
|
|
253
|
-
|
|
248
|
+
writeFileSync(join(workingDir, "MEMORY.md"), "", { flag: "wx" });
|
|
254
249
|
}
|
|
255
|
-
catch (
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
return true;
|
|
250
|
+
catch (err) {
|
|
251
|
+
if (err.code !== "EEXIST")
|
|
252
|
+
throw err;
|
|
259
253
|
}
|
|
260
|
-
const token = linkTokenStore.create(platform, platformUserId, conversationId, vaultId, "");
|
|
261
|
-
const vaultLabel = sandbox.type === "container" ? `container vault (${vaultId})` : "your vault";
|
|
262
|
-
await replyWithContext(responseCtx, `Open this link to store credentials in ${vaultLabel} (expires in 15 minutes):\n${baseUrl}/link?token=${token.token}`);
|
|
263
|
-
return true;
|
|
264
254
|
}
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
255
|
+
const linkTokenStore = new InMemoryLinkTokenStore();
|
|
256
|
+
const sessionViewTokenStore = new InMemorySessionViewTokenStore();
|
|
257
|
+
setInterval(() => linkTokenStore.purge(), 5 * 60 * 1000).unref();
|
|
258
|
+
setInterval(() => sessionViewTokenStore.purge(), 5 * 60 * 1000).unref();
|
|
259
|
+
function portalBaseUrl() {
|
|
260
|
+
if (MAMA_LINK_URL)
|
|
261
|
+
return MAMA_LINK_URL.replace(/\/+$/, "");
|
|
262
|
+
if (MAMA_LINK_PORT)
|
|
263
|
+
return `http://localhost:${MAMA_LINK_PORT}`;
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
274
266
|
/** Idle timeout for managed image containers (10 minutes) */
|
|
275
267
|
const IMAGE_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
276
268
|
if (provisioner) {
|
|
@@ -278,241 +270,37 @@ if (provisioner) {
|
|
|
278
270
|
await provisioner.stopIdle(IMAGE_IDLE_TIMEOUT_MS);
|
|
279
271
|
setInterval(() => provisioner.stopIdle(IMAGE_IDLE_TIMEOUT_MS), IMAGE_IDLE_TIMEOUT_MS).unref();
|
|
280
272
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
lastAccessedAt: Date.now(),
|
|
291
|
-
};
|
|
292
|
-
conversationStates.set(key, state);
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
state.lastAccessedAt = Date.now();
|
|
296
|
-
}
|
|
297
|
-
return state;
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Evict idle sessions from conversationStates to bound memory usage.
|
|
301
|
-
* Called after each handleEvent completes.
|
|
302
|
-
*/
|
|
303
|
-
function evictIdleSessions() {
|
|
304
|
-
const now = Date.now();
|
|
305
|
-
for (const [key, state] of conversationStates) {
|
|
306
|
-
if (!state.running && now - state.lastAccessedAt > IDLE_TIMEOUT_MS) {
|
|
307
|
-
conversationStates.delete(key);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
if (conversationStates.size > MAX_SESSIONS) {
|
|
311
|
-
const idleSessions = [];
|
|
312
|
-
for (const [key, state] of conversationStates) {
|
|
313
|
-
if (!state.running) {
|
|
314
|
-
idleSessions.push({ key, lastAccessedAt: state.lastAccessedAt });
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
idleSessions.sort((a, b) => a.lastAccessedAt - b.lastAccessedAt);
|
|
318
|
-
const toEvict = conversationStates.size - MAX_SESSIONS;
|
|
319
|
-
for (let i = 0; i < toEvict && i < idleSessions.length; i++) {
|
|
320
|
-
conversationStates.delete(idleSessions[i].key);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
// ============================================================================
|
|
325
|
-
// Handler
|
|
326
|
-
// ============================================================================
|
|
327
|
-
const handler = {
|
|
328
|
-
isRunning(sessionKey) {
|
|
329
|
-
const state = conversationStates.get(sessionKey);
|
|
330
|
-
return !!state?.running;
|
|
331
|
-
},
|
|
332
|
-
getRunningSessions() {
|
|
333
|
-
const sessions = [];
|
|
334
|
-
for (const [sessionKey, state] of conversationStates) {
|
|
335
|
-
if (state.running && state.startedAt) {
|
|
336
|
-
// Get current step from runner
|
|
337
|
-
const currentStep = state.runner.getCurrentStep();
|
|
338
|
-
sessions.push({
|
|
339
|
-
sessionKey,
|
|
340
|
-
startedAt: state.startedAt,
|
|
341
|
-
lastActivityAt: state.lastActivityAt,
|
|
342
|
-
currentTool: currentStep?.label || currentStep?.toolName,
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
return sessions;
|
|
347
|
-
},
|
|
348
|
-
async handleStop(sessionKey, conversationId, bot) {
|
|
349
|
-
const state = conversationStates.get(sessionKey);
|
|
350
|
-
if (state?.running) {
|
|
351
|
-
state.stopRequested = true;
|
|
352
|
-
state.runner.abort();
|
|
353
|
-
const ts = await bot.postMessage(conversationId, formatStopping(bot));
|
|
354
|
-
state.stopMessageTs = ts;
|
|
355
|
-
}
|
|
356
|
-
else {
|
|
357
|
-
await bot.postMessage(conversationId, formatNothingRunning(bot));
|
|
358
|
-
}
|
|
359
|
-
},
|
|
360
|
-
forceStop(sessionKey) {
|
|
361
|
-
const state = conversationStates.get(sessionKey);
|
|
362
|
-
if (state?.running) {
|
|
363
|
-
log.logInfo(`[Force Stop] Force stopping session: ${sessionKey}`);
|
|
364
|
-
state.stopRequested = true;
|
|
365
|
-
state.runner.abort();
|
|
366
|
-
state.running = false;
|
|
367
|
-
}
|
|
368
|
-
},
|
|
369
|
-
async handleNew(sessionKey, conversationId, bot) {
|
|
370
|
-
const state = conversationStates.get(sessionKey);
|
|
371
|
-
if (state?.running) {
|
|
372
|
-
state.stopRequested = true;
|
|
373
|
-
state.runner.abort();
|
|
374
|
-
}
|
|
375
|
-
// Conversation sessions rotate via current pointer. Thread sessions reset in place.
|
|
376
|
-
const conversationDir = join(workingDir, conversationId);
|
|
377
|
-
if (sessionKey.includes(":")) {
|
|
378
|
-
createManagedSessionFileAtPath(getThreadSessionFile(conversationDir, sessionKey), conversationDir);
|
|
379
|
-
}
|
|
380
|
-
else {
|
|
381
|
-
createManagedSessionFile(getChannelSessionDir(conversationDir), conversationDir);
|
|
382
|
-
}
|
|
383
|
-
// Remove from in-memory cache
|
|
384
|
-
conversationStates.delete(sessionKey);
|
|
385
|
-
log.logInfo(`[${conversationId}] Session reset: ${sessionKey}`);
|
|
386
|
-
await bot.postMessage(conversationId, "Conversation reset. Send a new message to start fresh.");
|
|
387
|
-
},
|
|
388
|
-
async handleEvent(event, bot, adapters, _isEvent) {
|
|
389
|
-
const conversationId = event.conversationId;
|
|
390
|
-
// Don't accept new events during shutdown
|
|
391
|
-
if (isShuttingDown) {
|
|
392
|
-
log.logInfo(`[${conversationId}] Rejected event during shutdown: ${event.text.substring(0, 50)}`);
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
const sessionKey = event.sessionKey ?? `${conversationId}:${event.thread_ts ?? event.ts}`;
|
|
396
|
-
const handledLogin = await handleLoginCommand(adapters.platform.name, event.user, conversationId, adapters.responseCtx, event.text, isPrivateConversation(event));
|
|
397
|
-
if (handledLogin)
|
|
398
|
-
return;
|
|
399
|
-
const state = await getState(conversationId, sessionKey);
|
|
400
|
-
// Start run
|
|
401
|
-
state.running = true;
|
|
402
|
-
state.stopRequested = false;
|
|
403
|
-
state.startedAt = Date.now();
|
|
404
|
-
state.lastActivityAt = Date.now();
|
|
405
|
-
log.logInfo(`[${conversationId}] Starting run: ${event.text.substring(0, 50)}`);
|
|
406
|
-
// Wrap in-flight run tracking
|
|
407
|
-
Sentry.metrics.count("agent.run.started", 1, {
|
|
408
|
-
attributes: { channel: conversationId },
|
|
409
|
-
});
|
|
410
|
-
Sentry.metrics.gauge("agent.sessions.active", inFlightRuns.size + 1);
|
|
411
|
-
const runPromise = Sentry.startSpan({ name: "agent.run", op: "agent", attributes: { conversationId, sessionKey } }, async () => {
|
|
412
|
-
return Sentry.withScope(async (scope) => {
|
|
413
|
-
const { message, responseCtx, platform } = adapters;
|
|
414
|
-
applyRunScope(scope, {
|
|
415
|
-
conversationId,
|
|
416
|
-
sessionKey,
|
|
417
|
-
messageId: message.id,
|
|
418
|
-
platform: platform.name,
|
|
419
|
-
userId: message.userId,
|
|
420
|
-
userName: message.userName,
|
|
421
|
-
threadTs: message.threadTs,
|
|
422
|
-
isEvent: _isEvent,
|
|
423
|
-
});
|
|
424
|
-
addLifecycleBreadcrumb("agent.run.started", {
|
|
425
|
-
channel_id: conversationId,
|
|
426
|
-
platform: platform.name,
|
|
427
|
-
has_attachments: (message.attachments?.length ?? 0) > 0,
|
|
428
|
-
});
|
|
429
|
-
try {
|
|
430
|
-
await responseCtx.setTyping(true);
|
|
431
|
-
await responseCtx.setWorking(true);
|
|
432
|
-
const result = await state.runner.run(message, responseCtx, platform);
|
|
433
|
-
await responseCtx.setWorking(false);
|
|
434
|
-
const durationMs = Date.now() - state.startedAt;
|
|
435
|
-
Sentry.metrics.distribution("agent.run.duration", durationMs, {
|
|
436
|
-
unit: "millisecond",
|
|
437
|
-
attributes: {
|
|
438
|
-
channel: conversationId,
|
|
439
|
-
platform: platform.name,
|
|
440
|
-
stop_reason: result.stopReason,
|
|
441
|
-
},
|
|
442
|
-
});
|
|
443
|
-
Sentry.metrics.count("agent.run.completed", 1, {
|
|
444
|
-
attributes: {
|
|
445
|
-
channel: conversationId,
|
|
446
|
-
platform: platform.name,
|
|
447
|
-
stop_reason: result.stopReason,
|
|
448
|
-
},
|
|
449
|
-
});
|
|
450
|
-
addLifecycleBreadcrumb("agent.run.completed", {
|
|
451
|
-
channel_id: conversationId,
|
|
452
|
-
platform: platform.name,
|
|
453
|
-
stop_reason: result.stopReason,
|
|
454
|
-
duration_ms: durationMs,
|
|
455
|
-
});
|
|
456
|
-
if (result.stopReason === "aborted" && state.stopRequested) {
|
|
457
|
-
if (state.stopMessageTs) {
|
|
458
|
-
await bot.updateMessage(conversationId, state.stopMessageTs, formatStopped(bot));
|
|
459
|
-
state.stopMessageTs = undefined;
|
|
460
|
-
}
|
|
461
|
-
else {
|
|
462
|
-
await bot.postMessage(conversationId, formatStopped(bot));
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
catch (err) {
|
|
467
|
-
scope.setContext("agent_run_error", {
|
|
468
|
-
conversationId,
|
|
469
|
-
sessionKey,
|
|
470
|
-
platform: adapters.platform.name,
|
|
471
|
-
messageId: adapters.message.id,
|
|
472
|
-
threadTs: adapters.message.threadTs,
|
|
473
|
-
});
|
|
474
|
-
Sentry.captureException(err);
|
|
475
|
-
Sentry.metrics.count("agent.run.errors", 1, {
|
|
476
|
-
attributes: { channel: conversationId, platform: adapters.platform.name },
|
|
477
|
-
});
|
|
478
|
-
log.logWarning(`[${conversationId}] Run error`, err instanceof Error ? err.message : String(err));
|
|
479
|
-
}
|
|
480
|
-
finally {
|
|
481
|
-
state.running = false;
|
|
482
|
-
state.lastAccessedAt = Date.now();
|
|
483
|
-
Sentry.metrics.gauge("agent.sessions.active", inFlightRuns.size - 1);
|
|
484
|
-
evictIdleSessions();
|
|
485
|
-
}
|
|
486
|
-
});
|
|
487
|
-
});
|
|
488
|
-
inFlightRuns.add(runPromise);
|
|
489
|
-
try {
|
|
490
|
-
await runPromise;
|
|
491
|
-
}
|
|
492
|
-
finally {
|
|
493
|
-
inFlightRuns.delete(runPromise);
|
|
494
|
-
}
|
|
495
|
-
},
|
|
496
|
-
};
|
|
497
|
-
// ============================================================================
|
|
498
|
-
// Start
|
|
499
|
-
// ============================================================================
|
|
273
|
+
const handler = createSessionRuntime({
|
|
274
|
+
workingDir,
|
|
275
|
+
sandbox,
|
|
276
|
+
vaultManager,
|
|
277
|
+
provisioner,
|
|
278
|
+
linkTokenStore,
|
|
279
|
+
sessionViewTokenStore,
|
|
280
|
+
portalBaseUrl: portalBaseUrl(),
|
|
281
|
+
});
|
|
500
282
|
const sandboxDesc = sandbox.type === "host"
|
|
501
283
|
? "host"
|
|
502
284
|
: sandbox.type === "container"
|
|
503
285
|
? `container:${sandbox.container}`
|
|
504
286
|
: sandbox.type === "image"
|
|
505
287
|
? `image:${sandbox.image}`
|
|
506
|
-
:
|
|
288
|
+
: sandbox.type === "firecracker"
|
|
289
|
+
? `firecracker:${sandbox.vmId}`
|
|
290
|
+
: `cloudflare:${sandbox.sandboxId}`;
|
|
507
291
|
log.logStartup(workingDir, sandboxDesc);
|
|
508
|
-
// Create platform bots
|
|
509
292
|
const bots = [];
|
|
510
293
|
const botsByPlatform = {};
|
|
511
294
|
if (hasSlack) {
|
|
512
|
-
const
|
|
295
|
+
const slackBotToken = MAMA_SLACK_BOT_TOKEN;
|
|
296
|
+
const slackAppToken = MAMA_SLACK_APP_TOKEN;
|
|
297
|
+
if (!slackBotToken || !slackAppToken) {
|
|
298
|
+
throw new Error("Slack startup requires both MAMA_SLACK_APP_TOKEN and MAMA_SLACK_BOT_TOKEN");
|
|
299
|
+
}
|
|
300
|
+
const sharedStore = new ChannelStore({ workingDir, botToken: slackBotToken });
|
|
513
301
|
const slackBot = new SlackBotClass(handler, {
|
|
514
|
-
appToken:
|
|
515
|
-
botToken:
|
|
302
|
+
appToken: slackAppToken,
|
|
303
|
+
botToken: slackBotToken,
|
|
516
304
|
workingDir,
|
|
517
305
|
store: sharedStore,
|
|
518
306
|
});
|
|
@@ -521,8 +309,12 @@ if (hasSlack) {
|
|
|
521
309
|
log.logInfo("Platform: Slack");
|
|
522
310
|
}
|
|
523
311
|
if (hasTelegram) {
|
|
312
|
+
const telegramToken = MAMA_TELEGRAM_BOT_TOKEN;
|
|
313
|
+
if (!telegramToken) {
|
|
314
|
+
throw new Error("Telegram startup requires MAMA_TELEGRAM_BOT_TOKEN");
|
|
315
|
+
}
|
|
524
316
|
const telegramBot = new TelegramBot(handler, {
|
|
525
|
-
token:
|
|
317
|
+
token: telegramToken,
|
|
526
318
|
workingDir,
|
|
527
319
|
});
|
|
528
320
|
bots.push(telegramBot);
|
|
@@ -530,20 +322,24 @@ if (hasTelegram) {
|
|
|
530
322
|
log.logInfo("Platform: Telegram");
|
|
531
323
|
}
|
|
532
324
|
if (hasDiscord) {
|
|
325
|
+
const discordToken = MAMA_DISCORD_BOT_TOKEN;
|
|
326
|
+
if (!discordToken) {
|
|
327
|
+
throw new Error("Discord startup requires MAMA_DISCORD_BOT_TOKEN");
|
|
328
|
+
}
|
|
533
329
|
const discordBot = new DiscordBot(handler, {
|
|
534
|
-
token:
|
|
330
|
+
token: discordToken,
|
|
535
331
|
workingDir,
|
|
536
332
|
});
|
|
537
333
|
bots.push(discordBot);
|
|
538
334
|
botsByPlatform.discord = discordBot;
|
|
539
335
|
log.logInfo("Platform: Discord");
|
|
540
336
|
}
|
|
541
|
-
if (
|
|
542
|
-
startLinkServer(
|
|
337
|
+
if (MAMA_LINK_PORT) {
|
|
338
|
+
startLinkServer(MAMA_LINK_PORT, linkTokenStore, vaultManager, async (platform, conversationId, message) => {
|
|
543
339
|
const bot = botsByPlatform[platform];
|
|
544
340
|
if (bot)
|
|
545
341
|
await bot.postMessage(conversationId, message);
|
|
546
|
-
});
|
|
342
|
+
}, sessionViewTokenStore, { handler, botsByPlatform });
|
|
547
343
|
}
|
|
548
344
|
// Start events watcher with explicit platform routing
|
|
549
345
|
const eventsWatcher = createEventsWatcher(workingDir, botsByPlatform);
|
|
@@ -554,17 +350,7 @@ if (slackBot) {
|
|
|
554
350
|
eventsWatcher.start();
|
|
555
351
|
// Handle shutdown
|
|
556
352
|
async function shutdown() {
|
|
557
|
-
|
|
558
|
-
return;
|
|
559
|
-
isShuttingDown = true;
|
|
560
|
-
log.logInfo("Shutting down gracefully...");
|
|
561
|
-
const timeout = Date.now() + 30000;
|
|
562
|
-
while (inFlightRuns.size > 0 && Date.now() < timeout) {
|
|
563
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
564
|
-
}
|
|
565
|
-
if (inFlightRuns.size > 0) {
|
|
566
|
-
log.logWarning(`Forcing exit with ${inFlightRuns.size} runs still in progress`);
|
|
567
|
-
}
|
|
353
|
+
await handler.shutdown();
|
|
568
354
|
eventsWatcher.stop();
|
|
569
355
|
await Sentry.close(5000);
|
|
570
356
|
process.exit(0);
|