@kendoo.agentdesk/agentdesk 0.28.1 → 0.28.3

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/CHANGELOG.md CHANGED
@@ -8,6 +8,22 @@ All user-facing changes to AgentDesk. Each entry is tagged:
8
8
 
9
9
  Internal refactors, infrastructure changes, and architectural notes are not listed here.
10
10
 
11
+ ## [0.28.3] — 2026-09-19
12
+
13
+ ### Changed
14
+ - `[UI]` The docked team status bar now defaults to a compact one-line strip (avatars + current status) instead of the full grid, with a drag handle (and keyboard resize) to expand it.
15
+ - `[Both]` Session messages are filtered to the session's declared roster, so nested/ad-hoc subagents (e.g. a generic search delegate) no longer show their internal narration in the feed as if they were a team member.
16
+
17
+ ### Fixed
18
+ - `[CLI]` Every phase (INTAKE, PLAN, EXECUTION, REVIEW, SUMMARY, SOLO) told the model to answer with the raw handoff JSON as its final chat reply, so the model printed that JSON into the session feed on top of the SDK's separate structured-output channel. Phase prompts no longer ask for that, and a code-side guard strips it if a model still echoes it.
19
+
20
+ ## [0.28.2] — 2026-09-18
21
+
22
+ ### Changed
23
+ - `[UI]` The session feed no longer prints a line for every file read, search, and shell command. Consecutive tool actions collapse into a single expandable "N actions" line, so the feed reads as a team conversation instead of a tool-call transcript.
24
+ - `[UI]` The team status bar is now docked at the bottom of the session view and no longer scrolls away with the conversation. The message list scrolls independently above it.
25
+ - `[CLI]` Added Fable 5.1 as a selectable per-phase model, and updated the Opus/Sonnet model labels in project settings.
26
+
11
27
  ## [0.28.1] — 2026-09-17
12
28
 
13
29
  ### Fixed
package/cli/config.mjs CHANGED
@@ -22,7 +22,7 @@ const DEFAULTS = {
22
22
  projectAgents: [],
23
23
  screenshots: true,
24
24
  // Model per phase — override the phase default.
25
- // Keys: INTAKE, PLAN, EXECUTION, REVIEW, SUMMARY. Values: "default" | "opus" | "sonnet" | "haiku".
25
+ // Keys: INTAKE, PLAN, EXECUTION, REVIEW, SUMMARY. Values: "default" | "opus" | "sonnet" | "haiku" | "fable".
26
26
  // Missing entry or "default" falls back to the phase default
27
27
  // (sonnet for INTAKE/PLAN/EXECUTION, haiku for REVIEW/SUMMARY).
28
28
  phaseModels: {},
@@ -33,7 +33,7 @@ export const PHASE_ROSTER = Object.freeze({
33
33
  const CUSTOM_AGENT_PHASES = new Set(["PLAN", "EXECUTION"]);
34
34
 
35
35
  // Today's defaults: REVIEW and SUMMARY on haiku, the rest on the CLI default.
36
- // `phaseModels` values are "opus" | "sonnet" | "haiku" | "default" | undefined.
36
+ // `phaseModels` values are "opus" | "sonnet" | "haiku" | "fable" | "default" | undefined.
37
37
  export function modelForPhase(phase, phaseModels = {}) {
38
38
  const choice = phaseModels?.[phase];
39
39
  if (choice && choice !== "default") return choice;
@@ -17,6 +17,8 @@
17
17
  // `phase:change` and `session:start|end|error` are emitted by the session
18
18
  // loop, not here — phase boundaries are engine facts, not prose signals.
19
19
 
20
+ import { PHASE_OUTPUT_SCHEMAS } from "./schemas.mjs";
21
+
20
22
  const TAG_RE = /\[(SAY|ACT|THINK|AGREE|ARGUE)\]\s*/gi;
21
23
  const AGENT_TOOL_NAMES = new Set(["Agent", "Task"]);
22
24
 
@@ -37,6 +39,46 @@ export function stripTags(text) {
37
39
  return String(text).replace(TAG_RE, "");
38
40
  }
39
41
 
42
+ function isPhaseOutput(raw) {
43
+ let value;
44
+ try { value = JSON.parse(raw); } catch { return false; }
45
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
46
+ return Object.values(PHASE_OUTPUT_SCHEMAS).some(schema =>
47
+ schema.required.every(key => Object.hasOwn(value, key)) &&
48
+ Object.keys(value).every(key => Object.hasOwn(schema.properties, key)) &&
49
+ Object.entries(schema.properties).every(([key, spec]) =>
50
+ spec.type === "array" ? Array.isArray(value[key]) : typeof value[key] === spec.type));
51
+ }
52
+
53
+ // Structured output travels separately in result.structured_output. Suppress
54
+ // complete copies of our known handoff shapes, not arbitrary JSON/code snippets.
55
+ export function stripStructuredOutput(text) {
56
+ const unfenced = String(text).replace(/```(?:json)?\s*\n([\s\S]*?)```/gi,
57
+ (block, body) => isPhaseOutput(body.trim()) ? "" : block);
58
+ let out = "", from = 0;
59
+ for (let start = 0; start < unfenced.length; start++) {
60
+ if (unfenced[start] !== "{") continue;
61
+ let depth = 0, quoted = false, escaped = false, end = start;
62
+ for (; end < unfenced.length; end++) {
63
+ const char = unfenced[end];
64
+ if (quoted) {
65
+ if (escaped) escaped = false;
66
+ else if (char === "\\") escaped = true;
67
+ else if (char === '"') quoted = false;
68
+ } else if (char === '"') quoted = true;
69
+ else if (char === "{") depth++;
70
+ else if (char === "}" && --depth === 0) break;
71
+ }
72
+ if (end === unfenced.length) break; // Partial/invalid text is kept verbatim.
73
+ if (isPhaseOutput(unfenced.slice(start, end + 1))) {
74
+ out += unfenced.slice(from, start);
75
+ from = end + 1;
76
+ }
77
+ start = end;
78
+ }
79
+ return (out + unfenced.slice(from)).trim();
80
+ }
81
+
40
82
  // Human-readable one-liner for the dashboard's tool feed. The "Reading|Editing|
41
83
  // Writing <path>" forms are load-bearing: cli/daemon.mjs derives
42
84
  // filePathsTouched from them.
@@ -122,8 +164,9 @@ export function createEventMapper({ leadAgent = "Jane", onEvent } = {}) {
122
164
  }
123
165
 
124
166
  function handleText(agent, raw, { isLead }) {
125
- const text = String(raw).trim();
126
- if (!text) return;
167
+ const text = stripStructuredOutput(raw);
168
+ const message = stripTags(text).replace(/\*+/g, "").trim();
169
+ if (!message) return;
127
170
 
128
171
  if (isLead) {
129
172
  const taskId = text.match(/TASK_ID:\s*(\S+)/)?.[1];
@@ -131,18 +174,18 @@ export function createEventMapper({ leadAgent = "Jane", onEvent } = {}) {
131
174
  if (taskId || title) emit("session:update", { ...(taskId && { taskId }), ...(title && { title }) });
132
175
  }
133
176
 
134
- emit("agent:message", { agent, tag: detectTag(text), message: stripTags(text).replace(/\*+/g, "").trim() });
177
+ emit("agent:message", { agent, tag: detectTag(text), message });
178
+ return true;
135
179
  }
136
180
 
137
181
  function handleAssistant(msg) {
138
182
  reportModel(msg.message?.model);
139
183
  const agent = agentFor(msg);
140
184
  const isLead = !msg.parent_tool_use_id;
141
- if (msg.parent_tool_use_id) sawText.add(msg.parent_tool_use_id);
142
185
 
143
186
  for (const block of msg.message?.content || []) {
144
187
  if (block.type === "text") {
145
- handleText(agent, block.text, { isLead });
188
+ if (handleText(agent, block.text, { isLead }) && msg.parent_tool_use_id) sawText.add(msg.parent_tool_use_id);
146
189
  } else if (block.type === "tool_use") {
147
190
  steps++;
148
191
  if (AGENT_TOOL_NAMES.has(block.name)) {
@@ -168,7 +211,7 @@ export function createEventMapper({ leadAgent = "Jane", onEvent } = {}) {
168
211
  // dashboard still shows the agent said something.
169
212
  const name = agents.get(block.tool_use_id);
170
213
  if (name && !sawText.has(block.tool_use_id)) {
171
- const text = blockText(block.content).trim();
214
+ const text = stripTags(stripStructuredOutput(blockText(block.content))).trim();
172
215
  if (text) emit("agent:message", { agent: name, tag: "SAY", message: stripTags(text).slice(0, 1200) });
173
216
  }
174
217
  }
@@ -42,4 +42,4 @@ Drive the plan from session memory step by step. Delegate each step with the Age
42
42
  5. **Bart reviews and publishes** — reads all changed files, checks edge cases and error handling, runs linter and build, captures screenshots if applicable, pushes and creates the PR, posts the PR link on the tracker, posts screenshots as a separate comment.
43
43
  6. Ask Dennis, Sam and Bart to post their brief tracker comments (files changed & decisions; architecture findings or clean audit with evidence; PR link, test results, screenshots).
44
44
 
45
- Finish by answering with the JSON object required by the output schema — what was implemented, files changed, the PR URL (empty string if none), QA results, issues fixed, and what the reviewers should look at. Nothing else after it.
45
+ The structured output required by the schema is captured automatically — what was implemented, files changed, the PR URL (empty string if none), QA results, issues fixed, and what the reviewers should look at. Do not repeat the JSON object in chat; keep any closing chat message brief and human-readable.
@@ -31,4 +31,4 @@ Task description:
31
31
  3. **Assess scope.** Restate the task as user outcomes and acceptance criteria. If it is too large for one session, decompose it into subtasks (basic vs deferred) in product terms and have Dennis create them in the tracker.
32
32
  4. Announce `SESSION_TITLE: <4-8 word title>` on its own line.
33
33
 
34
- Finish by answering with the JSON object required by the output schema — title, task summary, requirements, assessment (branches, PRs, patterns, resume context), subtasks, and what PLAN should focus on. Nothing else after it.
34
+ The structured output required by the schema is captured automatically — title, task summary, requirements, assessment (branches, PRs, patterns, resume context), subtasks, and what PLAN should focus on. Do not repeat the JSON object in chat; keep any closing chat message brief and human-readable.
@@ -23,4 +23,4 @@ Task: {{TASK_ID}}
23
23
  - Nora (only if user-facing behaviour changes): which docs/README/help surfaces must change.
24
24
  3. Relay the substance of each report in a few lines. Ask for objections once. Resolve them and declare the plan final — do not brainstorm beyond two rounds.
25
25
 
26
- Finish by answering with the JSON object required by the output schema — approach, files to modify, decisions, risks, agent assignments, and the ordered implementation steps. Nothing else after it.
26
+ The structured output required by the schema is captured automatically — approach, files to modify, decisions, risks, agent assignments, and the ordered implementation steps. Do not repeat the JSON object in chat; keep any closing chat message brief and human-readable.
@@ -18,4 +18,4 @@ Task: {{TASK_ID}}
18
18
  2. Weigh the reports. Be strict but not pedantic: only actual gaps against the task requirements and the plan — not stylistic preferences or speculative refactors.
19
19
  3. Decide: `APPROVED` or `NEEDS_MORE_WORK`.
20
20
 
21
- Finish by answering with the JSON object required by the output schema — the verdict, the findings (reviewer, title, detail, file, line), items explicitly out of scope, and any claims that were not backed by an observation. Nothing else after it.
21
+ The structured output required by the schema is captured automatically — the verdict, the findings (reviewer, title, detail, file, line), items explicitly out of scope, and any claims that were not backed by an observation. Do not repeat the JSON object in chat; keep any closing chat message brief and human-readable.
@@ -112,4 +112,4 @@ This keeps everything in one branch — no conflicts, one PR to review.
112
112
  If the task has no child items, just work on it normally as a single task.
113
113
  {{/CHILD_TASKS}}
114
114
 
115
- Finish by answering with the JSON object required by the output schema — a summary of what was done, files changed, the PR URL (empty string if none), deferred items, and manual steps. Nothing else after it.
115
+ The structured output required by the schema is captured automatically — a summary of what was done, files changed, the PR URL (empty string if none), deferred items, and manual steps. Do not repeat the JSON object in chat; keep any closing chat message brief and human-readable.
@@ -20,4 +20,4 @@ Task: {{TASK_ID}}
20
20
  - **Session link**: {{SESSION_URL}}
21
21
  2. Delegate the tracker writes to Dennis and require the command output for each: verify the PR link is attached (attach it if missing), transition the task to "In Review", post the final comment.
22
22
 
23
- Finish by answering with the JSON object required by the output schema — status, PR URL, deferred items, manual steps, and the summary comment exactly as posted. Nothing else after it.
23
+ The structured output required by the schema is captured automatically — status, PR URL, deferred items, manual steps, and the summary comment exactly as posted. Do not repeat the JSON object in chat; keep any closing chat message brief and human-readable.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kendoo.agentdesk/agentdesk",
3
- "version": "0.28.1",
3
+ "version": "0.28.3",
4
4
  "description": "AI team orchestrator for Claude Code — run collaborative agent sessions from your terminal",
5
5
  "type": "module",
6
6
  "bin": {