@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 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, and posts the session start/end comments.",
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![$(basename $f)](data:image/png;base64,$BASE64)\\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
- const tasks = a.execution.tasks.map((t, i) => `${i + 1}. ${t}`).join("\n");
257
- execSteps.push(`### Step ${stepNum} — ${a.name} ${a.execution.step.replace(/^[A-Za-z]+ /, "")}:\n${tasks}`);
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.11",
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)