pi-subagents-lite 1.3.0 → 1.4.1
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 +184 -235
- package/package.json +1 -1
- package/src/{agent-discovery.ts → agents/agent-discovery.ts} +10 -7
- package/src/{agent-manager.ts → agents/agent-manager.ts} +34 -74
- package/src/{agent-runner.ts → agents/agent-runner.ts} +130 -181
- package/src/{agent-status.ts → agents/agent-status.ts} +4 -4
- package/src/agents/agent-types.ts +339 -0
- package/src/{default-agents.ts → agents/default-agents.ts} +2 -5
- package/src/{output-file.ts → agents/output-file.ts} +68 -1
- package/src/{tool-execution.ts → agents/tool-execution.ts} +60 -222
- package/src/agents/types.ts +54 -0
- package/src/{usage.ts → agents/usage.ts} +7 -0
- package/src/{config-io.ts → config/config-io.ts} +20 -3
- package/src/config/config-store.ts +472 -0
- package/src/config/types.ts +26 -0
- package/src/events.ts +185 -0
- package/src/index.ts +8 -281
- package/src/{model-precedence.ts → models/model-precedence.ts} +33 -0
- package/src/{model-selector.ts → models/model-selector.ts} +1 -1
- package/src/{context.ts → prompt/context.ts} +1 -1
- package/src/prompt/prompts.ts +180 -0
- package/src/prompt/skill-loader.ts +195 -0
- package/src/registration.ts +101 -0
- package/src/shell.ts +101 -0
- package/src/spawn/spawn-coordinator.ts +232 -0
- package/src/status-note.ts +10 -0
- package/src/types.ts +47 -71
- package/src/ui/agent-widget.ts +61 -49
- package/src/{format.ts → ui/format.ts} +64 -26
- package/src/ui/menu/helpers.ts +93 -0
- package/src/ui/menu/menu-concurrency.ts +192 -0
- package/src/ui/menu/menu-debug.ts +125 -0
- package/src/ui/menu/menu-model-settings.ts +208 -0
- package/src/ui/menu/menu-running-agents.ts +224 -0
- package/src/ui/menu/menu-spawn-options.ts +87 -0
- package/src/ui/menu/menu-spawn-wizard.ts +418 -0
- package/src/ui/menu/menu-system-prompt.ts +109 -0
- package/src/ui/menu/menu-widget-settings.ts +130 -0
- package/src/ui/menu/menus.ts +101 -0
- package/src/ui/menu/submenus/confirm.ts +47 -0
- package/src/ui/menu/submenus/model-select.ts +70 -0
- package/src/ui/menu/submenus/numeric-input.ts +98 -0
- package/src/ui/menu/wrappers/settings-list.ts +205 -0
- package/src/{renderer.ts → ui/renderer.ts} +7 -6
- package/src/{result-viewer.ts → ui/result-viewer.ts} +7 -2
- package/src/ui/types.ts +11 -0
- package/src/agent-types.ts +0 -184
- package/src/config-mutator.ts +0 -183
- package/src/menus.ts +0 -1333
- package/src/prompts.ts +0 -94
- package/src/skill-loader.ts +0 -178
- package/src/state.ts +0 -83
- /package/src/{worktree-validator.ts → spawn/worktree-validator.ts} +0 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* menu-running-agents.ts — Running agents menu concern.
|
|
3
|
+
*
|
|
4
|
+
* Uses SelectList from @earendil-works/pi-tui via ctx.ui.custom.
|
|
5
|
+
* Agent list is a snapshot at construction time (stale until re-entry is acceptable).
|
|
6
|
+
* Selecting an agent opens an actions submenu (SelectList).
|
|
7
|
+
*
|
|
8
|
+
* Exports:
|
|
9
|
+
* - showRunningAgentsMenu: list running/queued/completed agents
|
|
10
|
+
* - buildAgentActionsList: per-agent action sub-menu (view result, steer, stop)
|
|
11
|
+
*
|
|
12
|
+
* Private helper (single-consumer, co-located):
|
|
13
|
+
* - showResultViewer: show ResultViewer for agent result/error/snapshot
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
17
|
+
import { Input, SelectList, type SelectItem } from "@earendil-works/pi-tui";
|
|
18
|
+
import type { AgentRecord } from "../../types.js";
|
|
19
|
+
import { SHORT_ID_LENGTH } from "../../types.js";
|
|
20
|
+
import { ResultViewer, type ResultViewerStats } from "../result-viewer.js";
|
|
21
|
+
import { getDisplayName, truncateDesc } from "../format.js";
|
|
22
|
+
import { buildSnapshotMarkdown } from "../../prompt/context.js";
|
|
23
|
+
import { buildSelectListTheme, createDelegatingComponent } from "./helpers.js";
|
|
24
|
+
import { getManager, getStore } from "../../shell.js";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Show a ResultViewer for an agent's result, error, or snapshot.
|
|
28
|
+
* @param kind — "result", "error", or "snapshot" — used for the title suffix
|
|
29
|
+
*/
|
|
30
|
+
async function showResultViewer(
|
|
31
|
+
ctx: ExtensionCommandContext,
|
|
32
|
+
record: AgentRecord,
|
|
33
|
+
kind: "result" | "error" | "snapshot",
|
|
34
|
+
text: string,
|
|
35
|
+
): Promise<void> {
|
|
36
|
+
const titleSuffix = kind === "result"
|
|
37
|
+
? record.id.slice(0, SHORT_ID_LENGTH)
|
|
38
|
+
: kind === "snapshot"
|
|
39
|
+
? `snapshot · ${record.id.slice(0, SHORT_ID_LENGTH)}`
|
|
40
|
+
: "Error";
|
|
41
|
+
const stats: ResultViewerStats = {
|
|
42
|
+
lifetimeUsage: record.stats.lifetimeUsage,
|
|
43
|
+
turnCount: record.stats.turnCount,
|
|
44
|
+
durationMs: (record.lifecycle.completedAt ?? Date.now()) - record.lifecycle.startedAt,
|
|
45
|
+
modelName: record.display.invocation?.modelName,
|
|
46
|
+
};
|
|
47
|
+
const refreshCallback =
|
|
48
|
+
kind === "snapshot" && record.execution.session
|
|
49
|
+
? () => buildSnapshotMarkdown(record.execution.session!.messages)
|
|
50
|
+
: undefined;
|
|
51
|
+
|
|
52
|
+
await ctx.ui.custom<void>(
|
|
53
|
+
(tui, theme, _kb, done) =>
|
|
54
|
+
new ResultViewer(
|
|
55
|
+
`${getDisplayName(record.display.type)} · ${titleSuffix}`,
|
|
56
|
+
text,
|
|
57
|
+
{ onClose: () => done(), onRefresh: refreshCallback },
|
|
58
|
+
theme,
|
|
59
|
+
tui.terminal.rows,
|
|
60
|
+
stats,
|
|
61
|
+
),
|
|
62
|
+
{ overlay: true },
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Build a SelectList of actions for a single agent (view result/error/snapshot,
|
|
68
|
+
* steer, stop) for use as a submenu inside a delegating component.
|
|
69
|
+
* @param done — return to the parent agent list (cancel / no actions).
|
|
70
|
+
* @param setActive — swap the delegating component's active child (steer input).
|
|
71
|
+
* @param onClose — close the entire menu (stop).
|
|
72
|
+
*/
|
|
73
|
+
export function buildAgentActionsList(
|
|
74
|
+
ctx: ExtensionCommandContext,
|
|
75
|
+
record: AgentRecord,
|
|
76
|
+
theme: any,
|
|
77
|
+
done: () => void,
|
|
78
|
+
setActive: (c: import("@earendil-works/pi-tui").Component) => void,
|
|
79
|
+
onClose: () => void,
|
|
80
|
+
): SelectList {
|
|
81
|
+
const items: SelectItem[] = [];
|
|
82
|
+
const shortId = record.id.slice(0, SHORT_ID_LENGTH);
|
|
83
|
+
const isRunning = record.lifecycle.status === "running" || record.lifecycle.status === "queued";
|
|
84
|
+
const hasSession = !!record.execution.session;
|
|
85
|
+
const hasResult = !!record.result && record.result.length > 0;
|
|
86
|
+
const hasError = !!record.error && record.error.length > 0;
|
|
87
|
+
|
|
88
|
+
if (record.lifecycle.status === "running" && hasSession) {
|
|
89
|
+
items.push({ value: "view-snapshot", label: "View snapshot" });
|
|
90
|
+
}
|
|
91
|
+
if (hasResult) {
|
|
92
|
+
items.push({ value: "view-result", label: "View result" });
|
|
93
|
+
}
|
|
94
|
+
if (hasError) {
|
|
95
|
+
items.push({ value: "view-error", label: "View error" });
|
|
96
|
+
}
|
|
97
|
+
if (isRunning) {
|
|
98
|
+
items.push({ value: "steer", label: "Steer" });
|
|
99
|
+
items.push({ value: "stop", label: "Stop" });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (items.length === 0) {
|
|
103
|
+
ctx.ui.notify(`Agent ${shortId} — no actions available`, "info");
|
|
104
|
+
done();
|
|
105
|
+
return new SelectList([], 5, buildSelectListTheme(theme));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const list = new SelectList(items, 10, buildSelectListTheme(theme));
|
|
109
|
+
list.onSelect = async (item) => {
|
|
110
|
+
if (item.value === "view-snapshot") {
|
|
111
|
+
const messages = record.execution.session!.messages;
|
|
112
|
+
const markdown = buildSnapshotMarkdown(messages);
|
|
113
|
+
await showResultViewer(ctx, record, "snapshot", markdown);
|
|
114
|
+
} else if (item.value === "view-result") {
|
|
115
|
+
await showResultViewer(ctx, record, "result", record.result!);
|
|
116
|
+
} else if (item.value === "view-error") {
|
|
117
|
+
await showResultViewer(ctx, record, "error", record.error!);
|
|
118
|
+
} else if (item.value === "steer") {
|
|
119
|
+
// Swap to an inline steer input within the menu context.
|
|
120
|
+
const input = new Input();
|
|
121
|
+
input.setValue("");
|
|
122
|
+
input.onSubmit = async (value) => {
|
|
123
|
+
const trimmed = value.trim();
|
|
124
|
+
if (trimmed) {
|
|
125
|
+
const sent = await getManager()!.steer(record.id, trimmed);
|
|
126
|
+
ctx.ui.notify(
|
|
127
|
+
sent ? `Steer sent to ${shortId}…` : `Steer failed for ${shortId}`,
|
|
128
|
+
sent ? "info" : "error",
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
setActive(list);
|
|
132
|
+
};
|
|
133
|
+
input.onEscape = () => setActive(list);
|
|
134
|
+
setActive(input);
|
|
135
|
+
} else if (item.value === "stop") {
|
|
136
|
+
getManager()?.abort(record.id);
|
|
137
|
+
ctx.ui.notify(`Stopped ${shortId}`, "info");
|
|
138
|
+
onClose();
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
list.onCancel = () => done();
|
|
142
|
+
return list;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function showRunningAgentsMenu(
|
|
146
|
+
ctx: ExtensionCommandContext,
|
|
147
|
+
): Promise<void> {
|
|
148
|
+
const agents = getManager()?.listAgents() ?? [];
|
|
149
|
+
if (agents.length === 0) {
|
|
150
|
+
ctx.ui.notify("No agents have been spawned this session", "info");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const running = agents.filter(
|
|
154
|
+
(r) => r.lifecycle.status === "running" || r.lifecycle.status === "queued",
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
await ctx.ui.custom((_tui, theme, _kb, done) => {
|
|
158
|
+
const buildAgentItems = (): SelectItem[] => {
|
|
159
|
+
const items: SelectItem[] = agents.map((record) => {
|
|
160
|
+
const elapsed = Math.round((Date.now() - record.lifecycle.startedAt) / 1000);
|
|
161
|
+
const statusIcon = record.lifecycle.status === "running" ? "\u25B6" :
|
|
162
|
+
record.lifecycle.status === "completed" ? "\u2713" :
|
|
163
|
+
record.lifecycle.status === "queued" ? "\u23F3" :
|
|
164
|
+
record.lifecycle.status === "error" ? "\u2717" : "\u2022";
|
|
165
|
+
const descLen = getStore().agent.widgetDescLengthFull;
|
|
166
|
+
const headline = record.display.description
|
|
167
|
+
? truncateDesc(record.display.description, descLen)
|
|
168
|
+
: "";
|
|
169
|
+
const suffix = headline ? ` \u2014 ${headline}` : "";
|
|
170
|
+
return {
|
|
171
|
+
value: record.id,
|
|
172
|
+
label: `${statusIcon} ${record.id.slice(0, SHORT_ID_LENGTH)} ${record.display.type} ${record.lifecycle.status} ${elapsed}s${suffix}`,
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
if (running.length > 0) {
|
|
176
|
+
items.push({ value: "__sep__", label: " " });
|
|
177
|
+
items.push({ value: "__stop-all", label: `Stop ${running.length} running agent(s)` });
|
|
178
|
+
}
|
|
179
|
+
return items;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const agentList = new SelectList(buildAgentItems(), 15, buildSelectListTheme(theme));
|
|
183
|
+
const delegator = createDelegatingComponent(agentList);
|
|
184
|
+
|
|
185
|
+
agentList.onSelect = async (item) => {
|
|
186
|
+
if (item.value === "__stop-all") {
|
|
187
|
+
for (const r of running) {
|
|
188
|
+
getManager()?.abort(r.id);
|
|
189
|
+
}
|
|
190
|
+
ctx.ui.notify(`Stopped ${running.length} agent(s)`, "info");
|
|
191
|
+
done(undefined);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const record = agents.find((r) => r.id === item.value);
|
|
195
|
+
if (record) {
|
|
196
|
+
const actionsList = buildAgentActionsList(ctx, record, theme, () => {
|
|
197
|
+
delegator.setActive(agentList);
|
|
198
|
+
}, delegator.setActive.bind(delegator), () => done(undefined));
|
|
199
|
+
delegator.setActive(actionsList);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
agentList.onCancel = () => done(undefined);
|
|
203
|
+
|
|
204
|
+
// Simple title wrapper — SettingsListWrapper doesn't work with delegators
|
|
205
|
+
// because it intercepts onSelect on the wrapper target, not on the active child.
|
|
206
|
+
const sep = "\u2500";
|
|
207
|
+
const title = theme.bold(theme.fg("accent", "Running Agents"));
|
|
208
|
+
return {
|
|
209
|
+
invalidate() { delegator.invalidate(); },
|
|
210
|
+
render(width: number) {
|
|
211
|
+
const lines: string[] = [];
|
|
212
|
+
lines.push(sep.repeat(width));
|
|
213
|
+
lines.push("");
|
|
214
|
+
lines.push(" " + title);
|
|
215
|
+
lines.push("");
|
|
216
|
+
lines.push(...delegator.render(width));
|
|
217
|
+
lines.push("");
|
|
218
|
+
lines.push(sep.repeat(width));
|
|
219
|
+
return lines;
|
|
220
|
+
},
|
|
221
|
+
handleInput(data: string) { delegator.handleInput?.(data); },
|
|
222
|
+
};
|
|
223
|
+
});
|
|
224
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* menu-spawn-options.ts — Spawn options menu concern.
|
|
3
|
+
*
|
|
4
|
+
* Uses SettingsList from @earendil-works/pi-tui via ctx.ui.custom.
|
|
5
|
+
* SettingsList maintains internal cursor state, fixing the cursor-position
|
|
6
|
+
* reset bug that occurred with ctx.ui.select.
|
|
7
|
+
*
|
|
8
|
+
* Exports:
|
|
9
|
+
* - showSpawnOptionsMenu: default spawn-time options (thinking, max turns, force background, grace turns)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
import { SettingsList, type SettingItem } from "@earendil-works/pi-tui";
|
|
14
|
+
import { buildSettingsListTheme } from "./helpers.js";
|
|
15
|
+
import { createNumericSubmenu } from "./submenus/numeric-input.js";
|
|
16
|
+
import { SettingsListWrapper } from "./wrappers/settings-list.js";
|
|
17
|
+
import type { ThinkingLevel } from "../../types.js";
|
|
18
|
+
import { DEFAULT_GRACE_TURNS } from "../../config/config-io.js";
|
|
19
|
+
import { getStore } from "../../shell.js";
|
|
20
|
+
|
|
21
|
+
export async function showSpawnOptionsMenu(ctx: ExtensionCommandContext): Promise<void> {
|
|
22
|
+
const store = getStore();
|
|
23
|
+
|
|
24
|
+
const items: SettingItem[] = [
|
|
25
|
+
{
|
|
26
|
+
id: "forceBackground",
|
|
27
|
+
label: "Force background",
|
|
28
|
+
currentValue: store.agent.forceBackground ? "ON" : "OFF",
|
|
29
|
+
values: ["ON", "OFF"],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "graceTurns",
|
|
33
|
+
label: "Grace turns",
|
|
34
|
+
currentValue: String(store.agent.graceTurns),
|
|
35
|
+
submenu: createNumericSubmenu(ctx, { min: 0, default: DEFAULT_GRACE_TURNS }, (parsed) => {
|
|
36
|
+
store.mutate.agent.setGraceTurns(parsed);
|
|
37
|
+
ctx.ui.notify(`Grace turns set to ${parsed}`, "info");
|
|
38
|
+
}),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "defaultMaxTurns",
|
|
42
|
+
label: "Default max turns",
|
|
43
|
+
currentValue: String(store.agent.defaultMaxTurns ?? "(not set)"),
|
|
44
|
+
submenu: createNumericSubmenu(ctx, { min: 1 }, (parsed) => {
|
|
45
|
+
store.mutate.agent.setDefaultMaxTurns(parsed);
|
|
46
|
+
ctx.ui.notify(`Default max turns set to ${parsed}`, "info");
|
|
47
|
+
}, () => {
|
|
48
|
+
store.mutate.agent.setDefaultMaxTurns(undefined);
|
|
49
|
+
ctx.ui.notify("Default max turns cleared", "info");
|
|
50
|
+
}),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: "defaultThinking",
|
|
54
|
+
label: "Default thinking level",
|
|
55
|
+
currentValue: store.agent.defaultThinking ?? "inherit",
|
|
56
|
+
values: ["off", "minimal", "low", "medium", "high", "xhigh", "inherit"],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "disableDefaultAgents",
|
|
60
|
+
label: "Disable default agents",
|
|
61
|
+
currentValue: store.agent.disableDefaultAgents ? "ON" : "OFF",
|
|
62
|
+
values: ["ON", "OFF"],
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
const onChange = (id: string, newValue: string) => {
|
|
67
|
+
switch (id) {
|
|
68
|
+
case "forceBackground":
|
|
69
|
+
store.mutate.agent.setForceBackground(newValue === "ON");
|
|
70
|
+
ctx.ui.notify(`Force background set to ${newValue}`, "info");
|
|
71
|
+
break;
|
|
72
|
+
case "defaultThinking":
|
|
73
|
+
store.mutate.agent.setDefaultThinking(newValue === "inherit" ? undefined : newValue as ThinkingLevel);
|
|
74
|
+
ctx.ui.notify(`Default thinking level set to ${newValue}`, "info");
|
|
75
|
+
break;
|
|
76
|
+
case "disableDefaultAgents":
|
|
77
|
+
store.mutate.agent.setDisableDefaultAgents(newValue === "ON");
|
|
78
|
+
ctx.ui.notify(`Disable default agents ${newValue} (takes effect on next session)`, "info");
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
await ctx.ui.custom((_tui, theme, _kb, done) => {
|
|
84
|
+
const settingsList = new SettingsList(items, 10, buildSettingsListTheme(theme), onChange, () => done(undefined));
|
|
85
|
+
return new SettingsListWrapper(settingsList, { title: "Spawn Options", theme, onCancel: () => done(undefined) });
|
|
86
|
+
});
|
|
87
|
+
}
|