@kendoo.agentdesk/agentdesk 0.9.11 → 0.9.13
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/cli/agents.mjs +189 -6
- package/cli/daemon.mjs +1 -1
- package/cli/team.mjs +1 -1
- package/package.json +2 -2
- package/prompts/team.md +82 -0
package/cli/agents.mjs
CHANGED
|
@@ -4,8 +4,8 @@ export const BUILT_IN_AGENTS = {
|
|
|
4
4
|
Jane: {
|
|
5
5
|
badge: "●● JANE ●●",
|
|
6
6
|
role: "Product Analyst / Team Lead",
|
|
7
|
-
description: "leads the session, clarifies requirements, coordinates the team, manages tracker status",
|
|
8
|
-
groundRules: "Jane focuses on requirements, scope, and coordination — she does not read code. She creates tracker tasks when needed, manages status transitions,
|
|
7
|
+
description: "leads the session, clarifies requirements, coordinates the team, manages tracker status, decomposes large tasks into subtasks",
|
|
8
|
+
groundRules: "Jane focuses on requirements, scope, and coordination — she does not read code. She creates tracker tasks when needed, manages status transitions, posts the session start/end comments, and decomposes large features into subtasks (basic vs deferred).",
|
|
9
9
|
planning: "Requirements: what we're building, acceptance criteria, scope. Flags UI tasks for Luna.",
|
|
10
10
|
execution: {
|
|
11
11
|
step: "Jane wraps up",
|
|
@@ -202,10 +202,153 @@ export function resolveTeam(config) {
|
|
|
202
202
|
return team;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Return tracker-specific command snippets for execution steps.
|
|
207
|
+
*/
|
|
208
|
+
function trackerCommands(tracker, config) {
|
|
209
|
+
if (!tracker) return null;
|
|
210
|
+
|
|
211
|
+
if (tracker === "linear") {
|
|
212
|
+
return {
|
|
213
|
+
postPrLink: [
|
|
214
|
+
'Post PR link on Linear using attachmentCreate:',
|
|
215
|
+
'```',
|
|
216
|
+
'curl -s -X POST https://api.linear.app/graphql \\',
|
|
217
|
+
' -H "Authorization: $LINEAR_API_KEY" -H "Content-Type: application/json" \\',
|
|
218
|
+
' -d \'{"query":"mutation { attachmentCreate(input: { issueId: \\"$ISSUE_ID\\", title: \\"Pull Request\\", url: \\"$PR_URL\\" }) { success } }"}\'',
|
|
219
|
+
'```',
|
|
220
|
+
].join('\n'),
|
|
221
|
+
attachScreenshots: [
|
|
222
|
+
'Upload each screenshot via Linear fileUpload mutation, then post a comment with the image URLs:',
|
|
223
|
+
'```bash',
|
|
224
|
+
'for f in screenshots/*.png; do',
|
|
225
|
+
' UPLOAD=$(curl -s -X POST https://api.linear.app/graphql \\',
|
|
226
|
+
' -H "Authorization: $LINEAR_API_KEY" -H "Content-Type: application/json" \\',
|
|
227
|
+
' -d \'{"query":"mutation { fileUpload(contentType: \\"image/png\\", filename: \\"\'$(basename "$f")\'\\", size: \'$(stat -f%z "$f")\') { uploadFile { uploadUrl assetUrl headers { key value } } } }"}\')',
|
|
228
|
+
' # Extract uploadUrl, assetUrl, headers from $UPLOAD — PUT the file to uploadUrl, then collect assetUrl',
|
|
229
|
+
'done',
|
|
230
|
+
'# Post a comment with all image URLs as markdown images',
|
|
231
|
+
'```',
|
|
232
|
+
].join('\n'),
|
|
233
|
+
agentComment: [
|
|
234
|
+
'Post a comment on Linear with key findings:',
|
|
235
|
+
'```',
|
|
236
|
+
'curl -s -X POST https://api.linear.app/graphql \\',
|
|
237
|
+
' -H "Authorization: $LINEAR_API_KEY" -H "Content-Type: application/json" \\',
|
|
238
|
+
' -d \'{"query":"mutation { commentCreate(input: { issueId: \\"$ISSUE_ID\\", body: \\"<COMMENT>\\" }) { success } }"}\'',
|
|
239
|
+
'```',
|
|
240
|
+
].join('\n'),
|
|
241
|
+
transitionToReview: [
|
|
242
|
+
'Transition to "In Review" on Linear:',
|
|
243
|
+
'```',
|
|
244
|
+
'curl -s -X POST https://api.linear.app/graphql \\',
|
|
245
|
+
' -H "Authorization: $LINEAR_API_KEY" -H "Content-Type: application/json" \\',
|
|
246
|
+
' -d \'{"query":"mutation { issueUpdate(id: \\"$ISSUE_ID\\", input: { stateId: \\"$IN_REVIEW_STATE_ID\\" }) { success } }"}\'',
|
|
247
|
+
'```',
|
|
248
|
+
'Find the "In Review" state ID from workflowStates first.',
|
|
249
|
+
].join('\n'),
|
|
250
|
+
postSummary: [
|
|
251
|
+
'Post final summary comment on Linear (use commentCreate mutation above).',
|
|
252
|
+
].join('\n'),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const baseUrl = config?.jira?.baseUrl || '$JIRA_BASE_URL';
|
|
257
|
+
if (tracker === "jira") {
|
|
258
|
+
return {
|
|
259
|
+
postPrLink: [
|
|
260
|
+
'Post PR link on Jira as a remote link:',
|
|
261
|
+
'```bash',
|
|
262
|
+
`curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" -H "Content-Type: application/json" \\`,
|
|
263
|
+
` "${baseUrl}/rest/api/3/issue/$TASK_ID/remotelink" \\`,
|
|
264
|
+
` -d '{"object":{"url":"$PR_URL","title":"Pull Request"}}'`,
|
|
265
|
+
'```',
|
|
266
|
+
].join('\n'),
|
|
267
|
+
attachScreenshots: [
|
|
268
|
+
'Upload each screenshot as a Jira attachment:',
|
|
269
|
+
'```bash',
|
|
270
|
+
`for f in screenshots/*.png; do`,
|
|
271
|
+
` curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \\`,
|
|
272
|
+
` -H "X-Atlassian-Token: no-check" \\`,
|
|
273
|
+
` -F "file=@$f" \\`,
|
|
274
|
+
` "${baseUrl}/rest/api/3/issue/$TASK_ID/attachments"`,
|
|
275
|
+
`done`,
|
|
276
|
+
'```',
|
|
277
|
+
].join('\n'),
|
|
278
|
+
agentComment: [
|
|
279
|
+
'Post a comment on Jira with key findings:',
|
|
280
|
+
'```bash',
|
|
281
|
+
`curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" -H "Content-Type: application/json" \\`,
|
|
282
|
+
` "${baseUrl}/rest/api/3/issue/$TASK_ID/comment" \\`,
|
|
283
|
+
` -d '{"body":{"type":"doc","version":1,"content":[{"type":"paragraph","content":[{"type":"text","text":"<COMMENT>"}]}]}}'`,
|
|
284
|
+
'```',
|
|
285
|
+
].join('\n'),
|
|
286
|
+
transitionToReview: [
|
|
287
|
+
'Transition task to "In Review" on Jira:',
|
|
288
|
+
'```bash',
|
|
289
|
+
`# First get available transitions:`,
|
|
290
|
+
`curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" "${baseUrl}/rest/api/3/issue/$TASK_ID/transitions"`,
|
|
291
|
+
`# Then transition (find the "In Review" transition ID):`,
|
|
292
|
+
`curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" -H "Content-Type: application/json" \\`,
|
|
293
|
+
` "${baseUrl}/rest/api/3/issue/$TASK_ID/transitions" \\`,
|
|
294
|
+
` -d '{"transition":{"id":"<TRANSITION_ID>"}}'`,
|
|
295
|
+
'```',
|
|
296
|
+
].join('\n'),
|
|
297
|
+
postSummary: [
|
|
298
|
+
`Post final summary comment on Jira (use comment endpoint above).`,
|
|
299
|
+
].join('\n'),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (tracker === "github") {
|
|
304
|
+
return {
|
|
305
|
+
postPrLink: [
|
|
306
|
+
'Reference the issue in the PR body ("Closes #$TASK_ID") and post a comment:',
|
|
307
|
+
'```bash',
|
|
308
|
+
'gh issue comment $TASK_ID --body "PR created: $PR_URL"',
|
|
309
|
+
'```',
|
|
310
|
+
].join('\n'),
|
|
311
|
+
attachScreenshots: [
|
|
312
|
+
'Upload screenshots and post them in a GitHub issue comment:',
|
|
313
|
+
'```bash',
|
|
314
|
+
'BODY=""',
|
|
315
|
+
'for f in screenshots/*.png; do',
|
|
316
|
+
' BASE64=$(base64 < "$f")',
|
|
317
|
+
' BODY="$BODY\\n"',
|
|
318
|
+
'done',
|
|
319
|
+
'gh issue comment $TASK_ID --body "$BODY"',
|
|
320
|
+
'```',
|
|
321
|
+
].join('\n'),
|
|
322
|
+
agentComment: [
|
|
323
|
+
'Post a comment on GitHub with key findings:',
|
|
324
|
+
'```bash',
|
|
325
|
+
'gh issue comment $TASK_ID --body "<COMMENT>"',
|
|
326
|
+
'```',
|
|
327
|
+
].join('\n'),
|
|
328
|
+
transitionToReview: [
|
|
329
|
+
'Add "in review" label on GitHub:',
|
|
330
|
+
'```bash',
|
|
331
|
+
'gh issue edit $TASK_ID --remove-label "in progress" --add-label "in review" 2>/dev/null || true',
|
|
332
|
+
'```',
|
|
333
|
+
].join('\n'),
|
|
334
|
+
postSummary: [
|
|
335
|
+
'Post final summary comment on GitHub:',
|
|
336
|
+
'```bash',
|
|
337
|
+
'gh issue comment $TASK_ID --body "<SUMMARY>"',
|
|
338
|
+
'```',
|
|
339
|
+
].join('\n'),
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
|
|
205
346
|
/**
|
|
206
347
|
* Generate the dynamic parts of the prompt from the team.
|
|
348
|
+
* @param {Array} team - resolved team array
|
|
349
|
+
* @param {Object} opts - optional { tracker, config } for tracker-specific execution steps
|
|
207
350
|
*/
|
|
208
|
-
export function generateTeamPrompt(team) {
|
|
351
|
+
export function generateTeamPrompt(team, opts = {}) {
|
|
209
352
|
const count = team.length;
|
|
210
353
|
const agentWord = count === 1 ? "one AI agent" : `${count} AI agents`;
|
|
211
354
|
|
|
@@ -246,15 +389,55 @@ export function generateTeamPrompt(team) {
|
|
|
246
389
|
`${a.badge} ${a.planning}`
|
|
247
390
|
).join("\n");
|
|
248
391
|
|
|
249
|
-
// Execution steps
|
|
392
|
+
// Execution steps — inject tracker-specific commands
|
|
393
|
+
const cmds = trackerCommands(opts.tracker, opts.config);
|
|
250
394
|
const execAgents = team
|
|
251
395
|
.filter(a => a.execution)
|
|
252
396
|
.sort((a, b) => a.execution.order - b.execution.order);
|
|
253
397
|
let stepNum = 1;
|
|
254
398
|
const execSteps = [];
|
|
255
399
|
for (const a of execAgents) {
|
|
256
|
-
|
|
257
|
-
|
|
400
|
+
let tasks = [...a.execution.tasks];
|
|
401
|
+
|
|
402
|
+
if (cmds) {
|
|
403
|
+
// Bart: expand PR link + tracker comment tasks with actual commands
|
|
404
|
+
if (a.name === "Bart") {
|
|
405
|
+
tasks = tasks.map(t => {
|
|
406
|
+
if (/PR link/i.test(t) || /Post PR.*tracker/i.test(t)) {
|
|
407
|
+
return `Push and create PR.\n ${cmds.postPrLink}`;
|
|
408
|
+
}
|
|
409
|
+
if (/screenshot.*tracker/i.test(t)) {
|
|
410
|
+
return `Upload screenshots to tracker, then post a comment.\n ${cmds.attachScreenshots}\n ${cmds.agentComment.replace('<COMMENT>', 'Screenshots attached — see attachments above.')}`;
|
|
411
|
+
}
|
|
412
|
+
return t;
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Jane: expand transition + summary tasks
|
|
417
|
+
if (a.name === "Jane") {
|
|
418
|
+
tasks = tasks.map(t => {
|
|
419
|
+
if (/transition.*in review/i.test(t) || /move.*in review/i.test(t)) {
|
|
420
|
+
return `Transition task to "In Review".\n ${cmds.transitionToReview}`;
|
|
421
|
+
}
|
|
422
|
+
if (/summary.*tracker/i.test(t) || /final summary/i.test(t)) {
|
|
423
|
+
return `Post final summary comment on tracker.\n ${cmds.postSummary}`;
|
|
424
|
+
}
|
|
425
|
+
return t;
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Dennis & Sam: expand tracker comment
|
|
430
|
+
if (a.name === "Dennis" || a.name === "Sam") {
|
|
431
|
+
const hasTrackerTask = tasks.some(t => /tracker/i.test(t) || /comment/i.test(t));
|
|
432
|
+
if (!hasTrackerTask) {
|
|
433
|
+
const findingsLabel = a.name === "Dennis" ? "Files changed, technical decisions" : "Architecture concerns or clean audit";
|
|
434
|
+
tasks.push(`Post comment on tracker with key findings (${findingsLabel}).\n ${cmds.agentComment}`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const taskList = tasks.map((t, i) => `${i + 1}. ${t}`).join("\n");
|
|
440
|
+
execSteps.push(`### Step ${stepNum} — ${a.name} ${a.execution.step.replace(/^[A-Za-z]+ /, "")}:\n${taskList}`);
|
|
258
441
|
stepNum++;
|
|
259
442
|
}
|
|
260
443
|
sections.executionSteps = execSteps.join("\n\n");
|
package/cli/daemon.mjs
CHANGED
|
@@ -365,7 +365,7 @@ export async function runDaemon() {
|
|
|
365
365
|
|
|
366
366
|
// Resolve team
|
|
367
367
|
const team = resolveTeam(config);
|
|
368
|
-
const teamSections = generateTeamPrompt(team);
|
|
368
|
+
const teamSections = generateTeamPrompt(team, { tracker, config });
|
|
369
369
|
|
|
370
370
|
const inboxUrl = `${agentdeskServer}/api/sessions/${sessionId}/inbox`;
|
|
371
371
|
const sessionUrl = `${agentdeskServer}/sessions/${sessionId}`;
|
package/cli/team.mjs
CHANGED
|
@@ -82,7 +82,7 @@ export async function runTeam(taskId, opts = {}) {
|
|
|
82
82
|
|
|
83
83
|
// Resolve team and generate dynamic prompt sections
|
|
84
84
|
const team = resolveTeam(config);
|
|
85
|
-
const teamSections = generateTeamPrompt(team);
|
|
85
|
+
const teamSections = generateTeamPrompt(team, { tracker, config });
|
|
86
86
|
|
|
87
87
|
// --- Verify tracker permissions before starting session ---
|
|
88
88
|
if (tracker) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kendoo.agentdesk/agentdesk",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.13",
|
|
4
4
|
"description": "AI team orchestrator for Claude Code — run collaborative agent sessions from your terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"server": "node server/index.mjs",
|
|
22
22
|
"build": "vite build",
|
|
23
23
|
"preview": "vite preview",
|
|
24
|
-
"test": "node --test tests/server.test.mjs"
|
|
24
|
+
"test": "node --test tests/server.test.mjs tests/agents.test.mjs tests/homepage.test.mjs"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"bcryptjs": "^3.0.3",
|
package/prompts/team.md
CHANGED
|
@@ -231,6 +231,88 @@ Jane MUST post the session start comment and set "In Progress" before moving on.
|
|
|
231
231
|
|
|
232
232
|
Output on its own line: `SESSION_TITLE: <4-8 word title>`
|
|
233
233
|
|
|
234
|
+
### Decompose (if needed)
|
|
235
|
+
|
|
236
|
+
After assessing the task, Jane evaluates whether it is **too large for a single session**. Signs of a large task:
|
|
237
|
+
- Multiple independent workstreams or features
|
|
238
|
+
- Cross-cutting concerns spanning 3+ areas of the codebase
|
|
239
|
+
- Estimated effort exceeding what a team session can deliver in one run
|
|
240
|
+
- The description explicitly describes an epic, initiative, or multi-part feature
|
|
241
|
+
|
|
242
|
+
**If the task is small/focused** — skip decomposition, proceed to PLAN.
|
|
243
|
+
|
|
244
|
+
**If the task is large**, Jane decomposes it:
|
|
245
|
+
|
|
246
|
+
1. **Check current issue type.** If the task is already a subtask of another issue, do NOT decompose further — just work on it.
|
|
247
|
+
|
|
248
|
+
2. **Break the feature into subtasks.** Draft 3-8 subtasks that together would deliver the full feature. Each subtask should be independently deliverable.
|
|
249
|
+
|
|
250
|
+
3. **Classify each subtask:**
|
|
251
|
+
- `[basic]` — straightforward, low-risk, can be completed by the team in this session
|
|
252
|
+
- `[deferred]` — complex, needs user input, or depends on external factors
|
|
253
|
+
|
|
254
|
+
4. **Create subtasks in the tracker:**
|
|
255
|
+
|
|
256
|
+
{{#JIRA}}
|
|
257
|
+
For each subtask, create a Jira issue with the original task as parent:
|
|
258
|
+
```bash
|
|
259
|
+
curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" -H "Content-Type: application/json" \
|
|
260
|
+
"{{JIRA_BASE_URL}}/rest/api/3/issue" \
|
|
261
|
+
-d '{
|
|
262
|
+
"fields": {
|
|
263
|
+
"project": {"key": "<PROJECT_KEY>"},
|
|
264
|
+
"parent": {"key": "{{TASK_ID}}"},
|
|
265
|
+
"summary": "<subtask summary>",
|
|
266
|
+
"description": {"type":"doc","version":1,"content":[{"type":"paragraph","content":[{"type":"text","text":"<subtask description>"}]}]},
|
|
267
|
+
"issuetype": {"name": "Subtask"},
|
|
268
|
+
"labels": ["basic"] or ["deferred"]
|
|
269
|
+
}
|
|
270
|
+
}'
|
|
271
|
+
```
|
|
272
|
+
Note: Extract the project key from {{TASK_ID}} (the part before the hyphen).
|
|
273
|
+
{{/JIRA}}
|
|
274
|
+
|
|
275
|
+
{{#LINEAR}}
|
|
276
|
+
For each subtask, create a Linear sub-issue:
|
|
277
|
+
```
|
|
278
|
+
mutation {
|
|
279
|
+
issueCreate(input: {
|
|
280
|
+
teamId: "$TEAM_ID"
|
|
281
|
+
parentId: "$PARENT_ISSUE_ID"
|
|
282
|
+
title: "<subtask title>"
|
|
283
|
+
description: "<subtask description>"
|
|
284
|
+
labelIds: ["$BASIC_OR_DEFERRED_LABEL_ID"]
|
|
285
|
+
}) { success issue { id identifier title } }
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
First fetch the parent issue's teamId and id. Create "basic" and "deferred" labels if they don't exist.
|
|
289
|
+
{{/LINEAR}}
|
|
290
|
+
|
|
291
|
+
{{#GITHUB}}
|
|
292
|
+
GitHub doesn't support native subtasks. Instead:
|
|
293
|
+
- Edit the original issue body to add a task list:
|
|
294
|
+
```
|
|
295
|
+
## Subtasks
|
|
296
|
+
- [ ] [basic] Subtask 1 description
|
|
297
|
+
- [ ] [basic] Subtask 2 description
|
|
298
|
+
- [ ] [deferred] Subtask 3 description
|
|
299
|
+
```
|
|
300
|
+
- Create separate linked issues for each subtask with a `subtask` label:
|
|
301
|
+
```bash
|
|
302
|
+
gh issue create --title "[basic] Subtask title" --body "Parent: #{{TASK_ID}}\n\nDescription..." --label subtask
|
|
303
|
+
```
|
|
304
|
+
{{/GITHUB}}
|
|
305
|
+
|
|
306
|
+
{{#NO_TRACKER}}
|
|
307
|
+
Without a tracker, list the subtasks in the session output with their classification. The user can create tracker tasks manually.
|
|
308
|
+
{{/NO_TRACKER}}
|
|
309
|
+
|
|
310
|
+
5. **Post decomposition summary** on the tracker as a comment. List all subtasks with their classification.
|
|
311
|
+
|
|
312
|
+
6. **Announce to the team** which `[basic]` subtasks will be tackled in this session. The `[deferred]` subtasks are left for the user to schedule.
|
|
313
|
+
|
|
314
|
+
7. **Continue to PLAN** with only the `[basic]` subtasks in scope.
|
|
315
|
+
|
|
234
316
|
---
|
|
235
317
|
|
|
236
318
|
## PLAN (1-2 rounds max)
|