@kendoo.agentdesk/agentdesk 0.7.3 → 0.8.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/bin/agentdesk.mjs +6 -2
- package/cli/agent-prompts.mjs +161 -0
- package/cli/agent-runner.mjs +148 -0
- package/cli/daemon.mjs +22 -97
- package/cli/orchestrator.mjs +280 -0
- package/cli/team.mjs +50 -37
- package/package.json +1 -1
package/bin/agentdesk.mjs
CHANGED
|
@@ -65,6 +65,7 @@ if (!command || command === "help" || command === "--help") {
|
|
|
65
65
|
Options:
|
|
66
66
|
--description, -d Task description or requirements
|
|
67
67
|
--cwd Working directory (defaults to current)
|
|
68
|
+
--legacy Use single-process mode (cheaper, less independent)
|
|
68
69
|
|
|
69
70
|
How it works:
|
|
70
71
|
With a tracker (Linear, Jira, GitHub Issues):
|
|
@@ -100,10 +101,11 @@ else if (command === "init") {
|
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
else if (command === "team") {
|
|
103
|
-
// Parse options first to find -d
|
|
104
|
+
// Parse options first to find -d, --cwd, --legacy
|
|
104
105
|
let description = "";
|
|
105
106
|
let cwd = process.cwd();
|
|
106
107
|
let taskId = null;
|
|
108
|
+
let legacy = false;
|
|
107
109
|
const remaining = args.slice(1);
|
|
108
110
|
|
|
109
111
|
for (let i = 0; i < remaining.length; i++) {
|
|
@@ -111,6 +113,8 @@ else if (command === "team") {
|
|
|
111
113
|
description = remaining[++i];
|
|
112
114
|
} else if (remaining[i] === "--cwd" && remaining[i + 1]) {
|
|
113
115
|
cwd = remaining[++i];
|
|
116
|
+
} else if (remaining[i] === "--legacy") {
|
|
117
|
+
legacy = true;
|
|
114
118
|
} else if (!taskId && !remaining[i].startsWith("-")) {
|
|
115
119
|
taskId = remaining[i];
|
|
116
120
|
}
|
|
@@ -123,7 +127,7 @@ else if (command === "team") {
|
|
|
123
127
|
}
|
|
124
128
|
|
|
125
129
|
const { runTeam } = await import("../cli/team.mjs");
|
|
126
|
-
const code = await runTeam(taskId, { description, cwd });
|
|
130
|
+
const code = await runTeam(taskId, { description, cwd, legacy });
|
|
127
131
|
process.exit(code);
|
|
128
132
|
}
|
|
129
133
|
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// Per-agent prompt generator for sub-agent architecture
|
|
2
|
+
|
|
3
|
+
import { generateContext } from "./detect.mjs";
|
|
4
|
+
|
|
5
|
+
const PHASE_INSTRUCTIONS = {
|
|
6
|
+
intake: {
|
|
7
|
+
Jane: `You are leading the intake for this task. Your job:
|
|
8
|
+
1. Understand the task requirements from the description and/or tracker.
|
|
9
|
+
2. Check for existing branches: \`git branch -a | grep <task-id>\`
|
|
10
|
+
3. Check for existing PRs: \`gh pr list --search <task-id> --json number,title,state\`
|
|
11
|
+
4. Explore the codebase briefly to understand what we're working with.
|
|
12
|
+
5. Summarize: what needs to be done, what already exists, and what the starting point is.
|
|
13
|
+
6. Recommend which phase to start from (BRAINSTORM for new work, EXECUTION if branch exists).`,
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
brainstorm: {
|
|
17
|
+
_default: (agent) => `Share your perspective on this task from your role as ${agent.role}.
|
|
18
|
+
Focus on: ${agent.brainstorm?.focus || "your area of expertise"}.
|
|
19
|
+
Use [${agent.brainstorm?.tag || "SAY"}] tag.
|
|
20
|
+
Be specific. If you disagree with anything in the conversation so far, use [ARGUE] and explain why.
|
|
21
|
+
If you agree, still add value — don't just say "I agree."`,
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
planning: {
|
|
25
|
+
_default: (agent) => `Present your plan for this task from your role as ${agent.role}.
|
|
26
|
+
Your expected output: ${agent.planning || "Your role-specific plan."}
|
|
27
|
+
Be concrete — name files, approaches, specific concerns.`,
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
"planning-review": {
|
|
31
|
+
_default: () => `Review all the plans presented by the team.
|
|
32
|
+
If you see problems, conflicts, or missing considerations, use [ARGUE] and explain.
|
|
33
|
+
If everything looks good, acknowledge with [AGREE] and note why.`,
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
execution: {
|
|
37
|
+
_default: (agent) => {
|
|
38
|
+
if (!agent.execution) return "This task has no execution step for your role. Skip this phase.";
|
|
39
|
+
return `Complete your execution tasks:
|
|
40
|
+
${agent.execution.tasks.map((t, i) => `${i + 1}. ${t}`).join("\n")}
|
|
41
|
+
|
|
42
|
+
Work carefully. Use tools as needed. Narrate what you're doing.`;
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
"execution-review": {
|
|
47
|
+
_default: (agent) => `Review the changes made so far.
|
|
48
|
+
From your perspective as ${agent.role}:
|
|
49
|
+
${agent.groundRules || "Review for issues in your area of expertise."}
|
|
50
|
+
If you find issues, describe them specifically with file paths and line numbers.
|
|
51
|
+
If everything looks good, confirm with [AGREE].`,
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
review: {
|
|
55
|
+
_default: (agent) => `The session is wrapping up. Provide your final notes:
|
|
56
|
+
- Any remaining concerns?
|
|
57
|
+
- Anything the team should watch out for?
|
|
58
|
+
- Overall assessment from your perspective as ${agent.role}.
|
|
59
|
+
Keep it brief.`,
|
|
60
|
+
Jane: `The session is wrapping up. Provide the final summary:
|
|
61
|
+
1. What was accomplished
|
|
62
|
+
2. Any remaining concerns from the team
|
|
63
|
+
3. What the next steps should be
|
|
64
|
+
Keep it concise.`,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export function buildAgentPrompt(agent, { phase, task, project, conversation, inboxUrl, sessionUrl }) {
|
|
69
|
+
const parts = [];
|
|
70
|
+
|
|
71
|
+
// Identity
|
|
72
|
+
parts.push(`You are ${agent.name}, ${agent.role} on a software development team.`);
|
|
73
|
+
parts.push("");
|
|
74
|
+
|
|
75
|
+
// Role
|
|
76
|
+
parts.push("## Your Role");
|
|
77
|
+
parts.push(agent.description);
|
|
78
|
+
if (agent.groundRules) parts.push(`\nGround rules: ${agent.groundRules}`);
|
|
79
|
+
if (agent.codePrinciple) parts.push(`\nCode principle: ${agent.codePrinciple}`);
|
|
80
|
+
parts.push("");
|
|
81
|
+
|
|
82
|
+
// Communication
|
|
83
|
+
parts.push("## Communication");
|
|
84
|
+
parts.push(`Prefix your output with your badge: ${agent.badge}`);
|
|
85
|
+
parts.push("Use these tags in your messages:");
|
|
86
|
+
parts.push("- [SAY] — Normal statements, announcements, questions");
|
|
87
|
+
parts.push("- [THINK] — Internal reasoning, analysis");
|
|
88
|
+
parts.push("- [ACT] — When using tools or taking actions");
|
|
89
|
+
parts.push("- [ARGUE] — When you disagree or see problems");
|
|
90
|
+
parts.push("- [AGREE] — When you agree (but still add value)");
|
|
91
|
+
parts.push("");
|
|
92
|
+
|
|
93
|
+
// Task
|
|
94
|
+
parts.push("## Task");
|
|
95
|
+
parts.push(`Task ID: ${task.id}`);
|
|
96
|
+
if (task.link) parts.push(`Task link: ${task.link}`);
|
|
97
|
+
if (task.description) parts.push(`\nDescription: ${task.description}`);
|
|
98
|
+
parts.push("");
|
|
99
|
+
|
|
100
|
+
// Project context
|
|
101
|
+
if (project) {
|
|
102
|
+
const context = generateContext(project);
|
|
103
|
+
parts.push("## Project Context");
|
|
104
|
+
parts.push(context);
|
|
105
|
+
parts.push("");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Conversation so far
|
|
109
|
+
if (conversation && conversation.length > 0) {
|
|
110
|
+
parts.push("## Conversation So Far");
|
|
111
|
+
for (const msg of conversation) {
|
|
112
|
+
if (msg.type === "phase") {
|
|
113
|
+
parts.push(`\n--- ${msg.phase} ---\n`);
|
|
114
|
+
} else if (msg.agent && msg.message) {
|
|
115
|
+
parts.push(`${msg.agent} [${msg.tag || "SAY"}]: ${msg.message}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
parts.push("");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Phase instructions
|
|
122
|
+
parts.push(`## Current Phase: ${phase.toUpperCase()}`);
|
|
123
|
+
const phaseConfig = PHASE_INSTRUCTIONS[phase] || {};
|
|
124
|
+
const agentInstr = phaseConfig[agent.name] || (phaseConfig._default ? phaseConfig._default(agent) : "Contribute from your area of expertise.");
|
|
125
|
+
parts.push(agentInstr);
|
|
126
|
+
parts.push("");
|
|
127
|
+
|
|
128
|
+
// Inbox (Jane only)
|
|
129
|
+
if (agent.name === "Jane" && inboxUrl) {
|
|
130
|
+
parts.push("## User Messages");
|
|
131
|
+
parts.push(`Before starting, check for user messages: \`curl -s ${inboxUrl}\``);
|
|
132
|
+
parts.push("If the response is not empty ([]), read the messages and incorporate the user's input.");
|
|
133
|
+
parts.push("");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Session URL
|
|
137
|
+
if (sessionUrl) {
|
|
138
|
+
parts.push(`Session: ${sessionUrl}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Time
|
|
142
|
+
const now = new Date();
|
|
143
|
+
parts.push(`\nCurrent date/time: ${now.toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" })} ${now.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" })}`);
|
|
144
|
+
|
|
145
|
+
return parts.join("\n");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Get tool list for an agent
|
|
149
|
+
const AGENT_TOOLS = {
|
|
150
|
+
Jane: ["Bash", "Read"],
|
|
151
|
+
Dennis: ["Bash", "Read", "Edit", "Write", "Glob", "Grep"],
|
|
152
|
+
Sam: ["Read", "Glob", "Grep"],
|
|
153
|
+
Bart: ["Bash", "Read", "Glob", "Grep"],
|
|
154
|
+
Vera: ["Bash", "Read", "Edit", "Write", "Glob", "Grep"],
|
|
155
|
+
Luna: ["Read", "Glob"],
|
|
156
|
+
Mark: ["Read", "Glob"],
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export function getAgentTools(agentName) {
|
|
160
|
+
return AGENT_TOOLS[agentName] || ["Bash", "Read", "Edit", "Write", "Glob", "Grep"];
|
|
161
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Spawns a single Claude process for one agent and parses its output
|
|
2
|
+
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import { createInterface } from "readline";
|
|
5
|
+
|
|
6
|
+
export async function runAgent(agentName, {
|
|
7
|
+
prompt,
|
|
8
|
+
allowedTools = ["Bash", "Read", "Edit", "Write", "Glob", "Grep"],
|
|
9
|
+
cwd,
|
|
10
|
+
env,
|
|
11
|
+
onMessage,
|
|
12
|
+
onToolUse,
|
|
13
|
+
onToolResult,
|
|
14
|
+
}) {
|
|
15
|
+
const messages = [];
|
|
16
|
+
const toolUses = [];
|
|
17
|
+
let rawOutput = "";
|
|
18
|
+
let inputTokens = 0;
|
|
19
|
+
let outputTokens = 0;
|
|
20
|
+
let steps = 0;
|
|
21
|
+
let hadArgue = false;
|
|
22
|
+
|
|
23
|
+
const child = spawn(
|
|
24
|
+
"claude",
|
|
25
|
+
[
|
|
26
|
+
"-p", prompt,
|
|
27
|
+
"--allowedTools", allowedTools.join(","),
|
|
28
|
+
"--verbose",
|
|
29
|
+
"--output-format", "stream-json",
|
|
30
|
+
],
|
|
31
|
+
{
|
|
32
|
+
stdio: ["pipe", "pipe", "inherit"],
|
|
33
|
+
shell: false,
|
|
34
|
+
env: env || process.env,
|
|
35
|
+
cwd,
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
child.stdin.end();
|
|
40
|
+
|
|
41
|
+
const rl = createInterface({ input: child.stdout });
|
|
42
|
+
|
|
43
|
+
for await (const line of rl) {
|
|
44
|
+
try {
|
|
45
|
+
const event = JSON.parse(line);
|
|
46
|
+
|
|
47
|
+
// Track tokens
|
|
48
|
+
if (event.usage) {
|
|
49
|
+
if (event.usage.input_tokens) inputTokens = Math.max(inputTokens, event.usage.input_tokens);
|
|
50
|
+
if (event.usage.output_tokens) outputTokens += (event.usage.output_tokens_delta || 0);
|
|
51
|
+
}
|
|
52
|
+
if (event.message?.usage) {
|
|
53
|
+
if (event.message.usage.input_tokens) inputTokens = Math.max(inputTokens, event.message.usage.input_tokens);
|
|
54
|
+
if (event.message.usage.output_tokens) outputTokens = Math.max(outputTokens, event.message.usage.output_tokens);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Text output
|
|
58
|
+
if (event.type === "assistant" && event.message?.content) {
|
|
59
|
+
for (const block of event.message.content) {
|
|
60
|
+
if (block.type === "text" && block.text.trim()) {
|
|
61
|
+
const text = block.text.trim();
|
|
62
|
+
rawOutput += text + "\n";
|
|
63
|
+
|
|
64
|
+
// Detect tags
|
|
65
|
+
let tag = "SAY";
|
|
66
|
+
if (/\[ARGUE\]/i.test(text)) { tag = "ARGUE"; hadArgue = true; }
|
|
67
|
+
else if (/\[AGREE\]/i.test(text)) tag = "AGREE";
|
|
68
|
+
else if (/\[THINK\]/i.test(text)) tag = "THINK";
|
|
69
|
+
else if (/\[ACT\]/i.test(text)) tag = "ACT";
|
|
70
|
+
|
|
71
|
+
const cleanMsg = text.replace(/\[(SAY|ACT|THINK|AGREE|ARGUE)\]\s*/gi, "").replace(/\*+/g, "");
|
|
72
|
+
const msg = { agent: agentName, tag, message: cleanMsg };
|
|
73
|
+
messages.push(msg);
|
|
74
|
+
onMessage?.(msg);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Tool use
|
|
80
|
+
if (event.type === "tool_use") {
|
|
81
|
+
const name = event.name || event.tool_name;
|
|
82
|
+
steps++;
|
|
83
|
+
|
|
84
|
+
let description = "Running command...";
|
|
85
|
+
if (name === "Bash") {
|
|
86
|
+
const cmd = event.input?.command || "";
|
|
87
|
+
if (cmd.includes("curl") && cmd.includes("linear")) description = "Calling Linear API...";
|
|
88
|
+
else if (cmd.includes("curl")) description = "Making API request...";
|
|
89
|
+
else {
|
|
90
|
+
const shortCmd = cmd.length > 80 ? cmd.slice(0, 80) + "..." : cmd;
|
|
91
|
+
description = `$ ${shortCmd}`;
|
|
92
|
+
}
|
|
93
|
+
} else if (name === "Read") {
|
|
94
|
+
const shortPath = (event.input?.file_path || "").split("/").slice(-3).join("/");
|
|
95
|
+
description = `Reading ${shortPath}`;
|
|
96
|
+
} else if (name === "Edit" || name === "Write") {
|
|
97
|
+
const shortPath = (event.input?.file_path || "").split("/").slice(-3).join("/");
|
|
98
|
+
description = `${name === "Edit" ? "Editing" : "Writing"} ${shortPath}`;
|
|
99
|
+
} else if (name === "Glob" || name === "Grep") {
|
|
100
|
+
description = `Searching ${event.input?.pattern || ""}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const use = { agent: agentName, tool: name, description };
|
|
104
|
+
toolUses.push(use);
|
|
105
|
+
onToolUse?.(use);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Tool result
|
|
109
|
+
if (event.type === "tool_result") {
|
|
110
|
+
const output = event.content || event.output;
|
|
111
|
+
let text = "";
|
|
112
|
+
if (typeof output === "string") text = output.trim();
|
|
113
|
+
else if (Array.isArray(output)) {
|
|
114
|
+
text = output.filter(b => b.type === "text").map(b => b.text.trim()).join("\n");
|
|
115
|
+
}
|
|
116
|
+
const hasError = text && (text.toLowerCase().includes("error") || text.toLowerCase().includes("failed"));
|
|
117
|
+
const summary = text?.length > 300 ? `Done (${text.length} chars)` : text || "Done";
|
|
118
|
+
onToolResult?.({ success: !hasError, summary });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Final result
|
|
122
|
+
if (event.type === "result") {
|
|
123
|
+
if (event.usage) {
|
|
124
|
+
if (event.usage.input_tokens) inputTokens = Math.max(inputTokens, event.usage.input_tokens);
|
|
125
|
+
if (event.usage.output_tokens) outputTokens = Math.max(outputTokens, event.usage.output_tokens);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
// skip non-JSON
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const exitCode = await new Promise(resolve => {
|
|
134
|
+
child.on("close", (code) => resolve(code || 0));
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
agent: agentName,
|
|
139
|
+
messages,
|
|
140
|
+
toolUses,
|
|
141
|
+
rawOutput,
|
|
142
|
+
inputTokens,
|
|
143
|
+
outputTokens,
|
|
144
|
+
steps,
|
|
145
|
+
hadArgue,
|
|
146
|
+
exitCode,
|
|
147
|
+
};
|
|
148
|
+
}
|
package/cli/daemon.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import { getStoredApiKey } from "./login.mjs";
|
|
|
12
12
|
import { resolveTeam, generateTeamPrompt } from "./agents.mjs";
|
|
13
13
|
import { buildPrompt } from "./prompt.mjs";
|
|
14
14
|
import { createStreamParser } from "./stream-parser.mjs";
|
|
15
|
+
import { runOrchestrator } from "./orchestrator.mjs";
|
|
15
16
|
import { getRegisteredProjects, registerLocalProject } from "./projects.mjs";
|
|
16
17
|
|
|
17
18
|
const CONFIG_DIR = join(process.env.HOME || process.env.USERPROFILE, ".agentdesk");
|
|
@@ -236,7 +237,8 @@ export async function runDaemon() {
|
|
|
236
237
|
ws = new WebSocket(DAEMON_URL);
|
|
237
238
|
|
|
238
239
|
ws.on("open", () => {
|
|
239
|
-
send(
|
|
240
|
+
// Send auth directly — send() requires connected=true which isn't set yet
|
|
241
|
+
ws.send(JSON.stringify({ type: "auth", apiKey }));
|
|
240
242
|
});
|
|
241
243
|
|
|
242
244
|
ws.on("message", (raw) => {
|
|
@@ -358,113 +360,36 @@ export async function runDaemon() {
|
|
|
358
360
|
const inboxUrl = `${agentdeskServer}/api/sessions/${sessionId}/inbox`;
|
|
359
361
|
const sessionUrl = `${agentdeskServer}/sessions/${sessionId}`;
|
|
360
362
|
|
|
361
|
-
//
|
|
362
|
-
const
|
|
363
|
+
// Run orchestrator (sub-agent mode)
|
|
364
|
+
const result = await runOrchestrator({
|
|
363
365
|
taskId, taskLink,
|
|
364
366
|
description: prompt || "",
|
|
365
367
|
createTask: false,
|
|
366
368
|
tracker, config,
|
|
367
|
-
project: detected,
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
title: prompt || taskId,
|
|
376
|
-
project: project.name,
|
|
377
|
-
sessionNumber: 1,
|
|
378
|
-
agents: teamSections.names,
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
sendBuffered(sessionId, { type: "phase:change", phase: "INTAKE" });
|
|
382
|
-
|
|
383
|
-
// Spawn Claude — NEVER uses --dangerously-skip-permissions
|
|
384
|
-
const child = spawn(
|
|
385
|
-
"claude",
|
|
386
|
-
[
|
|
387
|
-
"-p", fullPrompt,
|
|
388
|
-
"--allowedTools", "Bash,Read,Edit,Write,Glob,Grep",
|
|
389
|
-
"--verbose",
|
|
390
|
-
"--output-format", "stream-json",
|
|
391
|
-
],
|
|
392
|
-
{
|
|
393
|
-
stdio: ["pipe", "pipe", "inherit"],
|
|
394
|
-
shell: false,
|
|
395
|
-
env: { ...process.env, ...loadDotEnv(project.path) },
|
|
396
|
-
cwd: project.path,
|
|
397
|
-
}
|
|
398
|
-
);
|
|
399
|
-
|
|
400
|
-
// Close stdin so claude doesn't wait for input
|
|
401
|
-
child.stdin.end();
|
|
402
|
-
|
|
403
|
-
activeSession.child = child;
|
|
404
|
-
|
|
405
|
-
// Parse stream
|
|
406
|
-
const { parseLine } = createStreamParser({
|
|
407
|
-
teamNames: teamSections.names,
|
|
408
|
-
callbacks: {
|
|
409
|
-
onPhaseChange({ phase }) {
|
|
410
|
-
sendBuffered(sessionId, { type: "phase:change", phase });
|
|
411
|
-
},
|
|
412
|
-
onAgentMessage({ agent, tag, message }) {
|
|
413
|
-
sendBuffered(sessionId, { type: "agent:message", agent, tag, message });
|
|
414
|
-
},
|
|
415
|
-
onToolUse({ agent, tool, description }) {
|
|
416
|
-
sendBuffered(sessionId, { type: "tool:use", agent, tool, description });
|
|
417
|
-
// Track file paths for metadata logging
|
|
418
|
-
const pathMatch = description.match(/(?:Reading|Editing|Writing)\s+(.+)/);
|
|
369
|
+
project: detected, team, teamSections,
|
|
370
|
+
inboxUrl, sessionUrl,
|
|
371
|
+
cwd: project.path,
|
|
372
|
+
onEvent(event) {
|
|
373
|
+
sendBuffered(sessionId, event);
|
|
374
|
+
// Track file paths for metadata logging
|
|
375
|
+
if (event.type === "tool:use" && event.description) {
|
|
376
|
+
const pathMatch = event.description.match(/(?:Reading|Editing|Writing)\s+(.+)/);
|
|
419
377
|
if (pathMatch) filePathsTouched.add(pathMatch[1]);
|
|
420
|
-
}
|
|
421
|
-
onToolResult({ success, summary }) {
|
|
422
|
-
sendBuffered(sessionId, { type: "tool:result", success, summary });
|
|
423
|
-
},
|
|
424
|
-
onSessionUpdate({ taskId: newTaskId }) {
|
|
425
|
-
sendBuffered(sessionId, { type: "session:update", taskId: newTaskId });
|
|
426
|
-
},
|
|
427
|
-
onSessionEnd({ duration, steps, inputTokens, outputTokens }) {
|
|
428
|
-
sendBuffered(sessionId, { type: "session:end", duration, steps, inputTokens, outputTokens });
|
|
429
|
-
console.log(` ${green}Session complete${reset} ${dim}${sessionId}${reset} (${duration}, ${steps} steps)`);
|
|
430
|
-
|
|
431
|
-
// Log metadata
|
|
432
|
-
logSessionMetadata(sessionId, {
|
|
433
|
-
sessionId, projectId,
|
|
434
|
-
startedAt, endedAt: Date.now(),
|
|
435
|
-
duration, exitCode: 0, steps,
|
|
436
|
-
filePathsTouched: [...filePathsTouched],
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
activeSession = null;
|
|
440
|
-
},
|
|
378
|
+
}
|
|
441
379
|
},
|
|
442
380
|
});
|
|
443
381
|
|
|
444
|
-
|
|
445
|
-
for await (const line of rl) {
|
|
446
|
-
parseLine(line);
|
|
447
|
-
}
|
|
382
|
+
console.log(` ${green}Session complete${reset} ${dim}${sessionId}${reset} (${result.duration}, ${result.steps} steps)`);
|
|
448
383
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
sendBuffered(sessionId, { type: "session:end", duration, steps: 0, inputTokens: 0, outputTokens: 0 });
|
|
456
|
-
|
|
457
|
-
logSessionMetadata(sessionId, {
|
|
458
|
-
sessionId, projectId,
|
|
459
|
-
startedAt, endedAt: Date.now(),
|
|
460
|
-
duration, exitCode: code || 0, steps: 0,
|
|
461
|
-
filePathsTouched: [...filePathsTouched],
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
activeSession = null;
|
|
465
|
-
}
|
|
384
|
+
logSessionMetadata(sessionId, {
|
|
385
|
+
sessionId, projectId,
|
|
386
|
+
startedAt, endedAt: Date.now(),
|
|
387
|
+
duration: result.duration, exitCode: 0, steps: result.steps,
|
|
388
|
+
filePathsTouched: [...filePathsTouched],
|
|
466
389
|
});
|
|
467
390
|
|
|
391
|
+
activeSession = null;
|
|
392
|
+
|
|
468
393
|
} catch (err) {
|
|
469
394
|
console.log(` ${red}Failed to start session:${reset} ${err.message}`);
|
|
470
395
|
// Send generic error to server — don't leak internal details (paths, config, etc.)
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
// Orchestrator — manages agent sub-sessions across phases
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { runAgent } from "./agent-runner.mjs";
|
|
6
|
+
import { buildAgentPrompt, getAgentTools } from "./agent-prompts.mjs";
|
|
7
|
+
import { detectProject, generateContext } from "./detect.mjs";
|
|
8
|
+
|
|
9
|
+
function loadDotEnv(dir) {
|
|
10
|
+
const envPath = join(dir, ".env");
|
|
11
|
+
if (!existsSync(envPath)) return {};
|
|
12
|
+
const vars = {};
|
|
13
|
+
for (const line of readFileSync(envPath, "utf-8").split("\n")) {
|
|
14
|
+
const trimmed = line.trim();
|
|
15
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
16
|
+
const eq = trimmed.indexOf("=");
|
|
17
|
+
if (eq === -1) continue;
|
|
18
|
+
vars[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
|
|
19
|
+
}
|
|
20
|
+
return vars;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function timestamp() {
|
|
24
|
+
const d = new Date();
|
|
25
|
+
return [d.getHours(), d.getMinutes(), d.getSeconds()]
|
|
26
|
+
.map(n => String(n).padStart(2, "0")).join(":");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function runOrchestrator({
|
|
30
|
+
taskId, taskLink, description, createTask, tracker, config,
|
|
31
|
+
project, team, teamSections, inboxUrl, sessionUrl, cwd,
|
|
32
|
+
onEvent,
|
|
33
|
+
}) {
|
|
34
|
+
const state = {
|
|
35
|
+
task: { id: taskId, link: taskLink, description: description || "" },
|
|
36
|
+
conversationLog: [], // { type: "message"|"phase", agent?, tag?, message?, phase? }
|
|
37
|
+
phaseSummaries: {},
|
|
38
|
+
totalInputTokens: 0,
|
|
39
|
+
totalOutputTokens: 0,
|
|
40
|
+
totalSteps: 0,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const env = { ...process.env, ...loadDotEnv(cwd) };
|
|
44
|
+
|
|
45
|
+
// Emit event to WebSocket (same protocol as single-process mode)
|
|
46
|
+
function emit(event) {
|
|
47
|
+
onEvent?.({ ...event, timestamp: timestamp() });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Add to conversation log
|
|
51
|
+
function logMessage(agent, tag, message) {
|
|
52
|
+
state.conversationLog.push({ type: "message", agent, tag, message });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function logPhase(phase) {
|
|
56
|
+
state.conversationLog.push({ type: "phase", phase });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Run a single agent and collect results
|
|
60
|
+
async function spawnAgent(agentName, agent, phase, extraConversation) {
|
|
61
|
+
const conversation = extraConversation || state.conversationLog;
|
|
62
|
+
|
|
63
|
+
const prompt = buildAgentPrompt(agent, {
|
|
64
|
+
phase,
|
|
65
|
+
task: state.task,
|
|
66
|
+
project,
|
|
67
|
+
conversation,
|
|
68
|
+
inboxUrl,
|
|
69
|
+
sessionUrl,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const tools = getAgentTools(agentName);
|
|
73
|
+
|
|
74
|
+
const result = await runAgent(agentName, {
|
|
75
|
+
prompt,
|
|
76
|
+
allowedTools: tools,
|
|
77
|
+
cwd,
|
|
78
|
+
env,
|
|
79
|
+
onMessage(msg) {
|
|
80
|
+
emit({ type: "agent:message", agent: msg.agent, tag: msg.tag, message: msg.message });
|
|
81
|
+
logMessage(msg.agent, msg.tag, msg.message);
|
|
82
|
+
},
|
|
83
|
+
onToolUse(use) {
|
|
84
|
+
emit({ type: "tool:use", agent: use.agent, tool: use.tool, description: use.description });
|
|
85
|
+
},
|
|
86
|
+
onToolResult(res) {
|
|
87
|
+
emit({ type: "tool:result", success: res.success, summary: res.summary });
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
state.totalInputTokens += result.inputTokens;
|
|
92
|
+
state.totalOutputTokens += result.outputTokens;
|
|
93
|
+
state.totalSteps += result.steps;
|
|
94
|
+
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Run multiple agents in parallel
|
|
99
|
+
async function spawnAgentsParallel(agents, phase, extraConversation) {
|
|
100
|
+
const conversation = extraConversation || [...state.conversationLog];
|
|
101
|
+
return Promise.all(
|
|
102
|
+
agents.map(([name, agent]) => spawnAgent(name, agent, phase, conversation))
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Build summary from agent results
|
|
107
|
+
function summarizeResults(results) {
|
|
108
|
+
return results
|
|
109
|
+
.filter(r => r.messages.length > 0)
|
|
110
|
+
.map(r => r.messages.map(m => `${m.agent} [${m.tag}]: ${m.message}`).join("\n"))
|
|
111
|
+
.join("\n\n");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Detect if any agent argued
|
|
115
|
+
function hasDisagreement(results) {
|
|
116
|
+
return results.some(r => r.hadArgue);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// --- Session start ---
|
|
120
|
+
const startTime = Date.now();
|
|
121
|
+
emit({
|
|
122
|
+
type: "session:start",
|
|
123
|
+
taskId, taskLink,
|
|
124
|
+
title: description || taskId,
|
|
125
|
+
project: project?.name || null,
|
|
126
|
+
sessionNumber: 1,
|
|
127
|
+
agents: teamSections.names,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Build agent lookup
|
|
131
|
+
const agentMap = new Map();
|
|
132
|
+
for (const agent of team) {
|
|
133
|
+
agentMap.set(agent.name, agent);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Find specific agents
|
|
137
|
+
const jane = agentMap.get("Jane");
|
|
138
|
+
const dennis = agentMap.get("Dennis");
|
|
139
|
+
|
|
140
|
+
// ===========================
|
|
141
|
+
// PHASE 1: INTAKE (Jane only)
|
|
142
|
+
// ===========================
|
|
143
|
+
emit({ type: "phase:change", phase: "INTAKE" });
|
|
144
|
+
logPhase("INTAKE");
|
|
145
|
+
|
|
146
|
+
const intakeResult = await spawnAgent("Jane", jane, "intake");
|
|
147
|
+
state.phaseSummaries.intake = summarizeResults([intakeResult]);
|
|
148
|
+
|
|
149
|
+
// ===========================
|
|
150
|
+
// PHASE 2: BRAINSTORM (parallel, 3-5 rounds)
|
|
151
|
+
// ===========================
|
|
152
|
+
emit({ type: "phase:change", phase: "BRAINSTORM" });
|
|
153
|
+
logPhase("BRAINSTORM");
|
|
154
|
+
|
|
155
|
+
const allAgents = [...agentMap.entries()];
|
|
156
|
+
let brainstormRounds = 0;
|
|
157
|
+
const MAX_BRAINSTORM_ROUNDS = 5;
|
|
158
|
+
|
|
159
|
+
for (let round = 0; round < MAX_BRAINSTORM_ROUNDS; round++) {
|
|
160
|
+
brainstormRounds++;
|
|
161
|
+
const results = await spawnAgentsParallel(allAgents, "brainstorm");
|
|
162
|
+
|
|
163
|
+
// Check for consensus (no ARGUE tags = consensus)
|
|
164
|
+
if (!hasDisagreement(results) && round >= 2) {
|
|
165
|
+
break; // Minimum 3 rounds, then stop on consensus
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
state.phaseSummaries.brainstorm = `Brainstorm completed in ${brainstormRounds} rounds.`;
|
|
169
|
+
|
|
170
|
+
// ===========================
|
|
171
|
+
// PHASE 3: PLANNING (parallel + review)
|
|
172
|
+
// ===========================
|
|
173
|
+
emit({ type: "phase:change", phase: "PLANNING" });
|
|
174
|
+
logPhase("PLANNING");
|
|
175
|
+
|
|
176
|
+
// Round 1: each agent presents their plan
|
|
177
|
+
await spawnAgentsParallel(allAgents, "planning");
|
|
178
|
+
|
|
179
|
+
// Round 2: review all plans, raise objections
|
|
180
|
+
const planReviewResults = await spawnAgentsParallel(allAgents, "planning-review");
|
|
181
|
+
state.phaseSummaries.planning = "Plans finalized.";
|
|
182
|
+
|
|
183
|
+
// If there were objections, one more planning round
|
|
184
|
+
if (hasDisagreement(planReviewResults)) {
|
|
185
|
+
await spawnAgentsParallel(allAgents, "planning");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ===========================
|
|
189
|
+
// PHASE 4: EXECUTION (sequential)
|
|
190
|
+
// ===========================
|
|
191
|
+
emit({ type: "phase:change", phase: "EXECUTION" });
|
|
192
|
+
logPhase("EXECUTION");
|
|
193
|
+
|
|
194
|
+
// Step 1: Dennis implements
|
|
195
|
+
if (dennis) {
|
|
196
|
+
const dennisResult = await spawnAgent("Dennis", dennis, "execution");
|
|
197
|
+
|
|
198
|
+
// Step 2: Sam audits (if on team)
|
|
199
|
+
const sam = agentMap.get("Sam");
|
|
200
|
+
if (sam) {
|
|
201
|
+
let fixAttempts = 0;
|
|
202
|
+
const MAX_FIX_ATTEMPTS = 3;
|
|
203
|
+
|
|
204
|
+
while (fixAttempts < MAX_FIX_ATTEMPTS) {
|
|
205
|
+
const samResult = await spawnAgent("Sam", sam, "execution-review");
|
|
206
|
+
|
|
207
|
+
if (!samResult.hadArgue) break; // No issues found
|
|
208
|
+
|
|
209
|
+
// Dennis fixes Sam's issues
|
|
210
|
+
fixAttempts++;
|
|
211
|
+
if (fixAttempts < MAX_FIX_ATTEMPTS) {
|
|
212
|
+
await spawnAgent("Dennis", dennis, "execution");
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Step 3: Luna + Mark review in parallel (if on team)
|
|
218
|
+
const reviewAgents = [];
|
|
219
|
+
const luna = agentMap.get("Luna");
|
|
220
|
+
const mark = agentMap.get("Mark");
|
|
221
|
+
if (luna) reviewAgents.push(["Luna", luna]);
|
|
222
|
+
if (mark) reviewAgents.push(["Mark", mark]);
|
|
223
|
+
|
|
224
|
+
if (reviewAgents.length > 0) {
|
|
225
|
+
const reviewResults = await spawnAgentsParallel(reviewAgents, "execution-review");
|
|
226
|
+
|
|
227
|
+
// If Luna or Mark found issues, Dennis fixes
|
|
228
|
+
if (hasDisagreement(reviewResults)) {
|
|
229
|
+
await spawnAgent("Dennis", dennis, "execution");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Step 4: Vera writes tests (if on team)
|
|
234
|
+
const vera = agentMap.get("Vera");
|
|
235
|
+
if (vera) {
|
|
236
|
+
await spawnAgent("Vera", vera, "execution");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Step 5: Bart does QA (if on team)
|
|
240
|
+
const bart = agentMap.get("Bart");
|
|
241
|
+
if (bart) {
|
|
242
|
+
await spawnAgent("Bart", bart, "execution");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
state.phaseSummaries.execution = "Execution complete.";
|
|
247
|
+
|
|
248
|
+
// ===========================
|
|
249
|
+
// PHASE 5: REVIEW (Jane + parallel)
|
|
250
|
+
// ===========================
|
|
251
|
+
emit({ type: "phase:change", phase: "REVIEW" });
|
|
252
|
+
logPhase("REVIEW");
|
|
253
|
+
|
|
254
|
+
// All agents provide final notes in parallel
|
|
255
|
+
await spawnAgentsParallel(allAgents, "review");
|
|
256
|
+
|
|
257
|
+
// Jane wraps up
|
|
258
|
+
if (jane) {
|
|
259
|
+
await spawnAgent("Jane", jane, "review");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ===========================
|
|
263
|
+
// SESSION END
|
|
264
|
+
// ===========================
|
|
265
|
+
const duration = `${((Date.now() - startTime) / 1000).toFixed(1)}s`;
|
|
266
|
+
emit({
|
|
267
|
+
type: "session:end",
|
|
268
|
+
duration,
|
|
269
|
+
steps: state.totalSteps,
|
|
270
|
+
inputTokens: state.totalInputTokens,
|
|
271
|
+
outputTokens: state.totalOutputTokens,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
duration,
|
|
276
|
+
steps: state.totalSteps,
|
|
277
|
+
inputTokens: state.totalInputTokens,
|
|
278
|
+
outputTokens: state.totalOutputTokens,
|
|
279
|
+
};
|
|
280
|
+
}
|
package/cli/team.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import { getStoredApiKey } from "./login.mjs";
|
|
|
12
12
|
import { resolveTeam, generateTeamPrompt } from "./agents.mjs";
|
|
13
13
|
import { buildPrompt } from "./prompt.mjs";
|
|
14
14
|
import { createStreamParser } from "./stream-parser.mjs";
|
|
15
|
+
import { runOrchestrator } from "./orchestrator.mjs";
|
|
15
16
|
|
|
16
17
|
function loadDotEnv(dir) {
|
|
17
18
|
const envPath = join(dir, ".env");
|
|
@@ -30,6 +31,7 @@ function loadDotEnv(dir) {
|
|
|
30
31
|
export async function runTeam(taskId, opts = {}) {
|
|
31
32
|
const cwd = opts.cwd || process.cwd();
|
|
32
33
|
const description = opts.description || "";
|
|
34
|
+
const legacy = opts.legacy || false;
|
|
33
35
|
|
|
34
36
|
// Detect project and resolve API key early (needed for server config fetch)
|
|
35
37
|
const project = detectProject(cwd);
|
|
@@ -44,13 +46,11 @@ export async function runTeam(taskId, opts = {}) {
|
|
|
44
46
|
let createTask = false;
|
|
45
47
|
if (!taskId) {
|
|
46
48
|
if (tracker) {
|
|
47
|
-
// Tracker configured but no task ID — agents will create a task
|
|
48
49
|
createTask = true;
|
|
49
50
|
taskId = `new-${Date.now().toString(36)}`;
|
|
50
51
|
console.log(`Project: ${project.name || "unknown"} (${project.label})`);
|
|
51
52
|
console.log(`Mode: Create new ${tracker} task from description`);
|
|
52
53
|
} else {
|
|
53
|
-
// No tracker — generate a label from description
|
|
54
54
|
taskId = description
|
|
55
55
|
.toLowerCase()
|
|
56
56
|
.replace(/[^a-z0-9\s-]/g, "")
|
|
@@ -90,12 +90,6 @@ export async function runTeam(taskId, opts = {}) {
|
|
|
90
90
|
const inboxUrl = `${agentdeskServer}/api/sessions/${sessionId}/inbox`;
|
|
91
91
|
const sessionUrl = `${agentdeskServer}/sessions/${sessionId}`;
|
|
92
92
|
|
|
93
|
-
// Build prompt using shared builder
|
|
94
|
-
const fullPrompt = buildPrompt({
|
|
95
|
-
taskId, taskLink, description, createTask, tracker, config, project,
|
|
96
|
-
teamSections, inboxUrl, sessionUrl,
|
|
97
|
-
});
|
|
98
|
-
|
|
99
93
|
let vizWs = null;
|
|
100
94
|
let vizConnected = false;
|
|
101
95
|
const vizQueue = [];
|
|
@@ -131,19 +125,25 @@ export async function runTeam(taskId, opts = {}) {
|
|
|
131
125
|
} else {
|
|
132
126
|
console.log("AgentDesk: reconnected");
|
|
133
127
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
128
|
+
if (!legacy) {
|
|
129
|
+
// Orchestrator sends its own session:start — just flush queue
|
|
130
|
+
sessionStartSent = true;
|
|
131
|
+
while (vizQueue.length > 0 && vizWs.readyState === WebSocket.OPEN) {
|
|
132
|
+
vizWs.send(vizQueue.shift());
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
vizSend({
|
|
136
|
+
type: "session:start",
|
|
137
|
+
taskId, taskLink,
|
|
138
|
+
title: description || taskId,
|
|
139
|
+
project: project.name || null,
|
|
140
|
+
sessionNumber: 1,
|
|
141
|
+
agents: teamSections.names,
|
|
142
|
+
});
|
|
143
|
+
sessionStartSent = true;
|
|
144
|
+
while (vizQueue.length > 0 && vizWs.readyState === WebSocket.OPEN) {
|
|
145
|
+
vizWs.send(vizQueue.shift());
|
|
146
|
+
}
|
|
147
147
|
}
|
|
148
148
|
} else if (msg.type === "auth:error") {
|
|
149
149
|
console.log("AgentDesk: authentication failed — run 'agentdesk login'");
|
|
@@ -155,14 +155,12 @@ export async function runTeam(taskId, opts = {}) {
|
|
|
155
155
|
vizConnected = false;
|
|
156
156
|
if (code === 4001) {
|
|
157
157
|
console.log("AgentDesk: authentication required — run 'agentdesk login'");
|
|
158
|
-
return;
|
|
158
|
+
return;
|
|
159
159
|
}
|
|
160
|
-
// Auto-reconnect after 5 seconds
|
|
161
160
|
clearTimeout(reconnectTimer);
|
|
162
161
|
reconnectTimer = setTimeout(connectWs, 5000);
|
|
163
162
|
});
|
|
164
163
|
} catch {
|
|
165
|
-
// AgentDesk not running — retry in 10s
|
|
166
164
|
clearTimeout(reconnectTimer);
|
|
167
165
|
reconnectTimer = setTimeout(connectWs, 10000);
|
|
168
166
|
}
|
|
@@ -170,17 +168,37 @@ export async function runTeam(taskId, opts = {}) {
|
|
|
170
168
|
|
|
171
169
|
connectWs();
|
|
172
170
|
|
|
173
|
-
|
|
171
|
+
console.log(`Mode: ${legacy ? "legacy (single process)" : "sub-agents"}\n`);
|
|
172
|
+
|
|
173
|
+
// --- Sub-agent mode (default) ---
|
|
174
|
+
if (!legacy) {
|
|
175
|
+
const result = await runOrchestrator({
|
|
176
|
+
taskId, taskLink, description, createTask, tracker, config,
|
|
177
|
+
project, team, teamSections, inboxUrl, sessionUrl, cwd,
|
|
178
|
+
onEvent: vizSend,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
console.log(`\n━━━ DONE ━━━`);
|
|
182
|
+
const totalTokens = result.inputTokens + result.outputTokens;
|
|
183
|
+
console.log(` ${result.duration} | ${result.steps} steps${totalTokens ? ` | ${totalTokens.toLocaleString()} tokens` : ""}\n`);
|
|
184
|
+
|
|
185
|
+
setTimeout(() => { try { vizWs?.close(); } catch {} }, 500);
|
|
186
|
+
return 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// --- Legacy mode (single process) ---
|
|
190
|
+
const fullPrompt = buildPrompt({
|
|
191
|
+
taskId, taskLink, description, createTask, tracker, config, project,
|
|
192
|
+
teamSections, inboxUrl, sessionUrl,
|
|
193
|
+
});
|
|
194
|
+
|
|
174
195
|
const child = spawn(
|
|
175
196
|
"claude",
|
|
176
197
|
[
|
|
177
|
-
"-p",
|
|
178
|
-
|
|
179
|
-
"--allowedTools",
|
|
180
|
-
"Bash,Read,Edit,Write,Glob,Grep",
|
|
198
|
+
"-p", fullPrompt,
|
|
199
|
+
"--allowedTools", "Bash,Read,Edit,Write,Glob,Grep",
|
|
181
200
|
"--verbose",
|
|
182
|
-
"--output-format",
|
|
183
|
-
"stream-json",
|
|
201
|
+
"--output-format", "stream-json",
|
|
184
202
|
],
|
|
185
203
|
{
|
|
186
204
|
stdio: ["inherit", "pipe", "inherit"],
|
|
@@ -193,7 +211,6 @@ export async function runTeam(taskId, opts = {}) {
|
|
|
193
211
|
process.on("SIGINT", () => { try { child.kill(); } catch {} process.exit(1); });
|
|
194
212
|
process.on("SIGTERM", () => { try { child.kill(); } catch {} process.exit(1); });
|
|
195
213
|
|
|
196
|
-
// --- Stream parsing via shared parser ---
|
|
197
214
|
console.log("\n━━━ INTAKE ━━━\n");
|
|
198
215
|
vizSend({ type: "phase:change", phase: "INTAKE" });
|
|
199
216
|
|
|
@@ -215,11 +232,7 @@ export async function runTeam(taskId, opts = {}) {
|
|
|
215
232
|
vizSend({ type: "agent:message", agent, tag, message });
|
|
216
233
|
},
|
|
217
234
|
onToolUse({ agent, tool, description }) {
|
|
218
|
-
|
|
219
|
-
console.log(` ${agent} [ACT] ${description}`);
|
|
220
|
-
} else {
|
|
221
|
-
console.log(` ${agent} [ACT] ${description}`);
|
|
222
|
-
}
|
|
235
|
+
console.log(` ${agent} [ACT] ${description}`);
|
|
223
236
|
vizSend({ type: "tool:use", agent, tool, description });
|
|
224
237
|
},
|
|
225
238
|
onToolResult({ success, summary }) {
|