whale-code 6.4.0
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 +95 -0
- package/bin/swag-agent.js +9 -0
- package/bin/swagmanager-mcp.js +321 -0
- package/dist/cli/app.d.ts +26 -0
- package/dist/cli/app.js +64 -0
- package/dist/cli/chat/AgentSelector.d.ts +14 -0
- package/dist/cli/chat/AgentSelector.js +14 -0
- package/dist/cli/chat/ChatApp.d.ts +9 -0
- package/dist/cli/chat/ChatApp.js +267 -0
- package/dist/cli/chat/ChatInput.d.ts +39 -0
- package/dist/cli/chat/ChatInput.js +509 -0
- package/dist/cli/chat/MarkdownText.d.ts +10 -0
- package/dist/cli/chat/MarkdownText.js +20 -0
- package/dist/cli/chat/MessageList.d.ts +37 -0
- package/dist/cli/chat/MessageList.js +80 -0
- package/dist/cli/chat/ModelSelector.d.ts +20 -0
- package/dist/cli/chat/ModelSelector.js +73 -0
- package/dist/cli/chat/RewindViewer.d.ts +26 -0
- package/dist/cli/chat/RewindViewer.js +185 -0
- package/dist/cli/chat/StoreSelector.d.ts +14 -0
- package/dist/cli/chat/StoreSelector.js +24 -0
- package/dist/cli/chat/StreamingText.d.ts +12 -0
- package/dist/cli/chat/StreamingText.js +12 -0
- package/dist/cli/chat/SubagentPanel.d.ts +45 -0
- package/dist/cli/chat/SubagentPanel.js +110 -0
- package/dist/cli/chat/TeamPanel.d.ts +21 -0
- package/dist/cli/chat/TeamPanel.js +42 -0
- package/dist/cli/chat/ToolIndicator.d.ts +25 -0
- package/dist/cli/chat/ToolIndicator.js +436 -0
- package/dist/cli/chat/hooks/useAgentLoop.d.ts +39 -0
- package/dist/cli/chat/hooks/useAgentLoop.js +382 -0
- package/dist/cli/chat/hooks/useSlashCommands.d.ts +37 -0
- package/dist/cli/chat/hooks/useSlashCommands.js +387 -0
- package/dist/cli/commands/config-cmd.d.ts +10 -0
- package/dist/cli/commands/config-cmd.js +99 -0
- package/dist/cli/commands/doctor.d.ts +14 -0
- package/dist/cli/commands/doctor.js +172 -0
- package/dist/cli/commands/init.d.ts +16 -0
- package/dist/cli/commands/init.js +278 -0
- package/dist/cli/commands/mcp.d.ts +12 -0
- package/dist/cli/commands/mcp.js +162 -0
- package/dist/cli/login/LoginApp.d.ts +7 -0
- package/dist/cli/login/LoginApp.js +157 -0
- package/dist/cli/print-mode.d.ts +31 -0
- package/dist/cli/print-mode.js +202 -0
- package/dist/cli/serve-mode.d.ts +37 -0
- package/dist/cli/serve-mode.js +636 -0
- package/dist/cli/services/agent-definitions.d.ts +25 -0
- package/dist/cli/services/agent-definitions.js +91 -0
- package/dist/cli/services/agent-events.d.ts +178 -0
- package/dist/cli/services/agent-events.js +175 -0
- package/dist/cli/services/agent-loop.d.ts +90 -0
- package/dist/cli/services/agent-loop.js +762 -0
- package/dist/cli/services/agent-worker-base.d.ts +97 -0
- package/dist/cli/services/agent-worker-base.js +220 -0
- package/dist/cli/services/auth-service.d.ts +30 -0
- package/dist/cli/services/auth-service.js +160 -0
- package/dist/cli/services/background-processes.d.ts +126 -0
- package/dist/cli/services/background-processes.js +318 -0
- package/dist/cli/services/browser-auth.d.ts +24 -0
- package/dist/cli/services/browser-auth.js +180 -0
- package/dist/cli/services/claude-md-loader.d.ts +16 -0
- package/dist/cli/services/claude-md-loader.js +58 -0
- package/dist/cli/services/config-store.d.ts +47 -0
- package/dist/cli/services/config-store.js +79 -0
- package/dist/cli/services/debug-log.d.ts +10 -0
- package/dist/cli/services/debug-log.js +52 -0
- package/dist/cli/services/error-logger.d.ts +58 -0
- package/dist/cli/services/error-logger.js +269 -0
- package/dist/cli/services/file-history.d.ts +21 -0
- package/dist/cli/services/file-history.js +83 -0
- package/dist/cli/services/format-server-response.d.ts +16 -0
- package/dist/cli/services/format-server-response.js +440 -0
- package/dist/cli/services/git-context.d.ts +11 -0
- package/dist/cli/services/git-context.js +66 -0
- package/dist/cli/services/hooks.d.ts +85 -0
- package/dist/cli/services/hooks.js +258 -0
- package/dist/cli/services/interactive-tools.d.ts +125 -0
- package/dist/cli/services/interactive-tools.js +260 -0
- package/dist/cli/services/keybinding-manager.d.ts +52 -0
- package/dist/cli/services/keybinding-manager.js +115 -0
- package/dist/cli/services/local-tools.d.ts +22 -0
- package/dist/cli/services/local-tools.js +697 -0
- package/dist/cli/services/lsp-manager.d.ts +18 -0
- package/dist/cli/services/lsp-manager.js +717 -0
- package/dist/cli/services/mcp-client.d.ts +48 -0
- package/dist/cli/services/mcp-client.js +157 -0
- package/dist/cli/services/memory-manager.d.ts +16 -0
- package/dist/cli/services/memory-manager.js +57 -0
- package/dist/cli/services/model-manager.d.ts +18 -0
- package/dist/cli/services/model-manager.js +71 -0
- package/dist/cli/services/model-router.d.ts +26 -0
- package/dist/cli/services/model-router.js +149 -0
- package/dist/cli/services/permission-modes.d.ts +13 -0
- package/dist/cli/services/permission-modes.js +43 -0
- package/dist/cli/services/rewind.d.ts +84 -0
- package/dist/cli/services/rewind.js +194 -0
- package/dist/cli/services/ripgrep.d.ts +28 -0
- package/dist/cli/services/ripgrep.js +138 -0
- package/dist/cli/services/sandbox.d.ts +29 -0
- package/dist/cli/services/sandbox.js +97 -0
- package/dist/cli/services/server-tools.d.ts +61 -0
- package/dist/cli/services/server-tools.js +543 -0
- package/dist/cli/services/session-persistence.d.ts +23 -0
- package/dist/cli/services/session-persistence.js +99 -0
- package/dist/cli/services/subagent-worker.d.ts +19 -0
- package/dist/cli/services/subagent-worker.js +41 -0
- package/dist/cli/services/subagent.d.ts +47 -0
- package/dist/cli/services/subagent.js +647 -0
- package/dist/cli/services/system-prompt.d.ts +7 -0
- package/dist/cli/services/system-prompt.js +198 -0
- package/dist/cli/services/team-lead.d.ts +73 -0
- package/dist/cli/services/team-lead.js +512 -0
- package/dist/cli/services/team-state.d.ts +77 -0
- package/dist/cli/services/team-state.js +398 -0
- package/dist/cli/services/teammate.d.ts +31 -0
- package/dist/cli/services/teammate.js +689 -0
- package/dist/cli/services/telemetry.d.ts +61 -0
- package/dist/cli/services/telemetry.js +209 -0
- package/dist/cli/services/tools/agent-tools.d.ts +14 -0
- package/dist/cli/services/tools/agent-tools.js +347 -0
- package/dist/cli/services/tools/file-ops.d.ts +15 -0
- package/dist/cli/services/tools/file-ops.js +487 -0
- package/dist/cli/services/tools/search-tools.d.ts +8 -0
- package/dist/cli/services/tools/search-tools.js +186 -0
- package/dist/cli/services/tools/shell-exec.d.ts +10 -0
- package/dist/cli/services/tools/shell-exec.js +168 -0
- package/dist/cli/services/tools/task-manager.d.ts +28 -0
- package/dist/cli/services/tools/task-manager.js +209 -0
- package/dist/cli/services/tools/web-tools.d.ts +11 -0
- package/dist/cli/services/tools/web-tools.js +395 -0
- package/dist/cli/setup/SetupApp.d.ts +9 -0
- package/dist/cli/setup/SetupApp.js +191 -0
- package/dist/cli/shared/MatrixIntro.d.ts +4 -0
- package/dist/cli/shared/MatrixIntro.js +83 -0
- package/dist/cli/shared/Theme.d.ts +74 -0
- package/dist/cli/shared/Theme.js +127 -0
- package/dist/cli/shared/WhaleBanner.d.ts +10 -0
- package/dist/cli/shared/WhaleBanner.js +12 -0
- package/dist/cli/shared/markdown.d.ts +21 -0
- package/dist/cli/shared/markdown.js +756 -0
- package/dist/cli/status/StatusApp.d.ts +4 -0
- package/dist/cli/status/StatusApp.js +105 -0
- package/dist/cli/stores/StoreApp.d.ts +7 -0
- package/dist/cli/stores/StoreApp.js +81 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +538 -0
- package/dist/local-agent/connection.d.ts +48 -0
- package/dist/local-agent/connection.js +332 -0
- package/dist/local-agent/discovery.d.ts +18 -0
- package/dist/local-agent/discovery.js +146 -0
- package/dist/local-agent/executor.d.ts +34 -0
- package/dist/local-agent/executor.js +241 -0
- package/dist/local-agent/index.d.ts +14 -0
- package/dist/local-agent/index.js +198 -0
- package/dist/node/adapters/base.d.ts +35 -0
- package/dist/node/adapters/base.js +10 -0
- package/dist/node/adapters/discord.d.ts +29 -0
- package/dist/node/adapters/discord.js +299 -0
- package/dist/node/adapters/email.d.ts +23 -0
- package/dist/node/adapters/email.js +218 -0
- package/dist/node/adapters/imessage.d.ts +17 -0
- package/dist/node/adapters/imessage.js +118 -0
- package/dist/node/adapters/slack.d.ts +26 -0
- package/dist/node/adapters/slack.js +259 -0
- package/dist/node/adapters/sms.d.ts +23 -0
- package/dist/node/adapters/sms.js +161 -0
- package/dist/node/adapters/telegram.d.ts +17 -0
- package/dist/node/adapters/telegram.js +101 -0
- package/dist/node/adapters/webchat.d.ts +27 -0
- package/dist/node/adapters/webchat.js +160 -0
- package/dist/node/adapters/whatsapp.d.ts +28 -0
- package/dist/node/adapters/whatsapp.js +230 -0
- package/dist/node/cli.d.ts +2 -0
- package/dist/node/cli.js +325 -0
- package/dist/node/config.d.ts +17 -0
- package/dist/node/config.js +31 -0
- package/dist/node/runtime.d.ts +50 -0
- package/dist/node/runtime.js +351 -0
- package/dist/server/handlers/__test-utils__/mock-supabase.d.ts +11 -0
- package/dist/server/handlers/__test-utils__/mock-supabase.js +393 -0
- package/dist/server/handlers/analytics.d.ts +17 -0
- package/dist/server/handlers/analytics.js +266 -0
- package/dist/server/handlers/api-keys.d.ts +6 -0
- package/dist/server/handlers/api-keys.js +221 -0
- package/dist/server/handlers/billing.d.ts +33 -0
- package/dist/server/handlers/billing.js +272 -0
- package/dist/server/handlers/browser.d.ts +10 -0
- package/dist/server/handlers/browser.js +517 -0
- package/dist/server/handlers/catalog.d.ts +99 -0
- package/dist/server/handlers/catalog.js +976 -0
- package/dist/server/handlers/comms.d.ts +254 -0
- package/dist/server/handlers/comms.js +588 -0
- package/dist/server/handlers/creations.d.ts +6 -0
- package/dist/server/handlers/creations.js +479 -0
- package/dist/server/handlers/crm.d.ts +89 -0
- package/dist/server/handlers/crm.js +538 -0
- package/dist/server/handlers/discovery.d.ts +6 -0
- package/dist/server/handlers/discovery.js +288 -0
- package/dist/server/handlers/embeddings.d.ts +92 -0
- package/dist/server/handlers/embeddings.js +197 -0
- package/dist/server/handlers/enrichment.d.ts +8 -0
- package/dist/server/handlers/enrichment.js +768 -0
- package/dist/server/handlers/image-gen.d.ts +6 -0
- package/dist/server/handlers/image-gen.js +409 -0
- package/dist/server/handlers/inventory.d.ts +319 -0
- package/dist/server/handlers/inventory.js +447 -0
- package/dist/server/handlers/kali.d.ts +10 -0
- package/dist/server/handlers/kali.js +210 -0
- package/dist/server/handlers/llm-providers.d.ts +6 -0
- package/dist/server/handlers/llm-providers.js +673 -0
- package/dist/server/handlers/local-agent.d.ts +6 -0
- package/dist/server/handlers/local-agent.js +118 -0
- package/dist/server/handlers/meta-ads.d.ts +111 -0
- package/dist/server/handlers/meta-ads.js +2279 -0
- package/dist/server/handlers/nodes.d.ts +33 -0
- package/dist/server/handlers/nodes.js +699 -0
- package/dist/server/handlers/operations.d.ts +138 -0
- package/dist/server/handlers/operations.js +131 -0
- package/dist/server/handlers/platform.d.ts +23 -0
- package/dist/server/handlers/platform.js +227 -0
- package/dist/server/handlers/supply-chain.d.ts +19 -0
- package/dist/server/handlers/supply-chain.js +327 -0
- package/dist/server/handlers/transcription.d.ts +17 -0
- package/dist/server/handlers/transcription.js +121 -0
- package/dist/server/handlers/video-gen.d.ts +6 -0
- package/dist/server/handlers/video-gen.js +466 -0
- package/dist/server/handlers/voice.d.ts +8 -0
- package/dist/server/handlers/voice.js +1146 -0
- package/dist/server/handlers/workflow-steps.d.ts +86 -0
- package/dist/server/handlers/workflow-steps.js +2349 -0
- package/dist/server/handlers/workflows.d.ts +7 -0
- package/dist/server/handlers/workflows.js +989 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +2427 -0
- package/dist/server/lib/batch-client.d.ts +80 -0
- package/dist/server/lib/batch-client.js +467 -0
- package/dist/server/lib/code-worker-pool.d.ts +31 -0
- package/dist/server/lib/code-worker-pool.js +224 -0
- package/dist/server/lib/code-worker.d.ts +1 -0
- package/dist/server/lib/code-worker.js +188 -0
- package/dist/server/lib/compaction-service.d.ts +32 -0
- package/dist/server/lib/compaction-service.js +162 -0
- package/dist/server/lib/logger.d.ts +19 -0
- package/dist/server/lib/logger.js +46 -0
- package/dist/server/lib/otel.d.ts +38 -0
- package/dist/server/lib/otel.js +126 -0
- package/dist/server/lib/pg-rate-limiter.d.ts +21 -0
- package/dist/server/lib/pg-rate-limiter.js +86 -0
- package/dist/server/lib/prompt-sanitizer.d.ts +37 -0
- package/dist/server/lib/prompt-sanitizer.js +177 -0
- package/dist/server/lib/provider-capabilities.d.ts +85 -0
- package/dist/server/lib/provider-capabilities.js +190 -0
- package/dist/server/lib/provider-failover.d.ts +74 -0
- package/dist/server/lib/provider-failover.js +210 -0
- package/dist/server/lib/rate-limiter.d.ts +39 -0
- package/dist/server/lib/rate-limiter.js +147 -0
- package/dist/server/lib/server-agent-loop.d.ts +107 -0
- package/dist/server/lib/server-agent-loop.js +667 -0
- package/dist/server/lib/server-subagent.d.ts +78 -0
- package/dist/server/lib/server-subagent.js +203 -0
- package/dist/server/lib/session-checkpoint.d.ts +51 -0
- package/dist/server/lib/session-checkpoint.js +145 -0
- package/dist/server/lib/ssrf-guard.d.ts +13 -0
- package/dist/server/lib/ssrf-guard.js +240 -0
- package/dist/server/lib/supabase-client.d.ts +7 -0
- package/dist/server/lib/supabase-client.js +78 -0
- package/dist/server/lib/template-resolver.d.ts +31 -0
- package/dist/server/lib/template-resolver.js +215 -0
- package/dist/server/lib/utils.d.ts +16 -0
- package/dist/server/lib/utils.js +147 -0
- package/dist/server/local-agent-gateway.d.ts +82 -0
- package/dist/server/local-agent-gateway.js +426 -0
- package/dist/server/providers/anthropic.d.ts +20 -0
- package/dist/server/providers/anthropic.js +199 -0
- package/dist/server/providers/bedrock.d.ts +20 -0
- package/dist/server/providers/bedrock.js +194 -0
- package/dist/server/providers/gemini.d.ts +24 -0
- package/dist/server/providers/gemini.js +486 -0
- package/dist/server/providers/openai.d.ts +24 -0
- package/dist/server/providers/openai.js +522 -0
- package/dist/server/providers/registry.d.ts +32 -0
- package/dist/server/providers/registry.js +58 -0
- package/dist/server/providers/shared.d.ts +32 -0
- package/dist/server/providers/shared.js +124 -0
- package/dist/server/providers/types.d.ts +92 -0
- package/dist/server/providers/types.js +12 -0
- package/dist/server/proxy-handlers.d.ts +6 -0
- package/dist/server/proxy-handlers.js +89 -0
- package/dist/server/tool-router.d.ts +149 -0
- package/dist/server/tool-router.js +803 -0
- package/dist/server/validation.d.ts +24 -0
- package/dist/server/validation.js +301 -0
- package/dist/server/worker.d.ts +19 -0
- package/dist/server/worker.js +201 -0
- package/dist/setup.d.ts +8 -0
- package/dist/setup.js +181 -0
- package/dist/shared/agent-core.d.ts +157 -0
- package/dist/shared/agent-core.js +534 -0
- package/dist/shared/anthropic-types.d.ts +105 -0
- package/dist/shared/anthropic-types.js +7 -0
- package/dist/shared/api-client.d.ts +90 -0
- package/dist/shared/api-client.js +379 -0
- package/dist/shared/constants.d.ts +33 -0
- package/dist/shared/constants.js +80 -0
- package/dist/shared/sse-parser.d.ts +26 -0
- package/dist/shared/sse-parser.js +259 -0
- package/dist/shared/tool-dispatch.d.ts +52 -0
- package/dist/shared/tool-dispatch.js +191 -0
- package/dist/shared/types.d.ts +72 -0
- package/dist/shared/types.js +7 -0
- package/dist/updater.d.ts +25 -0
- package/dist/updater.js +140 -0
- package/dist/webchat/widget.d.ts +0 -0
- package/dist/webchat/widget.js +397 -0
- package/package.json +95 -0
- package/src/cli/services/builtin-skills/commit.md +19 -0
- package/src/cli/services/builtin-skills/review-pr.md +21 -0
- package/src/cli/services/builtin-skills/review.md +18 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { cpus, totalmem, hostname } from "node:os";
|
|
2
|
+
import { IMessageAdapter } from "./adapters/imessage.js";
|
|
3
|
+
import { TelegramAdapter } from "./adapters/telegram.js";
|
|
4
|
+
import { DiscordAdapter } from "./adapters/discord.js";
|
|
5
|
+
import { SlackAdapter } from "./adapters/slack.js";
|
|
6
|
+
import { WebchatAdapter } from "./adapters/webchat.js";
|
|
7
|
+
import { WhatsAppAdapter } from "./adapters/whatsapp.js";
|
|
8
|
+
import { SmsAdapter } from "./adapters/sms.js";
|
|
9
|
+
import { EmailAdapter } from "./adapters/email.js";
|
|
10
|
+
import { captureError, addBreadcrumb } from "../cli/services/error-logger.js";
|
|
11
|
+
const HEARTBEAT_INTERVAL_MS = 60_000;
|
|
12
|
+
const POLL_INTERVAL_MS = 2_000;
|
|
13
|
+
const VERSION = "1.1.0";
|
|
14
|
+
// Retry configuration
|
|
15
|
+
const MAX_RELAY_RETRIES = 3;
|
|
16
|
+
const INITIAL_RETRY_DELAY_MS = 1_000;
|
|
17
|
+
const MAX_RETRY_DELAY_MS = 30_000;
|
|
18
|
+
export class NodeRuntime {
|
|
19
|
+
config;
|
|
20
|
+
adapters = new Map();
|
|
21
|
+
heartbeatTimer = null;
|
|
22
|
+
pollTimers = new Map();
|
|
23
|
+
running = false;
|
|
24
|
+
startedAt = null;
|
|
25
|
+
// Connection health tracking
|
|
26
|
+
status = "starting";
|
|
27
|
+
consecutiveServerFailures = 0;
|
|
28
|
+
lastServerContact = null;
|
|
29
|
+
heartbeatsSent = 0;
|
|
30
|
+
heartbeatsFailed = 0;
|
|
31
|
+
messagesRelayed = 0;
|
|
32
|
+
messagesFailed = 0;
|
|
33
|
+
constructor(config) {
|
|
34
|
+
this.config = config;
|
|
35
|
+
}
|
|
36
|
+
getStats() {
|
|
37
|
+
const adapterStats = {};
|
|
38
|
+
for (const [id, adapter] of this.adapters) {
|
|
39
|
+
const s = adapter.getStats();
|
|
40
|
+
adapterStats[id] = {
|
|
41
|
+
type: adapter.type,
|
|
42
|
+
running: adapter.isRunning(),
|
|
43
|
+
messages_in: s.messages_in,
|
|
44
|
+
messages_out: s.messages_out,
|
|
45
|
+
errors: s.errors,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
status: this.status,
|
|
50
|
+
uptime_seconds: this.startedAt ? Math.floor((Date.now() - this.startedAt.getTime()) / 1000) : 0,
|
|
51
|
+
heartbeats_sent: this.heartbeatsSent,
|
|
52
|
+
heartbeats_failed: this.heartbeatsFailed,
|
|
53
|
+
messages_relayed: this.messagesRelayed,
|
|
54
|
+
messages_failed: this.messagesFailed,
|
|
55
|
+
consecutive_server_failures: this.consecutiveServerFailures,
|
|
56
|
+
last_server_contact: this.lastServerContact?.toISOString() || null,
|
|
57
|
+
adapters: adapterStats,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async start() {
|
|
61
|
+
this.running = true;
|
|
62
|
+
this.startedAt = new Date();
|
|
63
|
+
this.status = "starting";
|
|
64
|
+
addBreadcrumb("node", `Starting node ${this.config.node_id} v${VERSION}`, "info");
|
|
65
|
+
console.log(`[node] Starting WhaleNode "${this.config.node_id}" v${VERSION}...`);
|
|
66
|
+
// Start heartbeat — initial heartbeat validates server connectivity
|
|
67
|
+
const heartbeatOk = await this.sendHeartbeat();
|
|
68
|
+
this.status = heartbeatOk ? "connected" : "degraded";
|
|
69
|
+
this.heartbeatTimer = setInterval(() => this.sendHeartbeat(), HEARTBEAT_INTERVAL_MS);
|
|
70
|
+
// Initialize adapters for each channel
|
|
71
|
+
for (const ch of this.config.channels) {
|
|
72
|
+
await this.startAdapter(ch);
|
|
73
|
+
}
|
|
74
|
+
// Handle graceful shutdown
|
|
75
|
+
process.on("SIGTERM", () => this.stop());
|
|
76
|
+
process.on("SIGINT", () => this.stop());
|
|
77
|
+
console.log(`[node] Running with ${this.adapters.size} channel adapter(s). Status: ${this.status}. Press Ctrl+C to stop.`);
|
|
78
|
+
}
|
|
79
|
+
async stop() {
|
|
80
|
+
console.log(`[node] Shutting down...`);
|
|
81
|
+
this.running = false;
|
|
82
|
+
this.status = "disconnected";
|
|
83
|
+
if (this.heartbeatTimer)
|
|
84
|
+
clearInterval(this.heartbeatTimer);
|
|
85
|
+
for (const [, timer] of this.pollTimers)
|
|
86
|
+
clearInterval(timer);
|
|
87
|
+
for (const [name, adapter] of this.adapters) {
|
|
88
|
+
console.log(`[node] Stopping adapter: ${name}`);
|
|
89
|
+
await adapter.stop();
|
|
90
|
+
}
|
|
91
|
+
console.log(`[node] Stopped.`);
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
async startAdapter(ch) {
|
|
95
|
+
let adapter;
|
|
96
|
+
switch (ch.type) {
|
|
97
|
+
case "imessage":
|
|
98
|
+
adapter = new IMessageAdapter(ch.name, ch.config);
|
|
99
|
+
break;
|
|
100
|
+
case "telegram":
|
|
101
|
+
adapter = new TelegramAdapter(ch.name, ch.config);
|
|
102
|
+
break;
|
|
103
|
+
case "discord":
|
|
104
|
+
adapter = new DiscordAdapter(ch.name, ch.config);
|
|
105
|
+
break;
|
|
106
|
+
case "slack":
|
|
107
|
+
adapter = new SlackAdapter(ch.name, ch.config);
|
|
108
|
+
break;
|
|
109
|
+
case "webchat":
|
|
110
|
+
adapter = new WebchatAdapter(ch.name, ch.config);
|
|
111
|
+
break;
|
|
112
|
+
case "whatsapp":
|
|
113
|
+
adapter = new WhatsAppAdapter(ch.name, ch.config);
|
|
114
|
+
break;
|
|
115
|
+
case "sms":
|
|
116
|
+
adapter = new SmsAdapter(ch.name, ch.config);
|
|
117
|
+
break;
|
|
118
|
+
case "email":
|
|
119
|
+
adapter = new EmailAdapter(ch.name, ch.config);
|
|
120
|
+
break;
|
|
121
|
+
default:
|
|
122
|
+
console.warn(`[node] Unknown channel type: ${ch.type}`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
// Wire up inbound message handler — relay to server and deliver agent response
|
|
126
|
+
adapter.setMessageHandler(async (msg) => {
|
|
127
|
+
await this.relayAndDeliver(ch.id, msg);
|
|
128
|
+
});
|
|
129
|
+
await adapter.start();
|
|
130
|
+
this.adapters.set(ch.id, adapter);
|
|
131
|
+
// Start polling for queued outbound messages (async agent responses)
|
|
132
|
+
const pollTimer = setInterval(() => this.pollOutbound(ch.id, adapter), POLL_INTERVAL_MS);
|
|
133
|
+
this.pollTimers.set(ch.id, pollTimer);
|
|
134
|
+
console.log(`[node] Adapter started: ${ch.type} "${ch.name}"`);
|
|
135
|
+
}
|
|
136
|
+
/** Fetch with retry and exponential backoff */
|
|
137
|
+
async fetchWithRetry(url, options, maxRetries = MAX_RELAY_RETRIES) {
|
|
138
|
+
let lastError = null;
|
|
139
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
140
|
+
try {
|
|
141
|
+
const res = await fetch(url, {
|
|
142
|
+
...options,
|
|
143
|
+
signal: AbortSignal.timeout(30_000), // 30s timeout per attempt
|
|
144
|
+
});
|
|
145
|
+
// Retry on 5xx (server errors, including Cloudflare 520/522/524)
|
|
146
|
+
if (res.status >= 500 && attempt < maxRetries) {
|
|
147
|
+
const delay = Math.min(INITIAL_RETRY_DELAY_MS * Math.pow(2, attempt), MAX_RETRY_DELAY_MS);
|
|
148
|
+
console.warn(`[fetch] ${res.status} from ${url}, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`);
|
|
149
|
+
await sleep(delay);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
// Success or non-retryable error — update health tracking
|
|
153
|
+
this.recordServerContact(res.ok);
|
|
154
|
+
return res;
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
lastError = err;
|
|
158
|
+
if (attempt < maxRetries) {
|
|
159
|
+
const delay = Math.min(INITIAL_RETRY_DELAY_MS * Math.pow(2, attempt), MAX_RETRY_DELAY_MS);
|
|
160
|
+
console.warn(`[fetch] Error: ${lastError.message}, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`);
|
|
161
|
+
await sleep(delay);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
this.recordServerContact(false);
|
|
166
|
+
throw lastError || new Error("Fetch failed after retries");
|
|
167
|
+
}
|
|
168
|
+
/** Track server connectivity health */
|
|
169
|
+
recordServerContact(success) {
|
|
170
|
+
if (success) {
|
|
171
|
+
this.consecutiveServerFailures = 0;
|
|
172
|
+
this.lastServerContact = new Date();
|
|
173
|
+
if (this.status === "degraded" || this.status === "disconnected") {
|
|
174
|
+
console.log(`[node] Server connectivity restored`);
|
|
175
|
+
this.status = "connected";
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
this.consecutiveServerFailures++;
|
|
180
|
+
if (this.consecutiveServerFailures >= 3 && this.status === "connected") {
|
|
181
|
+
console.warn(`[node] Server connectivity degraded (${this.consecutiveServerFailures} consecutive failures)`);
|
|
182
|
+
this.status = "degraded";
|
|
183
|
+
}
|
|
184
|
+
if (this.consecutiveServerFailures >= 10 && this.status !== "disconnected") {
|
|
185
|
+
console.error(`[node] Server appears unreachable (${this.consecutiveServerFailures} consecutive failures)`);
|
|
186
|
+
this.status = "disconnected";
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/** Poll for undelivered outbound messages and deliver via adapter */
|
|
191
|
+
async pollOutbound(channelId, adapter) {
|
|
192
|
+
if (!this.running)
|
|
193
|
+
return;
|
|
194
|
+
try {
|
|
195
|
+
const url = `${this.config.server_url}/channels/${channelId}/messages?undelivered=true&limit=10`;
|
|
196
|
+
const res = await this.fetchWithRetry(url, {
|
|
197
|
+
method: "GET",
|
|
198
|
+
headers: {
|
|
199
|
+
Authorization: `Bearer ${this.config.api_key}`,
|
|
200
|
+
},
|
|
201
|
+
}, 1); // Only 1 retry for polling (runs every 2s anyway)
|
|
202
|
+
if (!res.ok)
|
|
203
|
+
return;
|
|
204
|
+
const data = await res.json();
|
|
205
|
+
if (!data.success || !data.messages?.length)
|
|
206
|
+
return;
|
|
207
|
+
for (const msg of data.messages) {
|
|
208
|
+
const outMsg = {
|
|
209
|
+
id: msg.id,
|
|
210
|
+
content: msg.content || "",
|
|
211
|
+
sender_id: msg.sender_id || "agent",
|
|
212
|
+
conversation_id: msg.conversation_id,
|
|
213
|
+
metadata: msg.metadata,
|
|
214
|
+
};
|
|
215
|
+
const delivered = await adapter.sendMessage(outMsg);
|
|
216
|
+
if (delivered) {
|
|
217
|
+
await this.markDelivered(channelId, msg.id);
|
|
218
|
+
console.log(`[poll] Delivered queued message ${msg.id}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
// Silent — polling errors are expected during connectivity blips
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/** Relay inbound and deliver response synchronously with retry */
|
|
227
|
+
async relayAndDeliver(channelId, msg) {
|
|
228
|
+
try {
|
|
229
|
+
const res = await this.fetchWithRetry(`${this.config.server_url}/channels/${channelId}/messages`, {
|
|
230
|
+
method: "POST",
|
|
231
|
+
headers: {
|
|
232
|
+
"Content-Type": "application/json",
|
|
233
|
+
Authorization: `Bearer ${this.config.api_key}`,
|
|
234
|
+
},
|
|
235
|
+
body: JSON.stringify({
|
|
236
|
+
direction: "inbound",
|
|
237
|
+
...msg,
|
|
238
|
+
}),
|
|
239
|
+
});
|
|
240
|
+
const data = await res.json();
|
|
241
|
+
this.messagesRelayed++;
|
|
242
|
+
if (data.success && data.agent_response) {
|
|
243
|
+
const adapter = this.adapters.get(channelId);
|
|
244
|
+
if (adapter) {
|
|
245
|
+
const outMsg = {
|
|
246
|
+
id: data.agent_response.id,
|
|
247
|
+
content: data.agent_response.content,
|
|
248
|
+
sender_id: "agent",
|
|
249
|
+
conversation_id: data.conversation_id || data.agent_response.conversation_id,
|
|
250
|
+
metadata: msg.metadata,
|
|
251
|
+
};
|
|
252
|
+
const delivered = await adapter.sendMessage(outMsg);
|
|
253
|
+
if (delivered) {
|
|
254
|
+
await this.markDelivered(channelId, data.agent_response.id);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
this.messagesFailed++;
|
|
261
|
+
console.error(`[relay] Failed after retries:`, err.message);
|
|
262
|
+
captureError({
|
|
263
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
264
|
+
severity: "error",
|
|
265
|
+
tags: { source: "whale-node", operation: "relay", node_id: this.config.node_id, channel_id: channelId },
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async markDelivered(channelId, messageId) {
|
|
270
|
+
try {
|
|
271
|
+
await this.fetchWithRetry(`${this.config.server_url}/channels/${channelId}/messages/${messageId}/delivered`, {
|
|
272
|
+
method: "POST",
|
|
273
|
+
headers: {
|
|
274
|
+
"Content-Type": "application/json",
|
|
275
|
+
Authorization: `Bearer ${this.config.api_key}`,
|
|
276
|
+
},
|
|
277
|
+
}, 1);
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
// Best effort — message was already delivered to user
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
async sendHeartbeat() {
|
|
284
|
+
try {
|
|
285
|
+
const channelStatuses = Array.from(this.adapters.entries()).map(([, adapter]) => ({
|
|
286
|
+
type: adapter.type,
|
|
287
|
+
name: adapter.name,
|
|
288
|
+
status: adapter.isRunning() ? "active" : "error",
|
|
289
|
+
stats: adapter.getStats(),
|
|
290
|
+
}));
|
|
291
|
+
const res = await this.fetchWithRetry(`${this.config.server_url}/nodes/heartbeat`, {
|
|
292
|
+
method: "POST",
|
|
293
|
+
headers: {
|
|
294
|
+
"Content-Type": "application/json",
|
|
295
|
+
Authorization: `Bearer ${this.config.api_key}`,
|
|
296
|
+
},
|
|
297
|
+
body: JSON.stringify({
|
|
298
|
+
hardware: getHardwareInfo(),
|
|
299
|
+
capabilities: getCapabilities(),
|
|
300
|
+
version: VERSION,
|
|
301
|
+
channels: channelStatuses,
|
|
302
|
+
runtime_stats: {
|
|
303
|
+
uptime_seconds: this.startedAt ? Math.floor((Date.now() - this.startedAt.getTime()) / 1000) : 0,
|
|
304
|
+
messages_relayed: this.messagesRelayed,
|
|
305
|
+
messages_failed: this.messagesFailed,
|
|
306
|
+
status: this.status,
|
|
307
|
+
},
|
|
308
|
+
}),
|
|
309
|
+
}, 2);
|
|
310
|
+
if (res.ok) {
|
|
311
|
+
this.heartbeatsSent++;
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
console.warn(`[heartbeat] Server returned ${res.status}`);
|
|
315
|
+
this.heartbeatsFailed++;
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
catch (err) {
|
|
319
|
+
console.warn(`[heartbeat] Failed:`, err.message);
|
|
320
|
+
this.heartbeatsFailed++;
|
|
321
|
+
// Only capture after multiple consecutive failures to avoid noise
|
|
322
|
+
if (this.consecutiveServerFailures >= 3) {
|
|
323
|
+
captureError({
|
|
324
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
325
|
+
severity: "warning",
|
|
326
|
+
tags: { source: "whale-node", operation: "heartbeat", node_id: this.config.node_id, failures: String(this.consecutiveServerFailures) },
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function sleep(ms) {
|
|
334
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
335
|
+
}
|
|
336
|
+
function getHardwareInfo() {
|
|
337
|
+
return {
|
|
338
|
+
os: process.platform === "darwin" ? "macOS" : process.platform,
|
|
339
|
+
arch: process.arch,
|
|
340
|
+
cpu: cpus()[0]?.model || "unknown",
|
|
341
|
+
cores: cpus().length,
|
|
342
|
+
ram_gb: Math.round(totalmem() / (1024 ** 3)),
|
|
343
|
+
hostname: hostname(),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function getCapabilities() {
|
|
347
|
+
const caps = ["messaging", "discord", "slack", "telegram", "webchat", "whatsapp", "sms", "email"];
|
|
348
|
+
if (process.platform === "darwin")
|
|
349
|
+
caps.push("imessage");
|
|
350
|
+
return caps;
|
|
351
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Pre-populate a table with rows for the current test */
|
|
2
|
+
export declare function setTableData(table: string, rows: unknown[]): void;
|
|
3
|
+
/** Pre-configure the return value for an RPC call */
|
|
4
|
+
export declare function setRpcResponse(name: string, data: unknown, error?: unknown): void;
|
|
5
|
+
/** Force a table to return an error on any operation */
|
|
6
|
+
export declare function setTableError(table: string, message: string, code?: string): void;
|
|
7
|
+
/** Reset all in-memory state between tests */
|
|
8
|
+
export declare function resetMock(): void;
|
|
9
|
+
/** Get current in-memory rows for a table (for post-operation assertions) */
|
|
10
|
+
export declare function getTableData(table: string): unknown[];
|
|
11
|
+
export declare function createMockSupabase(): unknown;
|