@jungjaehoon/mama-os 0.1.1
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/CHANGELOG.md +67 -0
- package/README.md +643 -0
- package/dist/agent/agent-loop.d.ts +98 -0
- package/dist/agent/agent-loop.d.ts.map +1 -0
- package/dist/agent/agent-loop.js +417 -0
- package/dist/agent/agent-loop.js.map +1 -0
- package/dist/agent/auto-recall.d.ts +48 -0
- package/dist/agent/auto-recall.d.ts.map +1 -0
- package/dist/agent/auto-recall.js +178 -0
- package/dist/agent/auto-recall.js.map +1 -0
- package/dist/agent/claude-cli-wrapper.d.ts +130 -0
- package/dist/agent/claude-cli-wrapper.d.ts.map +1 -0
- package/dist/agent/claude-cli-wrapper.js +227 -0
- package/dist/agent/claude-cli-wrapper.js.map +1 -0
- package/dist/agent/claude-client.d.ts +50 -0
- package/dist/agent/claude-client.d.ts.map +1 -0
- package/dist/agent/claude-client.js +214 -0
- package/dist/agent/claude-client.js.map +1 -0
- package/dist/agent/gateway-tool-executor.d.ts +75 -0
- package/dist/agent/gateway-tool-executor.d.ts.map +1 -0
- package/dist/agent/gateway-tool-executor.js +348 -0
- package/dist/agent/gateway-tool-executor.js.map +1 -0
- package/dist/agent/index.d.ts +13 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +18 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/mcp-executor.d.ts +75 -0
- package/dist/agent/mcp-executor.d.ts.map +1 -0
- package/dist/agent/mcp-executor.js +307 -0
- package/dist/agent/mcp-executor.js.map +1 -0
- package/dist/agent/session-pool.d.ts +148 -0
- package/dist/agent/session-pool.d.ts.map +1 -0
- package/dist/agent/session-pool.js +272 -0
- package/dist/agent/session-pool.js.map +1 -0
- package/dist/agent/streaming-callback-manager.d.ts +85 -0
- package/dist/agent/streaming-callback-manager.d.ts.map +1 -0
- package/dist/agent/streaming-callback-manager.js +103 -0
- package/dist/agent/streaming-callback-manager.js.map +1 -0
- package/dist/agent/types.d.ts +437 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +29 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/api/cron-handler.d.ts +44 -0
- package/dist/api/cron-handler.d.ts.map +1 -0
- package/dist/api/cron-handler.js +195 -0
- package/dist/api/cron-handler.js.map +1 -0
- package/dist/api/error-handler.d.ts +22 -0
- package/dist/api/error-handler.d.ts.map +1 -0
- package/dist/api/error-handler.js +104 -0
- package/dist/api/error-handler.js.map +1 -0
- package/dist/api/heartbeat-handler.d.ts +49 -0
- package/dist/api/heartbeat-handler.d.ts.map +1 -0
- package/dist/api/heartbeat-handler.js +91 -0
- package/dist/api/heartbeat-handler.js.map +1 -0
- package/dist/api/index.d.ts +61 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +145 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/types.d.ts +156 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +62 -0
- package/dist/api/types.js.map +1 -0
- package/dist/auth/index.d.ts +7 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +11 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/oauth-manager.d.ts +59 -0
- package/dist/auth/oauth-manager.d.ts.map +1 -0
- package/dist/auth/oauth-manager.js +237 -0
- package/dist/auth/oauth-manager.js.map +1 -0
- package/dist/auth/types.d.ts +92 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +23 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/cli/commands/init.d.ts +19 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +155 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/run.d.ts +19 -0
- package/dist/cli/commands/run.d.ts.map +1 -0
- package/dist/cli/commands/run.js +89 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/commands/setup.d.ts +19 -0
- package/dist/cli/commands/setup.d.ts.map +1 -0
- package/dist/cli/commands/setup.js +134 -0
- package/dist/cli/commands/setup.js.map +1 -0
- package/dist/cli/commands/start.d.ts +24 -0
- package/dist/cli/commands/start.d.ts.map +1 -0
- package/dist/cli/commands/start.js +1073 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/commands/status.d.ts +10 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +85 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/stop.d.ts +10 -0
- package/dist/cli/commands/stop.d.ts.map +1 -0
- package/dist/cli/commands/stop.js +65 -0
- package/dist/cli/commands/stop.js.map +1 -0
- package/dist/cli/config/config-manager.d.ts +51 -0
- package/dist/cli/config/config-manager.d.ts.map +1 -0
- package/dist/cli/config/config-manager.js +216 -0
- package/dist/cli/config/config-manager.js.map +1 -0
- package/dist/cli/config/types.d.ts +172 -0
- package/dist/cli/config/types.d.ts.map +1 -0
- package/dist/cli/config/types.js +48 -0
- package/dist/cli/config/types.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +92 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/utils/pid-manager.d.ts +66 -0
- package/dist/cli/utils/pid-manager.d.ts.map +1 -0
- package/dist/cli/utils/pid-manager.js +167 -0
- package/dist/cli/utils/pid-manager.js.map +1 -0
- package/dist/concurrency/index.d.ts +13 -0
- package/dist/concurrency/index.d.ts.map +1 -0
- package/dist/concurrency/index.js +22 -0
- package/dist/concurrency/index.js.map +1 -0
- package/dist/concurrency/lane-manager.d.ts +113 -0
- package/dist/concurrency/lane-manager.d.ts.map +1 -0
- package/dist/concurrency/lane-manager.js +245 -0
- package/dist/concurrency/lane-manager.js.map +1 -0
- package/dist/concurrency/session-key.d.ts +41 -0
- package/dist/concurrency/session-key.d.ts.map +1 -0
- package/dist/concurrency/session-key.js +61 -0
- package/dist/concurrency/session-key.js.map +1 -0
- package/dist/concurrency/types.d.ts +69 -0
- package/dist/concurrency/types.d.ts.map +1 -0
- package/dist/concurrency/types.js +16 -0
- package/dist/concurrency/types.js.map +1 -0
- package/dist/gateways/channel-history.d.ts +102 -0
- package/dist/gateways/channel-history.d.ts.map +1 -0
- package/dist/gateways/channel-history.js +181 -0
- package/dist/gateways/channel-history.js.map +1 -0
- package/dist/gateways/context-injector.d.ts +74 -0
- package/dist/gateways/context-injector.d.ts.map +1 -0
- package/dist/gateways/context-injector.js +121 -0
- package/dist/gateways/context-injector.js.map +1 -0
- package/dist/gateways/discord.d.ts +122 -0
- package/dist/gateways/discord.d.ts.map +1 -0
- package/dist/gateways/discord.js +602 -0
- package/dist/gateways/discord.js.map +1 -0
- package/dist/gateways/index.d.ts +30 -0
- package/dist/gateways/index.d.ts.map +1 -0
- package/dist/gateways/index.js +49 -0
- package/dist/gateways/index.js.map +1 -0
- package/dist/gateways/message-router.d.ts +116 -0
- package/dist/gateways/message-router.d.ts.map +1 -0
- package/dist/gateways/message-router.js +315 -0
- package/dist/gateways/message-router.js.map +1 -0
- package/dist/gateways/message-splitter.d.ts +54 -0
- package/dist/gateways/message-splitter.d.ts.map +1 -0
- package/dist/gateways/message-splitter.js +146 -0
- package/dist/gateways/message-splitter.js.map +1 -0
- package/dist/gateways/plugin-loader.d.ts +76 -0
- package/dist/gateways/plugin-loader.d.ts.map +1 -0
- package/dist/gateways/plugin-loader.js +221 -0
- package/dist/gateways/plugin-loader.js.map +1 -0
- package/dist/gateways/session-store.d.ts +77 -0
- package/dist/gateways/session-store.d.ts.map +1 -0
- package/dist/gateways/session-store.js +233 -0
- package/dist/gateways/session-store.js.map +1 -0
- package/dist/gateways/slack.d.ts +90 -0
- package/dist/gateways/slack.d.ts.map +1 -0
- package/dist/gateways/slack.js +281 -0
- package/dist/gateways/slack.js.map +1 -0
- package/dist/gateways/telegram.d.ts +79 -0
- package/dist/gateways/telegram.d.ts.map +1 -0
- package/dist/gateways/telegram.js +207 -0
- package/dist/gateways/telegram.js.map +1 -0
- package/dist/gateways/types.d.ts +340 -0
- package/dist/gateways/types.d.ts.map +1 -0
- package/dist/gateways/types.js +6 -0
- package/dist/gateways/types.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/memory-logger.d.ts +47 -0
- package/dist/memory/memory-logger.d.ts.map +1 -0
- package/dist/memory/memory-logger.js +126 -0
- package/dist/memory/memory-logger.js.map +1 -0
- package/dist/onboarding/all-tools.d.ts +18 -0
- package/dist/onboarding/all-tools.d.ts.map +1 -0
- package/dist/onboarding/all-tools.js +149 -0
- package/dist/onboarding/all-tools.js.map +1 -0
- package/dist/onboarding/autonomous-discovery-tools.d.ts +13 -0
- package/dist/onboarding/autonomous-discovery-tools.d.ts.map +1 -0
- package/dist/onboarding/autonomous-discovery-tools.js +268 -0
- package/dist/onboarding/autonomous-discovery-tools.js.map +1 -0
- package/dist/onboarding/bootstrap-template.d.ts +5 -0
- package/dist/onboarding/bootstrap-template.d.ts.map +1 -0
- package/dist/onboarding/bootstrap-template.js +142 -0
- package/dist/onboarding/bootstrap-template.js.map +1 -0
- package/dist/onboarding/complete-autonomous-prompt.d.ts +13 -0
- package/dist/onboarding/complete-autonomous-prompt.d.ts.map +1 -0
- package/dist/onboarding/complete-autonomous-prompt.js +1220 -0
- package/dist/onboarding/complete-autonomous-prompt.js.map +1 -0
- package/dist/onboarding/onboarding-state.d.ts +70 -0
- package/dist/onboarding/onboarding-state.d.ts.map +1 -0
- package/dist/onboarding/onboarding-state.js +184 -0
- package/dist/onboarding/onboarding-state.js.map +1 -0
- package/dist/onboarding/personality-quiz.d.ts +35 -0
- package/dist/onboarding/personality-quiz.d.ts.map +1 -0
- package/dist/onboarding/personality-quiz.js +219 -0
- package/dist/onboarding/personality-quiz.js.map +1 -0
- package/dist/onboarding/phase-5-summary.d.ts +22 -0
- package/dist/onboarding/phase-5-summary.d.ts.map +1 -0
- package/dist/onboarding/phase-5-summary.js +151 -0
- package/dist/onboarding/phase-5-summary.js.map +1 -0
- package/dist/onboarding/phase-6-security.d.ts +33 -0
- package/dist/onboarding/phase-6-security.d.ts.map +1 -0
- package/dist/onboarding/phase-6-security.js +473 -0
- package/dist/onboarding/phase-6-security.js.map +1 -0
- package/dist/onboarding/phase-7-integrations.d.ts +66 -0
- package/dist/onboarding/phase-7-integrations.d.ts.map +1 -0
- package/dist/onboarding/phase-7-integrations.js +619 -0
- package/dist/onboarding/phase-7-integrations.js.map +1 -0
- package/dist/onboarding/phase-8-demo.d.ts +43 -0
- package/dist/onboarding/phase-8-demo.d.ts.map +1 -0
- package/dist/onboarding/phase-8-demo.js +346 -0
- package/dist/onboarding/phase-8-demo.js.map +1 -0
- package/dist/onboarding/phase-9-finalization.d.ts +22 -0
- package/dist/onboarding/phase-9-finalization.d.ts.map +1 -0
- package/dist/onboarding/phase-9-finalization.js +375 -0
- package/dist/onboarding/phase-9-finalization.js.map +1 -0
- package/dist/onboarding/ritual-prompt.d.ts +2 -0
- package/dist/onboarding/ritual-prompt.d.ts.map +1 -0
- package/dist/onboarding/ritual-prompt.js +285 -0
- package/dist/onboarding/ritual-prompt.js.map +1 -0
- package/dist/onboarding/ritual-tools.d.ts +13 -0
- package/dist/onboarding/ritual-tools.d.ts.map +1 -0
- package/dist/onboarding/ritual-tools.js +93 -0
- package/dist/onboarding/ritual-tools.js.map +1 -0
- package/dist/runners/cli-runner.d.ts +59 -0
- package/dist/runners/cli-runner.d.ts.map +1 -0
- package/dist/runners/cli-runner.js +190 -0
- package/dist/runners/cli-runner.js.map +1 -0
- package/dist/runners/index.d.ts +11 -0
- package/dist/runners/index.d.ts.map +1 -0
- package/dist/runners/index.js +15 -0
- package/dist/runners/index.js.map +1 -0
- package/dist/runners/types.d.ts +81 -0
- package/dist/runners/types.d.ts.map +1 -0
- package/dist/runners/types.js +31 -0
- package/dist/runners/types.js.map +1 -0
- package/dist/scheduler/cron-scheduler.d.ts +115 -0
- package/dist/scheduler/cron-scheduler.d.ts.map +1 -0
- package/dist/scheduler/cron-scheduler.js +320 -0
- package/dist/scheduler/cron-scheduler.js.map +1 -0
- package/dist/scheduler/heartbeat.d.ts +53 -0
- package/dist/scheduler/heartbeat.d.ts.map +1 -0
- package/dist/scheduler/heartbeat.js +160 -0
- package/dist/scheduler/heartbeat.js.map +1 -0
- package/dist/scheduler/index.d.ts +22 -0
- package/dist/scheduler/index.d.ts.map +1 -0
- package/dist/scheduler/index.js +31 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/scheduler/job-lock.d.ts +85 -0
- package/dist/scheduler/job-lock.d.ts.map +1 -0
- package/dist/scheduler/job-lock.js +137 -0
- package/dist/scheduler/job-lock.js.map +1 -0
- package/dist/scheduler/recovery.d.ts +78 -0
- package/dist/scheduler/recovery.d.ts.map +1 -0
- package/dist/scheduler/recovery.js +124 -0
- package/dist/scheduler/recovery.js.map +1 -0
- package/dist/scheduler/schedule-store.d.ts +112 -0
- package/dist/scheduler/schedule-store.d.ts.map +1 -0
- package/dist/scheduler/schedule-store.js +259 -0
- package/dist/scheduler/schedule-store.js.map +1 -0
- package/dist/scheduler/token-keep-alive.d.ts +49 -0
- package/dist/scheduler/token-keep-alive.d.ts.map +1 -0
- package/dist/scheduler/token-keep-alive.js +102 -0
- package/dist/scheduler/token-keep-alive.js.map +1 -0
- package/dist/scheduler/types.d.ts +96 -0
- package/dist/scheduler/types.d.ts.map +1 -0
- package/dist/scheduler/types.js +21 -0
- package/dist/scheduler/types.js.map +1 -0
- package/dist/setup/setup-prompt.d.ts +2 -0
- package/dist/setup/setup-prompt.d.ts.map +1 -0
- package/dist/setup/setup-prompt.js +138 -0
- package/dist/setup/setup-prompt.js.map +1 -0
- package/dist/setup/setup-server.d.ts +8 -0
- package/dist/setup/setup-server.d.ts.map +1 -0
- package/dist/setup/setup-server.js +71 -0
- package/dist/setup/setup-server.js.map +1 -0
- package/dist/setup/setup-tools.d.ts +13 -0
- package/dist/setup/setup-tools.d.ts.map +1 -0
- package/dist/setup/setup-tools.js +103 -0
- package/dist/setup/setup-tools.js.map +1 -0
- package/dist/setup/setup-websocket.d.ts +6 -0
- package/dist/setup/setup-websocket.d.ts.map +1 -0
- package/dist/setup/setup-websocket.js +312 -0
- package/dist/setup/setup-websocket.js.map +1 -0
- package/dist/skills/index.d.ts +10 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +26 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/skill-executor.d.ts +48 -0
- package/dist/skills/skill-executor.d.ts.map +1 -0
- package/dist/skills/skill-executor.js +483 -0
- package/dist/skills/skill-executor.js.map +1 -0
- package/dist/skills/skill-loader.d.ts +40 -0
- package/dist/skills/skill-loader.d.ts.map +1 -0
- package/dist/skills/skill-loader.js +225 -0
- package/dist/skills/skill-loader.js.map +1 -0
- package/dist/skills/skill-matcher.d.ts +33 -0
- package/dist/skills/skill-matcher.d.ts.map +1 -0
- package/dist/skills/skill-matcher.js +190 -0
- package/dist/skills/skill-matcher.js.map +1 -0
- package/dist/skills/types.d.ts +123 -0
- package/dist/skills/types.d.ts.map +1 -0
- package/dist/skills/types.js +12 -0
- package/dist/skills/types.js.map +1 -0
- package/dist/tools/browser-tool.d.ts +149 -0
- package/dist/tools/browser-tool.d.ts.map +1 -0
- package/dist/tools/browser-tool.js +257 -0
- package/dist/tools/browser-tool.js.map +1 -0
- package/package.json +84 -0
- package/public/favicon.ico +0 -0
- package/public/setup.html +1026 -0
- package/public/viewer/icons/icon-192.png +0 -0
- package/public/viewer/icons/icon-512.png +0 -0
- package/public/viewer/js/modules/chat.js +1587 -0
- package/public/viewer/js/modules/dashboard.js +275 -0
- package/public/viewer/js/modules/graph.js +997 -0
- package/public/viewer/js/modules/memory.js +353 -0
- package/public/viewer/js/modules/settings.js +255 -0
- package/public/viewer/js/utils/api.js +169 -0
- package/public/viewer/js/utils/dom.js +92 -0
- package/public/viewer/js/utils/format.js +192 -0
- package/public/viewer/manifest.json +26 -0
- package/public/viewer/sw.js +131 -0
- package/public/viewer/viewer.css +500 -0
- package/public/viewer/viewer.html +1535 -0
- package/scripts/postinstall.js +118 -0
- package/templates/skills/document-analyze.md +63 -0
- package/templates/skills/heartbeat-report.md +75 -0
- package/templates/skills/image-translate.md +67 -0
- package/templates/workspace/skill-forge/DESIGN.md +115 -0
- package/templates/workspace/skill-forge/agents/architect.ts +295 -0
- package/templates/workspace/skill-forge/agents/developer.ts +364 -0
- package/templates/workspace/skill-forge/agents/qa.ts +313 -0
- package/templates/workspace/skill-forge/claude-api.ts +353 -0
- package/templates/workspace/skill-forge/discord-ui.ts +580 -0
- package/templates/workspace/skill-forge/error-handler.ts +354 -0
- package/templates/workspace/skill-forge/mama-integration.ts +357 -0
- package/templates/workspace/skill-forge/orchestrator.ts +495 -0
- package/templates/workspace/skill-forge/output/generated-skills/skills/hello-world/README.md +24 -0
- package/templates/workspace/skill-forge/output/generated-skills/skills/hello-world/index.ts +79 -0
- package/templates/workspace/skill-forge/output/generated-skills/skills/hello-world/types.ts +17 -0
- package/templates/workspace/skill-forge/package.json +21 -0
- package/templates/workspace/skill-forge/state/session.json +132 -0
- package/templates/workspace/skill-forge/test-e2e.ts +139 -0
- package/templates/workspace/skill-forge/tsconfig.json +20 -0
- package/templates/workspace/skill-forge/types.ts +159 -0
|
@@ -0,0 +1,1073 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* mama start command
|
|
4
|
+
*
|
|
5
|
+
* Start MAMA agent daemon
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
41
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
42
|
+
};
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.startCommand = startCommand;
|
|
45
|
+
exports.runAgentLoop = runAgentLoop;
|
|
46
|
+
const node_child_process_1 = require("node:child_process");
|
|
47
|
+
const node_fs_1 = require("node:fs");
|
|
48
|
+
const node_os_1 = require("node:os");
|
|
49
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
50
|
+
const express_1 = __importDefault(require("express"));
|
|
51
|
+
const node_path_1 = __importStar(require("node:path"));
|
|
52
|
+
const ws_1 = require("ws");
|
|
53
|
+
const config_manager_js_1 = require("../config/config-manager.js");
|
|
54
|
+
const pid_manager_js_1 = require("../utils/pid-manager.js");
|
|
55
|
+
const index_js_1 = require("../../auth/index.js");
|
|
56
|
+
const index_js_2 = require("../../agent/index.js");
|
|
57
|
+
const gateway_tool_executor_js_1 = require("../../agent/gateway-tool-executor.js");
|
|
58
|
+
const index_js_3 = require("../../gateways/index.js");
|
|
59
|
+
const index_js_4 = require("../../scheduler/index.js");
|
|
60
|
+
const heartbeat_js_1 = require("../../scheduler/heartbeat.js");
|
|
61
|
+
const index_js_5 = require("../../api/index.js");
|
|
62
|
+
const setup_websocket_js_1 = require("../../setup/setup-websocket.js");
|
|
63
|
+
const onboarding_state_js_1 = require("../../onboarding/onboarding-state.js");
|
|
64
|
+
const { createGraphHandler } = require('../../api/graph-api.js');
|
|
65
|
+
const node_http_1 = __importDefault(require("node:http"));
|
|
66
|
+
// MAMA embedding server (keeps model in memory)
|
|
67
|
+
let embeddingServer = null;
|
|
68
|
+
/**
|
|
69
|
+
* SECURITY P1: Wait for port to become available after shutdown
|
|
70
|
+
* Polls port availability instead of using fixed setTimeout
|
|
71
|
+
*/
|
|
72
|
+
async function waitForPortAvailable(port, maxWaitMs = 5000) {
|
|
73
|
+
const startTime = Date.now();
|
|
74
|
+
const pollInterval = 100;
|
|
75
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
76
|
+
const isAvailable = await new Promise((resolve) => {
|
|
77
|
+
const testServer = node_http_1.default.createServer();
|
|
78
|
+
testServer.once('error', (err) => {
|
|
79
|
+
if (err.code === 'EADDRINUSE') {
|
|
80
|
+
resolve(false);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
resolve(true);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
testServer.once('listening', () => {
|
|
87
|
+
testServer.close(() => resolve(true));
|
|
88
|
+
});
|
|
89
|
+
testServer.listen(port, '127.0.0.1');
|
|
90
|
+
});
|
|
91
|
+
if (isAvailable)
|
|
92
|
+
return true;
|
|
93
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Check existing embedding server and request takeover if needed
|
|
99
|
+
* Returns true if existing server has chat capability (no takeover needed)
|
|
100
|
+
*
|
|
101
|
+
* SECURITY P1: Uses authenticated shutdown with token
|
|
102
|
+
* SECURITY P1: Validates health response before reuse
|
|
103
|
+
* SECURITY P1: Uses port polling instead of fixed timeout
|
|
104
|
+
*/
|
|
105
|
+
async function checkAndTakeoverExistingServer(port) {
|
|
106
|
+
return new Promise((resolve) => {
|
|
107
|
+
const req = node_http_1.default.request({
|
|
108
|
+
hostname: '127.0.0.1',
|
|
109
|
+
port,
|
|
110
|
+
path: '/health',
|
|
111
|
+
method: 'GET',
|
|
112
|
+
timeout: 1000,
|
|
113
|
+
}, (res) => {
|
|
114
|
+
let data = '';
|
|
115
|
+
res.on('data', (chunk) => (data += chunk));
|
|
116
|
+
res.on('end', async () => {
|
|
117
|
+
try {
|
|
118
|
+
const health = JSON.parse(data);
|
|
119
|
+
// SECURITY P1: Validate health response before reuse
|
|
120
|
+
if (health.chatEnabled && health.status === 'ok' && health.modelLoaded) {
|
|
121
|
+
// Fully functional server, reuse it
|
|
122
|
+
console.log('✓ Fully functional embedding server (reusing)');
|
|
123
|
+
resolve(true);
|
|
124
|
+
}
|
|
125
|
+
else if (health.status === 'ok') {
|
|
126
|
+
// Server healthy but incomplete features
|
|
127
|
+
if (!health.modelLoaded) {
|
|
128
|
+
console.warn('[EmbeddingServer] Warning: Model not loaded');
|
|
129
|
+
}
|
|
130
|
+
// MCP server running without chat, request shutdown
|
|
131
|
+
console.log('[EmbeddingServer] MCP server detected, requesting takeover...');
|
|
132
|
+
const shutdownReq = node_http_1.default.request({
|
|
133
|
+
hostname: '127.0.0.1',
|
|
134
|
+
port,
|
|
135
|
+
path: '/shutdown',
|
|
136
|
+
method: 'POST',
|
|
137
|
+
timeout: 2000,
|
|
138
|
+
// SECURITY P1: Pass shutdown token
|
|
139
|
+
headers: {
|
|
140
|
+
'X-Shutdown-Token': process.env.MAMA_SHUTDOWN_TOKEN || '',
|
|
141
|
+
},
|
|
142
|
+
}, async () => {
|
|
143
|
+
console.log('[EmbeddingServer] MCP server shutdown requested');
|
|
144
|
+
// SECURITY P1: Use port polling instead of fixed timeout
|
|
145
|
+
const portAvailable = await waitForPortAvailable(port, 5000);
|
|
146
|
+
if (portAvailable) {
|
|
147
|
+
console.log('[EmbeddingServer] Port available, proceeding');
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
console.warn('[EmbeddingServer] Port still in use after 5s, proceeding anyway');
|
|
151
|
+
}
|
|
152
|
+
resolve(false);
|
|
153
|
+
});
|
|
154
|
+
shutdownReq.on('error', () => resolve(false));
|
|
155
|
+
shutdownReq.end();
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
// Server unhealthy
|
|
159
|
+
console.warn('[EmbeddingServer] Server unhealthy, starting fresh');
|
|
160
|
+
resolve(false);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
resolve(false);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
req.on('error', () => resolve(false));
|
|
169
|
+
req.on('timeout', () => {
|
|
170
|
+
req.destroy();
|
|
171
|
+
resolve(false);
|
|
172
|
+
});
|
|
173
|
+
req.end();
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
async function startEmbeddingServerIfAvailable(messageRouter, sessionStore, graphHandler) {
|
|
177
|
+
const port = 3847;
|
|
178
|
+
try {
|
|
179
|
+
// Check if server already running
|
|
180
|
+
const existingHasChat = await checkAndTakeoverExistingServer(port);
|
|
181
|
+
if (existingHasChat) {
|
|
182
|
+
// Another Standalone is running with chat, no need to start
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const embeddingServerModule = require('@jungjaehoon/mama-core/embedding-server');
|
|
186
|
+
embeddingServer = await embeddingServerModule.startEmbeddingServer(port, {
|
|
187
|
+
messageRouter,
|
|
188
|
+
sessionStore,
|
|
189
|
+
graphHandler,
|
|
190
|
+
});
|
|
191
|
+
if (embeddingServer) {
|
|
192
|
+
console.log('✓ Embedding server started (port 3847)');
|
|
193
|
+
if (messageRouter && sessionStore) {
|
|
194
|
+
console.log('✓ Mobile Chat integrated with MessageRouter');
|
|
195
|
+
}
|
|
196
|
+
await embeddingServerModule.warmModel();
|
|
197
|
+
console.log('✓ Embedding model preloaded');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch (err) {
|
|
201
|
+
console.warn('[EmbeddingServer] Failed to start (optional):', err.message);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Open URL in default browser (cross-platform)
|
|
206
|
+
*/
|
|
207
|
+
function openBrowser(url) {
|
|
208
|
+
const os = (0, node_os_1.platform)();
|
|
209
|
+
let command;
|
|
210
|
+
switch (os) {
|
|
211
|
+
case 'darwin':
|
|
212
|
+
command = `open "${url}"`;
|
|
213
|
+
break;
|
|
214
|
+
case 'win32':
|
|
215
|
+
command = `start "" "${url}"`;
|
|
216
|
+
break;
|
|
217
|
+
default:
|
|
218
|
+
command = `xdg-open "${url}"`;
|
|
219
|
+
}
|
|
220
|
+
(0, node_child_process_1.exec)(command, (error) => {
|
|
221
|
+
if (error) {
|
|
222
|
+
console.warn(`[Browser] Failed to open: ${error.message}`);
|
|
223
|
+
console.log(`\n🌐 Open MAMA OS manually: ${url}\n`);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Check if onboarding is complete (persona files exist)
|
|
229
|
+
*/
|
|
230
|
+
function isOnboardingComplete() {
|
|
231
|
+
const mamaHome = (0, node_path_1.join)((0, node_os_1.homedir)(), '.mama');
|
|
232
|
+
return (0, node_fs_1.existsSync)((0, node_path_1.join)(mamaHome, 'USER.md')) && (0, node_fs_1.existsSync)((0, node_path_1.join)(mamaHome, 'SOUL.md'));
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Execute start command
|
|
236
|
+
*/
|
|
237
|
+
async function startCommand(options = {}) {
|
|
238
|
+
console.log('\n🚀 Starting MAMA Standalone\n');
|
|
239
|
+
// Check if already running
|
|
240
|
+
const runningInfo = await (0, pid_manager_js_1.isDaemonRunning)();
|
|
241
|
+
if (runningInfo) {
|
|
242
|
+
console.log(`⚠️ MAMA is already running. (PID: ${runningInfo.pid})`);
|
|
243
|
+
console.log(' To stop it: mama stop\n');
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
// Check config exists
|
|
247
|
+
if (!(0, config_manager_js_1.configExists)()) {
|
|
248
|
+
console.log('⚠️ Config file not found.');
|
|
249
|
+
console.log(' Initialize first: mama init\n');
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
// Load config
|
|
253
|
+
let config;
|
|
254
|
+
try {
|
|
255
|
+
config = await (0, config_manager_js_1.loadConfig)();
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
console.error(`Failed to load config: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
if (!config.use_claude_cli) {
|
|
262
|
+
process.stdout.write('Checking OAuth token... ');
|
|
263
|
+
try {
|
|
264
|
+
const oauthManager = new index_js_1.OAuthManager();
|
|
265
|
+
await oauthManager.getToken();
|
|
266
|
+
console.log('✓');
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
console.log('❌');
|
|
270
|
+
console.error(`\nOAuth token error: ${error instanceof Error ? error.message : String(error)}`);
|
|
271
|
+
console.error('Please log in again to Claude Code.\n');
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
console.log('✓ Claude CLI mode (OAuth token not needed)');
|
|
277
|
+
}
|
|
278
|
+
if (options.foreground) {
|
|
279
|
+
// Run in foreground
|
|
280
|
+
console.log('Starting agent loop (foreground)... ✓\n');
|
|
281
|
+
console.log('MAMA is running in foreground.');
|
|
282
|
+
console.log('Press Ctrl+C to stop.\n');
|
|
283
|
+
// Auto-open browser (after a delay for server to start)
|
|
284
|
+
const needsOnboarding = !isOnboardingComplete();
|
|
285
|
+
const targetUrl = needsOnboarding
|
|
286
|
+
? 'http://localhost:3847/setup'
|
|
287
|
+
: 'http://localhost:3847/viewer';
|
|
288
|
+
setTimeout(() => {
|
|
289
|
+
if (needsOnboarding) {
|
|
290
|
+
console.log('🎭 First-time setup - Opening onboarding wizard...\n');
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
console.log('🌐 Opening MAMA OS...\n');
|
|
294
|
+
}
|
|
295
|
+
openBrowser(targetUrl);
|
|
296
|
+
}, 3000); // Wait for embedding server
|
|
297
|
+
await (0, pid_manager_js_1.writePid)(process.pid);
|
|
298
|
+
await runAgentLoop(config);
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
// Run as daemon
|
|
302
|
+
process.stdout.write('Starting agent loop... ');
|
|
303
|
+
try {
|
|
304
|
+
const daemonPid = await startDaemon();
|
|
305
|
+
console.log('✓');
|
|
306
|
+
console.log(`\nMAMA is running in the background.`);
|
|
307
|
+
console.log(`PID: ${daemonPid}\n`);
|
|
308
|
+
console.log('Check status: mama status');
|
|
309
|
+
console.log('Stop: mama stop\n');
|
|
310
|
+
// Auto-open browser after server is ready
|
|
311
|
+
const needsOnboarding = !isOnboardingComplete();
|
|
312
|
+
const targetUrl = needsOnboarding
|
|
313
|
+
? 'http://localhost:3847/setup'
|
|
314
|
+
: 'http://localhost:3847/viewer';
|
|
315
|
+
// Wait for server to be ready
|
|
316
|
+
setTimeout(() => {
|
|
317
|
+
if (needsOnboarding) {
|
|
318
|
+
console.log('🎭 First-time setup - Opening onboarding wizard...\n');
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
console.log('🌐 Opening MAMA OS...\n');
|
|
322
|
+
}
|
|
323
|
+
openBrowser(targetUrl);
|
|
324
|
+
}, 2000); // Wait 2 seconds for embedding server to start
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
console.log('❌');
|
|
328
|
+
console.error(`\nFailed to start daemon: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Start daemon process
|
|
335
|
+
*/
|
|
336
|
+
async function startDaemon() {
|
|
337
|
+
const { mkdirSync, openSync } = await import('node:fs');
|
|
338
|
+
const { homedir } = await import('node:os');
|
|
339
|
+
// Ensure log directory exists
|
|
340
|
+
const logDir = `${homedir()}/.mama/logs`;
|
|
341
|
+
mkdirSync(logDir, { recursive: true });
|
|
342
|
+
const logFile = `${logDir}/daemon.log`;
|
|
343
|
+
const out = openSync(logFile, 'a');
|
|
344
|
+
// Spawn daemon process directly
|
|
345
|
+
const child = (0, node_child_process_1.spawn)(process.execPath, [process.argv[1], 'daemon'], {
|
|
346
|
+
detached: true,
|
|
347
|
+
stdio: ['ignore', out, out],
|
|
348
|
+
cwd: homedir(),
|
|
349
|
+
env: {
|
|
350
|
+
...process.env,
|
|
351
|
+
MAMA_DAEMON: '1',
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
child.unref();
|
|
355
|
+
if (!child.pid) {
|
|
356
|
+
throw new Error('Failed to spawn daemon process');
|
|
357
|
+
}
|
|
358
|
+
// Give daemon a moment to start
|
|
359
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
360
|
+
await (0, pid_manager_js_1.writePid)(child.pid);
|
|
361
|
+
return child.pid;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Run agent loop (for foreground and daemon mode)
|
|
365
|
+
*/
|
|
366
|
+
async function runAgentLoop(config, options = {}) {
|
|
367
|
+
// Claude CLI is always used (Pi Agent removed for ToS compliance)
|
|
368
|
+
console.log('✓ Claude CLI mode (ToS compliance)');
|
|
369
|
+
const oauthManager = new index_js_1.OAuthManager();
|
|
370
|
+
// Initialize database for session storage
|
|
371
|
+
const dbPath = (0, config_manager_js_1.expandPath)(config.database.path).replace('mama-memory.db', 'mama-sessions.db');
|
|
372
|
+
const db = new better_sqlite3_1.default(dbPath);
|
|
373
|
+
const sessionStore = new index_js_3.SessionStore(db);
|
|
374
|
+
const mamaDbPath = (0, config_manager_js_1.expandPath)(config.database.path);
|
|
375
|
+
const toolExecutor = new gateway_tool_executor_js_1.GatewayToolExecutor({
|
|
376
|
+
mamaDbPath: mamaDbPath,
|
|
377
|
+
sessionStore: sessionStore,
|
|
378
|
+
});
|
|
379
|
+
void toolExecutor;
|
|
380
|
+
// Reasoning collector for Discord display
|
|
381
|
+
let reasoningLog = [];
|
|
382
|
+
let turnCount = 0;
|
|
383
|
+
let autoRecallUsed = false;
|
|
384
|
+
const mamaHome = (0, node_path_1.join)((0, node_os_1.homedir)(), '.mama');
|
|
385
|
+
const personaComplete = (0, node_fs_1.existsSync)((0, node_path_1.join)(mamaHome, 'USER.md')) && (0, node_fs_1.existsSync)((0, node_path_1.join)(mamaHome, 'SOUL.md'));
|
|
386
|
+
let systemPrompt = '';
|
|
387
|
+
let osCapabilities = '';
|
|
388
|
+
if (!personaComplete) {
|
|
389
|
+
console.log('⚙️ Onboarding mode (persona not found)');
|
|
390
|
+
const { COMPLETE_AUTONOMOUS_PROMPT, } = require('../../onboarding/complete-autonomous-prompt.js');
|
|
391
|
+
systemPrompt = COMPLETE_AUTONOMOUS_PROMPT;
|
|
392
|
+
// Check for resume context (interrupted onboarding)
|
|
393
|
+
if ((0, onboarding_state_js_1.isOnboardingInProgress)()) {
|
|
394
|
+
const resumeContext = (0, onboarding_state_js_1.getResumeContext)();
|
|
395
|
+
if (resumeContext) {
|
|
396
|
+
console.log('📋 Resuming previous onboarding session...');
|
|
397
|
+
systemPrompt += '\n\n---\n\n' + resumeContext;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
console.log('✓ Persona loaded (chat mode)');
|
|
403
|
+
}
|
|
404
|
+
// OS Agent mode (Viewer context only)
|
|
405
|
+
if (options.osAgentMode === true) {
|
|
406
|
+
const osAgentPath = (0, node_path_1.join)(__dirname, '../../agent/os-agent-capabilities.md');
|
|
407
|
+
if ((0, node_fs_1.existsSync)(osAgentPath)) {
|
|
408
|
+
osCapabilities = (0, node_fs_1.readFileSync)(osAgentPath, 'utf-8');
|
|
409
|
+
console.log('[start] ✓ OS Agent mode enabled (system control capabilities)');
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
// Initialize agent loop with lane-based concurrency and reasoning collection
|
|
413
|
+
const agentLoop = new index_js_2.AgentLoop(oauthManager, {
|
|
414
|
+
model: config.agent.model,
|
|
415
|
+
maxTurns: config.agent.max_turns,
|
|
416
|
+
useLanes: true, // Enable lane-based concurrency for Discord
|
|
417
|
+
sessionKey: 'default', // Will be updated per message
|
|
418
|
+
systemPrompt: systemPrompt + (osCapabilities ? '\n\n---\n\n' + osCapabilities : ''),
|
|
419
|
+
// Collect reasoning for Discord display
|
|
420
|
+
onTurn: (turn) => {
|
|
421
|
+
turnCount++;
|
|
422
|
+
if (Array.isArray(turn.content)) {
|
|
423
|
+
for (const block of turn.content) {
|
|
424
|
+
if (block.type === 'tool_use') {
|
|
425
|
+
reasoningLog.push(`🔧 ${block.name}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
onToolUse: (toolName, _input, result) => {
|
|
431
|
+
// Add tool result summary
|
|
432
|
+
const resultObj = result;
|
|
433
|
+
if (resultObj.error) {
|
|
434
|
+
reasoningLog.push(` ❌ ${resultObj.error}`);
|
|
435
|
+
}
|
|
436
|
+
else if (resultObj.results && Array.isArray(resultObj.results)) {
|
|
437
|
+
reasoningLog.push(` ✓ ${resultObj.results.length} items`);
|
|
438
|
+
}
|
|
439
|
+
else if (resultObj.success !== undefined) {
|
|
440
|
+
reasoningLog.push(` ✓ ${resultObj.success ? 'success' : 'failed'}`);
|
|
441
|
+
}
|
|
442
|
+
console.log(`[Tool] ${toolName} → ${JSON.stringify(result).slice(0, 80)}`);
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
console.log('✓ Lane-based concurrency enabled (reasoning collection)');
|
|
446
|
+
// Build reasoning header for Discord
|
|
447
|
+
const buildReasoningHeader = (turns, toolsUsed) => {
|
|
448
|
+
const parts = [];
|
|
449
|
+
if (autoRecallUsed)
|
|
450
|
+
parts.push('📚 Memory');
|
|
451
|
+
if (toolsUsed.length > 0)
|
|
452
|
+
parts.push(toolsUsed.join(', '));
|
|
453
|
+
parts.push(`⏱️ ${turns} turns`);
|
|
454
|
+
return `||${parts.join(' | ')}||`;
|
|
455
|
+
};
|
|
456
|
+
// Create AgentLoopClient wrapper (adapts AgentLoopResult -> { response })
|
|
457
|
+
// Also sets session key for lane-based concurrency and includes reasoning
|
|
458
|
+
const agentLoopClient = {
|
|
459
|
+
run: async (prompt, options) => {
|
|
460
|
+
// Reset reasoning log for new request
|
|
461
|
+
reasoningLog = [];
|
|
462
|
+
turnCount = 0;
|
|
463
|
+
autoRecallUsed = false;
|
|
464
|
+
// Set session key for lane-based concurrency
|
|
465
|
+
if (options?.source && options?.channelId) {
|
|
466
|
+
const sessionKey = `${options.source}:${options.channelId}:${options.userId || 'unknown'}`;
|
|
467
|
+
agentLoop.setSessionKey(sessionKey);
|
|
468
|
+
}
|
|
469
|
+
const result = await agentLoop.run(prompt, options);
|
|
470
|
+
// Check if auto-recall was used (by checking if relevant-memories was in the history)
|
|
471
|
+
if (result.history && result.history.length > 0) {
|
|
472
|
+
const firstMsg = result.history[0];
|
|
473
|
+
if (firstMsg && Array.isArray(firstMsg.content)) {
|
|
474
|
+
const textContent = firstMsg.content.find((b) => b.type === 'text');
|
|
475
|
+
if (textContent &&
|
|
476
|
+
typeof textContent.text === 'string' &&
|
|
477
|
+
textContent.text.includes('<relevant-memories>')) {
|
|
478
|
+
autoRecallUsed = true;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
// Always prepend reasoning header
|
|
483
|
+
const header = buildReasoningHeader(result.turns, reasoningLog.filter((l) => l.startsWith('🔧')));
|
|
484
|
+
const response = `${header}\n${result.response}`;
|
|
485
|
+
return { response };
|
|
486
|
+
},
|
|
487
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
488
|
+
runWithContent: async (content, options) => {
|
|
489
|
+
// Reset reasoning log for new request
|
|
490
|
+
reasoningLog = [];
|
|
491
|
+
turnCount = 0;
|
|
492
|
+
autoRecallUsed = false;
|
|
493
|
+
// Set session key for lane-based concurrency
|
|
494
|
+
if (options?.source && options?.channelId) {
|
|
495
|
+
const sessionKey = `${options.source}:${options.channelId}:${options.userId || 'unknown'}`;
|
|
496
|
+
agentLoop.setSessionKey(sessionKey);
|
|
497
|
+
}
|
|
498
|
+
console.log(`[AgentLoop] runWithContent called with ${content.length} blocks`);
|
|
499
|
+
const result = await agentLoop.runWithContent(content, options);
|
|
500
|
+
// Check if auto-recall was used
|
|
501
|
+
if (result.history && result.history.length > 0) {
|
|
502
|
+
const firstMsg = result.history[0];
|
|
503
|
+
if (firstMsg && Array.isArray(firstMsg.content)) {
|
|
504
|
+
const textContent = firstMsg.content.find((b) => b.type === 'text');
|
|
505
|
+
if (textContent &&
|
|
506
|
+
typeof textContent.text === 'string' &&
|
|
507
|
+
textContent.text.includes('<relevant-memories>')) {
|
|
508
|
+
autoRecallUsed = true;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
// Always prepend reasoning header
|
|
513
|
+
const header = buildReasoningHeader(result.turns, reasoningLog.filter((l) => l.startsWith('🔧')));
|
|
514
|
+
const response = `${header}\n${result.response}`;
|
|
515
|
+
return { response };
|
|
516
|
+
},
|
|
517
|
+
};
|
|
518
|
+
// Initialize message router with MAMA database
|
|
519
|
+
const { initDB } = require('@jungjaehoon/mama-core/db-manager');
|
|
520
|
+
const { search, save, update, loadCheckpoint } = require('@jungjaehoon/mama-core');
|
|
521
|
+
// Initialize MAMA database first
|
|
522
|
+
await initDB();
|
|
523
|
+
console.log('✓ MAMA memory API available (loaded directly in auto-recall)');
|
|
524
|
+
// Create MAMA API client for context injection
|
|
525
|
+
const mamaApiClient = {
|
|
526
|
+
search,
|
|
527
|
+
save,
|
|
528
|
+
update,
|
|
529
|
+
loadCheckpoint,
|
|
530
|
+
};
|
|
531
|
+
const messageRouter = new index_js_3.MessageRouter(sessionStore, agentLoopClient, mamaApiClient);
|
|
532
|
+
const graphHandler = createGraphHandler();
|
|
533
|
+
await startEmbeddingServerIfAvailable(messageRouter, sessionStore, graphHandler);
|
|
534
|
+
// Initialize cron scheduler
|
|
535
|
+
const scheduler = new index_js_4.CronScheduler();
|
|
536
|
+
scheduler.setExecuteCallback(async (prompt) => {
|
|
537
|
+
console.log(`[Cron] Executing: ${prompt.substring(0, 50)}...`);
|
|
538
|
+
try {
|
|
539
|
+
const result = await agentLoop.run(prompt);
|
|
540
|
+
console.log(`[Cron] Completed: ${result.response.substring(0, 100)}...`);
|
|
541
|
+
return result.response;
|
|
542
|
+
}
|
|
543
|
+
catch (error) {
|
|
544
|
+
console.error(`[Cron] Error: ${error}`);
|
|
545
|
+
throw error;
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
// Track active gateways for cleanup
|
|
549
|
+
const gateways = [];
|
|
550
|
+
// Initialize Discord gateway if enabled (before API server for reference)
|
|
551
|
+
let discordGateway = null;
|
|
552
|
+
if (config.discord?.enabled && config.discord?.token) {
|
|
553
|
+
console.log('Initializing Discord gateway...');
|
|
554
|
+
try {
|
|
555
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
556
|
+
const discordConfig = config.discord;
|
|
557
|
+
discordGateway = new index_js_3.DiscordGateway({
|
|
558
|
+
token: config.discord.token,
|
|
559
|
+
messageRouter,
|
|
560
|
+
config: discordConfig.guilds ? { guilds: discordConfig.guilds } : undefined,
|
|
561
|
+
});
|
|
562
|
+
const gatewayInterface = {
|
|
563
|
+
sendMessage: async (channelId, message) => discordGateway.sendMessage(channelId, message),
|
|
564
|
+
sendImage: async (channelId, imagePath, caption) => discordGateway.sendImage(channelId, imagePath, caption),
|
|
565
|
+
};
|
|
566
|
+
agentLoop.setDiscordGateway(gatewayInterface);
|
|
567
|
+
await discordGateway.start();
|
|
568
|
+
gateways.push(discordGateway);
|
|
569
|
+
console.log('✓ Discord connected');
|
|
570
|
+
}
|
|
571
|
+
catch (error) {
|
|
572
|
+
console.error(`Failed to connect Discord: ${error instanceof Error ? error.message : String(error)}`);
|
|
573
|
+
discordGateway = null;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
// Initialize gateway plugin loader (for additional gateways like Slack, Chatwork)
|
|
577
|
+
const pluginLoader = new index_js_3.PluginLoader({
|
|
578
|
+
gatewayConfigs: {
|
|
579
|
+
// Pass gateway configs from main config
|
|
580
|
+
...(config.slack
|
|
581
|
+
? {
|
|
582
|
+
'slack-gateway': {
|
|
583
|
+
enabled: config.slack.enabled,
|
|
584
|
+
botToken: config.slack.bot_token,
|
|
585
|
+
appToken: config.slack.app_token,
|
|
586
|
+
},
|
|
587
|
+
}
|
|
588
|
+
: {}),
|
|
589
|
+
...(config.chatwork
|
|
590
|
+
? {
|
|
591
|
+
'chatwork-gateway': {
|
|
592
|
+
enabled: config.chatwork.enabled,
|
|
593
|
+
apiToken: config.chatwork.api_token,
|
|
594
|
+
roomIds: config.chatwork.room_ids,
|
|
595
|
+
pollInterval: config.chatwork.poll_interval,
|
|
596
|
+
mentionRequired: config.chatwork.mention_required,
|
|
597
|
+
},
|
|
598
|
+
}
|
|
599
|
+
: {}),
|
|
600
|
+
},
|
|
601
|
+
agentLoop: {
|
|
602
|
+
run: async (prompt) => {
|
|
603
|
+
const result = await agentLoop.run(prompt);
|
|
604
|
+
return { response: result.response };
|
|
605
|
+
},
|
|
606
|
+
runWithContent: async (content) => {
|
|
607
|
+
// Cast to match the expected type (both use same structure)
|
|
608
|
+
console.log(`[AgentLoop] runWithContent called with ${content.length} blocks`);
|
|
609
|
+
const result = await agentLoop.runWithContent(content);
|
|
610
|
+
return { response: result.response };
|
|
611
|
+
},
|
|
612
|
+
},
|
|
613
|
+
});
|
|
614
|
+
// Discover and load gateway plugins
|
|
615
|
+
try {
|
|
616
|
+
const discoveredPlugins = await pluginLoader.discover();
|
|
617
|
+
if (discoveredPlugins.length > 0) {
|
|
618
|
+
console.log(`Plugins discovered: ${discoveredPlugins.map((p) => p.name).join(', ')}`);
|
|
619
|
+
const pluginGateways = await pluginLoader.loadAll();
|
|
620
|
+
for (const gateway of pluginGateways) {
|
|
621
|
+
try {
|
|
622
|
+
await gateway.start();
|
|
623
|
+
gateways.push(gateway);
|
|
624
|
+
console.log(`✓ Plugin gateway connected: ${gateway.source}`);
|
|
625
|
+
}
|
|
626
|
+
catch (error) {
|
|
627
|
+
console.error(`Plugin gateway failed (${gateway.source}):`, error);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
catch (error) {
|
|
633
|
+
console.warn('Plugin loading warning:', error);
|
|
634
|
+
}
|
|
635
|
+
// Initialize heartbeat scheduler
|
|
636
|
+
const heartbeatConfig = config.heartbeat || {};
|
|
637
|
+
const heartbeatScheduler = new heartbeat_js_1.HeartbeatScheduler(agentLoop, {
|
|
638
|
+
interval: heartbeatConfig.interval || 30 * 60 * 1000, // 30 minutes default
|
|
639
|
+
quietStart: heartbeatConfig.quiet_start || 23,
|
|
640
|
+
quietEnd: heartbeatConfig.quiet_end || 8,
|
|
641
|
+
notifyChannelId: heartbeatConfig.notify_channel_id || config.discord?.default_channel_id,
|
|
642
|
+
}, discordGateway
|
|
643
|
+
? async (channelId, message) => {
|
|
644
|
+
await discordGateway.sendMessage(channelId, message);
|
|
645
|
+
}
|
|
646
|
+
: undefined);
|
|
647
|
+
if (heartbeatConfig.enabled !== false) {
|
|
648
|
+
heartbeatScheduler.start();
|
|
649
|
+
console.log('✓ Heartbeat scheduler started');
|
|
650
|
+
}
|
|
651
|
+
// Initialize token keep-alive (prevents OAuth token expiration)
|
|
652
|
+
const tokenKeepAlive = new index_js_4.TokenKeepAlive({
|
|
653
|
+
intervalMs: 6 * 60 * 60 * 1000, // 6 hours
|
|
654
|
+
onRefresh: () => {
|
|
655
|
+
console.log('✓ OAuth token kept alive');
|
|
656
|
+
},
|
|
657
|
+
onError: (error) => {
|
|
658
|
+
console.warn(`⚠️ Token refresh warning: ${error.message}`);
|
|
659
|
+
},
|
|
660
|
+
});
|
|
661
|
+
tokenKeepAlive.start();
|
|
662
|
+
// Start API server
|
|
663
|
+
const apiServer = (0, index_js_5.createApiServer)({
|
|
664
|
+
scheduler,
|
|
665
|
+
port: 3848,
|
|
666
|
+
onHeartbeat: async (prompt) => {
|
|
667
|
+
try {
|
|
668
|
+
await agentLoop.run(prompt);
|
|
669
|
+
return { success: true };
|
|
670
|
+
}
|
|
671
|
+
catch (error) {
|
|
672
|
+
return { success: false, error: String(error) };
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
});
|
|
676
|
+
// Add Discord message sending endpoint
|
|
677
|
+
apiServer.app.post('/api/discord/send', async (req, res) => {
|
|
678
|
+
try {
|
|
679
|
+
const { channelId, message } = req.body;
|
|
680
|
+
if (!channelId || !message) {
|
|
681
|
+
res.status(400).json({ error: 'channelId and message are required' });
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
if (!discordGateway) {
|
|
685
|
+
res.status(503).json({ error: 'Discord gateway not connected' });
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
console.log(`[Discord Send] Sending to ${channelId}: ${message.substring(0, 50)}...`);
|
|
689
|
+
await discordGateway.sendMessage(channelId, message);
|
|
690
|
+
console.log(`[Discord Send] Success`);
|
|
691
|
+
res.json({ success: true });
|
|
692
|
+
}
|
|
693
|
+
catch (error) {
|
|
694
|
+
console.error('[Discord Send] Error:', error);
|
|
695
|
+
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
// Add Discord cron job endpoint (run prompt and send result to Discord)
|
|
699
|
+
apiServer.app.post('/api/discord/cron', async (req, res) => {
|
|
700
|
+
try {
|
|
701
|
+
const { channelId, prompt } = req.body;
|
|
702
|
+
if (!channelId || !prompt) {
|
|
703
|
+
res.status(400).json({ error: 'channelId and prompt are required' });
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if (!discordGateway) {
|
|
707
|
+
res.status(503).json({ error: 'Discord gateway not connected' });
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
console.log(`[Discord Cron] Executing: ${prompt.substring(0, 50)}...`);
|
|
711
|
+
const result = await agentLoop.run(prompt);
|
|
712
|
+
await discordGateway.sendMessage(channelId, result.response);
|
|
713
|
+
console.log(`[Discord Cron] Sent to Discord channel ${channelId}`);
|
|
714
|
+
res.json({ success: true, response: result.response.substring(0, 100) + '...' });
|
|
715
|
+
}
|
|
716
|
+
catch (error) {
|
|
717
|
+
res.status(500).json({ error: String(error) });
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
// Report endpoint - collect data and generate report (OpenClaw migration)
|
|
721
|
+
apiServer.app.post('/api/report', async (req, res) => {
|
|
722
|
+
const { exec } = await import('child_process');
|
|
723
|
+
const { promisify } = await import('util');
|
|
724
|
+
const execAsync = promisify(exec);
|
|
725
|
+
const fs = await import('fs/promises');
|
|
726
|
+
try {
|
|
727
|
+
const { channelId, reportType = 'delta' } = req.body;
|
|
728
|
+
if (!channelId) {
|
|
729
|
+
res.status(400).json({ error: 'channelId is required' });
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
if (!discordGateway) {
|
|
733
|
+
res.status(503).json({ error: 'Discord gateway not connected' });
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
console.log(`[Heartbeat] Starting ${reportType} report...`);
|
|
737
|
+
// Get paths from config (with fallbacks)
|
|
738
|
+
const workspacePath = config.workspace?.path?.replace('~', process.env.HOME || '') ||
|
|
739
|
+
`${process.env.HOME}/.mama/workspace`;
|
|
740
|
+
const collectScript = config.integrations?.heartbeat?.collect_script?.replace('~', process.env.HOME || '') ||
|
|
741
|
+
`${workspacePath}/scripts/heartbeat-collect.sh`;
|
|
742
|
+
const dataFile = config.integrations?.heartbeat?.data_file?.replace('~', process.env.HOME || '') ||
|
|
743
|
+
`${workspacePath}/data/heartbeat-report.json`;
|
|
744
|
+
const templateFile = config.integrations?.heartbeat?.template_file?.replace('~', process.env.HOME || '') ||
|
|
745
|
+
`${workspacePath}/HEARTBEAT.md`;
|
|
746
|
+
// 1. Run heartbeat-collect.sh
|
|
747
|
+
console.log('[Heartbeat] Collecting data...');
|
|
748
|
+
await execAsync(`bash ${collectScript}`, {
|
|
749
|
+
timeout: 60000,
|
|
750
|
+
cwd: workspacePath,
|
|
751
|
+
});
|
|
752
|
+
// 2. Read collected data (limit to 50KB to fit in prompt)
|
|
753
|
+
let jsonData = await fs.readFile(dataFile, 'utf-8');
|
|
754
|
+
if (jsonData.length > 50000) {
|
|
755
|
+
console.log(`[Heartbeat] JSON too large (${jsonData.length}), truncating to 50KB`);
|
|
756
|
+
jsonData = jsonData.substring(0, 50000) + '\n... (truncated)';
|
|
757
|
+
}
|
|
758
|
+
const heartbeatMd = await fs.readFile(templateFile, 'utf-8');
|
|
759
|
+
// 3. Generate report with Claude
|
|
760
|
+
console.log('[Heartbeat] Generating report...');
|
|
761
|
+
const prompt = `Here is the collected work data. Please write a ${reportType === 'full' ? 'comprehensive report' : 'delta report'} following the report format in HEARTBEAT.md.
|
|
762
|
+
|
|
763
|
+
## HEARTBEAT.md (Report Format)
|
|
764
|
+
${heartbeatMd}
|
|
765
|
+
|
|
766
|
+
## Collected Data (JSON)
|
|
767
|
+
${jsonData}
|
|
768
|
+
|
|
769
|
+
${reportType === 'full'
|
|
770
|
+
? '📋 Write a comprehensive report. Include all project status.'
|
|
771
|
+
: '🔔 Write a delta report. If there are no new messages, respond with HEARTBEAT_OK only.'}
|
|
772
|
+
|
|
773
|
+
Keep the report under 2000 characters as it will be sent to Discord.`;
|
|
774
|
+
const result = await agentLoop.run(prompt);
|
|
775
|
+
console.log(`[Heartbeat] Claude response length: ${result.response?.length || 0}`);
|
|
776
|
+
console.log(`[Heartbeat] Response preview: ${result.response?.substring(0, 100) || 'EMPTY'}`);
|
|
777
|
+
// 4. Send to Discord
|
|
778
|
+
if (!result.response || result.response.trim() === '') {
|
|
779
|
+
console.error('[Heartbeat] Empty response from Claude');
|
|
780
|
+
res.status(500).json({ error: 'Empty response from Claude' });
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
console.log('[Heartbeat] Sending to Discord...');
|
|
784
|
+
await discordGateway.sendMessage(channelId, result.response);
|
|
785
|
+
console.log('[Heartbeat] Complete');
|
|
786
|
+
res.json({ success: true, reportType, response: result.response.substring(0, 200) + '...' });
|
|
787
|
+
}
|
|
788
|
+
catch (error) {
|
|
789
|
+
console.error('[Heartbeat] Error:', error);
|
|
790
|
+
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
// Screenshot endpoint - take HTML screenshot and send to Discord
|
|
794
|
+
apiServer.app.post('/api/screenshot', async (req, res) => {
|
|
795
|
+
const { exec } = await import('child_process');
|
|
796
|
+
const { promisify } = await import('util');
|
|
797
|
+
const path = await import('path');
|
|
798
|
+
const execAsync = promisify(exec);
|
|
799
|
+
try {
|
|
800
|
+
const { channelId, htmlFile, caption } = req.body;
|
|
801
|
+
if (!channelId || !htmlFile) {
|
|
802
|
+
res.status(400).json({ error: 'channelId and htmlFile are required' });
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
if (!discordGateway) {
|
|
806
|
+
res.status(503).json({ error: 'Discord gateway not connected' });
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
const workspacePath = config.workspace?.path?.replace('~', process.env.HOME || '') ||
|
|
810
|
+
`${process.env.HOME}/.mama/workspace`;
|
|
811
|
+
// SECURITY P0: Path traversal prevention
|
|
812
|
+
if (path.isAbsolute(htmlFile)) {
|
|
813
|
+
res.status(400).json({ error: 'Absolute paths not allowed' });
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
const resolvedPath = path.resolve(workspacePath, htmlFile);
|
|
817
|
+
const normalizedWorkspace = path.resolve(workspacePath);
|
|
818
|
+
if (!resolvedPath.startsWith(normalizedWorkspace + path.sep)) {
|
|
819
|
+
res.status(400).json({ error: 'Path traversal detected' });
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
const fs = await import('fs/promises');
|
|
823
|
+
try {
|
|
824
|
+
await fs.access(resolvedPath);
|
|
825
|
+
}
|
|
826
|
+
catch {
|
|
827
|
+
res.status(404).json({ error: 'File not found' });
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
const allowedExtensions = ['.html', '.htm'];
|
|
831
|
+
if (!allowedExtensions.some((ext) => resolvedPath.toLowerCase().endsWith(ext))) {
|
|
832
|
+
res.status(400).json({ error: 'Only HTML files allowed' });
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
const htmlPath = resolvedPath;
|
|
836
|
+
const outputPath = `${workspacePath}/temp/screenshot-${Date.now()}.png`;
|
|
837
|
+
console.log(`[Screenshot] Taking screenshot of: ${htmlPath}`);
|
|
838
|
+
// Run screenshot script
|
|
839
|
+
await execAsync(`node ${workspacePath}/scripts/html-screenshot.mjs "${htmlPath}" "${outputPath}"`, {
|
|
840
|
+
timeout: 30000,
|
|
841
|
+
cwd: workspacePath,
|
|
842
|
+
});
|
|
843
|
+
// Send to Discord
|
|
844
|
+
console.log(`[Screenshot] Sending to Discord: ${outputPath}`);
|
|
845
|
+
await discordGateway.sendImage(channelId, outputPath, caption);
|
|
846
|
+
console.log('[Screenshot] Complete');
|
|
847
|
+
res.json({ success: true, screenshot: outputPath });
|
|
848
|
+
}
|
|
849
|
+
catch (error) {
|
|
850
|
+
console.error('[Screenshot] Error:', error);
|
|
851
|
+
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
// Send image endpoint
|
|
855
|
+
// SECURITY P0: Path traversal prevention with 4-layer validation
|
|
856
|
+
apiServer.app.post('/api/discord/image', async (req, res) => {
|
|
857
|
+
const path = await import('path');
|
|
858
|
+
const fs = await import('fs/promises');
|
|
859
|
+
try {
|
|
860
|
+
const { channelId, imagePath, caption } = req.body;
|
|
861
|
+
if (!channelId || !imagePath) {
|
|
862
|
+
res.status(400).json({ error: 'channelId and imagePath are required' });
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
if (!discordGateway) {
|
|
866
|
+
res.status(503).json({ error: 'Discord gateway not connected' });
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
// SECURITY P0: 4-layer path validation
|
|
870
|
+
const workspacePath = config.workspace?.path?.replace('~', process.env.HOME || '') ||
|
|
871
|
+
`${process.env.HOME}/.mama/workspace`;
|
|
872
|
+
const tempPath = path.join(workspacePath, 'temp');
|
|
873
|
+
const tmpPath = '/tmp';
|
|
874
|
+
// Layer 1: Reject absolute paths (unless in allowed directories)
|
|
875
|
+
if (path.isAbsolute(imagePath)) {
|
|
876
|
+
const normalizedInput = path.normalize(imagePath);
|
|
877
|
+
const isInWorkspace = normalizedInput.startsWith(path.resolve(workspacePath) + path.sep);
|
|
878
|
+
const isInTemp = normalizedInput.startsWith(path.resolve(tempPath) + path.sep);
|
|
879
|
+
const isInTmp = normalizedInput.startsWith(tmpPath + path.sep);
|
|
880
|
+
if (!isInWorkspace && !isInTemp && !isInTmp) {
|
|
881
|
+
console.warn(`[Discord Image] SECURITY: Absolute path blocked: ${imagePath}`);
|
|
882
|
+
res
|
|
883
|
+
.status(400)
|
|
884
|
+
.json({ error: 'Absolute paths only allowed in workspace, workspace/temp, or /tmp' });
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
// Layer 2: Resolve and verify within allowed directories
|
|
889
|
+
const resolvedImagePath = path.isAbsolute(imagePath)
|
|
890
|
+
? path.resolve(imagePath)
|
|
891
|
+
: path.resolve(workspacePath, imagePath);
|
|
892
|
+
const normalizedWorkspace = path.resolve(workspacePath);
|
|
893
|
+
const normalizedTemp = path.resolve(tempPath);
|
|
894
|
+
const isInWorkspace = resolvedImagePath.startsWith(normalizedWorkspace + path.sep);
|
|
895
|
+
const isInTemp = resolvedImagePath.startsWith(normalizedTemp + path.sep);
|
|
896
|
+
const isInTmp = resolvedImagePath.startsWith(tmpPath + path.sep);
|
|
897
|
+
if (!isInWorkspace && !isInTemp && !isInTmp) {
|
|
898
|
+
console.warn(`[Discord Image] SECURITY: Path traversal blocked: ${imagePath} -> ${resolvedImagePath}`);
|
|
899
|
+
res.status(400).json({ error: 'Path traversal detected' });
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
// Layer 3: Verify file exists
|
|
903
|
+
try {
|
|
904
|
+
await fs.access(resolvedImagePath);
|
|
905
|
+
}
|
|
906
|
+
catch {
|
|
907
|
+
res.status(404).json({ error: 'Image file not found' });
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
// Layer 4: Whitelist extensions
|
|
911
|
+
const allowedExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp'];
|
|
912
|
+
if (!allowedExtensions.some((ext) => resolvedImagePath.toLowerCase().endsWith(ext))) {
|
|
913
|
+
console.warn(`[Discord Image] SECURITY: Invalid extension blocked: ${resolvedImagePath}`);
|
|
914
|
+
res
|
|
915
|
+
.status(400)
|
|
916
|
+
.json({ error: 'Only image files allowed (.png, .jpg, .jpeg, .gif, .webp)' });
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
console.log(`[Discord Image] Sending: ${resolvedImagePath}`);
|
|
920
|
+
await discordGateway.sendImage(channelId, resolvedImagePath, caption);
|
|
921
|
+
console.log('[Discord Image] Complete');
|
|
922
|
+
res.json({ success: true });
|
|
923
|
+
}
|
|
924
|
+
catch (error) {
|
|
925
|
+
console.error('[Discord Image] Error:', error);
|
|
926
|
+
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
apiServer.app.use(async (req, res, next) => {
|
|
930
|
+
const handled = await graphHandler(req, res);
|
|
931
|
+
if (!handled)
|
|
932
|
+
next();
|
|
933
|
+
});
|
|
934
|
+
apiServer.app.use((req, res, next) => {
|
|
935
|
+
if (req.path.startsWith('/api/session')) {
|
|
936
|
+
const http = require('http');
|
|
937
|
+
const bodyData = req.body ? JSON.stringify(req.body) : '';
|
|
938
|
+
const options = {
|
|
939
|
+
hostname: 'localhost',
|
|
940
|
+
port: 3847,
|
|
941
|
+
path: req.url,
|
|
942
|
+
method: req.method,
|
|
943
|
+
headers: {
|
|
944
|
+
...req.headers,
|
|
945
|
+
host: 'localhost:3847',
|
|
946
|
+
'content-length': Buffer.byteLength(bodyData),
|
|
947
|
+
},
|
|
948
|
+
};
|
|
949
|
+
const proxy = http.request(options, (proxyRes) => {
|
|
950
|
+
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
|
951
|
+
proxyRes.pipe(res, { end: true });
|
|
952
|
+
});
|
|
953
|
+
if (bodyData) {
|
|
954
|
+
proxy.write(bodyData);
|
|
955
|
+
}
|
|
956
|
+
proxy.end();
|
|
957
|
+
proxy.on('error', (error) => {
|
|
958
|
+
if (!res.headersSent) {
|
|
959
|
+
res.status(500).json({ error: 'Failed to proxy session API', details: error.message });
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
else {
|
|
964
|
+
next();
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
console.log('✓ Session API proxied to port 3847');
|
|
968
|
+
const publicDir = node_path_1.default.resolve(process.cwd(), 'public');
|
|
969
|
+
// Serve setup page at /setup route
|
|
970
|
+
apiServer.app.get('/setup', (_req, res) => {
|
|
971
|
+
res.sendFile(node_path_1.default.join(publicDir, 'setup.html'));
|
|
972
|
+
});
|
|
973
|
+
apiServer.app.use(express_1.default.static(publicDir, {
|
|
974
|
+
setHeaders: (res) => {
|
|
975
|
+
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
976
|
+
res.setHeader('Pragma', 'no-cache');
|
|
977
|
+
res.setHeader('Expires', '0');
|
|
978
|
+
},
|
|
979
|
+
}));
|
|
980
|
+
console.log('✓ Viewer UI available at /viewer');
|
|
981
|
+
console.log('✓ Setup wizard available at /setup');
|
|
982
|
+
await apiServer.start();
|
|
983
|
+
console.log(`API server started: http://localhost:${apiServer.port}`);
|
|
984
|
+
if (apiServer.server) {
|
|
985
|
+
// Setup WebSocket - use noServer mode to avoid conflict
|
|
986
|
+
const setupWss = new ws_1.WebSocketServer({ noServer: true });
|
|
987
|
+
(0, setup_websocket_js_1.createSetupWebSocketHandler)(setupWss);
|
|
988
|
+
console.log('✓ Setup WebSocket handler ready for /setup-ws');
|
|
989
|
+
// Handle ALL WebSocket upgrades manually
|
|
990
|
+
apiServer.server.on('upgrade', (request, socket, head) => {
|
|
991
|
+
const url = new URL(request.url || '', `http://${request.headers.host}`);
|
|
992
|
+
if (url.pathname === '/setup-ws') {
|
|
993
|
+
// Handle setup WebSocket locally
|
|
994
|
+
setupWss.handleUpgrade(request, socket, head, (ws) => {
|
|
995
|
+
setupWss.emit('connection', ws, request);
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
else if (url.pathname === '/ws') {
|
|
999
|
+
// Proxy chat WebSocket to embedding server (port 3847)
|
|
1000
|
+
const http = require('http');
|
|
1001
|
+
const options = {
|
|
1002
|
+
hostname: '127.0.0.1',
|
|
1003
|
+
port: 3847,
|
|
1004
|
+
path: request.url,
|
|
1005
|
+
method: 'GET',
|
|
1006
|
+
headers: {
|
|
1007
|
+
...request.headers,
|
|
1008
|
+
host: '127.0.0.1:3847',
|
|
1009
|
+
},
|
|
1010
|
+
};
|
|
1011
|
+
const proxyReq = http.request(options);
|
|
1012
|
+
proxyReq.on('upgrade', (proxyRes, proxySocket, _proxyHead) => {
|
|
1013
|
+
socket.write(`HTTP/1.1 101 Switching Protocols\r\n` +
|
|
1014
|
+
`Upgrade: websocket\r\n` +
|
|
1015
|
+
`Connection: Upgrade\r\n` +
|
|
1016
|
+
`Sec-WebSocket-Accept: ${proxyRes.headers['sec-websocket-accept']}\r\n` +
|
|
1017
|
+
`\r\n`);
|
|
1018
|
+
proxySocket.pipe(socket);
|
|
1019
|
+
socket.pipe(proxySocket);
|
|
1020
|
+
});
|
|
1021
|
+
proxyReq.on('error', (err) => {
|
|
1022
|
+
console.error('[WS Proxy] Error:', err.message);
|
|
1023
|
+
socket.destroy();
|
|
1024
|
+
});
|
|
1025
|
+
proxyReq.end();
|
|
1026
|
+
}
|
|
1027
|
+
else {
|
|
1028
|
+
// Unknown WebSocket path - close connection
|
|
1029
|
+
socket.destroy();
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
console.log('✓ WebSocket upgrade handler registered (/ws → 3847, /setup-ws local)');
|
|
1033
|
+
}
|
|
1034
|
+
gateways.push(apiServer);
|
|
1035
|
+
// Handle graceful shutdown
|
|
1036
|
+
const shutdown = async () => {
|
|
1037
|
+
console.log('\n\n🛑 Shutting down MAMA...');
|
|
1038
|
+
// Stop all gateways
|
|
1039
|
+
for (const gateway of gateways) {
|
|
1040
|
+
try {
|
|
1041
|
+
await gateway.stop();
|
|
1042
|
+
}
|
|
1043
|
+
catch {
|
|
1044
|
+
// Ignore errors during shutdown
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
// Stop plugin gateways
|
|
1048
|
+
try {
|
|
1049
|
+
await pluginLoader.stopAll();
|
|
1050
|
+
}
|
|
1051
|
+
catch {
|
|
1052
|
+
// Ignore errors during shutdown
|
|
1053
|
+
}
|
|
1054
|
+
// Stop schedulers
|
|
1055
|
+
scheduler.shutdown();
|
|
1056
|
+
heartbeatScheduler.stop();
|
|
1057
|
+
tokenKeepAlive.stop();
|
|
1058
|
+
// Close session database
|
|
1059
|
+
sessionStore.close();
|
|
1060
|
+
const { deletePid } = await import('../utils/pid-manager.js');
|
|
1061
|
+
await deletePid();
|
|
1062
|
+
process.exit(0);
|
|
1063
|
+
};
|
|
1064
|
+
process.on('SIGINT', shutdown);
|
|
1065
|
+
process.on('SIGTERM', shutdown);
|
|
1066
|
+
console.log('MAMA agent is waiting...\n');
|
|
1067
|
+
// Keep process alive using setInterval
|
|
1068
|
+
// This ensures the Node.js event loop stays active
|
|
1069
|
+
setInterval(() => {
|
|
1070
|
+
// Heartbeat - keeps the process running
|
|
1071
|
+
}, 30000); // Every 30 seconds
|
|
1072
|
+
}
|
|
1073
|
+
//# sourceMappingURL=start.js.map
|