@kendoo.agentdesk/agentdesk 0.13.1 → 0.14.1
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 +30 -0
- package/cli/config.mjs +9 -0
- package/cli/daemon.mjs +2 -1
- package/cli/orchestrator.mjs +28 -7
- package/cli/prompt.mjs +20 -0
- package/package.json +1 -1
- package/prompts/phased.md +8 -1
- package/prompts/team.md +8 -1
package/README.md
CHANGED
|
@@ -141,6 +141,36 @@ Add project-specific rules that all agents follow. Set via Web UI or in `.agentd
|
|
|
141
141
|
|
|
142
142
|
Instructions are injected into the team prompt. Use them for project conventions that go beyond what `CLAUDE.md` covers (e.g., tracker workflow rules, PR policies, agent coordination preferences).
|
|
143
143
|
|
|
144
|
+
### Screenshots
|
|
145
|
+
|
|
146
|
+
Screenshots are captured by default for UI-related tasks. Bart captures desktop and mobile screenshots before creating the PR and attaches them to the task tracker.
|
|
147
|
+
|
|
148
|
+
To disable, set `screenshots` to `false` in `.agentdesk.json` or toggle it off in project settings:
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"screenshots": false
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
You can also toggle screenshots per session when starting a task from the Web UI.
|
|
157
|
+
|
|
158
|
+
### Model per phase
|
|
159
|
+
|
|
160
|
+
In phased mode (`INTAKE → PLAN → EXECUTION`), you can pick a different Claude model per phase from project settings. Useful when you want a stronger model for planning and implementation but a cheaper one for intake.
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"phaseModels": {
|
|
165
|
+
"INTAKE": "sonnet",
|
|
166
|
+
"PLAN": "opus",
|
|
167
|
+
"EXECUTION": "opus"
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Valid values: `"default"` (Claude Code's default), `"opus"`, `"sonnet"`, `"haiku"`. Non-phased runs use the `EXECUTION` model.
|
|
173
|
+
|
|
144
174
|
## How It Works
|
|
145
175
|
|
|
146
176
|
All agents collaborate in a single Claude process — each with distinct roles, ground rules, and areas of expertise.
|
package/cli/config.mjs
CHANGED
|
@@ -39,6 +39,15 @@ const DEFAULTS = {
|
|
|
39
39
|
// Each entry: { name, role, when, how }
|
|
40
40
|
projectAgents: [],
|
|
41
41
|
|
|
42
|
+
// Capture screenshots for UI tasks (default: on)
|
|
43
|
+
screenshots: true,
|
|
44
|
+
|
|
45
|
+
// Model per phase — override Claude Code's default per phase.
|
|
46
|
+
// Keys: INTAKE, PLAN, EXECUTION. Values: "default" | "opus" | "sonnet" | "haiku".
|
|
47
|
+
// Missing entry or "default" = use Claude Code's default model.
|
|
48
|
+
// Example: { "PLAN": "opus", "EXECUTION": "opus" }
|
|
49
|
+
phaseModels: {},
|
|
50
|
+
|
|
42
51
|
// Extra prompt instructions appended to the team prompt
|
|
43
52
|
instructions: null,
|
|
44
53
|
};
|
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, phased }) {
|
|
316
|
+
async function handleStartSession({ sessionId, projectId, taskId: remoteTaskId, prompt, phased, screenshots: screenshotsOverride }) {
|
|
317
317
|
// Validate project against local allowlist
|
|
318
318
|
const project = projects.find(p => p.id === projectId);
|
|
319
319
|
if (!project) {
|
|
@@ -343,6 +343,7 @@ export async function runDaemon() {
|
|
|
343
343
|
const projectEnv = loadDotEnv(project.path);
|
|
344
344
|
const projectApiKey = projectEnv.AGENTDESK_API_KEY || process.env.AGENTDESK_API_KEY || apiKey;
|
|
345
345
|
const config = await loadConfig(project.path, { apiKey: projectApiKey, serverUrl: agentdeskServer, projectName: project.name });
|
|
346
|
+
if (screenshotsOverride !== undefined) config.screenshots = screenshotsOverride;
|
|
346
347
|
const tracker = config.tracker || null;
|
|
347
348
|
|
|
348
349
|
// Use provided task ID or generate from description
|
package/cli/orchestrator.mjs
CHANGED
|
@@ -44,6 +44,21 @@ function timestamp() {
|
|
|
44
44
|
.map(n => String(n).padStart(2, "0")).join(":");
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// Resolve --model args for a given phase based on project settings.
|
|
48
|
+
// phaseModels is { INTAKE?, PLAN?, EXECUTION? } with values "opus"|"sonnet"|"haiku"|"default".
|
|
49
|
+
// Returns [] when no override (so Claude Code picks its default).
|
|
50
|
+
function modelArgsForPhase(phase, phaseModels) {
|
|
51
|
+
const choice = phaseModels?.[phase];
|
|
52
|
+
if (!choice || choice === "default") return [];
|
|
53
|
+
// Claude Code accepts short aliases: opus, sonnet, haiku
|
|
54
|
+
return ["--model", choice];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Display label for a phase's model ("opus"|"sonnet"|"haiku"|"default").
|
|
58
|
+
function modelLabelForPhase(phase, phaseModels) {
|
|
59
|
+
return phaseModels?.[phase] || "default";
|
|
60
|
+
}
|
|
61
|
+
|
|
47
62
|
export async function runOrchestrator({
|
|
48
63
|
taskId, taskLink, description, createTask, tracker, config,
|
|
49
64
|
project, team, teamSections, sessionUrl, cwd,
|
|
@@ -77,11 +92,16 @@ export async function runOrchestrator({
|
|
|
77
92
|
cliVersion: CLI_VERSION,
|
|
78
93
|
});
|
|
79
94
|
|
|
80
|
-
|
|
95
|
+
const initialPhase = soloAgent ? "EXECUTION" : "INTAKE";
|
|
96
|
+
// Non-phased single run spans all phases in one process — use EXECUTION's model
|
|
97
|
+
// as the representative choice (matches where the heavy lifting happens).
|
|
98
|
+
const representativeModel = modelLabelForPhase("EXECUTION", config?.phaseModels);
|
|
99
|
+
emit({ type: "phase:change", phase: initialPhase, model: representativeModel });
|
|
81
100
|
|
|
101
|
+
const modelArgs = modelArgsForPhase("EXECUTION", config?.phaseModels);
|
|
82
102
|
const child = spawn(
|
|
83
103
|
"claude",
|
|
84
|
-
["-p", fullPrompt, "--allowedTools", "Bash,Read,Edit,Write,Glob,Grep", "--verbose", "--output-format", "stream-json"],
|
|
104
|
+
["-p", fullPrompt, ...modelArgs, "--allowedTools", "Bash,Read,Edit,Write,Glob,Grep", "--verbose", "--output-format", "stream-json"],
|
|
85
105
|
{ stdio: ["pipe", "pipe", "inherit"], shell: false, env, cwd }
|
|
86
106
|
);
|
|
87
107
|
child.stdin.end();
|
|
@@ -89,7 +109,7 @@ export async function runOrchestrator({
|
|
|
89
109
|
const { parseLine } = createStreamParser({
|
|
90
110
|
teamNames: soloAgent ? [soloAgent] : teamSections.names,
|
|
91
111
|
callbacks: {
|
|
92
|
-
onPhaseChange({ phase }) { emit({ type: "phase:change", phase }); },
|
|
112
|
+
onPhaseChange({ phase }) { emit({ type: "phase:change", phase, model: representativeModel }); },
|
|
93
113
|
onAgentMessage({ agent, tag, message }) { emit({ type: "agent:message", agent, tag, message }); },
|
|
94
114
|
onToolUse({ agent, tool, description }) { totalSteps++; emit({ type: "tool:use", agent, tool, description }); },
|
|
95
115
|
onToolResult({ success, summary }) { emit({ type: "tool:result", success, summary }); },
|
|
@@ -174,10 +194,10 @@ export async function runOrchestrator({
|
|
|
174
194
|
|
|
175
195
|
// --- Phased orchestrator: runs 3 sequential Claude processes ---
|
|
176
196
|
|
|
177
|
-
async function runSinglePhase({ prompt, cwd, env, teamNames, emit }) {
|
|
197
|
+
async function runSinglePhase({ prompt, cwd, env, teamNames, emit, modelArgs = [] }) {
|
|
178
198
|
const child = spawn(
|
|
179
199
|
"claude",
|
|
180
|
-
["-p", prompt, "--allowedTools", "Bash,Read,Edit,Write,Glob,Grep", "--verbose", "--output-format", "stream-json"],
|
|
200
|
+
["-p", prompt, ...modelArgs, "--allowedTools", "Bash,Read,Edit,Write,Glob,Grep", "--verbose", "--output-format", "stream-json"],
|
|
181
201
|
{ stdio: ["pipe", "pipe", "inherit"], shell: false, env, cwd }
|
|
182
202
|
);
|
|
183
203
|
child.stdin.end();
|
|
@@ -252,7 +272,7 @@ export async function runPhasedOrchestrator({
|
|
|
252
272
|
if (existsSync(sessionMemoryPath)) sessionMemory = readFileSync(sessionMemoryPath, "utf-8").trim();
|
|
253
273
|
} catch {}
|
|
254
274
|
|
|
255
|
-
emit({ type: "phase:change", phase });
|
|
275
|
+
emit({ type: "phase:change", phase, model: modelLabelForPhase(phase, config?.phaseModels) });
|
|
256
276
|
|
|
257
277
|
const prompt = buildPhasedPrompt({
|
|
258
278
|
phase, taskId, taskLink, description,
|
|
@@ -260,7 +280,8 @@ export async function runPhasedOrchestrator({
|
|
|
260
280
|
tracker, config, project, teamSections, sessionUrl, cwd, sessionMemory,
|
|
261
281
|
});
|
|
262
282
|
|
|
263
|
-
const
|
|
283
|
+
const modelArgs = modelArgsForPhase(phase, config?.phaseModels);
|
|
284
|
+
const result = await runSinglePhase({ prompt, cwd, env, teamNames, emit, modelArgs });
|
|
264
285
|
|
|
265
286
|
// Allow daemon to track the child process for cancellation
|
|
266
287
|
if (onChild) onChild(result.child);
|
package/cli/prompt.mjs
CHANGED
|
@@ -77,6 +77,16 @@ export function buildPrompt({ taskId, taskLink, description, createTask, tracker
|
|
|
77
77
|
prompt += createInstr;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
// Screenshots toggle — default on if not explicitly set to false
|
|
81
|
+
const screenshotsEnabled = config.screenshots !== false;
|
|
82
|
+
if (screenshotsEnabled) {
|
|
83
|
+
prompt = prompt.replace(/\{\{#SCREENSHOTS_ENABLED\}\}([\s\S]*?)\{\{\/SCREENSHOTS_ENABLED\}\}/g, "$1");
|
|
84
|
+
prompt = prompt.replace(/\{\{#SCREENSHOTS_DISABLED\}\}[\s\S]*?\{\{\/SCREENSHOTS_DISABLED\}\}/g, "");
|
|
85
|
+
} else {
|
|
86
|
+
prompt = prompt.replace(/\{\{#SCREENSHOTS_ENABLED\}\}[\s\S]*?\{\{\/SCREENSHOTS_ENABLED\}\}/g, "");
|
|
87
|
+
prompt = prompt.replace(/\{\{#SCREENSHOTS_DISABLED\}\}([\s\S]*?)\{\{\/SCREENSHOTS_DISABLED\}\}/g, "$1");
|
|
88
|
+
}
|
|
89
|
+
|
|
80
90
|
// Tracker integration — enable the matching section, strip the rest
|
|
81
91
|
const trackers = ["LINEAR", "JIRA", "GITHUB"];
|
|
82
92
|
for (const t of trackers) {
|
|
@@ -395,6 +405,16 @@ export function buildPhasedPrompt({ phase, taskId, taskLink, description, create
|
|
|
395
405
|
prompt = prompt.replace(/\{\{#TASK_DESCRIPTION\}\}[\s\S]*?\{\{\/TASK_DESCRIPTION\}\}/g, "");
|
|
396
406
|
}
|
|
397
407
|
|
|
408
|
+
// Screenshots toggle
|
|
409
|
+
const phasedScreenshots = config.screenshots !== false;
|
|
410
|
+
if (phasedScreenshots) {
|
|
411
|
+
prompt = prompt.replace(/\{\{#SCREENSHOTS_ENABLED\}\}([\s\S]*?)\{\{\/SCREENSHOTS_ENABLED\}\}/g, "$1");
|
|
412
|
+
prompt = prompt.replace(/\{\{#SCREENSHOTS_DISABLED\}\}[\s\S]*?\{\{\/SCREENSHOTS_DISABLED\}\}/g, "");
|
|
413
|
+
} else {
|
|
414
|
+
prompt = prompt.replace(/\{\{#SCREENSHOTS_ENABLED\}\}[\s\S]*?\{\{\/SCREENSHOTS_ENABLED\}\}/g, "");
|
|
415
|
+
prompt = prompt.replace(/\{\{#SCREENSHOTS_DISABLED\}\}([\s\S]*?)\{\{\/SCREENSHOTS_DISABLED\}\}/g, "$1");
|
|
416
|
+
}
|
|
417
|
+
|
|
398
418
|
// Tracker integration
|
|
399
419
|
const trackers = ["LINEAR", "JIRA", "GITHUB"];
|
|
400
420
|
for (const t of trackers) {
|
package/package.json
CHANGED
package/prompts/phased.md
CHANGED
|
@@ -274,9 +274,16 @@ Speaking order:
|
|
|
274
274
|
- Do NOT modify files unrelated to the task.
|
|
275
275
|
- When posting screenshots to Linear, post them as a SEPARATE comment.
|
|
276
276
|
|
|
277
|
+
{{#SCREENSHOTS_ENABLED}}
|
|
277
278
|
## SCREENSHOTS
|
|
278
279
|
|
|
279
|
-
When a task involves UI changes, Bart
|
|
280
|
+
Screenshots are **enabled** for this project. When a task involves UI changes, Bart MUST capture screenshots after code changes are finalized, before creating the PR. Adapt auth to the actual project.
|
|
281
|
+
{{/SCREENSHOTS_ENABLED}}
|
|
282
|
+
{{#SCREENSHOTS_DISABLED}}
|
|
283
|
+
## SCREENSHOTS
|
|
284
|
+
|
|
285
|
+
Screenshots are **disabled** for this project. Do NOT capture screenshots unless the user explicitly requests them.
|
|
286
|
+
{{/SCREENSHOTS_DISABLED}}
|
|
280
287
|
|
|
281
288
|
{{#LINEAR}}
|
|
282
289
|
Upload via Linear's fileUpload mutation, then post image URLs as a separate comment.
|
package/prompts/team.md
CHANGED
|
@@ -56,9 +56,10 @@ This is a hard constraint that MUST NOT be violated under any circumstances:
|
|
|
56
56
|
- Do NOT modify files unrelated to the task.
|
|
57
57
|
- When posting screenshots to Linear, post them as a SEPARATE comment — never inside badge blocks.
|
|
58
58
|
|
|
59
|
+
{{#SCREENSHOTS_ENABLED}}
|
|
59
60
|
## SCREENSHOTS
|
|
60
61
|
|
|
61
|
-
When a task involves UI changes, capture screenshots
|
|
62
|
+
Screenshots are **enabled** for this project. When a task involves UI changes, Bart MUST capture screenshots — this is not optional.
|
|
62
63
|
|
|
63
64
|
- **Jane** flags UI tasks during planning.
|
|
64
65
|
- **Luna** defines what to capture (pages, viewports).
|
|
@@ -75,6 +76,12 @@ kill $DEV_PID 2>/dev/null
|
|
|
75
76
|
```
|
|
76
77
|
|
|
77
78
|
Adapt auth to the actual project — no placeholders. If the project uses OAuth with no test credentials, skip screenshots with an explanation.
|
|
79
|
+
{{/SCREENSHOTS_ENABLED}}
|
|
80
|
+
{{#SCREENSHOTS_DISABLED}}
|
|
81
|
+
## SCREENSHOTS
|
|
82
|
+
|
|
83
|
+
Screenshots are **disabled** for this project. Do NOT capture screenshots unless the user explicitly requests them in the task description.
|
|
84
|
+
{{/SCREENSHOTS_DISABLED}}
|
|
78
85
|
|
|
79
86
|
### Uploading screenshots
|
|
80
87
|
|