@kendoo.agentdesk/agentdesk 0.20.3 → 0.20.5

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.
@@ -135,6 +135,11 @@ export async function runOrchestrator({
135
135
  emit({ type: "phase:change", phase: initialPhase, model: representativeModel });
136
136
 
137
137
  const modelArgs = modelArgsForPhase("EXECUTION", config?.phaseModels);
138
+ const phaseModelsResolved = config?.phaseModels && Object.keys(config.phaseModels).length
139
+ ? JSON.stringify(config.phaseModels)
140
+ : "(none — using Claude Code defaults)";
141
+ console.error(`[agentdesk] phaseModels: ${phaseModelsResolved}`);
142
+ console.error(`[agentdesk] EXECUTION model: ${modelArgs.length ? modelArgs[1] : "default (no --model flag)"}`);
138
143
  const wrapped = wrapIsolatedSpawn({
139
144
  cmd: "claude",
140
145
  args: ["-p", fullPrompt, ...modelArgs, "--allowedTools", "Bash,Read,Edit,Write,Glob,Grep", "--verbose", "--output-format", "stream-json"],
@@ -387,6 +392,7 @@ export async function runPhasedOrchestrator({
387
392
  });
388
393
 
389
394
  const modelArgs = modelArgsForPhase(phase, config?.phaseModels);
395
+ console.error(`[agentdesk] phase ${phase} model: ${modelArgs.length ? modelArgs[1] : "default (no --model flag)"}`);
390
396
  const result = await runSinglePhase({ prompt, cwd, env, teamNames, emit, modelArgs, scratchHome: sandbox.home, sessionId, tracker, config });
391
397
 
392
398
  if (onChild) onChild(result.child);
package/cli/prompt.mjs CHANGED
@@ -68,7 +68,7 @@ export function buildPrompt({ taskId, taskLink, description, createTask, tracker
68
68
  } else {
69
69
  createInstr += `- ⚠ No Linear team key is configured. Ask the user which team this issue should live in BEFORE creating it.\n`;
70
70
  }
71
- createInstr += `- Set the title based on the description below\n- After creation, use the returned identifier (e.g., ${linearTeamKey || "KEN"}-530) as the task ID for the rest of the session\n`;
71
+ createInstr += `- Set the title based on the description below\n- **Assign the issue to the connected user.** First fetch the viewer id with \`{ viewer { id } }\`, then pass that id as \`assigneeId\` in the \`issueCreate\` input.\n- After creation, use the returned identifier (e.g., ${linearTeamKey || "KEN"}-530) as the task ID for the rest of the session\n`;
72
72
  } else if (tracker === "jira") {
73
73
  const jiraProject = config.jira?.project;
74
74
  createInstr += `Create a Jira issue:\n- Endpoint: ${config.jira?.baseUrl || ""}/rest/api/3/issue\n- Auth: Basic auth with $JIRA_EMAIL and $JIRA_API_TOKEN\n`;
@@ -77,9 +77,9 @@ export function buildPrompt({ taskId, taskLink, description, createTask, tracker
77
77
  } else {
78
78
  createInstr += `- ⚠ No Jira project key is configured. Ask the user which project this task should live in BEFORE creating it.\n`;
79
79
  }
80
- createInstr += `- Set the summary based on the description below\n- After creation, use the returned key (e.g., ${jiraProject || "PROJ"}-42) as the task ID for the rest of the session\n`;
80
+ createInstr += `- Set the summary based on the description below\n- **Assign the issue to the connected user.** First GET \`${config.jira?.baseUrl || ""}/rest/api/3/myself\` to fetch your \`accountId\`, then include \`fields.assignee = { "accountId": "<that id>" }\` in the create payload.\n- After creation, use the returned key (e.g., ${jiraProject || "PROJ"}-42) as the task ID for the rest of the session\n`;
81
81
  } else if (tracker === "github") {
82
- 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`;
82
+ createInstr += `Create a GitHub issue:\n- Run: gh issue create --title "..." --body "..." --assignee @me\n- After creation, use the returned issue number as the task ID for the rest of the session\n`;
83
83
  }
84
84
  createInstr += `\nTask description: ${description}\n`;
85
85
  createInstr += `\nAfter creating the task, Jane MUST:\n`;
@@ -461,14 +461,16 @@ export function buildPhasedPrompt({ phase, taskId, taskLink, description, create
461
461
  if (linearTeamKey) {
462
462
  createInstr += `**Team MUST be \`${linearTeamKey}\`.** Resolve its id via \`{ teams(filter: { key: { eq: "${linearTeamKey}" } }) { nodes { id } } }\` and pass as \`teamId\` in \`issueCreate\`. Do NOT pick any other team.\n`;
463
463
  }
464
+ createInstr += `**Assign to the connected user:** fetch \`{ viewer { id } }\` and pass that id as \`assigneeId\` in \`issueCreate\`.\n`;
464
465
  } else if (tracker === "jira") {
465
466
  const jiraProject = config.jira?.project;
466
467
  createInstr += `Create a Jira issue at ${config.jira?.baseUrl || ""}.\n`;
467
468
  if (jiraProject) {
468
469
  createInstr += `**Project MUST be \`${jiraProject}\`.** Set \`fields.project.key = "${jiraProject}"\` in the create payload. Do NOT pick any other project.\n`;
469
470
  }
471
+ createInstr += `**Assign to the connected user:** GET \`${config.jira?.baseUrl || ""}/rest/api/3/myself\` to fetch your \`accountId\`, then include \`fields.assignee = { "accountId": "<that id>" }\` in the create payload.\n`;
470
472
  } else if (tracker === "github") {
471
- createInstr += `Create a GitHub issue: gh issue create --title "..." --body "..."\n`;
473
+ createInstr += `Create a GitHub issue: gh issue create --title "..." --body "..." --assignee @me\n`;
472
474
  }
473
475
  createInstr += `\nTask description: ${description}\n`;
474
476
  createInstr += `\nAfter creating, output: TASK_ID: <identifier>\n`;
@@ -39,11 +39,32 @@ export function createStreamParser({ teamNames, callbacks }) {
39
39
 
40
40
  function detectPhase(text) {
41
41
  const upper = text.trim().toUpperCase();
42
- if (upper.startsWith("PHASE") || upper.startsWith("---") || upper.startsWith("#")) {
43
- for (const p of PHASE_NAMES) {
44
- if (upper.includes(p)) return p;
45
- }
46
- }
42
+ if (!upper) return null;
43
+
44
+ const phaseAlt = PHASE_NAMES.join("|");
45
+
46
+ // Canonical signal: "# PHASE: EXECUTION" / "PHASE EXECUTION" / "## EXECUTION".
47
+ // Accepted anywhere in a line so it survives leading bullets, emoji, etc.
48
+ const explicit = upper.match(new RegExp(`(?:^|\\s)#*\\s*PHASE\\s*[:\\-]?\\s*(${phaseAlt})\\b`));
49
+ if (explicit) return explicit[1];
50
+
51
+ // Markdown heading line that names a phase: "# EXECUTION", "## PLAN", etc.
52
+ const headingMatch = upper.match(new RegExp(`^#+\\s*(${phaseAlt})\\b`));
53
+ if (headingMatch) return headingMatch[1];
54
+
55
+ // Divider followed by a phase name: "--- EXECUTION".
56
+ const dividerMatch = upper.match(new RegExp(`^-{3,}\\s*(${phaseAlt})\\b`));
57
+ if (dividerMatch) return dividerMatch[1];
58
+
59
+ // Natural-language transitions ("entering EXECUTION", "moving to PLAN",
60
+ // "now in REVIEW", "EXECUTION phase begins"). Defensive fallback for when
61
+ // the canonical marker is missed.
62
+ const verbMatch = upper.match(new RegExp(`\\b(?:ENTERING|MOVING\\s+TO|STARTING|BEGIN(?:NING)?|NOW\\s+IN|TRANSITION(?:ING)?\\s+TO|PROCEED(?:ING)?\\s+TO|ADVANCING\\s+TO)\\s+(?:THE\\s+)?(?:PHASE\\s+)?(${phaseAlt})\\b(?:\\s+PHASE)?`));
63
+ if (verbMatch) return verbMatch[1];
64
+
65
+ const phaseSuffixMatch = upper.match(new RegExp(`\\b(${phaseAlt})\\s+PHASE\\s+(?:BEGINS?|STARTS?|HAS\\s+BEGUN|IS\\s+UNDERWAY)\\b`));
66
+ if (phaseSuffixMatch) return phaseSuffixMatch[1];
67
+
47
68
  return null;
48
69
  }
49
70
 
@@ -86,12 +107,6 @@ export function createStreamParser({ teamNames, callbacks }) {
86
107
  if (block.type === "text" && block.text.trim()) {
87
108
  const text = block.text.trim().replace(/\*+/g, "");
88
109
 
89
- const phase = detectPhase(text);
90
- if (phase) {
91
- callbacks.onPhaseChange?.({ phase, timestamp: timestamp() });
92
- continue;
93
- }
94
-
95
110
  // Detect TASK_ID announcement
96
111
  const taskIdMatch = text.match(/TASK_ID:\s*(\S+)/);
97
112
  if (taskIdMatch) {
@@ -110,6 +125,13 @@ export function createStreamParser({ teamNames, callbacks }) {
110
125
  const currentLine = textLines[i].trim();
111
126
  if (!currentLine) { i++; continue; }
112
127
 
128
+ const phase = detectPhase(currentLine);
129
+ if (phase) {
130
+ callbacks.onPhaseChange?.({ phase, timestamp: timestamp() });
131
+ i++;
132
+ continue;
133
+ }
134
+
113
135
  const detected = detectAgent(currentLine);
114
136
  if (detected) {
115
137
  let msg = detected.rest;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kendoo.agentdesk/agentdesk",
3
- "version": "0.20.3",
3
+ "version": "0.20.5",
4
4
  "description": "AI team orchestrator for Claude Code — run collaborative agent sessions from your terminal",
5
5
  "type": "module",
6
6
  "bin": {
package/prompts/team.md CHANGED
@@ -314,8 +314,22 @@ Keep comments to bullet points.
314
314
 
315
315
  ---
316
316
 
317
+ ## PHASE SIGNALS (MANDATORY — DASHBOARD DEPENDS ON THIS)
318
+
319
+ Whenever the team transitions phases, Jane MUST print a single bare line in this exact form on its own line, with nothing else on that line:
320
+
321
+ ```
322
+ # PHASE: <NAME>
323
+ ```
324
+
325
+ Where `<NAME>` is one of `INTAKE`, `PLAN`, `EXECUTION`, `REVIEW`, `SUMMARY`. Examples: `# PHASE: PLAN`, `# PHASE: EXECUTION`, `# PHASE: SUMMARY`. This must be printed BEFORE any agent dialogue for the new phase. Without this marker the dashboard timeline gets stuck and the user can't tell which phase the team is in. Emit it even if it feels redundant.
326
+
327
+ ---
328
+
317
329
  ## INTAKE
318
330
 
331
+ Begin this phase by printing `# PHASE: INTAKE` on its own line.
332
+
319
333
  **If a CREATE TASK section exists above**, Dennis executes the tracker API call to create the task. Jane then announces the task ID and what the team will be working on. Output `TASK_ID: <identifier>`, set status to "In Progress". Only then continue.
320
334
 
321
335
  **Reminder: Jane does NOT use tools or reference code during INTAKE. Dennis handles all tool calls (fetching tasks, reading files, checking branches). Jane interprets the findings in product terms.**
@@ -445,6 +459,8 @@ After Dennis reports his assessment findings, Jane evaluates whether the task is
445
459
 
446
460
  ## PLAN (1-2 rounds max)
447
461
 
462
+ Begin this phase by printing `# PHASE: PLAN` on its own line.
463
+
448
464
  Jane restates the task in product terms (no code, no file names, no technical jargon). Then each agent contributes their perspective in one pass:
449
465
 
450
466
  {{PLANNING_ORDER}}
@@ -457,12 +473,16 @@ After the first round, Jane asks for objections. If none, declare the plan final
457
473
 
458
474
  ## EXECUTION
459
475
 
476
+ Begin this phase by printing `# PHASE: EXECUTION` on its own line.
477
+
460
478
  {{EXECUTION_STEPS}}
461
479
 
462
480
  ---
463
481
 
464
482
  ## SUMMARY
465
483
 
484
+ Begin this phase by printing `# PHASE: SUMMARY` on its own line.
485
+
466
486
  Jane dictates the summary content in product terms; Dennis executes all tracker commands:
467
487
  1. Verify Bart posted the PR link. If not, Dennis posts it now.
468
488
  2. Dennis transitions the task to "In Review".