@howarewoo/pi-subagents 0.1.1 → 0.2.0
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 +32 -0
- package/README.md +265 -94
- package/docs/advanced-policies.md +4 -2
- package/docs/delegation-contract.md +57 -28
- package/docs/images/subagent-hub.svg +1 -1
- package/docs/images/subagent-profiles.svg +1 -1
- package/docs/images/subagent-widget.svg +1 -1
- package/docs/releases.md +111 -23
- package/docs/verification.md +1 -1
- package/package.json +4 -4
- package/src/child-execution.ts +459 -0
- package/src/coordinator.ts +608 -664
- package/src/delegation-types.ts +111 -0
- package/src/hub.ts +38 -267
- package/src/pi-extended-subagents.ts +83 -189
- package/src/profile-viewer.ts +109 -38
- package/src/scheduling.ts +1 -1
- package/src/subagent-widget.ts +115 -0
- package/src/supported-host.ts +26 -0
- package/src/task-input.ts +3 -2
- package/src/task-presentation.ts +165 -0
- package/src/tool-schemas.ts +165 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentSession,
|
|
3
|
+
AgentSessionEvent,
|
|
4
|
+
ExtensionToolContext,
|
|
5
|
+
Skill,
|
|
6
|
+
ToolDefinition,
|
|
7
|
+
} from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import type { CapabilityDenial, ToolMetadata } from "./capabilities.ts";
|
|
9
|
+
import type { StructuredOutcome } from "./outcomes.ts";
|
|
10
|
+
import type { ModelSelection } from "./model-policy.ts";
|
|
11
|
+
import type { ExecutionLimits } from "./scheduling.ts";
|
|
12
|
+
import type { BuiltinTool, Profile } from "./profiles.ts";
|
|
13
|
+
|
|
14
|
+
export type DelegationMessage = {
|
|
15
|
+
id: string;
|
|
16
|
+
sender: ChildRef | "root";
|
|
17
|
+
recipient: ChildRef | "root";
|
|
18
|
+
text: string;
|
|
19
|
+
mode: "inbox" | "steer" | "followUp";
|
|
20
|
+
status: "accepted" | "queued" | "handled" | "delivered" | "not-running" | "error";
|
|
21
|
+
error?: string;
|
|
22
|
+
};
|
|
23
|
+
export type CoordinationWait =
|
|
24
|
+
| { status: "timeout" | "cancelled" | "shutdown" }
|
|
25
|
+
| { status: "messages"; messages: DelegationMessage[] }
|
|
26
|
+
| { status: "message"; message: DelegationMessage }
|
|
27
|
+
| {
|
|
28
|
+
status: "outcome";
|
|
29
|
+
outcome: Pick<ChildSnapshot, "id" | "generation" | "status" | "limitReason" | "pauseReason">;
|
|
30
|
+
};
|
|
31
|
+
export type ChildRef = Readonly<{ id: string; generation: number }>;
|
|
32
|
+
export type ChildSnapshot = Readonly<
|
|
33
|
+
ChildRef & {
|
|
34
|
+
parentId: string;
|
|
35
|
+
parentGeneration?: number;
|
|
36
|
+
depth: number;
|
|
37
|
+
writerLease: boolean;
|
|
38
|
+
limits: ExecutionLimits;
|
|
39
|
+
limitReason?: string;
|
|
40
|
+
toolCalls: number;
|
|
41
|
+
spawned?: number;
|
|
42
|
+
pauseReason?: string;
|
|
43
|
+
retainedContext?: boolean;
|
|
44
|
+
window?: number;
|
|
45
|
+
status:
|
|
46
|
+
| "queued"
|
|
47
|
+
| "setup"
|
|
48
|
+
| "running"
|
|
49
|
+
| "pausing"
|
|
50
|
+
| "paused"
|
|
51
|
+
| "draining"
|
|
52
|
+
| "settled"
|
|
53
|
+
| "failed"
|
|
54
|
+
| "cancelled";
|
|
55
|
+
profile: string;
|
|
56
|
+
tools: readonly BuiltinTool[];
|
|
57
|
+
leafTools?: readonly string[];
|
|
58
|
+
skills?: readonly string[];
|
|
59
|
+
capabilityDenials?: readonly CapabilityDenial[];
|
|
60
|
+
cwd?: string;
|
|
61
|
+
assignment: string;
|
|
62
|
+
activity: string;
|
|
63
|
+
progress: string;
|
|
64
|
+
result: string;
|
|
65
|
+
model: { provider: string; id: string };
|
|
66
|
+
effort: string | null;
|
|
67
|
+
prewalk?: {
|
|
68
|
+
target: ModelSelection;
|
|
69
|
+
status: "armed" | "handed-off" | "suppressed-exact" | "no-op";
|
|
70
|
+
};
|
|
71
|
+
telemetry: {
|
|
72
|
+
requests: number | null;
|
|
73
|
+
tokens: ReturnType<AgentSession["getSessionStats"]>["tokens"] | null;
|
|
74
|
+
cost: number | null;
|
|
75
|
+
context: ReturnType<AgentSession["getContextUsage"]> | null;
|
|
76
|
+
};
|
|
77
|
+
outcome?: StructuredOutcome;
|
|
78
|
+
artifacts?: { text?: string; structured?: string; error?: string };
|
|
79
|
+
error?: string;
|
|
80
|
+
transcript?: string;
|
|
81
|
+
disposed: boolean;
|
|
82
|
+
createdAt: number;
|
|
83
|
+
endedAt: number | null;
|
|
84
|
+
}
|
|
85
|
+
>;
|
|
86
|
+
export type RegistryEvent = Readonly<{
|
|
87
|
+
type: "changed" | "sdk";
|
|
88
|
+
child: ChildSnapshot;
|
|
89
|
+
event?: AgentSessionEvent;
|
|
90
|
+
}>;
|
|
91
|
+
export type StartTask = {
|
|
92
|
+
cwd?: string;
|
|
93
|
+
model?: ModelSelection;
|
|
94
|
+
resultSchema?: unknown;
|
|
95
|
+
assignment: string;
|
|
96
|
+
profile: Profile;
|
|
97
|
+
skills: Skill[];
|
|
98
|
+
context: ExtensionToolContext;
|
|
99
|
+
assertTools: (names: readonly string[]) => void;
|
|
100
|
+
toolMetadata?: () => readonly ToolMetadata[];
|
|
101
|
+
/** Live parent codemode availability; never a grant to forward its dispatcher. */
|
|
102
|
+
codemodeEnabled?: () => boolean;
|
|
103
|
+
availableSkills?: () => readonly Skill[];
|
|
104
|
+
capabilityDenials?: CapabilityDenial[];
|
|
105
|
+
leafProvenance?: Readonly<Record<string, string>>;
|
|
106
|
+
callerId: string;
|
|
107
|
+
parent?: ChildRef;
|
|
108
|
+
limits?: Partial<ExecutionLimits>;
|
|
109
|
+
coordinationTools?: (actor: ChildRef) => ToolDefinition[];
|
|
110
|
+
nestedTool?: (parent: ChildRef) => ToolDefinition;
|
|
111
|
+
};
|
package/src/hub.ts
CHANGED
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
Input,
|
|
4
4
|
matchesKey,
|
|
5
5
|
oklchColor,
|
|
6
|
-
sliceByColumn,
|
|
7
6
|
truncateToWidth,
|
|
8
7
|
visibleWidth,
|
|
9
8
|
wrapTextWithAnsi,
|
|
@@ -15,229 +14,23 @@ import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
|
15
14
|
import type { ChildRef, ChildSnapshot, DelegationCoordinator } from "./coordinator.ts";
|
|
16
15
|
import { createNativeThread, safe, type LiveTool, type ThreadDependencies } from "./thread.ts";
|
|
17
16
|
export { safe } from "./thread.ts";
|
|
17
|
+
export { createChildWidget } from "./subagent-widget.ts";
|
|
18
|
+
export {
|
|
19
|
+
agentLabel,
|
|
20
|
+
fleetSummary,
|
|
21
|
+
taskCard,
|
|
22
|
+
transcriptLines,
|
|
23
|
+
writerWarning,
|
|
24
|
+
} from "./task-presentation.ts";
|
|
25
|
+
import {
|
|
26
|
+
agentLabel,
|
|
27
|
+
assignmentHeadline,
|
|
28
|
+
lineText,
|
|
29
|
+
taskDetails,
|
|
30
|
+
transcriptLines,
|
|
31
|
+
} from "./task-presentation.ts";
|
|
18
32
|
|
|
19
33
|
type Palette = Pick<Theme, "fg" | "style"> & { colors: Pick<Theme["colors"], "customMessageBg"> };
|
|
20
|
-
const lineText = (text: string) => safe(text).replaceAll("\n", " ");
|
|
21
|
-
const assignmentHeadline = (child: ChildSnapshot) =>
|
|
22
|
-
safe(child.assignment)
|
|
23
|
-
.split("\n")
|
|
24
|
-
.map((line) => line.trim())
|
|
25
|
-
.find(Boolean)
|
|
26
|
-
?.replace(/^#{1,6}\s+/, "") || "Assignment";
|
|
27
|
-
export const agentLabel = (child: ChildSnapshot) =>
|
|
28
|
-
`${assignmentHeadline(child)} · ${lineText(child.profile)}`;
|
|
29
|
-
const active = (child: ChildSnapshot) => !child.disposed;
|
|
30
|
-
const activity = (child: ChildSnapshot) =>
|
|
31
|
-
child.error ||
|
|
32
|
-
(active(child) ? child.activity || child.progress : child.result) ||
|
|
33
|
-
child.progress ||
|
|
34
|
-
"Waiting for SDK activity";
|
|
35
|
-
export const writerWarning = "Model-tool writer only: no conflicting manual !/!! or external edits";
|
|
36
|
-
const statuses = [
|
|
37
|
-
"queued",
|
|
38
|
-
"setup",
|
|
39
|
-
"running",
|
|
40
|
-
"draining",
|
|
41
|
-
"settled",
|
|
42
|
-
"failed",
|
|
43
|
-
"cancelled",
|
|
44
|
-
] as const;
|
|
45
|
-
export function fleetSummary(children: ChildSnapshot[]): string {
|
|
46
|
-
return children.length
|
|
47
|
-
? statuses
|
|
48
|
-
.map((status) => `${status} ${children.filter((child) => child.status === status).length}`)
|
|
49
|
-
.join(" · ")
|
|
50
|
-
: "No tasks · ask the model to delegate_task";
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Event-driven, bounded current-agent tree; history and full metrics remain in the Hub. */
|
|
54
|
-
export function createChildWidget(
|
|
55
|
-
snapshot: () => ChildSnapshot[],
|
|
56
|
-
theme: Pick<Theme, "fg"> & Partial<Pick<Theme, "bold">>,
|
|
57
|
-
availableRows = () => 30,
|
|
58
|
-
): Component {
|
|
59
|
-
return {
|
|
60
|
-
render(width) {
|
|
61
|
-
width = Math.max(0, width);
|
|
62
|
-
const children = snapshot();
|
|
63
|
-
if (!children.length) return [];
|
|
64
|
-
const queued = children.filter((child) => child.status === "queued").length;
|
|
65
|
-
const priority = (child: ChildSnapshot) =>
|
|
66
|
-
child.status === "draining"
|
|
67
|
-
? 0
|
|
68
|
-
: child.status === "running"
|
|
69
|
-
? 1
|
|
70
|
-
: child.status === "setup"
|
|
71
|
-
? 2
|
|
72
|
-
: 3;
|
|
73
|
-
const live = children
|
|
74
|
-
.filter(
|
|
75
|
-
(child) =>
|
|
76
|
-
!child.disposed &&
|
|
77
|
-
(child.status === "setup" || child.status === "running" || child.status === "draining"),
|
|
78
|
-
)
|
|
79
|
-
.sort((a, b) => priority(a) - priority(b));
|
|
80
|
-
const writer = children.some((child) => child.writerLease);
|
|
81
|
-
const budget = Math.max(1, Math.min(5, Math.floor(availableRows() / 4)));
|
|
82
|
-
const counts =
|
|
83
|
-
width < 60
|
|
84
|
-
? `${live.length} active · ${queued} queued`
|
|
85
|
-
: statuses
|
|
86
|
-
.flatMap((status) => {
|
|
87
|
-
const count = children.filter((child) => child.status === status).length;
|
|
88
|
-
return count ? [`${count} ${status}`] : [];
|
|
89
|
-
})
|
|
90
|
-
.join(" · ");
|
|
91
|
-
const rows = [
|
|
92
|
-
theme.fg("accent", `● Subagents ${children.length}`) + theme.fg("muted", ` · ${counts}`),
|
|
93
|
-
];
|
|
94
|
-
if (writer && budget === 1)
|
|
95
|
-
rows[0] = theme.fg(
|
|
96
|
-
"warning",
|
|
97
|
-
`● Subagents ${children.length} · Model writer: !/!! + external unsafe · ${queued} queued`,
|
|
98
|
-
);
|
|
99
|
-
const room = Math.max(0, budget - 1 - (writer && budget > 1 ? 1 : 0));
|
|
100
|
-
const activityRows = live.length && room >= (live.length > 1 ? 3 : 2) ? 1 : 0;
|
|
101
|
-
const slots = room - activityRows;
|
|
102
|
-
const overflow = live.length > slots;
|
|
103
|
-
const shown = live.slice(0, Math.max(0, slots - (overflow ? 1 : 0)));
|
|
104
|
-
shown.forEach((child, index) => {
|
|
105
|
-
const last = index === shown.length - 1 && shown.length === live.length;
|
|
106
|
-
const color = child.status === "draining" ? "warning" : "accent";
|
|
107
|
-
const role = lineText(child.profile);
|
|
108
|
-
const indent = " ".repeat(Math.min(3, Math.max(0, child.depth - 1)));
|
|
109
|
-
const prefix =
|
|
110
|
-
theme.fg("muted", `${indent}${last ? "└─" : "├─"} `) +
|
|
111
|
-
theme.fg(color, "● ") +
|
|
112
|
-
(theme.bold?.(role) ?? role) +
|
|
113
|
-
" ";
|
|
114
|
-
const elapsed = Math.max(
|
|
115
|
-
0,
|
|
116
|
-
Math.floor(((child.endedAt ?? Date.now()) - child.createdAt) / 1000),
|
|
117
|
-
);
|
|
118
|
-
const stats = [
|
|
119
|
-
child.status,
|
|
120
|
-
`${elapsed}s`,
|
|
121
|
-
`${child.toolCalls} tools`,
|
|
122
|
-
...(child.telemetry.requests === null ? [] : [`${child.telemetry.requests} req`]),
|
|
123
|
-
...(child.telemetry.tokens ? [`${child.telemetry.tokens.total} tok`] : []),
|
|
124
|
-
...(child.telemetry.cost === null ? [] : [`$${child.telemetry.cost.toFixed(3)}`]),
|
|
125
|
-
].join(" · ");
|
|
126
|
-
const metric = truncateToWidth(stats, Math.max(8, Math.floor(width / 2)));
|
|
127
|
-
const headline = assignmentHeadline(child);
|
|
128
|
-
rows.push(
|
|
129
|
-
prefix +
|
|
130
|
-
theme.fg(
|
|
131
|
-
"text",
|
|
132
|
-
truncateToWidth(
|
|
133
|
-
headline,
|
|
134
|
-
Math.max(4, width - visibleWidth(prefix) - visibleWidth(metric) - 3),
|
|
135
|
-
),
|
|
136
|
-
) +
|
|
137
|
-
theme.fg("muted", " · " + metric),
|
|
138
|
-
);
|
|
139
|
-
if (!index && activityRows) {
|
|
140
|
-
const prefix = `${indent}${last ? " " : "│ "} └ `;
|
|
141
|
-
const text = lineText(activity(child));
|
|
142
|
-
const columns = Math.max(0, width - visibleWidth(prefix));
|
|
143
|
-
const length = visibleWidth(text);
|
|
144
|
-
// Progress is an accumulated tail; keep its newest text, not a frozen opening paragraph.
|
|
145
|
-
const preview =
|
|
146
|
-
length > columns
|
|
147
|
-
? "…" +
|
|
148
|
-
sliceByColumn(text, length - Math.max(0, columns - 1), Math.max(0, columns - 1))
|
|
149
|
-
: text;
|
|
150
|
-
rows.push(theme.fg("muted", prefix + preview));
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
if (shown.length < live.length && room)
|
|
154
|
-
rows.push(theme.fg("muted", `└─ ${live.length - shown.length} more · /subagent-hub`));
|
|
155
|
-
if (writer && budget > 1)
|
|
156
|
-
rows.push(
|
|
157
|
-
theme.fg("warning", width < 75 ? "Model writer: !/!! + external unsafe" : writerWarning),
|
|
158
|
-
);
|
|
159
|
-
return rows.slice(0, budget).map((line) => truncateToWidth(line, width));
|
|
160
|
-
},
|
|
161
|
-
invalidate() {},
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export function taskCard(child: ChildSnapshot | undefined, expanded = false): string[] {
|
|
166
|
-
if (!child)
|
|
167
|
-
return [
|
|
168
|
-
"Task identity unavailable",
|
|
169
|
-
"See tool error; /subagent-hub lists current runtime tasks",
|
|
170
|
-
];
|
|
171
|
-
return [
|
|
172
|
-
`${truncateToWidth(agentLabel(child), 240, "… [preview truncated]")} #${child.generation} · ${child.status}`,
|
|
173
|
-
`Identity ${child.id} #${child.generation}`,
|
|
174
|
-
`${child.model.provider}/${child.model.id} · effort ${child.effort ?? "unavailable"}`,
|
|
175
|
-
truncateToWidth(lineText(activity(child)), 240, "… [preview truncated]"),
|
|
176
|
-
`Prewalk ${child.prewalk ? JSON.stringify(child.prewalk) : "off"} · advisor unavailable`,
|
|
177
|
-
`Parent ${child.parentId}${child.parentGeneration ? ` #${child.parentGeneration}` : ""} · depth ${child.depth} · writer lease ${child.writerLease}`,
|
|
178
|
-
...(child.outcome
|
|
179
|
-
? [
|
|
180
|
-
`Structured ${child.outcome.validation} · ${child.outcome.finalized ? "finalized" : "provisional"} · attempts ${child.outcome.attempts}`,
|
|
181
|
-
]
|
|
182
|
-
: []),
|
|
183
|
-
...(child.limitReason ? [`Limit: ${child.limitReason}`] : []),
|
|
184
|
-
...(child.artifacts ? [`Artifacts: ${JSON.stringify(child.artifacts)}`] : []),
|
|
185
|
-
...(expanded
|
|
186
|
-
? [
|
|
187
|
-
"Assignment: " +
|
|
188
|
-
truncateToWidth(lineText(child.assignment), 480, "… [preview truncated]"),
|
|
189
|
-
"Result: " +
|
|
190
|
-
truncateToWidth(
|
|
191
|
-
lineText(child.result || child.error || "Not available yet"),
|
|
192
|
-
480,
|
|
193
|
-
"… [preview truncated]",
|
|
194
|
-
),
|
|
195
|
-
]
|
|
196
|
-
: []),
|
|
197
|
-
child.transcript
|
|
198
|
-
? `Full transcript: ${child.transcript} · /subagent-hub`
|
|
199
|
-
: "Transcript unavailable (setup) · /subagent-hub",
|
|
200
|
-
].map(lineText);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/** Read-only content projection: never dumps provider/auth objects, image payloads or opaque signatures. */
|
|
204
|
-
export function transcriptLines(
|
|
205
|
-
messages: ReturnType<DelegationCoordinator["messages"]>,
|
|
206
|
-
toolsOnly = false,
|
|
207
|
-
): string[] {
|
|
208
|
-
const lines: string[] = [];
|
|
209
|
-
for (const message of messages) {
|
|
210
|
-
if (message.role === "system") continue;
|
|
211
|
-
const start = lines.length;
|
|
212
|
-
const blocks = "content" in message ? message.content : undefined;
|
|
213
|
-
if (!toolsOnly || message.role === "toolResult") {
|
|
214
|
-
lines.push(
|
|
215
|
-
`[${message.role}${message.role === "toolResult" ? ` ${message.toolName} ${message.toolCallId} ${message.isError ? "ERROR" : "result"}` : ""}]`,
|
|
216
|
-
);
|
|
217
|
-
if (typeof blocks === "string") lines.push(safe(blocks));
|
|
218
|
-
}
|
|
219
|
-
if (Array.isArray(blocks))
|
|
220
|
-
for (const block of blocks) {
|
|
221
|
-
if (block.type === "toolCall")
|
|
222
|
-
lines.push(
|
|
223
|
-
`Tool ${block.name} ${block.id}\nArguments: ${safe(JSON.stringify(block.arguments, null, 2))}`,
|
|
224
|
-
);
|
|
225
|
-
else if (!toolsOnly || message.role === "toolResult") {
|
|
226
|
-
if (block.type === "text") lines.push(safe(block.text));
|
|
227
|
-
else if (block.type === "thinking") lines.push("Thinking: " + safe(block.thinking));
|
|
228
|
-
else if (block.type === "image")
|
|
229
|
-
lines.push(`[image ${safe(block.mimeType)}; payload omitted]`);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
if (message.role === "assistant" && message.errorMessage)
|
|
233
|
-
lines.push("Error: " + safe(message.errorMessage));
|
|
234
|
-
if (start && lines.length > start) lines.splice(start, 0, "");
|
|
235
|
-
}
|
|
236
|
-
return lines.length
|
|
237
|
-
? lines.map(safe)
|
|
238
|
-
: [toolsOnly ? "No tool calls yet" : "Waiting for SDK messages"];
|
|
239
|
-
}
|
|
240
|
-
|
|
241
34
|
/** Observation only: closing or disposing never touches child execution. */
|
|
242
35
|
export function createHub(
|
|
243
36
|
coordinator: DelegationCoordinator,
|
|
@@ -337,6 +130,7 @@ export function createHub(
|
|
|
337
130
|
!child ||
|
|
338
131
|
child.disposed ||
|
|
339
132
|
child.status === "draining" ||
|
|
133
|
+
child.status === "pausing" ||
|
|
340
134
|
(text !== undefined && child.status !== "running")
|
|
341
135
|
)
|
|
342
136
|
throw new Error(
|
|
@@ -452,8 +246,6 @@ export function createHub(
|
|
|
452
246
|
footer.push(
|
|
453
247
|
theme.fg("muted", `Filter: ${lineText(query)} · ${children.length} generations`),
|
|
454
248
|
);
|
|
455
|
-
if (current.some((item) => item.writerLease))
|
|
456
|
-
footer.push(theme.fg("warning", "WRITER: model tools only; no manual/external writes"));
|
|
457
249
|
if (notice) {
|
|
458
250
|
const lines = wrapTextWithAnsi(lineText(notice), Math.max(1, inner));
|
|
459
251
|
footer.push(
|
|
@@ -517,40 +309,13 @@ export function createHub(
|
|
|
517
309
|
let nativeLines: string[] | undefined;
|
|
518
310
|
if (child) {
|
|
519
311
|
if (tab === "details") {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
"",
|
|
528
|
-
"Result",
|
|
529
|
-
safe(child.result || "Not available yet"),
|
|
530
|
-
"",
|
|
531
|
-
...(child.error ? ["Error", safe(child.error), ""] : []),
|
|
532
|
-
...(child.outcome
|
|
533
|
-
? ["Structured outcome", JSON.stringify(child.outcome, null, 2), ""]
|
|
534
|
-
: []),
|
|
535
|
-
...(child.artifacts
|
|
536
|
-
? ["Saved artifacts", JSON.stringify(child.artifacts, null, 2), ""]
|
|
537
|
-
: []),
|
|
538
|
-
`Workspace: ${safe(child.cwd ?? "parent workspace")}`,
|
|
539
|
-
`Effective builtin tools: ${child.tools.join(", ")}`,
|
|
540
|
-
`${child.model.provider}/${child.model.id} · effort ${child.effort ?? "unavailable"}`,
|
|
541
|
-
`Prewalk ${child.prewalk ? JSON.stringify(child.prewalk) : "off"} · advisor unavailable`,
|
|
542
|
-
`Presence ${coordinator.agents().find((agent) => agent.id === child.id && agent.generation === child.generation)?.presence ?? "historical"} · resources ${child.disposed ? "disposed" : "live"}`,
|
|
543
|
-
`Elapsed ${Math.max(0, Math.floor(((child.endedAt ?? Date.now()) - child.createdAt) / 1000))}s`,
|
|
544
|
-
`Direct requests ${metric(child.telemetry.requests)} · cost ${metric(child.telemetry.cost)}`,
|
|
545
|
-
`Direct tokens ${metric(child.telemetry.tokens)}`,
|
|
546
|
-
`Context ${metric(child.telemetry.context)}`,
|
|
547
|
-
`Parent ${child.parentId} #${child.parentGeneration ?? "root"} · depth ${child.depth} · direct generation only`,
|
|
548
|
-
`Limits ${JSON.stringify(child.limits)} · calls ${child.toolCalls} · reason ${child.limitReason ?? "none"}`,
|
|
549
|
-
"Activity: " + safe(activity(child)),
|
|
550
|
-
"Full transcript: " + safe(child.transcript ?? "unavailable (queued/setup)"),
|
|
551
|
-
"Followup/revive: native delegate_coordinate only (Hub has no tool dispatcher)",
|
|
552
|
-
...(child.writerLease ? [writerWarning] : []),
|
|
553
|
-
];
|
|
312
|
+
detail = taskDetails(
|
|
313
|
+
child,
|
|
314
|
+
coordinator
|
|
315
|
+
.agents()
|
|
316
|
+
.find((agent) => agent.id === child.id && agent.generation === child.generation)
|
|
317
|
+
?.presence,
|
|
318
|
+
);
|
|
554
319
|
} else {
|
|
555
320
|
try {
|
|
556
321
|
const messages = coordinator.messages(child);
|
|
@@ -626,7 +391,7 @@ export function createHub(
|
|
|
626
391
|
? "success"
|
|
627
392
|
: "accent";
|
|
628
393
|
const selectedLabel = child
|
|
629
|
-
? `${agentLabel(child)}
|
|
394
|
+
? `${agentLabel(child)} · ${child.status}${historical(child) ? " · history" : ""}`
|
|
630
395
|
: "No selection";
|
|
631
396
|
const viewLabel = `${tab.toUpperCase()} · Tab: ${tab === "thread" ? "details" : tab === "details" ? "tools" : "thread"}`;
|
|
632
397
|
const position = `${tab === "thread" ? (following ? "LIVE · " : "READING · ") : ""}${offset + 1}–${Math.min(wrapped.length, offset + viewport)}/${wrapped.length} lines${tab === "thread" ? (following ? " · k/PgUp read" : " · End: live") : offset < maxOffset ? " · more ↓" : ""}`;
|
|
@@ -636,16 +401,21 @@ export function createHub(
|
|
|
636
401
|
const start = Math.max(0, index - slots + 1);
|
|
637
402
|
const left = [
|
|
638
403
|
theme.fg("accent", `SUBAGENTS · ${children.length} generations`),
|
|
639
|
-
...children
|
|
640
|
-
.
|
|
641
|
-
|
|
404
|
+
...children.slice(start, start + slots).flatMap((item) => {
|
|
405
|
+
const indent = " ".repeat(Math.min(4, Math.max(0, item.depth - 1)));
|
|
406
|
+
const prefix = `${identity(item) === selected ? ">" : " "} ${indent}└ `;
|
|
407
|
+
return [
|
|
642
408
|
theme.fg(
|
|
643
409
|
identity(item) === selected ? "accent" : "text",
|
|
644
|
-
`${
|
|
410
|
+
`${prefix}${assignmentHeadline(item)}${historical(item) ? " · history" : ""}`,
|
|
411
|
+
),
|
|
412
|
+
theme.fg(
|
|
413
|
+
statusColor(item),
|
|
414
|
+
`${" ".repeat(visibleWidth(prefix))}${lineText(item.profile)} · ${item.status}`,
|
|
645
415
|
),
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
416
|
+
"", // Separate agents without exposing internal generation numbers.
|
|
417
|
+
];
|
|
418
|
+
}),
|
|
649
419
|
];
|
|
650
420
|
const right = [
|
|
651
421
|
theme.fg("accent", lineText(selectedLabel)),
|
|
@@ -674,7 +444,7 @@ export function createHub(
|
|
|
674
444
|
),
|
|
675
445
|
);
|
|
676
446
|
} else {
|
|
677
|
-
const suffix = child ? `
|
|
447
|
+
const suffix = child ? ` · ${index + 1}/${children.length}` : "";
|
|
678
448
|
pane.push(
|
|
679
449
|
theme.fg(
|
|
680
450
|
"accent",
|
|
@@ -798,6 +568,7 @@ export function createHub(
|
|
|
798
568
|
child &&
|
|
799
569
|
!child.disposed &&
|
|
800
570
|
child.status !== "draining" &&
|
|
571
|
+
child.status !== "pausing" &&
|
|
801
572
|
(data === "x" || child.status === "running")
|
|
802
573
|
) {
|
|
803
574
|
mode = data === "s" ? "steer" : data === "m" ? "message" : "stop";
|