ac-teams 0.0.4 → 0.0.6
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 +5 -1
- package/package.json +3 -1
- package/src/index.ts +7 -5
- package/src/render.ts +65 -5
- package/src/terminal-ui.ts +6 -5
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.0.6",
|
|
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/index.ts
CHANGED
|
@@ -211,11 +211,6 @@ export default function teams(pi: ExtensionAPI) {
|
|
|
211
211
|
await connection.open();
|
|
212
212
|
await connection.request("join", { sessionId: ctx.sessionManager.getSessionId(), sessionFile: ctx.sessionManager.getSessionFile(), tools: pi.getAllTools().map(tool => tool.name), trusted: ctx.isProjectTrusted() });
|
|
213
213
|
}
|
|
214
|
-
if (ctx.mode === "tui") {
|
|
215
|
-
try { Object.assign(display, readDisplaySettings()); }
|
|
216
|
-
catch (error) { Object.assign(display, defaultDisplaySettings); warn(String(error)); }
|
|
217
|
-
disposeUI?.(); disposeUI = installTeamFooter(pi, ctx, host(ctx), display);
|
|
218
|
-
}
|
|
219
214
|
startTelemetry(ctx);
|
|
220
215
|
if (!warned) {
|
|
221
216
|
warned = true;
|
|
@@ -223,6 +218,13 @@ export default function teams(pi: ExtensionAPI) {
|
|
|
223
218
|
if (competitors.length) warn(`Other delegation tools are active (${competitors.map(tool => tool.name).join(", ")}). ac-teams limits do not cover them; disable competing launchers for a single team authority.`);
|
|
224
219
|
}
|
|
225
220
|
});
|
|
221
|
+
// CLI-loaded teammates run session_start before other extensions install their editors.
|
|
222
|
+
pi.on("resources_discover", (_event, ctx) => {
|
|
223
|
+
if (ctx.mode !== "tui") return;
|
|
224
|
+
try { Object.assign(display, readDisplaySettings()); }
|
|
225
|
+
catch (error) { Object.assign(display, defaultDisplaySettings); warn(String(error)); }
|
|
226
|
+
disposeUI?.(); disposeUI = installTeamFooter(pi, ctx, host(ctx), display);
|
|
227
|
+
});
|
|
226
228
|
pi.on("session_shutdown", close);
|
|
227
229
|
const switchGuard = () => {
|
|
228
230
|
if (child || coordinator?.live().length) {
|
package/src/render.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { stripVTControlCharacters } from "node:util";
|
|
2
|
-
import type
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/terminal-ui.ts
CHANGED
|
@@ -94,7 +94,7 @@ export function installTeamFooter(_pi: ExtensionAPI, ctx: ExtensionContext, host
|
|
|
94
94
|
}
|
|
95
95
|
const next = await host.snapshot();
|
|
96
96
|
if (disposed) return;
|
|
97
|
-
members = next.members; tasks = next.tasks;
|
|
97
|
+
members = next.members.filter(isLive); tasks = next.tasks;
|
|
98
98
|
const count = ids().length;
|
|
99
99
|
ctx.ui.setStatus("ac-teams", `Teams ${[
|
|
100
100
|
...(display.agents ? [`${count} ${count === 1 ? "agent" : "agents"}`, `${members.filter(member => member.status === "busy").length} working`] : []),
|
|
@@ -126,14 +126,15 @@ export function installTeamFooter(_pi: ExtensionAPI, ctx: ExtensionContext, host
|
|
|
126
126
|
const titleRow = theme.fg(color, `${selected === id ? ">" : " "} ${host.self === id ? "●" : "○"} ${cleanText(title).replaceAll("\n", " ")}`);
|
|
127
127
|
const attention = member?.needsInput ? theme.fg("error", " · Needs input") : "";
|
|
128
128
|
const description = ` ${id === "root" ? "" : status + " · "}${modelContext(metrics)}${assigned ? ` · ${taskName(assigned)}` : ""}${member?.error || member?.activity ? ` · ${member.error || member.activity}` : ""}`;
|
|
129
|
-
|
|
129
|
+
const shortcut = id === "root" && host.self !== "root" ? theme.fg("dim", " [Ctrl+Shift+Left]") : "";
|
|
130
|
+
return titleRow + shortcut + attention + theme.fg(color, cleanText(description).replaceAll("\n", " "));
|
|
130
131
|
};
|
|
131
132
|
const peers = ids().slice(1);
|
|
132
133
|
return [
|
|
133
|
-
...(host.self !== "root" ? [theme.fg("dim", "Ctrl+Shift+Left: Main (keeps questions open)")] : []),
|
|
134
134
|
...(display.agents ? [row("root"), ...peers.slice(start, start + slots()).map(row),
|
|
135
|
-
...(peers.length > 4 ? [theme.fg("dim", ` ↑ ${start} above · ↓ ${Math.max(0, peers.length - start - slots())} below`)] : [])] :
|
|
136
|
-
|
|
135
|
+
...(peers.length > 4 ? [theme.fg("dim", ` ↑ ${start} above · ↓ ${Math.max(0, peers.length - start - slots())} below`)] : [])] :
|
|
136
|
+
host.self !== "root" ? [row("root")] : []),
|
|
137
|
+
].map(line => truncateToWidth(line, width, "..."));
|
|
137
138
|
},
|
|
138
139
|
invalidate() {},
|
|
139
140
|
};
|