claudeck 1.0.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/LICENSE +21 -0
- package/README.md +233 -0
- package/cli.js +2 -0
- package/config/agent-chains.json +16 -0
- package/config/agent-dags.json +16 -0
- package/config/agents.json +46 -0
- package/config/bot-prompt.json +3 -0
- package/config/folders.json +66 -0
- package/config/prompts.json +92 -0
- package/config/repos.json +86 -0
- package/config/telegram-config.json +17 -0
- package/config/workflows.json +90 -0
- package/db.js +1198 -0
- package/package.json +55 -0
- package/plugins/claude-editor/client.css +171 -0
- package/plugins/claude-editor/client.js +183 -0
- package/plugins/event-stream/client.css +207 -0
- package/plugins/event-stream/client.js +271 -0
- package/plugins/linear/client.css +345 -0
- package/plugins/linear/client.js +380 -0
- package/plugins/linear/config.json +5 -0
- package/plugins/linear/server.js +312 -0
- package/plugins/repos/client.css +549 -0
- package/plugins/repos/client.js +663 -0
- package/plugins/repos/server.js +232 -0
- package/plugins/sudoku/client.css +196 -0
- package/plugins/sudoku/client.js +329 -0
- package/plugins/tasks/client.css +414 -0
- package/plugins/tasks/client.js +394 -0
- package/plugins/tasks/server.js +116 -0
- package/plugins/tic-tac-toe/client.css +167 -0
- package/plugins/tic-tac-toe/client.js +241 -0
- package/public/css/core/components.css +232 -0
- package/public/css/core/layout.css +330 -0
- package/public/css/core/print.css +18 -0
- package/public/css/core/reset.css +36 -0
- package/public/css/core/responsive.css +378 -0
- package/public/css/core/theme.css +116 -0
- package/public/css/core/variables.css +93 -0
- package/public/css/features/agent-monitor.css +297 -0
- package/public/css/features/agent-sidebar.css +525 -0
- package/public/css/features/agents.css +996 -0
- package/public/css/features/analytics.css +181 -0
- package/public/css/features/background-sessions.css +321 -0
- package/public/css/features/cost-dashboard.css +168 -0
- package/public/css/features/home.css +313 -0
- package/public/css/features/retro-terminal.css +88 -0
- package/public/css/features/telegram.css +127 -0
- package/public/css/features/tour.css +148 -0
- package/public/css/features/voice-input.css +60 -0
- package/public/css/features/welcome.css +241 -0
- package/public/css/panels/assistant-bot.css +442 -0
- package/public/css/panels/dev-docs.css +292 -0
- package/public/css/panels/file-explorer.css +322 -0
- package/public/css/panels/git-panel.css +221 -0
- package/public/css/panels/mcp-manager.css +199 -0
- package/public/css/panels/tips-feed.css +353 -0
- package/public/css/ui/commands.css +273 -0
- package/public/css/ui/context-gauge.css +76 -0
- package/public/css/ui/file-picker.css +69 -0
- package/public/css/ui/image-attachments.css +106 -0
- package/public/css/ui/messages.css +884 -0
- package/public/css/ui/modals.css +122 -0
- package/public/css/ui/parallel.css +217 -0
- package/public/css/ui/permissions.css +110 -0
- package/public/css/ui/right-panel.css +481 -0
- package/public/css/ui/sessions.css +689 -0
- package/public/css/ui/status-bar.css +425 -0
- package/public/css/ui/toolbox.css +206 -0
- package/public/data/tips.json +218 -0
- package/public/icons/favicon.png +0 -0
- package/public/icons/icon-192.png +0 -0
- package/public/icons/icon-512.png +0 -0
- package/public/icons/whaly.png +0 -0
- package/public/index.html +1140 -0
- package/public/js/core/api.js +591 -0
- package/public/js/core/constants.js +3 -0
- package/public/js/core/dom.js +270 -0
- package/public/js/core/events.js +10 -0
- package/public/js/core/plugin-loader.js +153 -0
- package/public/js/core/store.js +39 -0
- package/public/js/core/utils.js +25 -0
- package/public/js/core/ws.js +64 -0
- package/public/js/features/agent-monitor.js +222 -0
- package/public/js/features/agents.js +1209 -0
- package/public/js/features/analytics.js +397 -0
- package/public/js/features/attachments.js +251 -0
- package/public/js/features/background-sessions.js +475 -0
- package/public/js/features/chat.js +589 -0
- package/public/js/features/cost-dashboard.js +152 -0
- package/public/js/features/dag-editor.js +399 -0
- package/public/js/features/easter-egg.js +46 -0
- package/public/js/features/home.js +270 -0
- package/public/js/features/projects.js +372 -0
- package/public/js/features/prompts.js +228 -0
- package/public/js/features/sessions.js +332 -0
- package/public/js/features/telegram.js +131 -0
- package/public/js/features/tour.js +210 -0
- package/public/js/features/voice-input.js +185 -0
- package/public/js/features/welcome.js +43 -0
- package/public/js/features/workflows.js +277 -0
- package/public/js/main.js +51 -0
- package/public/js/panels/assistant-bot.js +445 -0
- package/public/js/panels/dev-docs.js +380 -0
- package/public/js/panels/file-explorer.js +486 -0
- package/public/js/panels/git-panel.js +285 -0
- package/public/js/panels/mcp-manager.js +311 -0
- package/public/js/panels/tips-feed.js +303 -0
- package/public/js/ui/commands.js +114 -0
- package/public/js/ui/context-gauge.js +100 -0
- package/public/js/ui/diff.js +124 -0
- package/public/js/ui/disabled-tools.js +36 -0
- package/public/js/ui/export.js +74 -0
- package/public/js/ui/formatting.js +206 -0
- package/public/js/ui/header-dropdowns.js +72 -0
- package/public/js/ui/input-meta.js +71 -0
- package/public/js/ui/max-turns.js +21 -0
- package/public/js/ui/messages.js +387 -0
- package/public/js/ui/model-selector.js +20 -0
- package/public/js/ui/notifications.js +232 -0
- package/public/js/ui/parallel.js +176 -0
- package/public/js/ui/permissions.js +168 -0
- package/public/js/ui/right-panel.js +173 -0
- package/public/js/ui/shortcuts.js +143 -0
- package/public/js/ui/sidebar-toggle.js +29 -0
- package/public/js/ui/status-bar.js +172 -0
- package/public/js/ui/tab-sdk.js +623 -0
- package/public/js/ui/theme.js +38 -0
- package/public/manifest.json +13 -0
- package/public/offline.html +190 -0
- package/public/style.css +42 -0
- package/public/sw.js +91 -0
- package/server/agent-loop.js +385 -0
- package/server/dag-executor.js +265 -0
- package/server/orchestrator.js +514 -0
- package/server/paths.js +61 -0
- package/server/plugin-mount.js +56 -0
- package/server/push-sender.js +31 -0
- package/server/routes/agents.js +294 -0
- package/server/routes/bot.js +45 -0
- package/server/routes/exec.js +35 -0
- package/server/routes/files.js +218 -0
- package/server/routes/mcp.js +82 -0
- package/server/routes/messages.js +36 -0
- package/server/routes/notifications.js +37 -0
- package/server/routes/projects.js +207 -0
- package/server/routes/prompts.js +53 -0
- package/server/routes/sessions.js +103 -0
- package/server/routes/stats.js +143 -0
- package/server/routes/telegram.js +71 -0
- package/server/routes/tips.js +135 -0
- package/server/routes/workflows.js +81 -0
- package/server/summarizer.js +55 -0
- package/server/telegram-poller.js +205 -0
- package/server/telegram-sender.js +304 -0
- package/server/ws-handler.js +926 -0
- package/server.js +179 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { query } from "@anthropic-ai/claude-code";
|
|
2
|
+
import { execPath } from "process";
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
|
|
6
|
+
// Map short model names to current model IDs
|
|
7
|
+
const MODEL_MAP = {
|
|
8
|
+
haiku: "claude-haiku-4-5-20251001",
|
|
9
|
+
sonnet: "claude-sonnet-4-6",
|
|
10
|
+
opus: "claude-opus-4-6",
|
|
11
|
+
};
|
|
12
|
+
function resolveModel(name) {
|
|
13
|
+
if (!name) return undefined;
|
|
14
|
+
return MODEL_MAP[name] || name;
|
|
15
|
+
}
|
|
16
|
+
import {
|
|
17
|
+
createSession,
|
|
18
|
+
updateClaudeSessionId,
|
|
19
|
+
getSession,
|
|
20
|
+
touchSession,
|
|
21
|
+
addCost,
|
|
22
|
+
addMessage,
|
|
23
|
+
getTotalCost,
|
|
24
|
+
setClaudeSession,
|
|
25
|
+
updateSessionTitle,
|
|
26
|
+
setAgentContext,
|
|
27
|
+
getAllAgentContext,
|
|
28
|
+
recordAgentRunStart,
|
|
29
|
+
recordAgentRunComplete,
|
|
30
|
+
} from "../db.js";
|
|
31
|
+
import { getProjectSystemPrompt } from "./routes/projects.js";
|
|
32
|
+
import { sendPushNotification } from "./push-sender.js";
|
|
33
|
+
import { sendTelegramNotification } from "./telegram-sender.js";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Build the agent system prompt that instructs Claude to work autonomously
|
|
37
|
+
* toward the given goal.
|
|
38
|
+
*/
|
|
39
|
+
function buildAgentPrompt(agentDef, userContext, sharedContext) {
|
|
40
|
+
let prompt = `You are an autonomous AI agent. Work toward the following goal step by step, using any tools available to you.\n\n`;
|
|
41
|
+
prompt += `## Goal\n${agentDef.goal}\n\n`;
|
|
42
|
+
if (userContext) {
|
|
43
|
+
prompt += `## Additional Context from User\n${userContext}\n\n`;
|
|
44
|
+
}
|
|
45
|
+
if (sharedContext && sharedContext.length > 0) {
|
|
46
|
+
prompt += `## Context from Previous Agents\n`;
|
|
47
|
+
prompt += `The following is output from agents that ran before you in this chain. Use this context to inform your work.\n\n`;
|
|
48
|
+
for (const ctx of sharedContext) {
|
|
49
|
+
prompt += `### From: ${ctx.agent_id}\n${ctx.value}\n\n`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
prompt += `## Instructions\n`;
|
|
53
|
+
prompt += `- Break the goal into logical steps and execute them one by one.\n`;
|
|
54
|
+
prompt += `- Use tools (read files, search, write, run commands) as needed.\n`;
|
|
55
|
+
prompt += `- After completing all steps, provide a clear final summary of what you accomplished.\n`;
|
|
56
|
+
prompt += `- If you encounter a blocker you cannot resolve, explain it clearly and stop.\n`;
|
|
57
|
+
return prompt;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Run an autonomous agent.
|
|
62
|
+
* This uses a single SDK query() call with high maxTurns so Claude
|
|
63
|
+
* autonomously decides what tools to use and when to stop.
|
|
64
|
+
*/
|
|
65
|
+
export async function runAgent({
|
|
66
|
+
ws,
|
|
67
|
+
agentDef,
|
|
68
|
+
cwd,
|
|
69
|
+
sessionId: clientSid,
|
|
70
|
+
projectName,
|
|
71
|
+
permissionMode,
|
|
72
|
+
model,
|
|
73
|
+
sessionIds,
|
|
74
|
+
pendingApprovals,
|
|
75
|
+
makeCanUseTool,
|
|
76
|
+
userContext,
|
|
77
|
+
activeQueries,
|
|
78
|
+
chainResumeId,
|
|
79
|
+
runId,
|
|
80
|
+
runType,
|
|
81
|
+
parentRunId,
|
|
82
|
+
}) {
|
|
83
|
+
const agentId = agentDef.id;
|
|
84
|
+
const maxTurns = agentDef.constraints?.maxTurns || 50;
|
|
85
|
+
const timeoutMs = agentDef.constraints?.timeoutMs || 300000;
|
|
86
|
+
|
|
87
|
+
// Record run start for monitoring dashboard
|
|
88
|
+
const monitorRunId = runId || `single-${Date.now()}`;
|
|
89
|
+
const effectiveRunType = runType || 'single';
|
|
90
|
+
try {
|
|
91
|
+
recordAgentRunStart(monitorRunId, agentId, agentDef.title, effectiveRunType, parentRunId);
|
|
92
|
+
} catch (e) { /* ignore duplicates */ }
|
|
93
|
+
|
|
94
|
+
function agentSend(payload) {
|
|
95
|
+
if (ws.readyState !== 1) return;
|
|
96
|
+
ws.send(JSON.stringify(payload));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Notify client that agent has started
|
|
100
|
+
agentSend({
|
|
101
|
+
type: "agent_started",
|
|
102
|
+
agentId,
|
|
103
|
+
title: agentDef.title,
|
|
104
|
+
goal: agentDef.goal,
|
|
105
|
+
maxTurns,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const abortController = new AbortController();
|
|
109
|
+
const queryKey = `agent-${agentId}-${Date.now()}`;
|
|
110
|
+
if (activeQueries) {
|
|
111
|
+
activeQueries.set(queryKey, { abort: () => abortController.abort() });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Set up timeout
|
|
115
|
+
const timeoutHandle = setTimeout(() => {
|
|
116
|
+
abortController.abort();
|
|
117
|
+
agentSend({ type: "agent_error", error: "Agent reached time limit", agentId });
|
|
118
|
+
}, timeoutMs);
|
|
119
|
+
|
|
120
|
+
const effectivePermMode = permissionMode || "bypass";
|
|
121
|
+
const useBypass = effectivePermMode === "bypass";
|
|
122
|
+
const usePlan = effectivePermMode === "plan";
|
|
123
|
+
const resolvedCwd = (cwd && existsSync(cwd)) ? cwd : homedir();
|
|
124
|
+
|
|
125
|
+
const opts = {
|
|
126
|
+
cwd: resolvedCwd,
|
|
127
|
+
permissionMode: usePlan ? "plan" : (useBypass ? "bypassPermissions" : "default"),
|
|
128
|
+
abortController,
|
|
129
|
+
maxTurns,
|
|
130
|
+
executable: execPath,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
if (!useBypass && !usePlan) {
|
|
134
|
+
opts.canUseTool = makeCanUseTool(ws, pendingApprovals, effectivePermMode, null, agentDef.title || "Agent");
|
|
135
|
+
}
|
|
136
|
+
if (model) opts.model = resolveModel(model);
|
|
137
|
+
|
|
138
|
+
const projectPrompt = getProjectSystemPrompt(cwd);
|
|
139
|
+
if (projectPrompt) opts.appendSystemPrompt = projectPrompt;
|
|
140
|
+
|
|
141
|
+
// Resume existing session — explicit chainResumeId takes priority
|
|
142
|
+
const resumeId = chainResumeId || (clientSid ? sessionIds.get(clientSid) : undefined);
|
|
143
|
+
if (resumeId) opts.resume = resumeId;
|
|
144
|
+
|
|
145
|
+
// Load shared context from previous agents in this run
|
|
146
|
+
const sharedContext = runId ? getAllAgentContext(runId) : [];
|
|
147
|
+
const prompt = buildAgentPrompt(agentDef, userContext, sharedContext);
|
|
148
|
+
let resolvedSid = clientSid;
|
|
149
|
+
let claudeSessionId = null;
|
|
150
|
+
let sessionModel = null;
|
|
151
|
+
let turnCount = 0;
|
|
152
|
+
let lastAssistantText = "";
|
|
153
|
+
let lastAgentMetrics = {};
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const q = query({ prompt, options: opts });
|
|
157
|
+
|
|
158
|
+
for await (const sdkMsg of q) {
|
|
159
|
+
if (ws.readyState !== 1) break;
|
|
160
|
+
|
|
161
|
+
// Init message — session setup
|
|
162
|
+
if (sdkMsg.type === "system" && sdkMsg.subtype === "init") {
|
|
163
|
+
claudeSessionId = sdkMsg.session_id;
|
|
164
|
+
if (sdkMsg.model) sessionModel = sdkMsg.model;
|
|
165
|
+
const ourSid = clientSid || crypto.randomUUID();
|
|
166
|
+
resolvedSid = ourSid;
|
|
167
|
+
|
|
168
|
+
sessionIds.set(ourSid, claudeSessionId);
|
|
169
|
+
|
|
170
|
+
if (!getSession(ourSid)) {
|
|
171
|
+
createSession(ourSid, claudeSessionId, projectName || "Agent Session", cwd || "");
|
|
172
|
+
updateSessionTitle(ourSid, `Agent: ${agentDef.title}`);
|
|
173
|
+
} else {
|
|
174
|
+
updateClaudeSessionId(ourSid, claudeSessionId);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
agentSend({ type: "session", sessionId: ourSid });
|
|
178
|
+
addMessage(resolvedSid, "user", JSON.stringify({ text: `[Agent: ${agentDef.title}] ${agentDef.goal}` }), null);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Assistant message — text and tool_use blocks
|
|
183
|
+
if (sdkMsg.type === "assistant" && sdkMsg.message?.content) {
|
|
184
|
+
for (const block of sdkMsg.message.content) {
|
|
185
|
+
if (block.type === "text" && block.text) {
|
|
186
|
+
lastAssistantText = block.text;
|
|
187
|
+
agentSend({ type: "text", text: block.text });
|
|
188
|
+
if (resolvedSid) {
|
|
189
|
+
addMessage(resolvedSid, "assistant", JSON.stringify({ text: block.text }), null);
|
|
190
|
+
}
|
|
191
|
+
} else if (block.type === "tool_use") {
|
|
192
|
+
turnCount++;
|
|
193
|
+
agentSend({ type: "tool", id: block.id, name: block.name, input: block.input });
|
|
194
|
+
agentSend({
|
|
195
|
+
type: "agent_progress",
|
|
196
|
+
agentId,
|
|
197
|
+
turn: turnCount,
|
|
198
|
+
maxTurns,
|
|
199
|
+
action: block.name,
|
|
200
|
+
detail: typeof block.input === "object"
|
|
201
|
+
? (block.input.command || block.input.pattern || block.input.file_path || block.input.query || "").slice(0, 120)
|
|
202
|
+
: "",
|
|
203
|
+
});
|
|
204
|
+
if (resolvedSid) {
|
|
205
|
+
addMessage(resolvedSid, "tool", JSON.stringify({ id: block.id, name: block.name, input: block.input }), null);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Tool results
|
|
213
|
+
if (sdkMsg.type === "user" && sdkMsg.message?.content) {
|
|
214
|
+
const blocks = Array.isArray(sdkMsg.message.content) ? sdkMsg.message.content : [];
|
|
215
|
+
for (const block of blocks) {
|
|
216
|
+
if (block.type === "tool_result") {
|
|
217
|
+
const text = Array.isArray(block.content)
|
|
218
|
+
? block.content.map(c => c.type === "text" ? c.text : "").join("")
|
|
219
|
+
: typeof block.content === "string" ? block.content : "";
|
|
220
|
+
agentSend({
|
|
221
|
+
type: "tool_result",
|
|
222
|
+
toolUseId: block.tool_use_id,
|
|
223
|
+
content: text.slice(0, 2000),
|
|
224
|
+
isError: block.is_error || false,
|
|
225
|
+
});
|
|
226
|
+
if (resolvedSid) {
|
|
227
|
+
addMessage(resolvedSid, "tool_result", JSON.stringify({
|
|
228
|
+
toolUseId: block.tool_use_id,
|
|
229
|
+
content: text.slice(0, 10000),
|
|
230
|
+
isError: block.is_error || false,
|
|
231
|
+
}), null);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Result message
|
|
239
|
+
if (sdkMsg.type === "result") {
|
|
240
|
+
if (sdkMsg.subtype === "success" || sdkMsg.subtype === "error_max_turns") {
|
|
241
|
+
const costUsd = sdkMsg.total_cost_usd || 0;
|
|
242
|
+
const durationMs = sdkMsg.duration_ms || 0;
|
|
243
|
+
const numTurns = sdkMsg.num_turns || 0;
|
|
244
|
+
const inputTokens = sdkMsg.usage?.input_tokens || 0;
|
|
245
|
+
const outputTokens = sdkMsg.usage?.output_tokens || 0;
|
|
246
|
+
const cacheReadTokens = sdkMsg.usage?.cache_read_input_tokens || 0;
|
|
247
|
+
const cacheCreationTokens = sdkMsg.usage?.cache_creation_input_tokens || 0;
|
|
248
|
+
const resultModel = Object.keys(sdkMsg.modelUsage || {})[0] || sessionModel;
|
|
249
|
+
|
|
250
|
+
if (resolvedSid) {
|
|
251
|
+
addCost(resolvedSid, costUsd, durationMs, numTurns, inputTokens, outputTokens, {
|
|
252
|
+
model: resultModel,
|
|
253
|
+
stopReason: sdkMsg.subtype,
|
|
254
|
+
isError: 0,
|
|
255
|
+
cacheReadTokens,
|
|
256
|
+
cacheCreationTokens,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
agentSend({
|
|
261
|
+
type: "result",
|
|
262
|
+
duration_ms: durationMs,
|
|
263
|
+
num_turns: numTurns,
|
|
264
|
+
cost_usd: costUsd,
|
|
265
|
+
totalCost: getTotalCost(),
|
|
266
|
+
input_tokens: inputTokens,
|
|
267
|
+
output_tokens: outputTokens,
|
|
268
|
+
cache_read_tokens: cacheReadTokens,
|
|
269
|
+
cache_creation_tokens: cacheCreationTokens,
|
|
270
|
+
model: resultModel,
|
|
271
|
+
stop_reason: sdkMsg.subtype,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
lastAgentMetrics = { durationMs, costUsd, inputTokens, outputTokens, model: resultModel, turns: numTurns, isError: false };
|
|
275
|
+
|
|
276
|
+
agentSend({
|
|
277
|
+
type: "agent_completed",
|
|
278
|
+
agentId,
|
|
279
|
+
totalTurns: numTurns,
|
|
280
|
+
durationMs,
|
|
281
|
+
costUsd,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Record completion for monitoring
|
|
285
|
+
try {
|
|
286
|
+
recordAgentRunComplete(monitorRunId, agentId, 'completed', numTurns, costUsd, durationMs, inputTokens, outputTokens);
|
|
287
|
+
} catch (e) { /* ignore */ }
|
|
288
|
+
|
|
289
|
+
// Store agent output as shared context for downstream agents
|
|
290
|
+
if (runId && lastAssistantText) {
|
|
291
|
+
const summary = lastAssistantText.length > 4000
|
|
292
|
+
? lastAssistantText.slice(0, 4000) + "\n\n[truncated]"
|
|
293
|
+
: lastAssistantText;
|
|
294
|
+
setAgentContext(runId, agentId, "output", summary);
|
|
295
|
+
}
|
|
296
|
+
} else if (sdkMsg.subtype?.startsWith("error")) {
|
|
297
|
+
const errMsg = sdkMsg.errors?.join(", ") || "Unknown error";
|
|
298
|
+
const costUsd = sdkMsg.total_cost_usd || 0;
|
|
299
|
+
const durationMs = sdkMsg.duration_ms || 0;
|
|
300
|
+
const numTurns = sdkMsg.num_turns || 0;
|
|
301
|
+
const inputTokens = sdkMsg.usage?.input_tokens || 0;
|
|
302
|
+
const outputTokens = sdkMsg.usage?.output_tokens || 0;
|
|
303
|
+
const cacheReadTokens = sdkMsg.usage?.cache_read_input_tokens || 0;
|
|
304
|
+
const cacheCreationTokens = sdkMsg.usage?.cache_creation_input_tokens || 0;
|
|
305
|
+
const resultModel = Object.keys(sdkMsg.modelUsage || {})[0] || sessionModel;
|
|
306
|
+
|
|
307
|
+
if (resolvedSid) {
|
|
308
|
+
addCost(resolvedSid, costUsd, durationMs, numTurns, inputTokens, outputTokens, {
|
|
309
|
+
model: resultModel,
|
|
310
|
+
stopReason: sdkMsg.subtype,
|
|
311
|
+
isError: 1,
|
|
312
|
+
cacheReadTokens,
|
|
313
|
+
cacheCreationTokens,
|
|
314
|
+
});
|
|
315
|
+
addMessage(resolvedSid, "error", JSON.stringify({ error: errMsg, subtype: sdkMsg.subtype }), null);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
lastAgentMetrics = { durationMs, costUsd, inputTokens, outputTokens, model: resultModel, turns: numTurns, isError: true, error: errMsg };
|
|
319
|
+
agentSend({ type: "error", error: errMsg });
|
|
320
|
+
agentSend({ type: "agent_error", agentId, error: errMsg, turn: turnCount });
|
|
321
|
+
|
|
322
|
+
// Record error for monitoring
|
|
323
|
+
try {
|
|
324
|
+
recordAgentRunComplete(monitorRunId, agentId, 'error', numTurns, costUsd, durationMs, inputTokens, outputTokens, errMsg);
|
|
325
|
+
} catch (e) { /* ignore */ }
|
|
326
|
+
}
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
} catch (err) {
|
|
331
|
+
if (err.name === "AbortError") {
|
|
332
|
+
agentSend({ type: "agent_aborted", agentId, turn: turnCount });
|
|
333
|
+
agentSend({ type: "aborted" });
|
|
334
|
+
try { recordAgentRunComplete(monitorRunId, agentId, 'aborted', turnCount, 0, 0, 0, 0, 'Aborted'); } catch (e) { /* ignore */ }
|
|
335
|
+
} else {
|
|
336
|
+
agentSend({ type: "agent_error", agentId, error: err.message, turn: turnCount });
|
|
337
|
+
agentSend({ type: "error", error: err.message });
|
|
338
|
+
try { recordAgentRunComplete(monitorRunId, agentId, 'error', turnCount, 0, 0, 0, 0, err.message); } catch (e) { /* ignore */ }
|
|
339
|
+
}
|
|
340
|
+
throw err; // Re-throw so callers (chains, DAGs) know the agent failed
|
|
341
|
+
} finally {
|
|
342
|
+
clearTimeout(timeoutHandle);
|
|
343
|
+
if (activeQueries) activeQueries.delete(queryKey);
|
|
344
|
+
agentSend({ type: "done" });
|
|
345
|
+
sendPushNotification("Claudeck", `Agent "${agentDef.title}" completed`, `agent-${resolvedSid}`);
|
|
346
|
+
|
|
347
|
+
// Rich Telegram notification — meaningful for AFK developer
|
|
348
|
+
const goalSnippet = agentDef.goal ? agentDef.goal.slice(0, 150).split("\n")[0] : "";
|
|
349
|
+
const outputSnippet = lastAssistantText
|
|
350
|
+
? lastAssistantText.slice(0, 300).replace(/\n{2,}/g, "\n")
|
|
351
|
+
: "";
|
|
352
|
+
|
|
353
|
+
if (lastAgentMetrics.isError) {
|
|
354
|
+
const errorBody = [
|
|
355
|
+
agentDef.title,
|
|
356
|
+
goalSnippet ? `Goal: ${goalSnippet}` : "",
|
|
357
|
+
`Error: ${lastAgentMetrics.error || "Unknown error"}`,
|
|
358
|
+
].filter(Boolean).join("\n");
|
|
359
|
+
sendTelegramNotification("error", "Agent Failed", errorBody, {
|
|
360
|
+
durationMs: lastAgentMetrics.durationMs,
|
|
361
|
+
costUsd: lastAgentMetrics.costUsd,
|
|
362
|
+
inputTokens: lastAgentMetrics.inputTokens,
|
|
363
|
+
outputTokens: lastAgentMetrics.outputTokens,
|
|
364
|
+
model: lastAgentMetrics.model,
|
|
365
|
+
turns: lastAgentMetrics.turns,
|
|
366
|
+
});
|
|
367
|
+
} else {
|
|
368
|
+
const body = [
|
|
369
|
+
agentDef.title,
|
|
370
|
+
goalSnippet ? `Goal: ${goalSnippet}` : "",
|
|
371
|
+
outputSnippet ? `\nResult: ${outputSnippet}` : "",
|
|
372
|
+
].filter(Boolean).join("\n");
|
|
373
|
+
sendTelegramNotification("agent", "Agent Completed", body, {
|
|
374
|
+
durationMs: lastAgentMetrics.durationMs,
|
|
375
|
+
costUsd: lastAgentMetrics.costUsd,
|
|
376
|
+
inputTokens: lastAgentMetrics.inputTokens,
|
|
377
|
+
outputTokens: lastAgentMetrics.outputTokens,
|
|
378
|
+
model: lastAgentMetrics.model,
|
|
379
|
+
turns: lastAgentMetrics.turns,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return { resolvedSid, claudeSessionId };
|
|
385
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DAG Executor — runs agents in dependency order with parallelism.
|
|
3
|
+
*
|
|
4
|
+
* Topologically sorts nodes, groups by level, and runs each level
|
|
5
|
+
* with Promise.all (max 3 concurrent agents). Shared context flows
|
|
6
|
+
* from completed nodes to dependents via the agent_context table.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { runAgent } from "./agent-loop.js";
|
|
10
|
+
import { sendPushNotification } from "./push-sender.js";
|
|
11
|
+
import { sendTelegramNotification } from "./telegram-sender.js";
|
|
12
|
+
|
|
13
|
+
const MAX_CONCURRENT = 3;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Topological sort — returns array of arrays (levels).
|
|
17
|
+
* Each level contains nodes whose dependencies are all in earlier levels.
|
|
18
|
+
*/
|
|
19
|
+
function topologicalLevels(nodes, edges) {
|
|
20
|
+
const inDegree = new Map();
|
|
21
|
+
const adj = new Map();
|
|
22
|
+
for (const n of nodes) {
|
|
23
|
+
inDegree.set(n.id, 0);
|
|
24
|
+
adj.set(n.id, []);
|
|
25
|
+
}
|
|
26
|
+
for (const e of edges) {
|
|
27
|
+
adj.get(e.from)?.push(e.to);
|
|
28
|
+
inDegree.set(e.to, (inDegree.get(e.to) || 0) + 1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const levels = [];
|
|
32
|
+
const remaining = new Set(nodes.map((n) => n.id));
|
|
33
|
+
|
|
34
|
+
while (remaining.size > 0) {
|
|
35
|
+
const level = [];
|
|
36
|
+
for (const id of remaining) {
|
|
37
|
+
if (inDegree.get(id) === 0) level.push(id);
|
|
38
|
+
}
|
|
39
|
+
if (level.length === 0) {
|
|
40
|
+
// Cycle detected — break to avoid infinite loop
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
levels.push(level);
|
|
44
|
+
for (const id of level) {
|
|
45
|
+
remaining.delete(id);
|
|
46
|
+
for (const next of adj.get(id) || []) {
|
|
47
|
+
inDegree.set(next, (inDegree.get(next) || 0) - 1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return levels;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Run agents in batches with a concurrency limit.
|
|
57
|
+
*/
|
|
58
|
+
async function runBatch(tasks, limit) {
|
|
59
|
+
const results = [];
|
|
60
|
+
for (let i = 0; i < tasks.length; i += limit) {
|
|
61
|
+
const batch = tasks.slice(i, i + limit);
|
|
62
|
+
const batchResults = await Promise.allSettled(batch.map((fn) => fn()));
|
|
63
|
+
results.push(...batchResults);
|
|
64
|
+
}
|
|
65
|
+
return results;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function runDag({
|
|
69
|
+
ws,
|
|
70
|
+
dag,
|
|
71
|
+
agents,
|
|
72
|
+
cwd,
|
|
73
|
+
sessionId: clientSid,
|
|
74
|
+
projectName,
|
|
75
|
+
permissionMode,
|
|
76
|
+
model,
|
|
77
|
+
sessionIds,
|
|
78
|
+
pendingApprovals,
|
|
79
|
+
makeCanUseTool,
|
|
80
|
+
activeQueries,
|
|
81
|
+
}) {
|
|
82
|
+
const runId = crypto.randomUUID();
|
|
83
|
+
|
|
84
|
+
function dagSend(payload) {
|
|
85
|
+
if (ws.readyState !== 1) return;
|
|
86
|
+
ws.send(JSON.stringify(payload));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const levels = topologicalLevels(dag.nodes, dag.edges);
|
|
90
|
+
const nodeMap = new Map(dag.nodes.map((n) => [n.id, n]));
|
|
91
|
+
|
|
92
|
+
dagSend({
|
|
93
|
+
type: "dag_started",
|
|
94
|
+
dagId: dag.id,
|
|
95
|
+
runId,
|
|
96
|
+
title: dag.title,
|
|
97
|
+
nodes: dag.nodes.map((n) => ({
|
|
98
|
+
id: n.id,
|
|
99
|
+
agentId: n.agentId,
|
|
100
|
+
title: agents.find((a) => a.id === n.agentId)?.title || n.agentId,
|
|
101
|
+
})),
|
|
102
|
+
edges: dag.edges,
|
|
103
|
+
levels: levels.map((l) => [...l]),
|
|
104
|
+
totalNodes: dag.nodes.length,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Telegram start notification
|
|
108
|
+
const nodeNames = dag.nodes.map((n) => {
|
|
109
|
+
const title = agents.find((a) => a.id === n.agentId)?.title || n.agentId;
|
|
110
|
+
return ` \u{2022} ${title}`;
|
|
111
|
+
}).join("\n");
|
|
112
|
+
sendTelegramNotification("start", "DAG Started", `${dag.title}\n\n${dag.nodes.length} nodes:\n${nodeNames}`);
|
|
113
|
+
|
|
114
|
+
let resolvedSid = clientSid;
|
|
115
|
+
const failedNodes = new Set();
|
|
116
|
+
let aborted = false;
|
|
117
|
+
// Track which nodes have dependents that failed
|
|
118
|
+
const edgeMap = new Map();
|
|
119
|
+
for (const e of dag.edges) {
|
|
120
|
+
if (!edgeMap.has(e.to)) edgeMap.set(e.to, []);
|
|
121
|
+
edgeMap.get(e.to).push(e.from);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (let li = 0; li < levels.length; li++) {
|
|
125
|
+
const level = levels[li];
|
|
126
|
+
if (ws.readyState !== 1 || aborted) break;
|
|
127
|
+
|
|
128
|
+
dagSend({
|
|
129
|
+
type: "dag_level",
|
|
130
|
+
dagId: dag.id,
|
|
131
|
+
levelIndex: li,
|
|
132
|
+
nodeIds: level,
|
|
133
|
+
status: "running",
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const tasks = level.map((nodeId) => {
|
|
137
|
+
return async () => {
|
|
138
|
+
const node = nodeMap.get(nodeId);
|
|
139
|
+
if (!node) return;
|
|
140
|
+
|
|
141
|
+
// Check if any dependency failed
|
|
142
|
+
const deps = edgeMap.get(nodeId) || [];
|
|
143
|
+
const failedDep = deps.find((d) => failedNodes.has(d));
|
|
144
|
+
if (failedDep) {
|
|
145
|
+
failedNodes.add(nodeId);
|
|
146
|
+
dagSend({
|
|
147
|
+
type: "dag_node",
|
|
148
|
+
dagId: dag.id,
|
|
149
|
+
nodeId,
|
|
150
|
+
agentId: node.agentId,
|
|
151
|
+
status: "skipped",
|
|
152
|
+
reason: `Dependency "${failedDep}" failed`,
|
|
153
|
+
});
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const agentDef = agents.find((a) => a.id === node.agentId);
|
|
158
|
+
if (!agentDef) {
|
|
159
|
+
failedNodes.add(nodeId);
|
|
160
|
+
dagSend({
|
|
161
|
+
type: "dag_node",
|
|
162
|
+
dagId: dag.id,
|
|
163
|
+
nodeId,
|
|
164
|
+
agentId: node.agentId,
|
|
165
|
+
status: "skipped",
|
|
166
|
+
reason: "Agent not found",
|
|
167
|
+
});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
dagSend({
|
|
172
|
+
type: "dag_node",
|
|
173
|
+
dagId: dag.id,
|
|
174
|
+
nodeId,
|
|
175
|
+
agentId: agentDef.id,
|
|
176
|
+
agentTitle: agentDef.title,
|
|
177
|
+
status: "running",
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const result = await runAgent({
|
|
182
|
+
ws,
|
|
183
|
+
agentDef,
|
|
184
|
+
cwd,
|
|
185
|
+
sessionId: resolvedSid,
|
|
186
|
+
projectName: projectName || `DAG: ${dag.title}`,
|
|
187
|
+
permissionMode,
|
|
188
|
+
model,
|
|
189
|
+
sessionIds,
|
|
190
|
+
pendingApprovals,
|
|
191
|
+
makeCanUseTool,
|
|
192
|
+
activeQueries,
|
|
193
|
+
runId,
|
|
194
|
+
runType: 'dag',
|
|
195
|
+
parentRunId: dag.id,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
if (result?.resolvedSid) resolvedSid = result.resolvedSid;
|
|
199
|
+
|
|
200
|
+
dagSend({
|
|
201
|
+
type: "dag_node",
|
|
202
|
+
dagId: dag.id,
|
|
203
|
+
nodeId,
|
|
204
|
+
agentId: agentDef.id,
|
|
205
|
+
agentTitle: agentDef.title,
|
|
206
|
+
status: "completed",
|
|
207
|
+
});
|
|
208
|
+
} catch (err) {
|
|
209
|
+
failedNodes.add(nodeId);
|
|
210
|
+
if (err.name === "AbortError") aborted = true;
|
|
211
|
+
dagSend({
|
|
212
|
+
type: "dag_node",
|
|
213
|
+
dagId: dag.id,
|
|
214
|
+
nodeId,
|
|
215
|
+
agentId: agentDef.id,
|
|
216
|
+
agentTitle: agentDef.title,
|
|
217
|
+
status: err.name === "AbortError" ? "aborted" : "error",
|
|
218
|
+
error: err.message,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
await runBatch(tasks, MAX_CONCURRENT);
|
|
225
|
+
|
|
226
|
+
dagSend({
|
|
227
|
+
type: "dag_level",
|
|
228
|
+
dagId: dag.id,
|
|
229
|
+
levelIndex: li,
|
|
230
|
+
nodeIds: level,
|
|
231
|
+
status: "completed",
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
dagSend({
|
|
236
|
+
type: "dag_completed",
|
|
237
|
+
dagId: dag.id,
|
|
238
|
+
runId,
|
|
239
|
+
totalNodes: dag.nodes.length,
|
|
240
|
+
succeeded: dag.nodes.length - failedNodes.size,
|
|
241
|
+
failed: failedNodes.size,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
sendPushNotification(
|
|
245
|
+
"Claudeck",
|
|
246
|
+
`DAG "${dag.title}" completed`,
|
|
247
|
+
`dag-${resolvedSid}`,
|
|
248
|
+
);
|
|
249
|
+
const nodeStatus = dag.nodes.map((n) => {
|
|
250
|
+
const title = agents.find((a) => a.id === n.agentId)?.title || n.agentId;
|
|
251
|
+
const icon = failedNodes.has(n.id) ? "\u{274C}" : "\u{2705}";
|
|
252
|
+
return ` ${icon} ${title}`;
|
|
253
|
+
}).join("\n");
|
|
254
|
+
const dagEventType = failedNodes.size > 0 ? "error" : "dag";
|
|
255
|
+
const dagLabel = failedNodes.size > 0 ? "DAG Completed with Failures" : "DAG Completed";
|
|
256
|
+
sendTelegramNotification(
|
|
257
|
+
dagEventType,
|
|
258
|
+
dagLabel,
|
|
259
|
+
`${dag.title}\n\n${nodeStatus}`,
|
|
260
|
+
{
|
|
261
|
+
succeeded: dag.nodes.length - failedNodes.size,
|
|
262
|
+
failed: failedNodes.size,
|
|
263
|
+
},
|
|
264
|
+
);
|
|
265
|
+
}
|