ac-teams 0.0.4 → 0.0.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.
package/docs/usage.md CHANGED
@@ -32,7 +32,11 @@ A task request is a proposal, not an instruction to create a task. Main receives
32
32
 
33
33
  The send receipt confirms delivery, not acceptance or task creation. This is agent guidance, not an automatic semantic duplicate detector.
34
34
 
35
- Team tool results and messages are compact by default. Ctrl+O expands them into labeled fields. UI state colors do not replace the underlying progress, ownership, or dependency fields.
35
+ Each team tool call uses one line with a status dot, tool name, action, and result preview. Bold, colored badges such as `[todo]` separate statuses from titles and names.
36
+
37
+ The `+N lines` hint shows the hidden detail count. Ctrl+O expands the full request and response JSON; mouse-enabled Pi views also support clicking the row.
38
+
39
+ Team notification messages remain compact; Ctrl+O expands their labeled fields. UI state colors do not replace the underlying progress, ownership, or dependency fields.
36
40
 
37
41
  ## Show tasks and configure the displays
38
42
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ac-teams",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Small, persistent teams for Pi",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -15,10 +15,12 @@
15
15
  "engines": { "node": ">=22.19.0" },
16
16
  "peerDependencies": {
17
17
  "@earendil-works/pi-coding-agent": "^1.4.0",
18
+ "@earendil-works/pi-tui": "^1.4.0",
18
19
  "typebox": "^1.0.0"
19
20
  },
20
21
  "devDependencies": {
21
22
  "@earendil-works/pi-coding-agent": "1.4.0",
23
+ "@earendil-works/pi-tui": "1.4.0",
22
24
  "@types/node": "^22.0.0",
23
25
  "typebox": "^1.0.0",
24
26
  "typescript": "^5.9.0"
package/src/render.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { stripVTControlCharacters } from "node:util";
2
- import type { MessageRenderer, Theme, ToolRenderers } from "@earendil-works/pi-coding-agent";
3
- import { truncateToWidth, wrapTextWithAnsi, type Component } from "@earendil-works/pi-tui";
2
+ import { keyHint, type MessageRenderer, type Theme, type ToolRenderers } from "@earendil-works/pi-coding-agent";
3
+ import { Container, Text, TruncatedText, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component } from "@earendil-works/pi-tui";
4
4
 
5
5
  export const cleanText = (text: string): string => stripVTControlCharacters(text).replaceAll("\t", " ").replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, "");
6
6
  const inline = (value: unknown) => cleanText(String(value)).replace(/[\n\r\t]/g, " ");
@@ -46,18 +46,78 @@ function entry(title: string, value: unknown, expanded: boolean, theme: Theme, e
46
46
  };
47
47
  }
48
48
 
49
+ function preview(items: unknown[], count: string, theme: Theme): string {
50
+ const entries = items.slice(0, 3).map(item => toolSummary(item, theme)).filter(Boolean);
51
+ if (items.length > 3) entries.push(`+${items.length - 3} more`);
52
+ return [count, ...entries].join(" · ");
53
+ }
54
+
55
+ function toolSummary(data: unknown, theme: Theme): string {
56
+ if (Array.isArray(data)) return preview(data, `${data.length} messages`, theme);
57
+ if (data && typeof data === "object") {
58
+ const value = data as Record<string, unknown>;
59
+ if (Array.isArray(value.members)) return preview(value.members, `${value.members.length} members`, theme);
60
+ if (Array.isArray(value.tasks)) return preview(value.tasks, `${value.tasks.length} of ${value.total} tasks`, theme);
61
+ const label = value.title ?? value.name ?? value.brief ?? value.text ?? value.query ?? value.id;
62
+ const status = value.status === undefined ? "" : inline(value.status);
63
+ const color = status === "done" ? "success"
64
+ : status === "interrupted" ? "error"
65
+ : status === "busy" || status === "in_progress" ? "accent"
66
+ : status === "todo" || status === "starting" || status === "stopping" ? "warning" : "muted";
67
+ const parts = [status ? theme.fg(color, theme.bold(`[${status}]`)) : "", label === undefined ? "" : inline(label)];
68
+ if (Array.isArray(value.recipients)) parts.push(`to ${value.recipients.map(inline).join(", ")}`);
69
+ for (const key of ["seq", "limit", "depth"]) {
70
+ if (value[key] !== undefined) parts.push(`${key}: ${inline(value[key])}`);
71
+ }
72
+ return parts.filter(Boolean).join(" ");
73
+ }
74
+ return data === undefined || data === null ? "" : inline(data);
75
+ }
76
+
49
77
  export function teamToolRenderers(name: string): ToolRenderers {
50
78
  return {
79
+ renderShell: "self",
51
80
  renderCall(args, theme, context) {
52
- return entry(name, args, context.expanded, theme);
81
+ if (!context.isPartial) return new Container();
82
+ const { action, ...fields } = (args ?? {}) as Record<string, unknown>;
83
+ const request = toolSummary(fields, theme);
84
+ const text = `${theme.fg("warning", "●")} ${theme.fg("toolTitle", name)} · ${inline(action ?? "...")} → running${request ? ` · ${request}` : ""}`;
85
+ return new TruncatedText(text, context.outputPad, 0);
53
86
  },
54
87
  renderResult(result, options, theme, context) {
88
+ if (options.isPartial) return new Container();
89
+ const { action, ...fields } = (context.args ?? {}) as Record<string, unknown>;
90
+ const text = result.content.filter(block => block.type === "text").map(block => block.text).join("\n");
55
91
  let data: unknown = result.details;
56
92
  if (data === undefined) {
57
- const text = result.content.filter(part => part.type === "text").map(part => part.text).join("\n");
58
93
  try { data = JSON.parse(text); } catch { data = text; }
59
94
  }
60
- return entry(`${name} ${options.isPartial ? "receiving" : "result"}`, data, options.expanded, theme, context.isError);
95
+ const outcome = context.isError ? `error · ${inline(text)}` : toolSummary(data, theme) || "done";
96
+ const request = toolSummary(fields, theme);
97
+ const heading = `${theme.fg(context.isError ? "error" : "success", "●")} ${theme.fg("toolTitle", name)} · ${inline(action ?? "...")} → ${outcome}${request && !outcome.includes(request) ? ` · ${request}` : ""}`;
98
+ const requestJSON = cleanText(JSON.stringify(context.args, null, 2));
99
+ const responseJSON = cleanText(result.details === undefined ? text : JSON.stringify(result.details, null, 2));
100
+ const hiddenLines = requestJSON.split("\n").length + responseJSON.split("\n").length + 2;
101
+ const hint = theme.fg("muted", options.expanded ? " (" : ` … +${hiddenLines} lines (`)
102
+ + keyHint("app.tools.expand", options.expanded ? "to collapse" : "to expand") + theme.fg("muted", ")");
103
+ const row = new Container();
104
+ row.addChild({
105
+ invalidate() {},
106
+ render(width) {
107
+ const pad = " ".repeat(Math.min(context.outputPad, Math.floor(width / 2)));
108
+ const available = Math.max(0, width - pad.length * 2);
109
+ // Keep the hint visible unless the terminal is too narrow to also identify the tool.
110
+ const suffix = available >= visibleWidth(hint) + 30 ? hint : "";
111
+ return [pad + truncateToWidth(heading, available - visibleWidth(suffix)) + suffix + pad];
112
+ },
113
+ });
114
+ if (options.expanded) {
115
+ row.addChild(new Text(theme.fg("muted", "Request"), context.outputPad, 0));
116
+ row.addChild(new Text(requestJSON, context.outputPad, 0));
117
+ row.addChild(new Text(theme.fg("muted", "Response"), context.outputPad, 0));
118
+ row.addChild(new Text(responseJSON, context.outputPad, 0));
119
+ }
120
+ return row;
61
121
  },
62
122
  };
63
123
  }