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,689 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Teammate — Independent agent instance in a Team
|
|
3
|
+
*
|
|
4
|
+
* Following Anthropic's official patterns:
|
|
5
|
+
* - Runs in separate context window (worker thread)
|
|
6
|
+
* - Works on tasks from shared task list
|
|
7
|
+
* - Can message other teammates directly
|
|
8
|
+
* - Full tool access (not restricted like subagents)
|
|
9
|
+
*/
|
|
10
|
+
import { Worker, parentPort, workerData, isMainThread } from "worker_threads";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
import { loadTeam, claimTask, completeTask, failTask, getAvailableTasks, sendMessage, getUnreadMessages, markMessagesRead, updateTeammate, } from "./team-state.js";
|
|
13
|
+
import { LoopDetector, estimateCostUsd } from "../../shared/agent-core.js";
|
|
14
|
+
import { MODEL_MAP, getProvider } from "../../shared/constants.js";
|
|
15
|
+
import { LOCAL_TOOL_DEFINITIONS, } from "./local-tools.js";
|
|
16
|
+
import { loadServerToolDefinitions, } from "./server-tools.js";
|
|
17
|
+
import { getValidToken } from "./auth-service.js";
|
|
18
|
+
import { logSpan, generateSpanId, generateTraceId, getConversationId, initializeTelemetryClient } from "./telemetry.js";
|
|
19
|
+
import { callAgentAPI, executeToolBlocks, extractTextBlocks, extractToolUseBlocks, } from "./agent-worker-base.js";
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// CONSTANTS
|
|
22
|
+
// ============================================================================
|
|
23
|
+
const MAX_TURNS_PER_TASK = 12; // More turns than subagent since tasks are larger
|
|
24
|
+
const MAX_OUTPUT_TOKENS = 16384;
|
|
25
|
+
const MAX_TEAMMATE_TURNS = 50; // Outer loop safety: max task claim cycles
|
|
26
|
+
const MAX_TEAMMATE_DURATION_MS = 10 * 60 * 1000; // 10 minute hard timeout
|
|
27
|
+
const API_TIMEOUT_MS = 90_000; // 90s timeout on proxy/API fetch calls
|
|
28
|
+
const TOOL_TIMEOUT_MS = 60_000; // 60s timeout on individual tool execution
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// TEAMMATE SYSTEM PROMPT
|
|
31
|
+
// ============================================================================
|
|
32
|
+
function buildTeammatePrompt(teammateName, team, currentTask, cwd) {
|
|
33
|
+
const taskList = team.tasks.map(t => {
|
|
34
|
+
const status = t.status === "in_progress" && t.assignedTo
|
|
35
|
+
? `in_progress (${team.teammates.find(tm => tm.id === t.assignedTo)?.name || "unknown"})`
|
|
36
|
+
: t.status;
|
|
37
|
+
return `- [${status}] ${t.description}${t.id === currentTask?.id ? " (YOUR TASK)" : ""}`;
|
|
38
|
+
}).join("\n");
|
|
39
|
+
const teammates = team.teammates.map(t => `- ${t.name} (${t.id}): ${t.status}${t.currentTask ? ` - working on task` : ""}`).join("\n");
|
|
40
|
+
return `You are ${teammateName}, a teammate in the "${team.name}" team.
|
|
41
|
+
|
|
42
|
+
## Working Directory
|
|
43
|
+
${cwd}
|
|
44
|
+
|
|
45
|
+
## Your Current Task
|
|
46
|
+
${currentTask ? `
|
|
47
|
+
**Task ID**: ${currentTask.id}
|
|
48
|
+
**Description**: ${currentTask.description}
|
|
49
|
+
${currentTask.files?.length ? `**Files to modify**: ${currentTask.files.join(", ")}` : ""}
|
|
50
|
+
${currentTask.dependencies?.length ? `**Dependencies**: ${currentTask.dependencies.join(", ")} (all completed)` : ""}
|
|
51
|
+
` : "No task assigned yet. Use team_claim_task to claim an available task."}
|
|
52
|
+
|
|
53
|
+
## Team Task List
|
|
54
|
+
${taskList}
|
|
55
|
+
|
|
56
|
+
## Teammates
|
|
57
|
+
${teammates}
|
|
58
|
+
|
|
59
|
+
## Team Communication Tools
|
|
60
|
+
You have special tools to communicate with your team:
|
|
61
|
+
- **team_message**: Send a message to another teammate or the team lead
|
|
62
|
+
- **team_broadcast**: Send a message to all teammates
|
|
63
|
+
- **team_claim_task**: Claim an available task to work on
|
|
64
|
+
- **team_complete_task**: Mark your current task as complete with results
|
|
65
|
+
- **team_check_messages**: Check for messages from teammates
|
|
66
|
+
|
|
67
|
+
## Subagent Delegation (task tool)
|
|
68
|
+
You can delegate sub-tasks to specialized subagents using the 'task' tool:
|
|
69
|
+
- Use subagent_type="explore" for quick file/code searches
|
|
70
|
+
- Use subagent_type="general-purpose" for autonomous sub-tasks
|
|
71
|
+
- Use model="haiku" for simple lookups (cheapest/fastest)
|
|
72
|
+
- This lets you work on multiple things in parallel while staying focused on your main task
|
|
73
|
+
|
|
74
|
+
## Guidelines
|
|
75
|
+
1. Focus on YOUR assigned task - don't work on others' tasks
|
|
76
|
+
2. Communicate blockers or discoveries that affect other tasks
|
|
77
|
+
3. When done, use team_complete_task with a clear summary
|
|
78
|
+
4. Check messages periodically for updates from teammates
|
|
79
|
+
5. Avoid modifying files assigned to other teammates' tasks
|
|
80
|
+
6. Delegate research or exploration to subagents when your task requires checking multiple areas
|
|
81
|
+
|
|
82
|
+
## Self-Monitoring
|
|
83
|
+
If a tool fails, use audit_trail (action="errors") to check patterns before retrying.
|
|
84
|
+
Use audit_trail (action="tool_stats") to see your tool success rates.
|
|
85
|
+
If a tool fails repeatedly, try a different approach instead of retrying the same call.
|
|
86
|
+
|
|
87
|
+
## Output
|
|
88
|
+
Be concise. Report progress and results clearly. Use tools to do the work.`;
|
|
89
|
+
}
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// TEAM-SPECIFIC TOOLS
|
|
92
|
+
// ============================================================================
|
|
93
|
+
const TEAM_TOOLS = [
|
|
94
|
+
{
|
|
95
|
+
name: "team_message",
|
|
96
|
+
description: "Send a message to a specific teammate or the team lead",
|
|
97
|
+
input_schema: {
|
|
98
|
+
type: "object",
|
|
99
|
+
properties: {
|
|
100
|
+
to: {
|
|
101
|
+
type: "string",
|
|
102
|
+
description: "Teammate ID or 'lead' for team lead",
|
|
103
|
+
},
|
|
104
|
+
message: {
|
|
105
|
+
type: "string",
|
|
106
|
+
description: "Message content",
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
required: ["to", "message"],
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "team_broadcast",
|
|
114
|
+
description: "Send a message to all teammates",
|
|
115
|
+
input_schema: {
|
|
116
|
+
type: "object",
|
|
117
|
+
properties: {
|
|
118
|
+
message: {
|
|
119
|
+
type: "string",
|
|
120
|
+
description: "Message to broadcast",
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
required: ["message"],
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: "team_claim_task",
|
|
128
|
+
description: "Claim an available task to work on",
|
|
129
|
+
input_schema: {
|
|
130
|
+
type: "object",
|
|
131
|
+
properties: {
|
|
132
|
+
task_id: {
|
|
133
|
+
type: "string",
|
|
134
|
+
description: "ID of the task to claim",
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
required: ["task_id"],
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "team_complete_task",
|
|
142
|
+
description: "Mark your current task as complete",
|
|
143
|
+
input_schema: {
|
|
144
|
+
type: "object",
|
|
145
|
+
properties: {
|
|
146
|
+
result: {
|
|
147
|
+
type: "string",
|
|
148
|
+
description: "Summary of what was accomplished",
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
required: ["result"],
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "team_check_messages",
|
|
156
|
+
description: "Check for unread messages from teammates",
|
|
157
|
+
input_schema: {
|
|
158
|
+
type: "object",
|
|
159
|
+
properties: {},
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
// ============================================================================
|
|
164
|
+
// TOOL EXECUTION
|
|
165
|
+
// ============================================================================
|
|
166
|
+
async function executeTeamTool(toolName, input, teamId, teammateId, currentTaskId) {
|
|
167
|
+
switch (toolName) {
|
|
168
|
+
case "team_message": {
|
|
169
|
+
const to = input.to;
|
|
170
|
+
const message = input.message;
|
|
171
|
+
const result = await sendMessage(teamId, teammateId, to, message);
|
|
172
|
+
return result
|
|
173
|
+
? { success: true, output: `Message sent to ${to}` }
|
|
174
|
+
: { success: false, output: "Failed to send message" };
|
|
175
|
+
}
|
|
176
|
+
case "team_broadcast": {
|
|
177
|
+
const message = input.message;
|
|
178
|
+
const result = await sendMessage(teamId, teammateId, "all", message);
|
|
179
|
+
return result
|
|
180
|
+
? { success: true, output: "Message broadcast to all teammates" }
|
|
181
|
+
: { success: false, output: "Failed to broadcast message" };
|
|
182
|
+
}
|
|
183
|
+
case "team_claim_task": {
|
|
184
|
+
const taskId = input.task_id;
|
|
185
|
+
const task = await claimTask(teamId, taskId, teammateId);
|
|
186
|
+
return task
|
|
187
|
+
? { success: true, output: `Claimed task: ${task.description}` }
|
|
188
|
+
: { success: false, output: "Failed to claim task (may be unavailable or have unmet dependencies)" };
|
|
189
|
+
}
|
|
190
|
+
case "team_complete_task": {
|
|
191
|
+
if (!currentTaskId) {
|
|
192
|
+
return { success: false, output: "No task currently assigned" };
|
|
193
|
+
}
|
|
194
|
+
const result = input.result;
|
|
195
|
+
const success = await completeTask(teamId, currentTaskId, result);
|
|
196
|
+
return success
|
|
197
|
+
? { success: true, output: `Task completed: ${result}` }
|
|
198
|
+
: { success: false, output: "Failed to complete task" };
|
|
199
|
+
}
|
|
200
|
+
case "team_check_messages": {
|
|
201
|
+
const messages = await getUnreadMessages(teamId, teammateId);
|
|
202
|
+
if (messages.length === 0) {
|
|
203
|
+
return { success: true, output: "No unread messages" };
|
|
204
|
+
}
|
|
205
|
+
const msgList = messages.map(m => `From ${m.from}: ${m.content}`).join("\n");
|
|
206
|
+
await markMessagesRead(teamId, messages.map(m => m.id));
|
|
207
|
+
return { success: true, output: `${messages.length} messages:\n${msgList}` };
|
|
208
|
+
}
|
|
209
|
+
default:
|
|
210
|
+
return { success: false, output: `Unknown team tool: ${toolName}` };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// ============================================================================
|
|
214
|
+
// API CLIENT — uses shared callAgentAPI from agent-worker-base
|
|
215
|
+
// ============================================================================
|
|
216
|
+
async function callAPI(modelId, systemPrompt, messages, tools, thinkingEnabled = false) {
|
|
217
|
+
return callAgentAPI({
|
|
218
|
+
modelId,
|
|
219
|
+
contextProfile: "teammate",
|
|
220
|
+
systemPrompt,
|
|
221
|
+
messages,
|
|
222
|
+
tools,
|
|
223
|
+
thinkingEnabled,
|
|
224
|
+
maxOutputTokens: MAX_OUTPUT_TOKENS,
|
|
225
|
+
timeoutMs: API_TIMEOUT_MS,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
// ============================================================================
|
|
229
|
+
// TEAMMATE WORKER LOOP
|
|
230
|
+
// ============================================================================
|
|
231
|
+
async function runTeammateLoop(data) {
|
|
232
|
+
const { teamId, teammateId, teammateName, model, cwd, parentConversationId, teamName, authToken } = data;
|
|
233
|
+
const modelId = MODEL_MAP[model] || MODEL_MAP.opus; // Inherit parent default
|
|
234
|
+
const startTime = Date.now();
|
|
235
|
+
// Initialize telemetry client with auth token if provided
|
|
236
|
+
if (authToken) {
|
|
237
|
+
initializeTelemetryClient(authToken);
|
|
238
|
+
}
|
|
239
|
+
// Each teammate gets its own conversation_id (separate trace)
|
|
240
|
+
// but links to parent via parent_conversation_id
|
|
241
|
+
const teammateConversationId = getConversationId(); // Worker's own ID
|
|
242
|
+
// Create trace context for this teammate
|
|
243
|
+
const teammateTraceId = generateTraceId();
|
|
244
|
+
const teammateSpanId = generateSpanId();
|
|
245
|
+
// Log teammate start - links to parent for tree hierarchy
|
|
246
|
+
logSpan({
|
|
247
|
+
action: "team.teammate_start",
|
|
248
|
+
durationMs: 0,
|
|
249
|
+
context: {
|
|
250
|
+
traceId: teammateTraceId,
|
|
251
|
+
spanId: teammateSpanId,
|
|
252
|
+
conversationId: teammateConversationId,
|
|
253
|
+
source: "claude_code",
|
|
254
|
+
serviceName: "whale-cli",
|
|
255
|
+
serviceVersion: "2.1.0",
|
|
256
|
+
model: modelId,
|
|
257
|
+
},
|
|
258
|
+
details: {
|
|
259
|
+
is_team: true,
|
|
260
|
+
is_teammate: true,
|
|
261
|
+
team_id: teamId,
|
|
262
|
+
teammate_id: teammateId,
|
|
263
|
+
teammate_name: teammateName,
|
|
264
|
+
team_name: teamName,
|
|
265
|
+
parent_conversation_id: parentConversationId, // Link to parent trace
|
|
266
|
+
model: model,
|
|
267
|
+
display_name: teammateName,
|
|
268
|
+
display_icon: "person.fill",
|
|
269
|
+
display_color: "#3B82F6",
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
const report = (msg) => {
|
|
273
|
+
if (parentPort) {
|
|
274
|
+
parentPort.postMessage(msg);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
// Report early — let parent know we're alive before async init
|
|
278
|
+
report({ type: "progress", teammateId, content: `${teammateName} initializing...` });
|
|
279
|
+
// Get all tools (local + server + team)
|
|
280
|
+
const localTools = LOCAL_TOOL_DEFINITIONS.map(t => ({
|
|
281
|
+
name: t.name,
|
|
282
|
+
description: t.description,
|
|
283
|
+
input_schema: t.input_schema,
|
|
284
|
+
}));
|
|
285
|
+
let serverTools = [];
|
|
286
|
+
try {
|
|
287
|
+
serverTools = await loadServerToolDefinitions();
|
|
288
|
+
}
|
|
289
|
+
catch { /* server tools unavailable — continue with local tools only */ }
|
|
290
|
+
// Deduplicate: local tools take priority over server tools with the same name
|
|
291
|
+
const localNames = new Set(localTools.map(t => t.name));
|
|
292
|
+
const uniqueServerTools = serverTools.filter(t => !localNames.has(t.name));
|
|
293
|
+
const allTools = [...localTools, ...uniqueServerTools, ...TEAM_TOOLS];
|
|
294
|
+
const loopDetector = new LoopDetector();
|
|
295
|
+
let totalIn = 0;
|
|
296
|
+
let totalOut = 0;
|
|
297
|
+
let currentTaskId = null;
|
|
298
|
+
let currentTaskModelId = modelId; // Per-task model (defaults to team model)
|
|
299
|
+
let currentTaskThinking = false; // Per-task thinking toggle
|
|
300
|
+
let messages = [];
|
|
301
|
+
let tasksCompleted = 0;
|
|
302
|
+
report({ type: "progress", teammateId, content: `${teammateName} ready (${allTools.length} tools)` });
|
|
303
|
+
// Helper to log teammate completion
|
|
304
|
+
const logTeammateComplete = (reason) => {
|
|
305
|
+
const teammateCost = estimateCostUsd(totalIn, totalOut, modelId);
|
|
306
|
+
logSpan({
|
|
307
|
+
action: "team.teammate_done",
|
|
308
|
+
durationMs: Date.now() - startTime,
|
|
309
|
+
context: {
|
|
310
|
+
traceId: teammateTraceId,
|
|
311
|
+
spanId: generateSpanId(),
|
|
312
|
+
parentSpanId: teammateSpanId,
|
|
313
|
+
conversationId: teammateConversationId,
|
|
314
|
+
source: "claude_code",
|
|
315
|
+
serviceName: "whale-cli",
|
|
316
|
+
serviceVersion: "2.1.0",
|
|
317
|
+
model: modelId,
|
|
318
|
+
inputTokens: totalIn,
|
|
319
|
+
outputTokens: totalOut,
|
|
320
|
+
totalCost: teammateCost,
|
|
321
|
+
},
|
|
322
|
+
details: {
|
|
323
|
+
is_team: true,
|
|
324
|
+
is_teammate: true,
|
|
325
|
+
team_id: teamId,
|
|
326
|
+
teammate_id: teammateId,
|
|
327
|
+
teammate_name: teammateName,
|
|
328
|
+
team_name: teamName,
|
|
329
|
+
parent_conversation_id: parentConversationId,
|
|
330
|
+
tasks_completed: tasksCompleted,
|
|
331
|
+
completion_reason: reason,
|
|
332
|
+
"gen_ai.request.model": modelId,
|
|
333
|
+
"gen_ai.usage.input_tokens": totalIn,
|
|
334
|
+
"gen_ai.usage.output_tokens": totalOut,
|
|
335
|
+
"gen_ai.usage.cost": teammateCost,
|
|
336
|
+
display_name: `${teammateName} done`,
|
|
337
|
+
display_icon: "checkmark.circle.fill",
|
|
338
|
+
display_color: "#10B981",
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
};
|
|
342
|
+
// Main work loop - keep working until no more tasks
|
|
343
|
+
let outerTurn = 0;
|
|
344
|
+
while (true) {
|
|
345
|
+
// Safety: max turns guard
|
|
346
|
+
if (++outerTurn > MAX_TEAMMATE_TURNS) {
|
|
347
|
+
logTeammateComplete("max_turns_reached");
|
|
348
|
+
report({ type: "done", teammateId, content: `${teammateName} reached max turns (${MAX_TEAMMATE_TURNS})`, tokensUsed: { input: totalIn, output: totalOut } });
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
// Safety: elapsed time guard
|
|
352
|
+
if (Date.now() - startTime > MAX_TEAMMATE_DURATION_MS) {
|
|
353
|
+
logTeammateComplete("timeout");
|
|
354
|
+
report({ type: "done", teammateId, content: `${teammateName} timed out after ${Math.round(MAX_TEAMMATE_DURATION_MS / 60_000)}min`, tokensUsed: { input: totalIn, output: totalOut } });
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
const team = loadTeam(teamId);
|
|
358
|
+
if (!team || team.status !== "active") {
|
|
359
|
+
logTeammateComplete("team_inactive");
|
|
360
|
+
report({ type: "done", teammateId, content: "Team completed or inactive", tokensUsed: { input: totalIn, output: totalOut } });
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
// Find current task or claim a new one
|
|
364
|
+
const currentTask = currentTaskId
|
|
365
|
+
? team.tasks.find(t => t.id === currentTaskId)
|
|
366
|
+
: null;
|
|
367
|
+
// If no current task, try to claim one
|
|
368
|
+
if (!currentTask || currentTask.status === "completed") {
|
|
369
|
+
const available = getAvailableTasks(team);
|
|
370
|
+
if (available.length === 0) {
|
|
371
|
+
// No tasks available, check if all done or waiting
|
|
372
|
+
const inProgress = team.tasks.filter(t => t.status === "in_progress").length;
|
|
373
|
+
if (inProgress === 0) {
|
|
374
|
+
logTeammateComplete("all_tasks_done");
|
|
375
|
+
report({ type: "done", teammateId, content: "All tasks completed", tokensUsed: { input: totalIn, output: totalOut } });
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
// Wait and retry
|
|
379
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
// Claim first available task
|
|
383
|
+
const claimed = await claimTask(teamId, available[0].id, teammateId);
|
|
384
|
+
if (!claimed) {
|
|
385
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
currentTaskId = claimed.id;
|
|
389
|
+
// Resolve per-task model: task-level → team default
|
|
390
|
+
const taskModel = claimed.model || model;
|
|
391
|
+
currentTaskModelId = MODEL_MAP[taskModel] || MODEL_MAP[model] || MODEL_MAP.opus;
|
|
392
|
+
// Enable thinking for capable models (Opus/Sonnet adaptive, others budget/disabled)
|
|
393
|
+
const taskProvider = getProvider(currentTaskModelId);
|
|
394
|
+
currentTaskThinking = (taskProvider === "anthropic" || taskProvider === "bedrock")
|
|
395
|
+
&& (currentTaskModelId.includes("opus") || currentTaskModelId.includes("sonnet-4-6") || currentTaskModelId.includes("sonnet-4-5"));
|
|
396
|
+
await updateTeammate(teamId, teammateId, { status: "working", currentTask: currentTaskId });
|
|
397
|
+
report({ type: "task_started", teammateId, taskId: currentTaskId, content: claimed.description });
|
|
398
|
+
// Start fresh conversation for new task
|
|
399
|
+
messages = [{
|
|
400
|
+
role: "user",
|
|
401
|
+
content: `Your task: ${claimed.description}\n\nBegin working on this task. Use the available tools to complete it, then use team_complete_task when done.`,
|
|
402
|
+
}];
|
|
403
|
+
}
|
|
404
|
+
// Build system prompt with current state
|
|
405
|
+
const freshTeam = loadTeam(teamId);
|
|
406
|
+
const freshTask = freshTeam.tasks.find(t => t.id === currentTaskId) || null;
|
|
407
|
+
const systemPrompt = buildTeammatePrompt(teammateName, freshTeam, freshTask, cwd);
|
|
408
|
+
// Agent loop for current task
|
|
409
|
+
let apiError = null;
|
|
410
|
+
for (let turn = 0; turn < MAX_TURNS_PER_TASK; turn++) {
|
|
411
|
+
const apiStart = Date.now();
|
|
412
|
+
let response;
|
|
413
|
+
try {
|
|
414
|
+
response = await callAPI(currentTaskModelId, systemPrompt, messages, allTools, currentTaskThinking);
|
|
415
|
+
}
|
|
416
|
+
catch (err) {
|
|
417
|
+
apiError = err.message || String(err);
|
|
418
|
+
report({ type: "progress", teammateId, content: `API error: ${apiError.slice(0, 80)}` });
|
|
419
|
+
logSpan({
|
|
420
|
+
action: "claude_api_request",
|
|
421
|
+
durationMs: Date.now() - apiStart,
|
|
422
|
+
severity: "error",
|
|
423
|
+
error: apiError || undefined,
|
|
424
|
+
context: {
|
|
425
|
+
traceId: teammateTraceId,
|
|
426
|
+
spanId: generateSpanId(),
|
|
427
|
+
parentSpanId: teammateSpanId,
|
|
428
|
+
conversationId: teammateConversationId,
|
|
429
|
+
source: "claude_code",
|
|
430
|
+
serviceName: "whale-cli",
|
|
431
|
+
serviceVersion: "2.1.0",
|
|
432
|
+
model: currentTaskModelId,
|
|
433
|
+
},
|
|
434
|
+
details: {
|
|
435
|
+
is_team: true,
|
|
436
|
+
is_teammate: true,
|
|
437
|
+
team_id: teamId,
|
|
438
|
+
teammate_id: teammateId,
|
|
439
|
+
teammate_name: teammateName,
|
|
440
|
+
parent_conversation_id: parentConversationId,
|
|
441
|
+
turn_number: turn + 1,
|
|
442
|
+
task_id: currentTaskId,
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
break; // Exit inner loop — force-complete handler below will deal with the task
|
|
446
|
+
}
|
|
447
|
+
const apiDuration = Date.now() - apiStart;
|
|
448
|
+
totalIn += response.usage.input_tokens;
|
|
449
|
+
totalOut += response.usage.output_tokens;
|
|
450
|
+
// Log Claude API request telemetry
|
|
451
|
+
const turnCostUsd = estimateCostUsd(response.usage.input_tokens, response.usage.output_tokens, currentTaskModelId);
|
|
452
|
+
logSpan({
|
|
453
|
+
action: "claude_api_request",
|
|
454
|
+
durationMs: apiDuration,
|
|
455
|
+
context: {
|
|
456
|
+
traceId: teammateTraceId,
|
|
457
|
+
spanId: generateSpanId(),
|
|
458
|
+
parentSpanId: teammateSpanId,
|
|
459
|
+
conversationId: teammateConversationId,
|
|
460
|
+
source: "claude_code",
|
|
461
|
+
serviceName: "whale-cli",
|
|
462
|
+
serviceVersion: "2.1.0",
|
|
463
|
+
model: currentTaskModelId,
|
|
464
|
+
inputTokens: response.usage.input_tokens,
|
|
465
|
+
outputTokens: response.usage.output_tokens,
|
|
466
|
+
totalCost: turnCostUsd,
|
|
467
|
+
},
|
|
468
|
+
details: {
|
|
469
|
+
is_team: true,
|
|
470
|
+
is_teammate: true,
|
|
471
|
+
team_id: teamId,
|
|
472
|
+
teammate_id: teammateId,
|
|
473
|
+
teammate_name: teammateName,
|
|
474
|
+
parent_conversation_id: parentConversationId,
|
|
475
|
+
turn_number: turn + 1,
|
|
476
|
+
task_id: currentTaskId,
|
|
477
|
+
stop_reason: response.stop_reason,
|
|
478
|
+
"gen_ai.request.model": currentTaskModelId,
|
|
479
|
+
"gen_ai.usage.input_tokens": response.usage.input_tokens,
|
|
480
|
+
"gen_ai.usage.output_tokens": response.usage.output_tokens,
|
|
481
|
+
"gen_ai.usage.cost": turnCostUsd,
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
const textBlocks = extractTextBlocks(response.content);
|
|
485
|
+
const toolBlocks = extractToolUseBlocks(response.content);
|
|
486
|
+
// Report progress
|
|
487
|
+
if (textBlocks.length) {
|
|
488
|
+
report({ type: "progress", teammateId, taskId: currentTaskId || undefined, content: textBlocks[0].text.slice(0, 200) });
|
|
489
|
+
}
|
|
490
|
+
// No tools = done with this turn
|
|
491
|
+
if (toolBlocks.length === 0)
|
|
492
|
+
break;
|
|
493
|
+
// Execute tools using shared executeToolBlocks with team-specific custom executor
|
|
494
|
+
let taskCompleted = false;
|
|
495
|
+
const { toolResults } = await executeToolBlocks({
|
|
496
|
+
toolBlocks,
|
|
497
|
+
loopDetector,
|
|
498
|
+
toolTimeoutMs: TOOL_TIMEOUT_MS,
|
|
499
|
+
customExecutor: async (toolName, input) => {
|
|
500
|
+
// Only handle team tools; return null to fall through to standard routing
|
|
501
|
+
if (!TEAM_TOOLS.some(t => t.name === toolName))
|
|
502
|
+
return null;
|
|
503
|
+
const result = await executeTeamTool(toolName, input, teamId, teammateId, currentTaskId);
|
|
504
|
+
// Check if task was completed
|
|
505
|
+
if (toolName === "team_complete_task" && result.success) {
|
|
506
|
+
taskCompleted = true;
|
|
507
|
+
tasksCompleted++;
|
|
508
|
+
report({ type: "task_completed", teammateId, taskId: currentTaskId || undefined, content: result.output });
|
|
509
|
+
// Log task completion to telemetry
|
|
510
|
+
logSpan({
|
|
511
|
+
action: "team.task_complete",
|
|
512
|
+
durationMs: 0,
|
|
513
|
+
context: {
|
|
514
|
+
traceId: teammateTraceId,
|
|
515
|
+
spanId: generateSpanId(),
|
|
516
|
+
parentSpanId: teammateSpanId,
|
|
517
|
+
conversationId: teammateConversationId,
|
|
518
|
+
source: "claude_code",
|
|
519
|
+
serviceName: "whale-cli",
|
|
520
|
+
serviceVersion: "2.1.0",
|
|
521
|
+
},
|
|
522
|
+
details: {
|
|
523
|
+
is_team: true,
|
|
524
|
+
is_teammate: true,
|
|
525
|
+
team_id: teamId,
|
|
526
|
+
teammate_id: teammateId,
|
|
527
|
+
teammate_name: teammateName,
|
|
528
|
+
parent_conversation_id: parentConversationId,
|
|
529
|
+
task_id: currentTaskId,
|
|
530
|
+
task_result: result.output.slice(0, 500),
|
|
531
|
+
display_name: "Task completed",
|
|
532
|
+
display_icon: "checkmark.square.fill",
|
|
533
|
+
display_color: "#10B981",
|
|
534
|
+
},
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
return result;
|
|
538
|
+
},
|
|
539
|
+
callbacks: {
|
|
540
|
+
onToolEnd: (toolName, success, durationMs) => {
|
|
541
|
+
// Determine tool category for telemetry
|
|
542
|
+
const toolCategory = TEAM_TOOLS.some(t => t.name === toolName) ? "team" : "standard";
|
|
543
|
+
logSpan({
|
|
544
|
+
action: `tool.${toolName}`,
|
|
545
|
+
durationMs,
|
|
546
|
+
severity: success ? "info" : "error",
|
|
547
|
+
context: {
|
|
548
|
+
traceId: teammateTraceId,
|
|
549
|
+
spanId: generateSpanId(),
|
|
550
|
+
parentSpanId: teammateSpanId,
|
|
551
|
+
conversationId: teammateConversationId,
|
|
552
|
+
source: "claude_code",
|
|
553
|
+
serviceName: "whale-cli",
|
|
554
|
+
serviceVersion: "2.1.0",
|
|
555
|
+
},
|
|
556
|
+
error: success ? undefined : "(see tool result)",
|
|
557
|
+
details: {
|
|
558
|
+
is_team: true,
|
|
559
|
+
is_teammate: true,
|
|
560
|
+
team_id: teamId,
|
|
561
|
+
teammate_id: teammateId,
|
|
562
|
+
teammate_name: teammateName,
|
|
563
|
+
parent_conversation_id: parentConversationId,
|
|
564
|
+
tool_category: toolCategory,
|
|
565
|
+
},
|
|
566
|
+
});
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
});
|
|
570
|
+
// Append to messages
|
|
571
|
+
messages.push({ role: "assistant", content: response.content });
|
|
572
|
+
messages.push({ role: "user", content: toolResults });
|
|
573
|
+
// If task was completed, break out to get next task
|
|
574
|
+
if (taskCompleted) {
|
|
575
|
+
currentTaskId = null;
|
|
576
|
+
await updateTeammate(teamId, teammateId, { status: "idle", currentTask: undefined });
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
// If inner loop ended without completing the task (exhausted turns, API error, or no-tool response)
|
|
581
|
+
if (currentTaskId) {
|
|
582
|
+
const team = loadTeam(teamId);
|
|
583
|
+
const task = team?.tasks.find(t => t.id === currentTaskId);
|
|
584
|
+
if (task && task.status === "in_progress") {
|
|
585
|
+
if (apiError) {
|
|
586
|
+
// API failed — mark task as failed, not completed
|
|
587
|
+
await failTask(teamId, currentTaskId, apiError);
|
|
588
|
+
// Report as progress (not error) — error type causes red flash in UI via handleTeammateFailure double-handling
|
|
589
|
+
report({ type: "progress", teammateId, taskId: currentTaskId, content: `Task failed: ${apiError.slice(0, 80)}` });
|
|
590
|
+
}
|
|
591
|
+
else if (totalIn === 0 && totalOut === 0) {
|
|
592
|
+
// Zero tokens used means no real work was done — fail the task
|
|
593
|
+
await failTask(teamId, currentTaskId, "No API response received (0 tokens)");
|
|
594
|
+
report({ type: "progress", teammateId, taskId: currentTaskId, content: "Task failed: no API response" });
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
// Exhausted turns or model stopped — auto-complete with partial result
|
|
598
|
+
const lastText = messages.length > 0 ? messages[messages.length - 1] : null;
|
|
599
|
+
let partialResult = "Task auto-completed after reaching turn limit.";
|
|
600
|
+
if (lastText && typeof lastText === "object" && "content" in lastText) {
|
|
601
|
+
const content = lastText.content;
|
|
602
|
+
if (typeof content === "string")
|
|
603
|
+
partialResult = content.slice(0, 500);
|
|
604
|
+
else if (Array.isArray(content)) {
|
|
605
|
+
const txt = content.find((b) => b.type === "text");
|
|
606
|
+
if (txt && "text" in txt)
|
|
607
|
+
partialResult = txt.text.slice(0, 500);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
await completeTask(teamId, currentTaskId, partialResult);
|
|
611
|
+
tasksCompleted++;
|
|
612
|
+
report({ type: "task_completed", teammateId, taskId: currentTaskId, content: partialResult });
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
currentTaskId = null;
|
|
616
|
+
await updateTeammate(teamId, teammateId, { status: "idle", currentTask: undefined });
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
// Final update
|
|
620
|
+
await updateTeammate(teamId, teammateId, {
|
|
621
|
+
status: "done",
|
|
622
|
+
currentTask: undefined,
|
|
623
|
+
tokensUsed: { input: totalIn, output: totalOut },
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
// ============================================================================
|
|
627
|
+
// WORKER ENTRY POINT
|
|
628
|
+
// ============================================================================
|
|
629
|
+
if (!isMainThread && parentPort) {
|
|
630
|
+
const data = workerData;
|
|
631
|
+
// Catch ALL errors including module-level and unhandled rejections
|
|
632
|
+
process.on("uncaughtException", (err) => {
|
|
633
|
+
try {
|
|
634
|
+
parentPort.postMessage({
|
|
635
|
+
type: "error",
|
|
636
|
+
teammateId: data.teammateId,
|
|
637
|
+
content: `Uncaught exception: ${err.message || String(err)}`,
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
catch { /* last resort — nothing we can do */ }
|
|
641
|
+
process.exit(1);
|
|
642
|
+
});
|
|
643
|
+
process.on("unhandledRejection", (reason) => {
|
|
644
|
+
try {
|
|
645
|
+
parentPort.postMessage({
|
|
646
|
+
type: "error",
|
|
647
|
+
teammateId: data.teammateId,
|
|
648
|
+
content: `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`,
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
catch { /* last resort */ }
|
|
652
|
+
});
|
|
653
|
+
runTeammateLoop(data)
|
|
654
|
+
.catch(err => {
|
|
655
|
+
parentPort.postMessage({
|
|
656
|
+
type: "error",
|
|
657
|
+
teammateId: data.teammateId,
|
|
658
|
+
content: err.message || String(err),
|
|
659
|
+
});
|
|
660
|
+
})
|
|
661
|
+
.finally(() => {
|
|
662
|
+
// Force-exit the Worker thread — lingering async resources (HTTP connections,
|
|
663
|
+
// Supabase clients, telemetry timers) prevent natural exit and cause the
|
|
664
|
+
// team to hang forever waiting for worker.on("exit").
|
|
665
|
+
setTimeout(() => process.exit(0), 100); // Brief delay for final messages to flush
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
// ============================================================================
|
|
669
|
+
// SPAWN TEAMMATE (from main thread)
|
|
670
|
+
// ============================================================================
|
|
671
|
+
export async function spawnTeammate(teamId, teammateId, teammateName, model, cwd, parentConversationId, teamName) {
|
|
672
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
673
|
+
const workerPath = __filename.replace(".js", ".js"); // Same file
|
|
674
|
+
// Get auth token to pass to worker for telemetry
|
|
675
|
+
const authToken = await getValidToken();
|
|
676
|
+
const worker = new Worker(workerPath, {
|
|
677
|
+
workerData: {
|
|
678
|
+
teamId,
|
|
679
|
+
teammateId,
|
|
680
|
+
teammateName,
|
|
681
|
+
model,
|
|
682
|
+
cwd,
|
|
683
|
+
parentConversationId,
|
|
684
|
+
teamName,
|
|
685
|
+
authToken: authToken || undefined,
|
|
686
|
+
},
|
|
687
|
+
});
|
|
688
|
+
return worker;
|
|
689
|
+
}
|