@kendoo.agentdesk/agentdesk 0.9.4 → 0.9.6
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 +4 -13
- package/bin/agentdesk.mjs +7 -7
- package/cli/agents.mjs +4 -1
- package/cli/daemon.mjs +1 -2
- package/cli/init.mjs +27 -13
- package/cli/orchestrator.mjs +2 -271
- package/cli/prompt.mjs +7 -2
- package/cli/team.mjs +4 -25
- package/package.json +1 -1
- package/prompts/team.md +113 -2
- package/cli/agent-prompts.mjs +0 -166
- package/cli/agent-runner.mjs +0 -148
package/README.md
CHANGED
|
@@ -79,7 +79,6 @@ agentdesk update Update to the latest version
|
|
|
79
79
|
|------|-------------|
|
|
80
80
|
| `--description`, `-d` | Task description or requirements |
|
|
81
81
|
| `--cwd` | Working directory (defaults to current) |
|
|
82
|
-
| `--lite` | Lite mode — all agents share one process (faster, cheaper) |
|
|
83
82
|
|
|
84
83
|
## Configuration
|
|
85
84
|
|
|
@@ -116,22 +115,14 @@ AgentDesk also auto-discovers agents from `.claude/agents/`, `.claude/commands/`
|
|
|
116
115
|
|
|
117
116
|
## How It Works
|
|
118
117
|
|
|
119
|
-
|
|
118
|
+
All agents collaborate in a single Claude process — each with distinct roles, ground rules, and areas of expertise.
|
|
120
119
|
|
|
121
120
|
1. You run `agentdesk team TASK-ID` in your project directory
|
|
122
121
|
2. AgentDesk detects your project type, reads `CLAUDE.md`, and discovers existing agents
|
|
123
122
|
3. The orchestrator manages 5 phases: **Intake** > **Brainstorm** > **Planning** > **Execution** > **Review**
|
|
124
|
-
4.
|
|
125
|
-
5.
|
|
126
|
-
6.
|
|
127
|
-
7. Token usage is tracked and displayed per session
|
|
128
|
-
|
|
129
|
-
### Agent Modes
|
|
130
|
-
|
|
131
|
-
| Mode | Command | Description |
|
|
132
|
-
|------|---------|-------------|
|
|
133
|
-
| Full (default) | `agentdesk team -d "..."` | Each agent runs in its own process. Better output. |
|
|
134
|
-
| Lite | `agentdesk team -d "..." --lite` | All agents share one process. Faster and cheaper. |
|
|
123
|
+
4. Agents discuss, disagree, and build on each other's ideas within a shared context
|
|
124
|
+
5. The session streams live to [agentdesk.live](https://agentdesk.live) where you can watch and send messages to the team
|
|
125
|
+
6. Token usage is tracked and displayed per session
|
|
135
126
|
|
|
136
127
|
## Daemon (Remote Sessions)
|
|
137
128
|
|
package/bin/agentdesk.mjs
CHANGED
|
@@ -44,6 +44,11 @@ if (command !== "update") {
|
|
|
44
44
|
} catch {}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
if (command === "--version" || command === "-v" || command === "version") {
|
|
48
|
+
console.log(pkg.version);
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
47
52
|
if (!command || command === "help" || command === "--help") {
|
|
48
53
|
console.log(`
|
|
49
54
|
AgentDesk — AI team orchestrator for Claude Code
|
|
@@ -65,7 +70,7 @@ if (!command || command === "help" || command === "--help") {
|
|
|
65
70
|
Options:
|
|
66
71
|
--description, -d Task description or requirements
|
|
67
72
|
--cwd Working directory (defaults to current)
|
|
68
|
-
|
|
73
|
+
|
|
69
74
|
|
|
70
75
|
How it works:
|
|
71
76
|
With a tracker (Linear, Jira, GitHub Issues):
|
|
@@ -105,7 +110,6 @@ else if (command === "team") {
|
|
|
105
110
|
let description = "";
|
|
106
111
|
let cwd = process.cwd();
|
|
107
112
|
let taskId = null;
|
|
108
|
-
let lite = false;
|
|
109
113
|
const remaining = args.slice(1);
|
|
110
114
|
|
|
111
115
|
for (let i = 0; i < remaining.length; i++) {
|
|
@@ -113,10 +117,6 @@ else if (command === "team") {
|
|
|
113
117
|
description = remaining[++i];
|
|
114
118
|
} else if (remaining[i] === "--cwd" && remaining[i + 1]) {
|
|
115
119
|
cwd = remaining[++i];
|
|
116
|
-
} else if (remaining[i] === "--lite" || remaining[i] === "--legacy") {
|
|
117
|
-
lite = true;
|
|
118
|
-
} else if (remaining[i] === "--full") {
|
|
119
|
-
lite = false;
|
|
120
120
|
} else if (!taskId && !remaining[i].startsWith("-")) {
|
|
121
121
|
taskId = remaining[i];
|
|
122
122
|
}
|
|
@@ -129,7 +129,7 @@ else if (command === "team") {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
const { runTeam } = await import("../cli/team.mjs");
|
|
132
|
-
const code = await runTeam(taskId, { description, cwd
|
|
132
|
+
const code = await runTeam(taskId, { description, cwd });
|
|
133
133
|
process.exit(code);
|
|
134
134
|
}
|
|
135
135
|
|
package/cli/agents.mjs
CHANGED
|
@@ -5,13 +5,15 @@ export const BUILT_IN_AGENTS = {
|
|
|
5
5
|
badge: "●● JANE ●●",
|
|
6
6
|
role: "Product Analyst / Team Lead",
|
|
7
7
|
description: "facilitates discussion, clarifies requirements, keeps the team focused, evaluates team performance at the end",
|
|
8
|
-
groundRules: "Jane resolves disagreements and keeps the discussion productive. She NEVER reads code
|
|
8
|
+
groundRules: "Jane resolves disagreements and keeps the discussion productive. She NEVER reads code or inspects files — that's the developer and auditor's job. She focuses on requirements, user impact, scope, and coordination. If no task ID was provided (only a description), Jane MUST create a new task in the tracker FIRST, announce the new task ID clearly, and then set its status. Jane is responsible for tracker status updates: posting the session start comment, changing task status to 'In Progress' at start and 'In Review' at end, and posting the final summary comment. Jane keeps the task status current throughout the session.",
|
|
9
9
|
brainstorm: { tag: "SAY", focus: "facilitation, question, or summary" },
|
|
10
10
|
planning: "Requirements Summary (what we're building, acceptance criteria, scope boundaries). If this task involves UI changes, Jane declares it a UI task and tells Luna to define screenshot requirements.",
|
|
11
11
|
execution: {
|
|
12
12
|
step: "Jane wraps up",
|
|
13
13
|
tasks: [
|
|
14
|
+
"Transition the task status to 'In Review' (or equivalent) in the tracker.",
|
|
14
15
|
"Review the PR description. Ensure it explains what changed and why.",
|
|
16
|
+
"Post a final summary comment on the tracker task.",
|
|
15
17
|
"Summarize the session.",
|
|
16
18
|
],
|
|
17
19
|
order: 99, // last
|
|
@@ -70,6 +72,7 @@ export const BUILT_IN_AGENTS = {
|
|
|
70
72
|
"Run linter and build.",
|
|
71
73
|
"If this is a UI task, capture screenshots following Luna's screenshot plan (see SCREENSHOTS section in prompt).",
|
|
72
74
|
"If ALL criteria pass, push and create PR: `gh pr create --title \"...\" --body \"...\"`",
|
|
75
|
+
"Post the PR link as a comment on the tracker task and attach it to the issue (see tracker integration section for API calls).",
|
|
73
76
|
"Post screenshots to the task tracker as a separate comment (not inside badge blocks).",
|
|
74
77
|
"Approve the PR if clean.",
|
|
75
78
|
],
|
package/cli/daemon.mjs
CHANGED
|
@@ -313,7 +313,7 @@ export async function runDaemon() {
|
|
|
313
313
|
|
|
314
314
|
// 4. Session handling
|
|
315
315
|
|
|
316
|
-
async function handleStartSession({ sessionId, projectId, taskId: remoteTaskId, prompt
|
|
316
|
+
async function handleStartSession({ sessionId, projectId, taskId: remoteTaskId, prompt }) {
|
|
317
317
|
// Validate project against local allowlist
|
|
318
318
|
const project = projects.find(p => p.id === projectId);
|
|
319
319
|
if (!project) {
|
|
@@ -379,7 +379,6 @@ export async function runDaemon() {
|
|
|
379
379
|
project: detected, team, teamSections,
|
|
380
380
|
inboxUrl, sessionUrl,
|
|
381
381
|
cwd: project.path,
|
|
382
|
-
mode: mode || "full",
|
|
383
382
|
apiKey,
|
|
384
383
|
serverUrl: agentdeskServer,
|
|
385
384
|
onEvent(event) {
|
package/cli/init.mjs
CHANGED
|
@@ -62,6 +62,12 @@ export async function runInit(cwd) {
|
|
|
62
62
|
if (project.lintCommand) console.log(` Lint: ${project.lintCommand}`);
|
|
63
63
|
if (project.testCommand || project.buildCommand || project.lintCommand) console.log("");
|
|
64
64
|
|
|
65
|
+
// --- Project key ---
|
|
66
|
+
const defaultKey = existingConfig.projectKey || projectId;
|
|
67
|
+
const keyAnswer = await ask(rl, ` Project key (${defaultKey}): `);
|
|
68
|
+
const finalProjectKey = keyAnswer.trim() || defaultKey;
|
|
69
|
+
console.log("");
|
|
70
|
+
|
|
65
71
|
// --- Tracker selection ---
|
|
66
72
|
const trackerOptions = [
|
|
67
73
|
{ label: "Linear", value: "linear" },
|
|
@@ -75,22 +81,28 @@ export async function runInit(cwd) {
|
|
|
75
81
|
console.log("");
|
|
76
82
|
|
|
77
83
|
// Build config
|
|
78
|
-
const config = {};
|
|
84
|
+
const config = { projectKey: finalProjectKey };
|
|
79
85
|
if (tracker) config.tracker = tracker;
|
|
80
86
|
|
|
81
87
|
if (tracker === "linear") {
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const ws =
|
|
85
|
-
|
|
88
|
+
const currentWs = existingConfig.linear?.workspace || "";
|
|
89
|
+
const wsAnswer = await ask(rl, ` Linear workspace slug${currentWs ? ` (${currentWs})` : ""}: `);
|
|
90
|
+
const ws = wsAnswer.trim() || currentWs;
|
|
91
|
+
const currentKey = existingConfig.linear?.teamKey || "";
|
|
92
|
+
const keyAnswer = await ask(rl, ` Linear team key${currentKey ? ` (${currentKey})` : ""} (e.g. KEN): `);
|
|
93
|
+
const teamKey = keyAnswer.trim() || currentKey;
|
|
94
|
+
if (ws || teamKey) config.linear = { ...(ws && { workspace: ws }), ...(teamKey && { teamKey }) };
|
|
86
95
|
console.log("");
|
|
87
96
|
}
|
|
88
97
|
|
|
89
98
|
if (tracker === "jira") {
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
const url =
|
|
93
|
-
|
|
99
|
+
const currentUrl = existingConfig.jira?.baseUrl || "";
|
|
100
|
+
const urlAnswer = await ask(rl, ` Jira base URL${currentUrl ? ` (${currentUrl})` : ""}: `);
|
|
101
|
+
const url = urlAnswer.trim() || currentUrl;
|
|
102
|
+
const currentProj = existingConfig.jira?.project || "";
|
|
103
|
+
const projAnswer = await ask(rl, ` Jira project key${currentProj ? ` (${currentProj})` : ""} (e.g. PROJ): `);
|
|
104
|
+
const proj = projAnswer.trim() || currentProj;
|
|
105
|
+
if (url || proj) config.jira = { ...(url && { baseUrl: url }), ...(proj && { project: proj }) };
|
|
94
106
|
console.log("");
|
|
95
107
|
}
|
|
96
108
|
|
|
@@ -108,6 +120,8 @@ export async function runInit(cwd) {
|
|
|
108
120
|
try { merged = JSON.parse(readFileSync(configPath, "utf-8")); } catch {}
|
|
109
121
|
}
|
|
110
122
|
|
|
123
|
+
merged.projectKey = finalProjectKey;
|
|
124
|
+
|
|
111
125
|
if (tracker) {
|
|
112
126
|
merged.tracker = tracker;
|
|
113
127
|
if (config.linear) merged.linear = config.linear;
|
|
@@ -124,7 +138,7 @@ export async function runInit(cwd) {
|
|
|
124
138
|
console.log(` Saved .agentdesk.json`);
|
|
125
139
|
|
|
126
140
|
// Register in local project index (for daemon discovery)
|
|
127
|
-
registerLocalProject(
|
|
141
|
+
registerLocalProject(finalProjectKey, project.name || finalProjectKey, project.dir);
|
|
128
142
|
|
|
129
143
|
// --- Register with server ---
|
|
130
144
|
try {
|
|
@@ -135,8 +149,8 @@ export async function runInit(cwd) {
|
|
|
135
149
|
...(loadApiKey(cwd) ? { "x-api-key": loadApiKey(cwd) } : {}),
|
|
136
150
|
},
|
|
137
151
|
body: JSON.stringify({
|
|
138
|
-
id:
|
|
139
|
-
name: project.name ||
|
|
152
|
+
id: finalProjectKey,
|
|
153
|
+
name: project.name || finalProjectKey,
|
|
140
154
|
path: project.dir,
|
|
141
155
|
type: project.type,
|
|
142
156
|
tracker,
|
|
@@ -148,7 +162,7 @@ export async function runInit(cwd) {
|
|
|
148
162
|
// Push settings to server
|
|
149
163
|
const key = loadApiKey(cwd);
|
|
150
164
|
if (key) {
|
|
151
|
-
await fetch(`${SERVER}/api/projects/${
|
|
165
|
+
await fetch(`${SERVER}/api/projects/${finalProjectKey}/settings`, {
|
|
152
166
|
method: "PUT",
|
|
153
167
|
headers: { "Content-Type": "application/json", "x-api-key": key },
|
|
154
168
|
body: JSON.stringify(merged),
|
package/cli/orchestrator.mjs
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
// Orchestrator —
|
|
1
|
+
// Orchestrator — runs a single Claude process with all agents as personas
|
|
2
2
|
|
|
3
3
|
import { spawn } from "child_process";
|
|
4
4
|
import { existsSync, readFileSync } from "fs";
|
|
5
5
|
import { createInterface } from "readline";
|
|
6
6
|
import { join, dirname } from "path";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
|
-
import { runAgent } from "./agent-runner.mjs";
|
|
9
8
|
import { buildPrompt } from "./prompt.mjs";
|
|
10
9
|
import { createStreamParser } from "./stream-parser.mjs";
|
|
11
10
|
|
|
12
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
12
|
const CLI_VERSION = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8")).version;
|
|
14
|
-
import { buildAgentPrompt, getAgentTools } from "./agent-prompts.mjs";
|
|
15
|
-
import { detectProject, generateContext } from "./detect.mjs";
|
|
16
13
|
|
|
17
14
|
// Fetch decrypted tracker credentials from server
|
|
18
15
|
async function fetchTrackerCredentials(projectName, apiKey, serverUrl) {
|
|
@@ -50,274 +47,8 @@ function timestamp() {
|
|
|
50
47
|
export async function runOrchestrator({
|
|
51
48
|
taskId, taskLink, description, createTask, tracker, config,
|
|
52
49
|
project, team, teamSections, inboxUrl, sessionUrl, cwd,
|
|
53
|
-
onEvent,
|
|
50
|
+
onEvent, apiKey, serverUrl,
|
|
54
51
|
}) {
|
|
55
|
-
// --- Lite mode: single process, legacy behavior ---
|
|
56
|
-
if (mode === "lite") {
|
|
57
|
-
return runLiteMode({ taskId, taskLink, description, createTask, tracker, config, project, teamSections, inboxUrl, sessionUrl, cwd, onEvent, apiKey, serverUrl });
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// --- Full mode: independent sub-agents ---
|
|
61
|
-
const state = {
|
|
62
|
-
task: { id: taskId, link: taskLink, description: description || "" },
|
|
63
|
-
conversationLog: [], // { type: "message"|"phase", agent?, tag?, message?, phase? }
|
|
64
|
-
phaseSummaries: {},
|
|
65
|
-
totalInputTokens: 0,
|
|
66
|
-
totalOutputTokens: 0,
|
|
67
|
-
totalSteps: 0,
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const trackerCreds = await fetchTrackerCredentials(project?.name, apiKey, serverUrl);
|
|
71
|
-
const env = { ...process.env, ...loadDotEnv(cwd), ...trackerCreds };
|
|
72
|
-
|
|
73
|
-
// Emit event to WebSocket (same protocol as single-process mode)
|
|
74
|
-
function emit(event) {
|
|
75
|
-
onEvent?.({ ...event, timestamp: timestamp() });
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Add to conversation log
|
|
79
|
-
function logMessage(agent, tag, message) {
|
|
80
|
-
state.conversationLog.push({ type: "message", agent, tag, message });
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function logPhase(phase) {
|
|
84
|
-
state.conversationLog.push({ type: "phase", phase });
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Run a single agent and collect results
|
|
88
|
-
async function spawnAgent(agentName, agent, phase, extraConversation) {
|
|
89
|
-
const conversation = extraConversation || state.conversationLog;
|
|
90
|
-
|
|
91
|
-
const prompt = buildAgentPrompt(agent, {
|
|
92
|
-
phase,
|
|
93
|
-
task: state.task,
|
|
94
|
-
project,
|
|
95
|
-
conversation,
|
|
96
|
-
inboxUrl,
|
|
97
|
-
sessionUrl,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
const tools = getAgentTools(agentName);
|
|
101
|
-
|
|
102
|
-
const result = await runAgent(agentName, {
|
|
103
|
-
prompt,
|
|
104
|
-
allowedTools: tools,
|
|
105
|
-
cwd,
|
|
106
|
-
env,
|
|
107
|
-
onMessage(msg) {
|
|
108
|
-
emit({ type: "agent:message", agent: msg.agent, tag: msg.tag, message: msg.message });
|
|
109
|
-
logMessage(msg.agent, msg.tag, msg.message);
|
|
110
|
-
},
|
|
111
|
-
onToolUse(use) {
|
|
112
|
-
emit({ type: "tool:use", agent: use.agent, tool: use.tool, description: use.description });
|
|
113
|
-
},
|
|
114
|
-
onToolResult(res) {
|
|
115
|
-
emit({ type: "tool:result", success: res.success, summary: res.summary });
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
state.totalInputTokens += result.inputTokens;
|
|
120
|
-
state.totalOutputTokens += result.outputTokens;
|
|
121
|
-
state.totalSteps += result.steps;
|
|
122
|
-
|
|
123
|
-
return result;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Run multiple agents in parallel
|
|
127
|
-
async function spawnAgentsParallel(agents, phase, extraConversation) {
|
|
128
|
-
const conversation = extraConversation || [...state.conversationLog];
|
|
129
|
-
return Promise.all(
|
|
130
|
-
agents.map(([name, agent]) => spawnAgent(name, agent, phase, conversation))
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Build summary from agent results
|
|
135
|
-
function summarizeResults(results) {
|
|
136
|
-
return results
|
|
137
|
-
.filter(r => r.messages.length > 0)
|
|
138
|
-
.map(r => r.messages.map(m => `${m.agent} [${m.tag}]: ${m.message}`).join("\n"))
|
|
139
|
-
.join("\n\n");
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Detect if any agent argued
|
|
143
|
-
function hasDisagreement(results) {
|
|
144
|
-
return results.some(r => r.hadArgue);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// --- Session start ---
|
|
148
|
-
const startTime = Date.now();
|
|
149
|
-
emit({
|
|
150
|
-
type: "session:start",
|
|
151
|
-
taskId, taskLink,
|
|
152
|
-
title: description || taskId,
|
|
153
|
-
project: project?.name || null,
|
|
154
|
-
sessionNumber: 1,
|
|
155
|
-
agents: teamSections.names,
|
|
156
|
-
cliVersion: CLI_VERSION,
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
// Build agent lookup
|
|
160
|
-
const agentMap = new Map();
|
|
161
|
-
for (const agent of team) {
|
|
162
|
-
agentMap.set(agent.name, agent);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Find specific agents
|
|
166
|
-
const jane = agentMap.get("Jane");
|
|
167
|
-
const dennis = agentMap.get("Dennis");
|
|
168
|
-
|
|
169
|
-
// ===========================
|
|
170
|
-
// PHASE 1: INTAKE (Jane only)
|
|
171
|
-
// ===========================
|
|
172
|
-
emit({ type: "phase:change", phase: "INTAKE" });
|
|
173
|
-
logPhase("INTAKE");
|
|
174
|
-
|
|
175
|
-
const intakeResult = await spawnAgent("Jane", jane, "intake");
|
|
176
|
-
state.phaseSummaries.intake = summarizeResults([intakeResult]);
|
|
177
|
-
|
|
178
|
-
// Extract short title from Jane's intake (SESSION_TITLE: ...)
|
|
179
|
-
const titleMatch = intakeResult.rawOutput.match(/SESSION_TITLE:\s*(.+)/);
|
|
180
|
-
if (titleMatch) {
|
|
181
|
-
const shortTitle = titleMatch[1].trim().slice(0, 60);
|
|
182
|
-
emit({ type: "session:update", title: shortTitle });
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// ===========================
|
|
186
|
-
// PHASE 2: BRAINSTORM (parallel, 3-5 rounds)
|
|
187
|
-
// ===========================
|
|
188
|
-
emit({ type: "phase:change", phase: "BRAINSTORM" });
|
|
189
|
-
logPhase("BRAINSTORM");
|
|
190
|
-
|
|
191
|
-
const allAgents = [...agentMap.entries()];
|
|
192
|
-
let brainstormRounds = 0;
|
|
193
|
-
const MAX_BRAINSTORM_ROUNDS = 5;
|
|
194
|
-
|
|
195
|
-
for (let round = 0; round < MAX_BRAINSTORM_ROUNDS; round++) {
|
|
196
|
-
brainstormRounds++;
|
|
197
|
-
const results = await spawnAgentsParallel(allAgents, "brainstorm");
|
|
198
|
-
|
|
199
|
-
// Check for consensus (no ARGUE tags = consensus)
|
|
200
|
-
if (!hasDisagreement(results) && round >= 2) {
|
|
201
|
-
break; // Minimum 3 rounds, then stop on consensus
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
state.phaseSummaries.brainstorm = `Brainstorm completed in ${brainstormRounds} rounds.`;
|
|
205
|
-
|
|
206
|
-
// ===========================
|
|
207
|
-
// PHASE 3: PLANNING (parallel + review)
|
|
208
|
-
// ===========================
|
|
209
|
-
emit({ type: "phase:change", phase: "PLANNING" });
|
|
210
|
-
logPhase("PLANNING");
|
|
211
|
-
|
|
212
|
-
// Round 1: each agent presents their plan
|
|
213
|
-
await spawnAgentsParallel(allAgents, "planning");
|
|
214
|
-
|
|
215
|
-
// Round 2: review all plans, raise objections
|
|
216
|
-
const planReviewResults = await spawnAgentsParallel(allAgents, "planning-review");
|
|
217
|
-
state.phaseSummaries.planning = "Plans finalized.";
|
|
218
|
-
|
|
219
|
-
// If there were objections, one more planning round
|
|
220
|
-
if (hasDisagreement(planReviewResults)) {
|
|
221
|
-
await spawnAgentsParallel(allAgents, "planning");
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// ===========================
|
|
225
|
-
// PHASE 4: EXECUTION (sequential)
|
|
226
|
-
// ===========================
|
|
227
|
-
emit({ type: "phase:change", phase: "EXECUTION" });
|
|
228
|
-
logPhase("EXECUTION");
|
|
229
|
-
|
|
230
|
-
// Step 1: Dennis implements
|
|
231
|
-
if (dennis) {
|
|
232
|
-
const dennisResult = await spawnAgent("Dennis", dennis, "execution");
|
|
233
|
-
|
|
234
|
-
// Step 2: Sam audits (if on team)
|
|
235
|
-
const sam = agentMap.get("Sam");
|
|
236
|
-
if (sam) {
|
|
237
|
-
let fixAttempts = 0;
|
|
238
|
-
const MAX_FIX_ATTEMPTS = 3;
|
|
239
|
-
|
|
240
|
-
while (fixAttempts < MAX_FIX_ATTEMPTS) {
|
|
241
|
-
const samResult = await spawnAgent("Sam", sam, "execution-review");
|
|
242
|
-
|
|
243
|
-
if (!samResult.hadArgue) break; // No issues found
|
|
244
|
-
|
|
245
|
-
// Dennis fixes Sam's issues
|
|
246
|
-
fixAttempts++;
|
|
247
|
-
if (fixAttempts < MAX_FIX_ATTEMPTS) {
|
|
248
|
-
await spawnAgent("Dennis", dennis, "execution");
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Step 3: Luna + Mark review in parallel (if on team)
|
|
254
|
-
const reviewAgents = [];
|
|
255
|
-
const luna = agentMap.get("Luna");
|
|
256
|
-
const mark = agentMap.get("Mark");
|
|
257
|
-
if (luna) reviewAgents.push(["Luna", luna]);
|
|
258
|
-
if (mark) reviewAgents.push(["Mark", mark]);
|
|
259
|
-
|
|
260
|
-
if (reviewAgents.length > 0) {
|
|
261
|
-
const reviewResults = await spawnAgentsParallel(reviewAgents, "execution-review");
|
|
262
|
-
|
|
263
|
-
// If Luna or Mark found issues, Dennis fixes
|
|
264
|
-
if (hasDisagreement(reviewResults)) {
|
|
265
|
-
await spawnAgent("Dennis", dennis, "execution");
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Step 4: Vera writes tests (if on team)
|
|
270
|
-
const vera = agentMap.get("Vera");
|
|
271
|
-
if (vera) {
|
|
272
|
-
await spawnAgent("Vera", vera, "execution");
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Step 5: Bart does QA (if on team)
|
|
276
|
-
const bart = agentMap.get("Bart");
|
|
277
|
-
if (bart) {
|
|
278
|
-
await spawnAgent("Bart", bart, "execution");
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
state.phaseSummaries.execution = "Execution complete.";
|
|
283
|
-
|
|
284
|
-
// ===========================
|
|
285
|
-
// PHASE 5: REVIEW (Jane + parallel)
|
|
286
|
-
// ===========================
|
|
287
|
-
emit({ type: "phase:change", phase: "REVIEW" });
|
|
288
|
-
logPhase("REVIEW");
|
|
289
|
-
|
|
290
|
-
// All agents provide final notes in parallel
|
|
291
|
-
await spawnAgentsParallel(allAgents, "review");
|
|
292
|
-
|
|
293
|
-
// Jane wraps up
|
|
294
|
-
if (jane) {
|
|
295
|
-
await spawnAgent("Jane", jane, "review");
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// ===========================
|
|
299
|
-
// SESSION END
|
|
300
|
-
// ===========================
|
|
301
|
-
const duration = `${((Date.now() - startTime) / 1000).toFixed(1)}s`;
|
|
302
|
-
emit({
|
|
303
|
-
type: "session:end",
|
|
304
|
-
duration,
|
|
305
|
-
steps: state.totalSteps,
|
|
306
|
-
inputTokens: state.totalInputTokens,
|
|
307
|
-
outputTokens: state.totalOutputTokens,
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
return {
|
|
311
|
-
duration,
|
|
312
|
-
steps: state.totalSteps,
|
|
313
|
-
inputTokens: state.totalInputTokens,
|
|
314
|
-
outputTokens: state.totalOutputTokens,
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// --- Lite mode: single Claude process with all agents as personas ---
|
|
319
|
-
|
|
320
|
-
async function runLiteMode({ taskId, taskLink, description, createTask, tracker, config, project, teamSections, inboxUrl, sessionUrl, cwd, onEvent, apiKey, serverUrl }) {
|
|
321
52
|
const trackerCreds = await fetchTrackerCredentials(project?.name, apiKey, serverUrl);
|
|
322
53
|
const env = { ...process.env, ...loadDotEnv(cwd), ...trackerCreds };
|
|
323
54
|
const startTime = Date.now();
|
package/cli/prompt.mjs
CHANGED
|
@@ -35,7 +35,7 @@ export function buildPrompt({ taskId, taskLink, description, createTask, tracker
|
|
|
35
35
|
|
|
36
36
|
// Create task instruction
|
|
37
37
|
if (createTask && description) {
|
|
38
|
-
let createInstr = "\n\n## CREATE TASK\n\nNo task ID was provided
|
|
38
|
+
let createInstr = "\n\n## CREATE TASK (MANDATORY — FIRST ACTION IN INTAKE)\n\nNo task ID was provided — only a description. Jane MUST create a new task in the tracker as the VERY FIRST action before anything else.\n\n";
|
|
39
39
|
if (tracker === "linear") {
|
|
40
40
|
createInstr += `Create a Linear issue using the GraphQL API:\n- Endpoint: https://api.linear.app/graphql\n- Auth: Authorization: $LINEAR_API_KEY\n- Set the title based on the description below\n- After creation, use the returned identifier (e.g., KEN-530) as the task ID for the rest of the session\n`;
|
|
41
41
|
} else if (tracker === "jira") {
|
|
@@ -44,7 +44,12 @@ export function buildPrompt({ taskId, taskLink, description, createTask, tracker
|
|
|
44
44
|
createInstr += `Create a GitHub issue:\n- Run: gh issue create --title "..." --body "..."\n- After creation, use the returned issue number as the task ID for the rest of the session\n`;
|
|
45
45
|
}
|
|
46
46
|
createInstr += `\nTask description: ${description}\n`;
|
|
47
|
-
createInstr += `\
|
|
47
|
+
createInstr += `\nAfter creating the task, Jane MUST:\n`;
|
|
48
|
+
createInstr += `1. ANNOUNCE the new task ID clearly to the team: "I've created task <ID> in ${tracker}. This is our task for this session."\n`;
|
|
49
|
+
createInstr += `2. Output the task ID on its own line in this exact format (required for dashboard linking):\n TASK_ID: <identifier>\n Example: TASK_ID: KEN-530\n`;
|
|
50
|
+
createInstr += `3. Immediately set the task status to "In Progress" and post the session start comment.\n`;
|
|
51
|
+
createInstr += `4. Use this new task ID for ALL subsequent tracker operations (comments, status updates, PR linking).\n`;
|
|
52
|
+
createInstr += `\nDo NOT proceed to BRAINSTORM until the task is created, announced, and set to "In Progress".\n`;
|
|
48
53
|
prompt += createInstr;
|
|
49
54
|
}
|
|
50
55
|
|
package/cli/team.mjs
CHANGED
|
@@ -31,7 +31,6 @@ function loadDotEnv(dir) {
|
|
|
31
31
|
export async function runTeam(taskId, opts = {}) {
|
|
32
32
|
const cwd = opts.cwd || process.cwd();
|
|
33
33
|
const description = opts.description || "";
|
|
34
|
-
const lite = opts.lite || false;
|
|
35
34
|
|
|
36
35
|
// Detect project and resolve API key early (needed for server config fetch)
|
|
37
36
|
const project = detectProject(cwd);
|
|
@@ -125,26 +124,10 @@ export async function runTeam(taskId, opts = {}) {
|
|
|
125
124
|
} else {
|
|
126
125
|
console.log("AgentDesk: reconnected");
|
|
127
126
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
cliVersion: CLI_VERSION,
|
|
143
|
-
});
|
|
144
|
-
sessionStartSent = true;
|
|
145
|
-
while (vizQueue.length > 0 && vizWs.readyState === WebSocket.OPEN) {
|
|
146
|
-
vizWs.send(vizQueue.shift());
|
|
147
|
-
}
|
|
127
|
+
// Orchestrator sends its own session:start — just flush queue
|
|
128
|
+
sessionStartSent = true;
|
|
129
|
+
while (vizQueue.length > 0 && vizWs.readyState === WebSocket.OPEN) {
|
|
130
|
+
vizWs.send(vizQueue.shift());
|
|
148
131
|
}
|
|
149
132
|
} else if (msg.type === "auth:error") {
|
|
150
133
|
console.log("AgentDesk: authentication failed — run 'agentdesk login'");
|
|
@@ -169,14 +152,10 @@ export async function runTeam(taskId, opts = {}) {
|
|
|
169
152
|
|
|
170
153
|
connectWs();
|
|
171
154
|
|
|
172
|
-
const agentMode = lite ? "lite" : "full";
|
|
173
|
-
console.log(`Agents: ${agentMode === "lite" ? "lite (shared process)" : "full (independent process)"}\n`);
|
|
174
|
-
|
|
175
155
|
const result = await runOrchestrator({
|
|
176
156
|
taskId, taskLink, description, createTask, tracker, config,
|
|
177
157
|
project, team, teamSections, inboxUrl, sessionUrl, cwd,
|
|
178
158
|
onEvent: vizSend,
|
|
179
|
-
mode: agentMode,
|
|
180
159
|
apiKey,
|
|
181
160
|
serverUrl: agentdeskServer,
|
|
182
161
|
});
|
package/package.json
CHANGED
package/prompts/team.md
CHANGED
|
@@ -258,6 +258,7 @@ GraphQL query — fetch the task by identifier:
|
|
|
258
258
|
title
|
|
259
259
|
description
|
|
260
260
|
state { name }
|
|
261
|
+
labels { nodes { name } }
|
|
261
262
|
comments {
|
|
262
263
|
nodes {
|
|
263
264
|
body
|
|
@@ -279,6 +280,36 @@ Session : {{SESSION_URL}}
|
|
|
279
280
|
```
|
|
280
281
|
|
|
281
282
|
IMPORTANT: The first comment on any task MUST include the session link: {{SESSION_URL}}
|
|
283
|
+
|
|
284
|
+
### Every agent must comment on the tracker
|
|
285
|
+
|
|
286
|
+
When an agent completes meaningful work or has important findings, they MUST post a comment on the tracker task. Not just Jane and Bart — everyone relevant:
|
|
287
|
+
- **Dennis**: After implementing — what was changed, which files, any technical decisions made
|
|
288
|
+
- **Sam**: After audit — architecture concerns found (or "clean audit, no violations")
|
|
289
|
+
- **Vera**: After writing tests — which tests were added, coverage notes
|
|
290
|
+
- **Luna**: After UI review — any UX issues found and fixes applied
|
|
291
|
+
- **Mark**: After content review — any copy changes made
|
|
292
|
+
- **Bart**: After QA + PR — PR link, test results, screenshot links
|
|
293
|
+
|
|
294
|
+
Each agent posts using their own badge format. Keep comments concise — bullet points, not essays. The tracker should tell the full story of what happened without needing to watch the dashboard.
|
|
295
|
+
|
|
296
|
+
### Required tracker actions (Jane MUST do these — not optional)
|
|
297
|
+
|
|
298
|
+
1. **On session start (INTAKE):** Post a comment: "Team session started. Session: {{SESSION_URL}}" and move the task to "In Progress" state:
|
|
299
|
+
```
|
|
300
|
+
mutation { issueUpdate(id: "$ISSUE_ID", input: { stateId: "$IN_PROGRESS_STATE_ID" }) { success } }
|
|
301
|
+
```
|
|
302
|
+
To find the state ID, query: `{ workflowStates(filter: { team: { issues: { id: { eq: "$ISSUE_ID" } } } }) { nodes { id name } } }`
|
|
303
|
+
|
|
304
|
+
2. **After PR is created (EXECUTION — Bart does this, not Jane):** Bart MUST post a comment with the PR link and attach it to the issue:
|
|
305
|
+
```
|
|
306
|
+
mutation { attachmentCreate(input: { issueId: "$ISSUE_ID", title: "Pull Request", url: "$PR_URL" }) { success } }
|
|
307
|
+
```
|
|
308
|
+
Also post a comment: "PR created: $PR_URL"
|
|
309
|
+
|
|
310
|
+
3. **On session end (SUMMARY):** Jane moves the task to "In Review" state and posts a final summary comment.
|
|
311
|
+
|
|
312
|
+
Steps 1 and 3 are Jane's responsibility. Step 2 is Bart's responsibility (he creates the PR, so he links it). If any step fails, retry once. These are not suggestions — they are required protocol.
|
|
282
313
|
{{/LINEAR}}
|
|
283
314
|
|
|
284
315
|
{{#JIRA}}
|
|
@@ -287,31 +318,97 @@ IMPORTANT: The first comment on any task MUST include the session link: {{SESSIO
|
|
|
287
318
|
Fetch the task from Jira using curl:
|
|
288
319
|
- Endpoint: {{JIRA_BASE_URL}}/rest/api/3/issue/{{TASK_ID}}
|
|
289
320
|
- Auth: Use $JIRA_EMAIL and $JIRA_API_TOKEN as basic auth
|
|
290
|
-
- curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" "{{JIRA_BASE_URL}}/rest/api/3/issue/{{TASK_ID}}?fields=summary,description,status,comment"
|
|
321
|
+
- curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" "{{JIRA_BASE_URL}}/rest/api/3/issue/{{TASK_ID}}?fields=summary,description,status,comment,transition"
|
|
291
322
|
|
|
292
323
|
Post updates as comments on the Jira task:
|
|
293
324
|
- Endpoint: {{JIRA_BASE_URL}}/rest/api/3/issue/{{TASK_ID}}/comment
|
|
294
325
|
- Body: { "body": { "type": "doc", "version": 1, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "..." }] }] } }
|
|
295
326
|
|
|
296
327
|
IMPORTANT: The first comment on any task MUST include the session link: {{SESSION_URL}}
|
|
328
|
+
|
|
329
|
+
### Every agent must comment on the tracker
|
|
330
|
+
|
|
331
|
+
When an agent completes meaningful work or has important findings, they MUST post a comment on the tracker task. Not just Jane and Bart — everyone relevant:
|
|
332
|
+
- **Dennis**: After implementing — what was changed, which files, any technical decisions made
|
|
333
|
+
- **Sam**: After audit — architecture concerns found (or "clean audit, no violations")
|
|
334
|
+
- **Vera**: After writing tests — which tests were added, coverage notes
|
|
335
|
+
- **Luna**: After UI review — any UX issues found and fixes applied
|
|
336
|
+
- **Mark**: After content review — any copy changes made
|
|
337
|
+
- **Bart**: After QA + PR — PR link, test results, screenshot links
|
|
338
|
+
|
|
339
|
+
Each agent posts using their own badge format. Keep comments concise — bullet points, not essays. The tracker should tell the full story of what happened without needing to watch the dashboard.
|
|
340
|
+
|
|
341
|
+
### Required tracker actions (Jane MUST do these — not optional)
|
|
342
|
+
|
|
343
|
+
1. **On session start (INTAKE):** Post a comment: "Team session started. Session: {{SESSION_URL}}" and transition the task to "In Progress":
|
|
344
|
+
```bash
|
|
345
|
+
# Get available transitions
|
|
346
|
+
curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" "{{JIRA_BASE_URL}}/rest/api/3/issue/{{TASK_ID}}/transitions"
|
|
347
|
+
# Find the transition ID for "In Progress", then:
|
|
348
|
+
curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" -H "Content-Type: application/json" \
|
|
349
|
+
"{{JIRA_BASE_URL}}/rest/api/3/issue/{{TASK_ID}}/transitions" \
|
|
350
|
+
-d '{"transition":{"id":"<TRANSITION_ID>"}}'
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
2. **After PR is created (EXECUTION — Bart does this, not Jane):** Bart MUST post a comment with the PR link and attach it as a remote link:
|
|
354
|
+
```bash
|
|
355
|
+
curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" -H "Content-Type: application/json" \
|
|
356
|
+
"{{JIRA_BASE_URL}}/rest/api/3/issue/{{TASK_ID}}/remotelink" \
|
|
357
|
+
-d '{"object":{"url":"$PR_URL","title":"Pull Request"}}'
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
3. **On session end (SUMMARY):** Jane transitions the task to "In Review" (or equivalent) and posts a final summary comment.
|
|
361
|
+
|
|
362
|
+
Steps 1 and 3 are Jane's responsibility. Step 2 is Bart's responsibility (he creates the PR, so he links it). If any step fails, retry once. These are not suggestions — they are required protocol.
|
|
297
363
|
{{/JIRA}}
|
|
298
364
|
|
|
299
365
|
{{#GITHUB}}
|
|
300
366
|
## GITHUB ISSUES INTEGRATION
|
|
301
367
|
|
|
302
368
|
Fetch the task from GitHub Issues:
|
|
303
|
-
- Run: gh issue view {{TASK_ID}} --json title,body,state,comments
|
|
369
|
+
- Run: gh issue view {{TASK_ID}} --json title,body,state,comments,labels
|
|
304
370
|
|
|
305
371
|
Post updates as comments:
|
|
306
372
|
- Run: gh issue comment {{TASK_ID}} --body "..."
|
|
307
373
|
|
|
308
374
|
IMPORTANT: The first comment on any task MUST include the session link: {{SESSION_URL}}
|
|
375
|
+
|
|
376
|
+
### Every agent must comment on the tracker
|
|
377
|
+
|
|
378
|
+
When an agent completes meaningful work or has important findings, they MUST post a comment on the issue. Not just Jane and Bart — everyone relevant:
|
|
379
|
+
- **Dennis**: After implementing — what was changed, which files, any technical decisions made
|
|
380
|
+
- **Sam**: After audit — architecture concerns found (or "clean audit, no violations")
|
|
381
|
+
- **Vera**: After writing tests — which tests were added, coverage notes
|
|
382
|
+
- **Luna**: After UI review — any UX issues found and fixes applied
|
|
383
|
+
- **Mark**: After content review — any copy changes made
|
|
384
|
+
- **Bart**: After QA + PR — PR link, test results, screenshot links
|
|
385
|
+
|
|
386
|
+
Keep comments concise — bullet points, not essays. The tracker should tell the full story of what happened without needing to watch the dashboard.
|
|
387
|
+
|
|
388
|
+
### Required tracker actions (Jane MUST do these — not optional)
|
|
389
|
+
|
|
390
|
+
1. **On session start (INTAKE):** Post a comment: "Team session started. Session: {{SESSION_URL}}" and add an "in progress" label (if the project uses one):
|
|
391
|
+
```bash
|
|
392
|
+
gh issue comment {{TASK_ID}} --body "Team session started. Session: {{SESSION_URL}}"
|
|
393
|
+
gh issue edit {{TASK_ID}} --add-label "in progress" 2>/dev/null || true
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
2. **After PR is created (EXECUTION — Bart does this, not Jane):** The PR should reference the issue (e.g., "Closes #{{TASK_ID}}" in the PR body). Bart MUST also post a comment linking the PR:
|
|
397
|
+
```bash
|
|
398
|
+
gh issue comment {{TASK_ID}} --body "PR created: $PR_URL"
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
3. **On session end (SUMMARY):** Jane posts a final summary comment on the issue.
|
|
402
|
+
|
|
403
|
+
Steps 1 and 3 are Jane's responsibility. Step 2 is Bart's responsibility (he creates the PR, so he links it). If any step fails, retry once. These are not suggestions — they are required protocol.
|
|
309
404
|
{{/GITHUB}}
|
|
310
405
|
|
|
311
406
|
---
|
|
312
407
|
|
|
313
408
|
## INTAKE
|
|
314
409
|
|
|
410
|
+
**If a CREATE TASK section exists above**, Jane MUST execute it NOW — before fetching, before assessing, before anything else. Create the task, announce the new ID to the team, output the TASK_ID line, and set the status to "In Progress". Only then continue with the steps below using the newly created task ID.
|
|
411
|
+
|
|
315
412
|
{{#LINEAR}}
|
|
316
413
|
Fetch the task from Linear and print the title, description, current state, and any existing comments.
|
|
317
414
|
{{/LINEAR}}
|
|
@@ -359,6 +456,8 @@ Based on what you find, Jane determines the starting point:
|
|
|
359
456
|
- Branch exists but no PR: Review what's implemented, continue from EXECUTION.
|
|
360
457
|
- PR exists: Review the PR status, continue accordingly.
|
|
361
458
|
|
|
459
|
+
MANDATORY: At the end of intake, Jane MUST post the first comment on the tracker task ("Team session started. Session: {{SESSION_URL}}") and move the task to "In Progress" status. See the tracker integration section above for exact API calls. Do this BEFORE moving to the next phase.
|
|
460
|
+
|
|
362
461
|
IMPORTANT: At the end of the intake, Jane MUST provide a short title for this session on its own line in this exact format:
|
|
363
462
|
SESSION_TITLE: <4-8 word title>
|
|
364
463
|
Example: SESSION_TITLE: Fix checkout total calculation
|
|
@@ -401,6 +500,18 @@ After all agents present, Jane asks: "Any objections or additions?" Then declare
|
|
|
401
500
|
|
|
402
501
|
## SUMMARY
|
|
403
502
|
|
|
503
|
+
Jane MUST verify the task status is up to date. Throughout the session, Jane is responsible for keeping the tracker status current:
|
|
504
|
+
- **INTAKE**: "In Progress" (set when session starts or task is created)
|
|
505
|
+
- **EXECUTION complete**: Still "In Progress" (work is happening)
|
|
506
|
+
- **SUMMARY**: Transition to "In Review" (below)
|
|
507
|
+
|
|
508
|
+
Jane MUST complete these tracker actions before ending the session:
|
|
509
|
+
1. Verify Bart posted the PR link to the tracker. If he didn't, do it now.
|
|
510
|
+
2. Transition the task to "In Review" (or equivalent status) in the tracker.
|
|
511
|
+
3. Post a final summary comment on the tracker task with what was accomplished.
|
|
512
|
+
|
|
513
|
+
If any of these were already done during execution, skip that step. But verify — don't assume.
|
|
514
|
+
|
|
404
515
|
Print a final console summary:
|
|
405
516
|
echo "Team Session Complete."
|
|
406
517
|
echo "Task: {{TASK_ID}}"
|
package/cli/agent-prompts.mjs
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
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
|
-
IMPORTANT: At the end of your intake summary, you MUST provide a short title for this session on its own line in this exact format:
|
|
16
|
-
SESSION_TITLE: <4-8 word title>
|
|
17
|
-
Example: SESSION_TITLE: Fix checkout total calculation
|
|
18
|
-
This title will be displayed in the dashboard navbar, so keep it short and descriptive.`,
|
|
19
|
-
},
|
|
20
|
-
|
|
21
|
-
brainstorm: {
|
|
22
|
-
_default: (agent) => `Share your perspective on this task from your role as ${agent.role}.
|
|
23
|
-
Focus on: ${agent.brainstorm?.focus || "your area of expertise"}.
|
|
24
|
-
Use [${agent.brainstorm?.tag || "SAY"}] tag.
|
|
25
|
-
Be specific. If you disagree with anything in the conversation so far, use [ARGUE] and explain why.
|
|
26
|
-
If you agree, still add value — don't just say "I agree."`,
|
|
27
|
-
},
|
|
28
|
-
|
|
29
|
-
planning: {
|
|
30
|
-
_default: (agent) => `Present your plan for this task from your role as ${agent.role}.
|
|
31
|
-
Your expected output: ${agent.planning || "Your role-specific plan."}
|
|
32
|
-
Be concrete — name files, approaches, specific concerns.`,
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
"planning-review": {
|
|
36
|
-
_default: () => `Review all the plans presented by the team.
|
|
37
|
-
If you see problems, conflicts, or missing considerations, use [ARGUE] and explain.
|
|
38
|
-
If everything looks good, acknowledge with [AGREE] and note why.`,
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
execution: {
|
|
42
|
-
_default: (agent) => {
|
|
43
|
-
if (!agent.execution) return "This task has no execution step for your role. Skip this phase.";
|
|
44
|
-
return `Complete your execution tasks:
|
|
45
|
-
${agent.execution.tasks.map((t, i) => `${i + 1}. ${t}`).join("\n")}
|
|
46
|
-
|
|
47
|
-
Work carefully. Use tools as needed. Narrate what you're doing.`;
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
|
|
51
|
-
"execution-review": {
|
|
52
|
-
_default: (agent) => `Review the changes made so far.
|
|
53
|
-
From your perspective as ${agent.role}:
|
|
54
|
-
${agent.groundRules || "Review for issues in your area of expertise."}
|
|
55
|
-
If you find issues, describe them specifically with file paths and line numbers.
|
|
56
|
-
If everything looks good, confirm with [AGREE].`,
|
|
57
|
-
},
|
|
58
|
-
|
|
59
|
-
review: {
|
|
60
|
-
_default: (agent) => `The session is wrapping up. Provide your final notes:
|
|
61
|
-
- Any remaining concerns?
|
|
62
|
-
- Anything the team should watch out for?
|
|
63
|
-
- Overall assessment from your perspective as ${agent.role}.
|
|
64
|
-
Keep it brief.`,
|
|
65
|
-
Jane: `The session is wrapping up. Provide the final summary:
|
|
66
|
-
1. What was accomplished
|
|
67
|
-
2. Any remaining concerns from the team
|
|
68
|
-
3. What the next steps should be
|
|
69
|
-
Keep it concise.`,
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export function buildAgentPrompt(agent, { phase, task, project, conversation, inboxUrl, sessionUrl }) {
|
|
74
|
-
const parts = [];
|
|
75
|
-
|
|
76
|
-
// Identity
|
|
77
|
-
parts.push(`You are ${agent.name}, ${agent.role} on a software development team.`);
|
|
78
|
-
parts.push("");
|
|
79
|
-
|
|
80
|
-
// Role
|
|
81
|
-
parts.push("## Your Role");
|
|
82
|
-
parts.push(agent.description);
|
|
83
|
-
if (agent.groundRules) parts.push(`\nGround rules: ${agent.groundRules}`);
|
|
84
|
-
if (agent.codePrinciple) parts.push(`\nCode principle: ${agent.codePrinciple}`);
|
|
85
|
-
parts.push("");
|
|
86
|
-
|
|
87
|
-
// Communication
|
|
88
|
-
parts.push("## Communication");
|
|
89
|
-
parts.push(`Prefix your output with your badge: ${agent.badge}`);
|
|
90
|
-
parts.push("Use these tags in your messages:");
|
|
91
|
-
parts.push("- [SAY] — Normal statements, announcements, questions");
|
|
92
|
-
parts.push("- [THINK] — Internal reasoning, analysis");
|
|
93
|
-
parts.push("- [ACT] — When using tools or taking actions");
|
|
94
|
-
parts.push("- [ARGUE] — When you disagree or see problems");
|
|
95
|
-
parts.push("- [AGREE] — When you agree (but still add value)");
|
|
96
|
-
parts.push("");
|
|
97
|
-
|
|
98
|
-
// Task
|
|
99
|
-
parts.push("## Task");
|
|
100
|
-
parts.push(`Task ID: ${task.id}`);
|
|
101
|
-
if (task.link) parts.push(`Task link: ${task.link}`);
|
|
102
|
-
if (task.description) parts.push(`\nDescription: ${task.description}`);
|
|
103
|
-
parts.push("");
|
|
104
|
-
|
|
105
|
-
// Project context
|
|
106
|
-
if (project) {
|
|
107
|
-
const context = generateContext(project);
|
|
108
|
-
parts.push("## Project Context");
|
|
109
|
-
parts.push(context);
|
|
110
|
-
parts.push("");
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Conversation so far
|
|
114
|
-
if (conversation && conversation.length > 0) {
|
|
115
|
-
parts.push("## Conversation So Far");
|
|
116
|
-
for (const msg of conversation) {
|
|
117
|
-
if (msg.type === "phase") {
|
|
118
|
-
parts.push(`\n--- ${msg.phase} ---\n`);
|
|
119
|
-
} else if (msg.agent && msg.message) {
|
|
120
|
-
parts.push(`${msg.agent} [${msg.tag || "SAY"}]: ${msg.message}`);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
parts.push("");
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Phase instructions
|
|
127
|
-
parts.push(`## Current Phase: ${phase.toUpperCase()}`);
|
|
128
|
-
const phaseConfig = PHASE_INSTRUCTIONS[phase] || {};
|
|
129
|
-
const agentInstr = phaseConfig[agent.name] || (phaseConfig._default ? phaseConfig._default(agent) : "Contribute from your area of expertise.");
|
|
130
|
-
parts.push(agentInstr);
|
|
131
|
-
parts.push("");
|
|
132
|
-
|
|
133
|
-
// Inbox (Jane only)
|
|
134
|
-
if (agent.name === "Jane" && inboxUrl) {
|
|
135
|
-
parts.push("## User Messages");
|
|
136
|
-
parts.push(`Before starting, check for user messages: \`curl -s ${inboxUrl}\``);
|
|
137
|
-
parts.push("If the response is not empty ([]), read the messages and incorporate the user's input.");
|
|
138
|
-
parts.push("");
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Session URL
|
|
142
|
-
if (sessionUrl) {
|
|
143
|
-
parts.push(`Session: ${sessionUrl}`);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Time
|
|
147
|
-
const now = new Date();
|
|
148
|
-
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" })}`);
|
|
149
|
-
|
|
150
|
-
return parts.join("\n");
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Get tool list for an agent
|
|
154
|
-
const AGENT_TOOLS = {
|
|
155
|
-
Jane: ["Bash", "Read"],
|
|
156
|
-
Dennis: ["Bash", "Read", "Edit", "Write", "Glob", "Grep"],
|
|
157
|
-
Sam: ["Read", "Glob", "Grep"],
|
|
158
|
-
Bart: ["Bash", "Read", "Glob", "Grep"],
|
|
159
|
-
Vera: ["Bash", "Read", "Edit", "Write", "Glob", "Grep"],
|
|
160
|
-
Luna: ["Read", "Glob"],
|
|
161
|
-
Mark: ["Read", "Glob"],
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
export function getAgentTools(agentName) {
|
|
165
|
-
return AGENT_TOOLS[agentName] || ["Bash", "Read", "Edit", "Write", "Glob", "Grep"];
|
|
166
|
-
}
|
package/cli/agent-runner.mjs
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
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
|
-
}
|