ac-teams 0.0.2 → 0.0.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/README.md +2 -2
- package/docs/usage.md +5 -3
- package/package.json +1 -1
- package/src/terminal-ui.ts +54 -42
package/README.md
CHANGED
|
@@ -26,9 +26,9 @@ Your Pi session is the root; its child agents are members. Ask Pi to delegate a
|
|
|
26
26
|
|
|
27
27
|
`/teams` or `/teams panel` opens Tasks, Teammates, and Activity. `/teams teammate <name-or-id>` opens a teammate directly; subcommands and teammate names autocomplete. Use the task graph or list, send feedback, and open a member's full live Pi session.
|
|
28
28
|
|
|
29
|
-
The task summary sits above the input. The agent selector below
|
|
29
|
+
The task summary sits above the input. The agent selector sits below the input, above your existing footer, and shows descriptive names, models, and context usage. ac-teams publishes a compact team status without replacing the footer or editor supplied by other extensions. `/teams config` toggles these displays independently.
|
|
30
30
|
|
|
31
|
-
From an empty editor, press Down to select the team
|
|
31
|
+
From an empty editor, press Down to select the team row, then Enter to open a member. Ctrl+Shift+Left returns to Main without stopping the member. `/teams stop` stops every descendant.
|
|
32
32
|
|
|
33
33
|
Defaults allow up to **10 live members** and **two levels of delegation**. Use the fewest useful agents.
|
|
34
34
|
|
package/docs/usage.md
CHANGED
|
@@ -12,11 +12,11 @@ In interactive Pi, `/teams` or `/teams panel` opens the team panel. `/teams team
|
|
|
12
12
|
|
|
13
13
|
## Navigate without stopping members
|
|
14
14
|
|
|
15
|
-
The agent selector sits below
|
|
15
|
+
The agent selector sits below the input editor, above the existing footer. It shows each member's name, state, model display name, and context usage. It keeps Main pinned and shows at most five rows, including an overflow indicator when needed. A finished assignment leaves its member idle and selectable.
|
|
16
16
|
|
|
17
17
|
Context shows Pi's reported token estimate, window size, and percentage, for example `Claude Opus 4.6 · 48k / 200k (24%)`. Reports refresh about once a second when they change. Unknown usage stays marked unknown, not zero. Stopped members retain their last report. These are context estimates, not billing totals.
|
|
18
18
|
|
|
19
|
-
- Press Down from an empty editor to focus Main in the
|
|
19
|
+
- Press Down from an empty editor to focus Main in the agent selector. Up/Down select members; Enter opens the selected live session; Esc returns to editing.
|
|
20
20
|
- Ctrl+Shift+Left returns from a member to Main. Switching preserves the member's process, session, and draft. Returning does not stop it.
|
|
21
21
|
- `/teams` opens three tabs. Tab moves between Tasks, Teammates, and Activity; Esc closes the panel.
|
|
22
22
|
- **Tasks:** Arrow keys select nodes in the dependency graph. G toggles graph/list, and Enter shows full details. Green means Done, red Blocked, cyan Working, and neutral Ready; written labels remain visible. Large or unsupported graphs fall back to the list.
|
|
@@ -32,7 +32,7 @@ Team tool results and messages are compact by default. Ctrl+O expands them into
|
|
|
32
32
|
|
|
33
33
|
## Show tasks and configure the displays
|
|
34
34
|
|
|
35
|
-
The task summary sits above the input editor; the agent selector
|
|
35
|
+
The task summary sits above the input editor; the agent selector sits below the editor. The task summary shows completed/total counts and up to five numbered task labels, ordered by:
|
|
36
36
|
|
|
37
37
|
1. In-progress work
|
|
38
38
|
2. Ready prerequisites blocking other tasks, with the most dependents first
|
|
@@ -41,6 +41,8 @@ The task summary sits above the input editor; the agent selector stays below the
|
|
|
41
41
|
|
|
42
42
|
Ordinary ready tasks remain in `/teams`; the summary is not the full task list. Completion time determines recent order for newly completed tasks. Older completed tasks without timestamps fall back to task-number order.
|
|
43
43
|
|
|
44
|
+
ac-teams leaves your existing footer intact and publishes a compact summary under the `ac-teams` extension-status key. Custom footers must support extension statuses to show it. In Zentui, use `/zentui extensions` to choose a placement for `ac-teams` if statuses are hidden by default. Agent navigation remains available in its own row even when the footer hides statuses.
|
|
45
|
+
|
|
44
46
|
Use `/teams config` to toggle **Task summary** and **Agent selector** independently. Up/Down select a setting; Space or Enter toggles it; Esc closes. Changes save immediately for the current and future sessions. Other running sessions are not updated automatically. Both displays default to On.
|
|
45
47
|
|
|
46
48
|
Hiding a display does not stop agents, change tasks, or disable `/teams`. With the agent selector hidden, ordinary editor Down behavior returns. These global display preferences do not change the team's resource ceilings.
|
package/package.json
CHANGED
package/src/terminal-ui.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn, execFile } from "node:child_process";
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
3
|
import { CustomEditor, getSettingsListTheme, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { matchesKey, renderFakeCursor, truncateToWidth, visibleWidth, SettingsList } from "@earendil-works/pi-tui";
|
|
4
|
+
import { matchesKey, renderFakeCursor, truncateToWidth, visibleWidth, SettingsList, type EditorComponent, type Focusable } from "@earendil-works/pi-tui";
|
|
5
5
|
import { cleanText } from "./render.ts";
|
|
6
6
|
import { isLive, taskName, memberName } from "./types.ts";
|
|
7
7
|
import { saveDisplaySettings, type DisplaySettings } from "./config.ts";
|
|
@@ -68,11 +68,11 @@ export async function showDisplayConfig(ctx: ExtensionContext, settings: Display
|
|
|
68
68
|
}, { overlay: true, overlayOptions: { width: 64, minWidth: 30, anchor: "center" } });
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
export function installTeamFooter(
|
|
71
|
+
export function installTeamFooter(_pi: ExtensionAPI, ctx: ExtensionContext, host: PanelHost, display: DisplaySettings): () => void {
|
|
72
72
|
let members: Awaited<ReturnType<PanelHost["snapshot"]>>["members"] = [];
|
|
73
73
|
let tasks: Awaited<ReturnType<PanelHost["snapshot"]>>["tasks"] = [];
|
|
74
74
|
let selected: string | undefined, start = 0;
|
|
75
|
-
let editor:
|
|
75
|
+
let editor: (EditorComponent & Partial<Focusable> & { isShowingAutocomplete?(): boolean }) | undefined;
|
|
76
76
|
let redraw = () => {};
|
|
77
77
|
let disposed = false;
|
|
78
78
|
let timer: NodeJS.Timeout | undefined;
|
|
@@ -87,10 +87,19 @@ export function installTeamFooter(pi: ExtensionAPI, ctx: ExtensionContext, host:
|
|
|
87
87
|
};
|
|
88
88
|
async function refresh() {
|
|
89
89
|
try {
|
|
90
|
-
if (!display.tasks && !display.agents)
|
|
90
|
+
if (!display.tasks && !display.agents) {
|
|
91
|
+
ctx.ui.setStatus("ac-teams", undefined);
|
|
92
|
+
redraw();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
91
95
|
const next = await host.snapshot();
|
|
92
96
|
if (disposed) return;
|
|
93
97
|
members = next.members; tasks = next.tasks;
|
|
98
|
+
const count = ids().length;
|
|
99
|
+
ctx.ui.setStatus("ac-teams", `Teams ${[
|
|
100
|
+
...(display.agents ? [`${count} ${count === 1 ? "agent" : "agents"}`, `${members.filter(member => member.status === "busy").length} working`] : []),
|
|
101
|
+
...(display.tasks ? [`${tasks.filter(task => task.status === "done").length}/${tasks.length} tasks done`] : []),
|
|
102
|
+
].join(" · ")}`);
|
|
94
103
|
if (selected && !ids().includes(selected)) { selected = undefined; if (editor) editor.focused = true; }
|
|
95
104
|
start = Math.min(start, Math.max(0, ids().length - 1 - slots()));
|
|
96
105
|
reveal(); redraw();
|
|
@@ -102,13 +111,11 @@ export function installTeamFooter(pi: ExtensionAPI, ctx: ExtensionContext, host:
|
|
|
102
111
|
render: width => display.tasks ? renderTaskSummary(tasks, theme, width) : [],
|
|
103
112
|
invalidate() {},
|
|
104
113
|
}), { placement: "aboveEditor" });
|
|
105
|
-
ctx.ui.
|
|
114
|
+
ctx.ui.setWidget("ac-teams-agents", (tui, theme) => {
|
|
106
115
|
redraw = () => tui.requestRender();
|
|
107
|
-
const unsubscribe = data.onBranchChange(redraw);
|
|
108
116
|
return {
|
|
109
117
|
render(width) {
|
|
110
118
|
if (!display.agents && selected !== undefined) { selected = undefined; if (editor) editor.focused = true; }
|
|
111
|
-
const branch = data.getGitBranch(), usage = ctx.getContextUsage()?.percent;
|
|
112
119
|
const row = (id: string) => {
|
|
113
120
|
const member = members.find(member => member.id === id);
|
|
114
121
|
const assigned = tasks.find(task => task.owner === id && task.status === "in_progress") ?? tasks.find(task => task.owner === id && task.status !== "done");
|
|
@@ -119,44 +126,49 @@ export function installTeamFooter(pi: ExtensionAPI, ctx: ExtensionContext, host:
|
|
|
119
126
|
return theme.fg(selected === id || host.self === id ? "accent" : "muted", `${selected === id ? ">" : " "} ${host.self === id ? "●" : "○"} ${cleanText(description).replaceAll("\n", " ")}`);
|
|
120
127
|
};
|
|
121
128
|
const peers = ids().slice(1);
|
|
122
|
-
return [
|
|
123
|
-
theme.fg("dim",
|
|
124
|
-
|
|
125
|
-
...(display.agents ? ["", row("root"), ...peers.slice(start, start + slots()).map(row),
|
|
126
|
-
...(peers.length > 4 ? [theme.fg("dim", ` ↑ ${start} above · ↓ ${Math.max(0, peers.length - start - slots())} below`)] : [])] : []),
|
|
127
|
-
].map(line => truncateToWidth(line, width));
|
|
129
|
+
return (display.agents ? [row("root"), ...peers.slice(start, start + slots()).map(row),
|
|
130
|
+
...(peers.length > 4 ? [theme.fg("dim", ` ↑ ${start} above · ↓ ${Math.max(0, peers.length - start - slots())} below`)] : [])] : []
|
|
131
|
+
).map(line => truncateToWidth(line, width));
|
|
128
132
|
},
|
|
129
133
|
invalidate() {},
|
|
130
|
-
dispose() { disposed = true; clearTimeout(timer); unsubscribe(); },
|
|
131
134
|
};
|
|
132
|
-
});
|
|
133
|
-
ctx.ui.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
}, { placement: "belowEditor" });
|
|
136
|
+
const previousEditor = ctx.ui.getEditorComponent();
|
|
137
|
+
const teamEditor: NonNullable<typeof previousEditor> = (tui, theme, keys) => {
|
|
138
|
+
const current: NonNullable<typeof editor> = previousEditor?.(tui, theme, keys) ?? new CustomEditor(tui, theme, keys);
|
|
139
|
+
// Decorate the instance so custom editor methods and Pi's app-control wiring survive.
|
|
140
|
+
const render = current.render.bind(current), handleInput = current.handleInput.bind(current);
|
|
141
|
+
current.render = (width: number) => {
|
|
142
|
+
const lines = render(width);
|
|
143
|
+
return disposed || selected === undefined ? lines : lines.map(line => line.replace(renderFakeCursor(" "), " "));
|
|
144
|
+
};
|
|
145
|
+
current.handleInput = (input: string) => {
|
|
146
|
+
if (disposed) { handleInput(input); return; }
|
|
147
|
+
if (!display.agents) { selected = undefined; current.focused = true; handleInput(input); return; }
|
|
148
|
+
if (selected !== undefined) {
|
|
149
|
+
const destinations = ids(), index = destinations.indexOf(selected);
|
|
150
|
+
if (matchesKey(input, "down")) selected = destinations[Math.min(destinations.length - 1, index + 1)];
|
|
151
|
+
else if (matchesKey(input, "up")) selected = index <= 0 ? undefined : destinations[index - 1];
|
|
152
|
+
else if (matchesKey(input, "escape")) selected = undefined;
|
|
153
|
+
else if (matchesKey(input, "enter")) {
|
|
154
|
+
const id = selected; selected = undefined;
|
|
155
|
+
void host.visit(id).catch(error => ctx.ui.notify(cleanText(String(error)), "error"));
|
|
156
|
+
} else { selected = undefined; handleInput(input); }
|
|
157
|
+
current.focused = selected === undefined; reveal(); redraw(); return;
|
|
138
158
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (selected !== undefined) {
|
|
142
|
-
const destinations = ids(), index = destinations.indexOf(selected);
|
|
143
|
-
if (matchesKey(input, "down")) selected = destinations[Math.min(destinations.length - 1, index + 1)];
|
|
144
|
-
else if (matchesKey(input, "up")) selected = index <= 0 ? undefined : destinations[index - 1];
|
|
145
|
-
else if (matchesKey(input, "escape")) selected = undefined;
|
|
146
|
-
else if (matchesKey(input, "enter")) {
|
|
147
|
-
const id = selected; selected = undefined;
|
|
148
|
-
void host.visit(id).catch(error => ctx.ui.notify(cleanText(String(error)), "error"));
|
|
149
|
-
} else { selected = undefined; super.handleInput(input); }
|
|
150
|
-
this.focused = selected === undefined; reveal(); redraw(); return;
|
|
151
|
-
}
|
|
152
|
-
if (matchesKey(input, "down") && !this.getText() && !this.isShowingAutocomplete()) {
|
|
153
|
-
selected = "root"; start = 0; this.focused = false; redraw(); return;
|
|
154
|
-
}
|
|
155
|
-
super.handleInput(input);
|
|
159
|
+
if (matchesKey(input, "down") && !current.getText() && !current.isShowingAutocomplete?.()) {
|
|
160
|
+
selected = "root"; start = 0; current.focused = false; redraw(); return;
|
|
156
161
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
handleInput(input);
|
|
163
|
+
};
|
|
164
|
+
editor = current;
|
|
165
|
+
return current;
|
|
166
|
+
};
|
|
167
|
+
ctx.ui.setEditorComponent(teamEditor);
|
|
168
|
+
return () => {
|
|
169
|
+
disposed = true; clearTimeout(timer);
|
|
170
|
+
ctx.ui.setWidget("ac-teams-tasks", undefined); ctx.ui.setWidget("ac-teams-agents", undefined);
|
|
171
|
+
ctx.ui.setStatus("ac-teams", undefined);
|
|
172
|
+
if (ctx.ui.getEditorComponent() === teamEditor) ctx.ui.setEditorComponent(previousEditor);
|
|
173
|
+
};
|
|
162
174
|
}
|