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,636 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serve Mode — WebSocket agent server for WhaleChat desktop app
|
|
3
|
+
*
|
|
4
|
+
* Speaks the exact bidirectional JSON protocol that AgentClient.swift expects.
|
|
5
|
+
* Runs the full agent loop with all local + server tools.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* whale serve Start on default port 3847
|
|
9
|
+
* whale serve --port 6090 Custom port
|
|
10
|
+
* whale serve --host 0.0.0.0 Bind to all interfaces
|
|
11
|
+
*
|
|
12
|
+
* Protocol: WebSocket (ws://host:port)
|
|
13
|
+
*
|
|
14
|
+
* Client → Server messages:
|
|
15
|
+
* query, abort, ping, get_tools, new_conversation, load_conversation, get_conversations
|
|
16
|
+
*
|
|
17
|
+
* Server → Client messages:
|
|
18
|
+
* ready, started, text, tool_start, tool_result, done, error, aborted,
|
|
19
|
+
* pong, tools, debug, conversation_created, conversations, conversation_loaded,
|
|
20
|
+
* subagent_start, subagent_progress, subagent_done, subagent_tool_start, subagent_tool_end,
|
|
21
|
+
* team_start, team_progress, team_task, team_done
|
|
22
|
+
*/
|
|
23
|
+
import { createServer } from "http";
|
|
24
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
25
|
+
import { runAgentLoop, setModel, setPermissionMode, resetSessionState, getModel, estimateCostUsd, AgentEventEmitter, } from "./services/agent-loop.js";
|
|
26
|
+
import { resolveConfig } from "./services/config-store.js";
|
|
27
|
+
import { createClient } from "@supabase/supabase-js";
|
|
28
|
+
import { getValidToken, createAuthenticatedClient } from "./services/auth-service.js";
|
|
29
|
+
import { loadServerToolDefinitions } from "./services/server-tools.js";
|
|
30
|
+
import { LOCAL_TOOL_DEFINITIONS } from "./services/local-tools.js";
|
|
31
|
+
import { createRequire } from "module";
|
|
32
|
+
const require = createRequire(import.meta.url);
|
|
33
|
+
const PKG_VERSION = require("../../package.json").version;
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// SUPABASE CLIENT (for conversation persistence)
|
|
36
|
+
// ============================================================================
|
|
37
|
+
let supabase = null;
|
|
38
|
+
let supabaseCreatedAt = 0;
|
|
39
|
+
const SUPABASE_TTL_MS = 45 * 60 * 1000; // Refresh client every 45 min (JWT expires ~60 min)
|
|
40
|
+
async function getSupabase() {
|
|
41
|
+
const now = Date.now();
|
|
42
|
+
// Return cached client if still fresh
|
|
43
|
+
if (supabase && (now - supabaseCreatedAt) < SUPABASE_TTL_MS)
|
|
44
|
+
return supabase;
|
|
45
|
+
// Invalidate stale client
|
|
46
|
+
supabase = null;
|
|
47
|
+
const config = resolveConfig();
|
|
48
|
+
// Tier 1: Service role key (never expires)
|
|
49
|
+
if (config.supabaseUrl && config.supabaseKey && process.env.SUPABASE_SERVICE_ROLE_KEY) {
|
|
50
|
+
supabase = createClient(config.supabaseUrl, config.supabaseKey, {
|
|
51
|
+
auth: { autoRefreshToken: false, persistSession: false },
|
|
52
|
+
});
|
|
53
|
+
supabaseCreatedAt = now;
|
|
54
|
+
return supabase;
|
|
55
|
+
}
|
|
56
|
+
// Tier 2: User JWT — always get a fresh token
|
|
57
|
+
try {
|
|
58
|
+
const token = await getValidToken();
|
|
59
|
+
if (token) {
|
|
60
|
+
supabase = createAuthenticatedClient(token);
|
|
61
|
+
supabaseCreatedAt = now;
|
|
62
|
+
return supabase;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Fall through
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// CONVERSATION PERSISTENCE
|
|
72
|
+
// ============================================================================
|
|
73
|
+
async function createConversation(storeId, title, agentName) {
|
|
74
|
+
const db = await getSupabase();
|
|
75
|
+
if (!db)
|
|
76
|
+
return null;
|
|
77
|
+
try {
|
|
78
|
+
const { data, error } = await db
|
|
79
|
+
.from("ai_conversations")
|
|
80
|
+
.insert({
|
|
81
|
+
store_id: storeId || null,
|
|
82
|
+
title: title.substring(0, 100),
|
|
83
|
+
metadata: {
|
|
84
|
+
source: "whale-serve",
|
|
85
|
+
agentName: agentName || "whale",
|
|
86
|
+
},
|
|
87
|
+
status: "active",
|
|
88
|
+
})
|
|
89
|
+
.select("id")
|
|
90
|
+
.single();
|
|
91
|
+
if (error) {
|
|
92
|
+
console.error("[serve] Failed to create conversation:", error.message);
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return data?.id || null;
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
console.error("[serve] Conversation create error:", err);
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function appendMessage(conversationId, role, content, tokenCount, toolNames) {
|
|
103
|
+
const db = await getSupabase();
|
|
104
|
+
if (!db || !conversationId)
|
|
105
|
+
return;
|
|
106
|
+
try {
|
|
107
|
+
// Insert into ai_messages for individual message tracking
|
|
108
|
+
await db.from("ai_messages").insert({
|
|
109
|
+
conversation_id: conversationId,
|
|
110
|
+
role,
|
|
111
|
+
content: [{ type: "text", text: content }],
|
|
112
|
+
is_tool_use: (toolNames && toolNames.length > 0) || false,
|
|
113
|
+
tool_names: toolNames || [],
|
|
114
|
+
token_count: tokenCount || 0,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Non-fatal — persistence is best-effort
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function updateConversationMetadata(conversationId, metadata) {
|
|
122
|
+
const db = await getSupabase();
|
|
123
|
+
if (!db || !conversationId)
|
|
124
|
+
return;
|
|
125
|
+
try {
|
|
126
|
+
// Fetch current metadata, merge, and update
|
|
127
|
+
const { data } = await db
|
|
128
|
+
.from("ai_conversations")
|
|
129
|
+
.select("metadata")
|
|
130
|
+
.eq("id", conversationId)
|
|
131
|
+
.single();
|
|
132
|
+
const existing = data?.metadata || {};
|
|
133
|
+
await db
|
|
134
|
+
.from("ai_conversations")
|
|
135
|
+
.update({ metadata: { ...existing, ...metadata }, updated_at: new Date().toISOString() })
|
|
136
|
+
.eq("id", conversationId);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Non-fatal
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async function loadConversation(conversationId) {
|
|
143
|
+
const db = await getSupabase();
|
|
144
|
+
if (!db)
|
|
145
|
+
return null;
|
|
146
|
+
try {
|
|
147
|
+
const { data, error } = await db
|
|
148
|
+
.from("ai_conversations")
|
|
149
|
+
.select("id, title, messages")
|
|
150
|
+
.eq("id", conversationId)
|
|
151
|
+
.single();
|
|
152
|
+
if (error || !data)
|
|
153
|
+
return null;
|
|
154
|
+
return {
|
|
155
|
+
id: data.id,
|
|
156
|
+
title: data.title || "Untitled",
|
|
157
|
+
messages: data.messages || [],
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async function listConversations(storeId, limit = 20) {
|
|
165
|
+
const db = await getSupabase();
|
|
166
|
+
if (!db)
|
|
167
|
+
return [];
|
|
168
|
+
try {
|
|
169
|
+
const { data, error } = await db
|
|
170
|
+
.from("ai_conversations")
|
|
171
|
+
.select("id, title, agent_id, metadata, turn_count, created_at, updated_at")
|
|
172
|
+
.eq("store_id", storeId)
|
|
173
|
+
.order("updated_at", { ascending: false })
|
|
174
|
+
.limit(limit);
|
|
175
|
+
if (error || !data)
|
|
176
|
+
return [];
|
|
177
|
+
return data.map((c) => ({
|
|
178
|
+
id: c.id,
|
|
179
|
+
title: c.title || "Untitled",
|
|
180
|
+
agentId: c.agent_id || null,
|
|
181
|
+
agentName: c.metadata?.agentName || null,
|
|
182
|
+
messageCount: c.turn_count || 0,
|
|
183
|
+
createdAt: c.created_at,
|
|
184
|
+
updatedAt: c.updated_at,
|
|
185
|
+
}));
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// ============================================================================
|
|
192
|
+
// TOOL LIST (for ready/tools messages)
|
|
193
|
+
// ============================================================================
|
|
194
|
+
async function getToolList() {
|
|
195
|
+
const tools = [];
|
|
196
|
+
// Local tools
|
|
197
|
+
for (const t of LOCAL_TOOL_DEFINITIONS) {
|
|
198
|
+
tools.push({
|
|
199
|
+
id: `local-${t.name}`,
|
|
200
|
+
name: t.name,
|
|
201
|
+
description: t.description,
|
|
202
|
+
category: "local",
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
// Server tools
|
|
206
|
+
try {
|
|
207
|
+
const serverTools = await loadServerToolDefinitions();
|
|
208
|
+
for (const t of serverTools) {
|
|
209
|
+
if (!tools.some((existing) => existing.name === t.name)) {
|
|
210
|
+
tools.push({
|
|
211
|
+
id: `server-${t.name}`,
|
|
212
|
+
name: t.name,
|
|
213
|
+
description: t.description || "",
|
|
214
|
+
category: "server",
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
// Server tools unavailable — local-only mode
|
|
221
|
+
}
|
|
222
|
+
return tools;
|
|
223
|
+
}
|
|
224
|
+
// ============================================================================
|
|
225
|
+
// WEBSOCKET MESSAGE HELPERS
|
|
226
|
+
// ============================================================================
|
|
227
|
+
function send(ws, message) {
|
|
228
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
229
|
+
ws.send(JSON.stringify(message));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function sendDebug(ws, level, message, data) {
|
|
233
|
+
send(ws, { type: "debug", level, message, data });
|
|
234
|
+
}
|
|
235
|
+
// ============================================================================
|
|
236
|
+
// QUERY HANDLER — runs the full agent loop
|
|
237
|
+
// ============================================================================
|
|
238
|
+
async function handleQuery(session, msg, opts) {
|
|
239
|
+
const { ws } = session;
|
|
240
|
+
const prompt = msg.prompt;
|
|
241
|
+
if (!prompt) {
|
|
242
|
+
send(ws, { type: "error", error: "Missing 'prompt' field" });
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
// Extract image attachments sent by WhaleChat
|
|
246
|
+
const rawAttachments = msg.attachments || [];
|
|
247
|
+
const images = rawAttachments
|
|
248
|
+
.filter(att => att.type === "image" && att.media_type && att.data)
|
|
249
|
+
.map(att => ({ base64: att.data, mediaType: att.media_type }));
|
|
250
|
+
const storeId = msg.storeId || resolveConfig().storeId || null;
|
|
251
|
+
const config = msg.config || {};
|
|
252
|
+
const requestedConvId = msg.conversationId || session.conversationId;
|
|
253
|
+
// Restore conversation history from client if session is fresh (new WS connection)
|
|
254
|
+
const clientHistory = msg.conversationHistory;
|
|
255
|
+
if (session.conversationHistory.length === 0 && clientHistory && clientHistory.length > 0) {
|
|
256
|
+
session.conversationHistory = clientHistory
|
|
257
|
+
.filter((m) => m.content && m.content.trim().length > 0)
|
|
258
|
+
.map((m) => ({
|
|
259
|
+
role: m.role,
|
|
260
|
+
content: m.content,
|
|
261
|
+
}));
|
|
262
|
+
}
|
|
263
|
+
// Apply model from config or server defaults
|
|
264
|
+
const model = config.model || opts.model;
|
|
265
|
+
if (model)
|
|
266
|
+
setModel(model);
|
|
267
|
+
// Create or continue conversation
|
|
268
|
+
let conversationId = requestedConvId;
|
|
269
|
+
if (!conversationId) {
|
|
270
|
+
conversationId = await createConversation(storeId, prompt, config.agentName);
|
|
271
|
+
if (conversationId) {
|
|
272
|
+
send(ws, {
|
|
273
|
+
type: "conversation_created",
|
|
274
|
+
conversation: {
|
|
275
|
+
id: conversationId,
|
|
276
|
+
title: prompt.substring(0, 100),
|
|
277
|
+
agentId: config.agentId || null,
|
|
278
|
+
agentName: config.agentName || "whale",
|
|
279
|
+
messageCount: 0,
|
|
280
|
+
createdAt: new Date().toISOString(),
|
|
281
|
+
updatedAt: new Date().toISOString(),
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
session.conversationId = conversationId;
|
|
287
|
+
// Persist user message
|
|
288
|
+
if (conversationId) {
|
|
289
|
+
appendMessage(conversationId, "user", prompt);
|
|
290
|
+
}
|
|
291
|
+
// Signal started
|
|
292
|
+
send(ws, {
|
|
293
|
+
type: "started",
|
|
294
|
+
model: getModel(),
|
|
295
|
+
conversationId,
|
|
296
|
+
});
|
|
297
|
+
// Set up abort controller
|
|
298
|
+
const abortController = new AbortController();
|
|
299
|
+
session.abortController = abortController;
|
|
300
|
+
let totalIn = 0;
|
|
301
|
+
let totalOut = 0;
|
|
302
|
+
let lastCostUsd = 0;
|
|
303
|
+
let fullText = "";
|
|
304
|
+
const toolsUsed = [];
|
|
305
|
+
const startTime = Date.now();
|
|
306
|
+
// Create event emitter for subagent/team events → forward to WebSocket
|
|
307
|
+
const emitter = new AgentEventEmitter();
|
|
308
|
+
const forwardTypes = new Set([
|
|
309
|
+
"subagent_start", "subagent_progress", "subagent_done",
|
|
310
|
+
"subagent_tool_start", "subagent_tool_end",
|
|
311
|
+
"team_start", "team_progress", "team_task", "team_done",
|
|
312
|
+
]);
|
|
313
|
+
const eventHandler = (event) => {
|
|
314
|
+
if (event.type === "text") {
|
|
315
|
+
// Emitter captures text events — forward them to WebSocket + accumulate
|
|
316
|
+
const text = event.text;
|
|
317
|
+
if (text) {
|
|
318
|
+
fullText += text;
|
|
319
|
+
send(ws, { type: "text", text });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else if (forwardTypes.has(event.type)) {
|
|
323
|
+
send(ws, event);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
emitter.on("event", eventHandler);
|
|
327
|
+
try {
|
|
328
|
+
const callbacks = {
|
|
329
|
+
onText: (text) => {
|
|
330
|
+
fullText += text;
|
|
331
|
+
send(ws, { type: "text", text });
|
|
332
|
+
},
|
|
333
|
+
onToolStart: (name, input) => {
|
|
334
|
+
if (!toolsUsed.includes(name))
|
|
335
|
+
toolsUsed.push(name);
|
|
336
|
+
send(ws, { type: "tool_start", tool: name, input: input || {} });
|
|
337
|
+
},
|
|
338
|
+
onToolResult: (name, success, result, _input, durationMs) => {
|
|
339
|
+
// Truncate large results for the wire
|
|
340
|
+
let wireResult = result;
|
|
341
|
+
if (typeof result === "string" && result.length > 10000) {
|
|
342
|
+
wireResult = result.substring(0, 10000) + "\n... (truncated)";
|
|
343
|
+
}
|
|
344
|
+
send(ws, {
|
|
345
|
+
type: "tool_result",
|
|
346
|
+
tool: name,
|
|
347
|
+
success,
|
|
348
|
+
result: wireResult,
|
|
349
|
+
error: success ? undefined : (typeof result === "string" ? result : "Tool failed"),
|
|
350
|
+
duration_ms: durationMs,
|
|
351
|
+
});
|
|
352
|
+
},
|
|
353
|
+
onUsage: (input_tokens, output_tokens, _thinking_tokens, _model, costUsd) => {
|
|
354
|
+
totalIn += input_tokens;
|
|
355
|
+
totalOut += output_tokens;
|
|
356
|
+
if (costUsd != null)
|
|
357
|
+
lastCostUsd = costUsd;
|
|
358
|
+
},
|
|
359
|
+
onDone: (finalMessages) => {
|
|
360
|
+
session.conversationHistory = finalMessages;
|
|
361
|
+
},
|
|
362
|
+
onError: (error) => {
|
|
363
|
+
send(ws, { type: "error", error });
|
|
364
|
+
},
|
|
365
|
+
onAutoCompact: (before, after, tokensSaved) => {
|
|
366
|
+
sendDebug(ws, "info", `Context compacted: ${before} → ${after} messages (saved ~${tokensSaved} tokens)`);
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
const loopOpts = {
|
|
370
|
+
message: prompt,
|
|
371
|
+
images: images.length > 0 ? images : undefined,
|
|
372
|
+
conversationHistory: session.conversationHistory,
|
|
373
|
+
callbacks,
|
|
374
|
+
abortSignal: abortController.signal,
|
|
375
|
+
emitter,
|
|
376
|
+
maxTurns: config.maxTurns || opts.maxTurns,
|
|
377
|
+
maxBudgetUsd: opts.maxBudgetUsd,
|
|
378
|
+
effort: (opts.effort || "medium"),
|
|
379
|
+
allowedTools: config.enabledTools || opts.allowedTools,
|
|
380
|
+
disallowedTools: opts.disallowedTools,
|
|
381
|
+
fallbackModel: opts.fallbackModel,
|
|
382
|
+
};
|
|
383
|
+
await runAgentLoop(loopOpts);
|
|
384
|
+
// Calculate cost
|
|
385
|
+
session.totalInputTokens += totalIn;
|
|
386
|
+
session.totalOutputTokens += totalOut;
|
|
387
|
+
const totalCost = lastCostUsd || estimateCostUsd(session.totalInputTokens, session.totalOutputTokens);
|
|
388
|
+
const durationMs = Date.now() - startTime;
|
|
389
|
+
// Persist assistant response
|
|
390
|
+
if (conversationId && fullText) {
|
|
391
|
+
appendMessage(conversationId, "assistant", fullText, totalOut, toolsUsed);
|
|
392
|
+
}
|
|
393
|
+
// Update conversation metadata
|
|
394
|
+
if (conversationId) {
|
|
395
|
+
updateConversationMetadata(conversationId, {
|
|
396
|
+
lastTurnTokens: { input: totalIn, output: totalOut },
|
|
397
|
+
lastToolCalls: toolsUsed,
|
|
398
|
+
lastDurationMs: durationMs,
|
|
399
|
+
model: getModel(),
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
// Send done
|
|
403
|
+
send(ws, {
|
|
404
|
+
type: "done",
|
|
405
|
+
status: "complete",
|
|
406
|
+
conversationId: conversationId || "",
|
|
407
|
+
usage: {
|
|
408
|
+
inputTokens: session.totalInputTokens,
|
|
409
|
+
outputTokens: session.totalOutputTokens,
|
|
410
|
+
totalCost,
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
catch (err) {
|
|
415
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
416
|
+
if (abortController.signal.aborted) {
|
|
417
|
+
send(ws, { type: "aborted" });
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
send(ws, { type: "error", error: errorMsg });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
finally {
|
|
424
|
+
emitter.off("event", eventHandler);
|
|
425
|
+
session.abortController = null;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
// ============================================================================
|
|
429
|
+
// CONNECTION HANDLER
|
|
430
|
+
// ============================================================================
|
|
431
|
+
async function handleConnection(ws, opts) {
|
|
432
|
+
const session = {
|
|
433
|
+
ws,
|
|
434
|
+
abortController: null,
|
|
435
|
+
conversationHistory: [],
|
|
436
|
+
conversationId: null,
|
|
437
|
+
totalInputTokens: 0,
|
|
438
|
+
totalOutputTokens: 0,
|
|
439
|
+
};
|
|
440
|
+
// Send ready with tool list
|
|
441
|
+
const tools = await getToolList();
|
|
442
|
+
send(ws, {
|
|
443
|
+
type: "ready",
|
|
444
|
+
version: PKG_VERSION,
|
|
445
|
+
tools,
|
|
446
|
+
});
|
|
447
|
+
if (opts.verbose) {
|
|
448
|
+
console.log("[serve] Client connected, sent ready with", tools.length, "tools");
|
|
449
|
+
}
|
|
450
|
+
ws.on("message", async (data) => {
|
|
451
|
+
let msg;
|
|
452
|
+
try {
|
|
453
|
+
const text = typeof data === "string" ? data : data.toString("utf-8");
|
|
454
|
+
msg = JSON.parse(text);
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
send(ws, { type: "error", error: "Invalid JSON" });
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const type = msg.type;
|
|
461
|
+
if (!type) {
|
|
462
|
+
send(ws, { type: "error", error: "Missing 'type' field" });
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
switch (type) {
|
|
466
|
+
case "query":
|
|
467
|
+
await handleQuery(session, msg, opts);
|
|
468
|
+
break;
|
|
469
|
+
case "abort":
|
|
470
|
+
if (session.abortController) {
|
|
471
|
+
session.abortController.abort();
|
|
472
|
+
// aborted message is sent by handleQuery's catch block
|
|
473
|
+
}
|
|
474
|
+
break;
|
|
475
|
+
case "ping":
|
|
476
|
+
send(ws, { type: "pong" });
|
|
477
|
+
break;
|
|
478
|
+
case "get_tools": {
|
|
479
|
+
const toolList = await getToolList();
|
|
480
|
+
send(ws, { type: "tools", tools: toolList });
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
case "new_conversation":
|
|
484
|
+
session.conversationHistory = [];
|
|
485
|
+
session.conversationId = null;
|
|
486
|
+
session.totalInputTokens = 0;
|
|
487
|
+
session.totalOutputTokens = 0;
|
|
488
|
+
resetSessionState(); // Reset module-level token counters, caches
|
|
489
|
+
if (opts.verbose) {
|
|
490
|
+
console.log("[serve] New conversation started");
|
|
491
|
+
}
|
|
492
|
+
break;
|
|
493
|
+
case "get_conversations": {
|
|
494
|
+
const storeId = msg.storeId;
|
|
495
|
+
const limit = msg.limit || 20;
|
|
496
|
+
if (!storeId) {
|
|
497
|
+
send(ws, { type: "conversations", conversations: [] });
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
const convs = await listConversations(storeId, limit);
|
|
501
|
+
send(ws, { type: "conversations", conversations: convs });
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
case "load_conversation": {
|
|
505
|
+
const convId = msg.conversationId;
|
|
506
|
+
if (!convId) {
|
|
507
|
+
send(ws, { type: "error", error: "Missing conversationId" });
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
const conv = await loadConversation(convId);
|
|
511
|
+
if (conv) {
|
|
512
|
+
session.conversationId = conv.id;
|
|
513
|
+
send(ws, {
|
|
514
|
+
type: "conversation_loaded",
|
|
515
|
+
conversationId: conv.id,
|
|
516
|
+
title: conv.title,
|
|
517
|
+
messages: conv.messages,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
send(ws, { type: "error", error: `Conversation not found: ${convId}` });
|
|
522
|
+
}
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
default:
|
|
526
|
+
if (opts.verbose) {
|
|
527
|
+
console.log("[serve] Unknown message type:", type);
|
|
528
|
+
}
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
ws.on("close", () => {
|
|
533
|
+
// Abort any running query when client disconnects
|
|
534
|
+
if (session.abortController) {
|
|
535
|
+
session.abortController.abort();
|
|
536
|
+
}
|
|
537
|
+
if (opts.verbose) {
|
|
538
|
+
console.log("[serve] Client disconnected");
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
ws.on("error", (err) => {
|
|
542
|
+
console.error("[serve] WebSocket error:", err.message);
|
|
543
|
+
if (session.abortController) {
|
|
544
|
+
session.abortController.abort();
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
// ============================================================================
|
|
549
|
+
// SERVER STARTUP
|
|
550
|
+
// ============================================================================
|
|
551
|
+
export async function runServeMode(opts) {
|
|
552
|
+
// Apply global settings
|
|
553
|
+
if (opts.model)
|
|
554
|
+
setModel(opts.model);
|
|
555
|
+
setPermissionMode(opts.permissionMode || "yolo");
|
|
556
|
+
const port = opts.port;
|
|
557
|
+
const host = opts.host;
|
|
558
|
+
// HTTP server for health check + WebSocket upgrade
|
|
559
|
+
const server = createServer((req, res) => {
|
|
560
|
+
// Health check endpoint
|
|
561
|
+
if (req.method === "GET" && (req.url === "/health" || req.url === "/")) {
|
|
562
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
563
|
+
res.end(JSON.stringify({
|
|
564
|
+
status: "ok",
|
|
565
|
+
version: PKG_VERSION,
|
|
566
|
+
model: getModel(),
|
|
567
|
+
port,
|
|
568
|
+
uptime: Math.floor(process.uptime()),
|
|
569
|
+
}));
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
// Everything else: 404
|
|
573
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
574
|
+
res.end(JSON.stringify({ error: "Not found. Connect via WebSocket." }));
|
|
575
|
+
});
|
|
576
|
+
// WebSocket server
|
|
577
|
+
const wss = new WebSocketServer({ server });
|
|
578
|
+
wss.on("connection", (ws) => {
|
|
579
|
+
handleConnection(ws, opts);
|
|
580
|
+
});
|
|
581
|
+
// Graceful shutdown
|
|
582
|
+
const shutdown = () => {
|
|
583
|
+
console.log("\n[serve] Shutting down...");
|
|
584
|
+
wss.clients.forEach((client) => {
|
|
585
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
586
|
+
client.close(1001, "Server shutting down");
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
server.close(() => {
|
|
590
|
+
console.log("[serve] Server stopped.");
|
|
591
|
+
process.exit(0);
|
|
592
|
+
});
|
|
593
|
+
// Force exit after 3s
|
|
594
|
+
setTimeout(() => process.exit(0), 3000);
|
|
595
|
+
};
|
|
596
|
+
process.on("SIGINT", shutdown);
|
|
597
|
+
process.on("SIGTERM", shutdown);
|
|
598
|
+
// Verify Supabase connectivity (non-blocking)
|
|
599
|
+
getSupabase().then((db) => {
|
|
600
|
+
if (db) {
|
|
601
|
+
console.log("[serve] Supabase connected — conversation persistence enabled");
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
console.log("[serve] Supabase not configured — conversations are session-only");
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
// Start listening
|
|
608
|
+
return new Promise((resolve, reject) => {
|
|
609
|
+
server.on("error", (err) => {
|
|
610
|
+
if (err.code === "EADDRINUSE") {
|
|
611
|
+
console.error(`[serve] Port ${port} is already in use.`);
|
|
612
|
+
console.error(`[serve] Try: whale serve --port ${port + 1}`);
|
|
613
|
+
process.exit(1);
|
|
614
|
+
}
|
|
615
|
+
reject(err);
|
|
616
|
+
});
|
|
617
|
+
server.listen(port, host, () => {
|
|
618
|
+
const d = "\x1b[2m";
|
|
619
|
+
const B = "\x1b[1m";
|
|
620
|
+
const r = "\x1b[0m";
|
|
621
|
+
const c = "\x1b[38;2;99;102;241m";
|
|
622
|
+
console.log();
|
|
623
|
+
console.log(` ${c}${B}◆ whale serve${r} ${d}v${PKG_VERSION}${r}`);
|
|
624
|
+
console.log();
|
|
625
|
+
console.log(` ${B}WebSocket${r} ${d}ws://${host}:${port}${r}`);
|
|
626
|
+
console.log(` ${B}Health${r} ${d}http://${host}:${port}/health${r}`);
|
|
627
|
+
console.log(` ${B}Model${r} ${d}${getModel()}${r}`);
|
|
628
|
+
console.log(` ${B}Mode${r} ${d}yolo (local server trusts local client)${r}`);
|
|
629
|
+
console.log();
|
|
630
|
+
console.log(` ${d}WhaleChat will auto-connect on port 3847${r}`);
|
|
631
|
+
console.log(` ${d}Press Ctrl+C to stop${r}`);
|
|
632
|
+
console.log();
|
|
633
|
+
resolve();
|
|
634
|
+
});
|
|
635
|
+
});
|
|
636
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Definitions — custom agent types from markdown files
|
|
3
|
+
*
|
|
4
|
+
* Matches Claude Code's .claude/agents/ pattern:
|
|
5
|
+
*
|
|
6
|
+
* .whale/agents/reviewer.md:
|
|
7
|
+
* ---
|
|
8
|
+
* description: Reviews code for security issues
|
|
9
|
+
* tools: read_file, glob, grep
|
|
10
|
+
* ---
|
|
11
|
+
* You are a security review agent...
|
|
12
|
+
*
|
|
13
|
+
* Load order:
|
|
14
|
+
* 1. ~/.swagmanager/agents/*.md (global)
|
|
15
|
+
* 2. .whale/agents/*.md (local, overrides global)
|
|
16
|
+
*/
|
|
17
|
+
export interface AgentDefinition {
|
|
18
|
+
name: string;
|
|
19
|
+
prompt: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
tools?: string[];
|
|
22
|
+
source: "global" | "local";
|
|
23
|
+
}
|
|
24
|
+
export declare function loadAgentDefinitions(): AgentDefinition[];
|
|
25
|
+
export declare function getAgentDefinition(name: string): AgentDefinition | undefined;
|