pantheon-opencode 1.5.0-beta.8 → 1.5.0-beta.9
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 +16 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/pyproject.toml +1 -1
- package/src/plugins/tui/dist/tui.d.ts +31 -10
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +51 -22
- package/src/plugins/tui/dist/tui.tsx +111 -35
- package/src/plugins/tui/package-lock.json +2 -2
- package/src/plugins/tui/package.json +1 -1
- package/src/plugins/tui/src/index.tsx +111 -35
package/CHANGELOG.md
CHANGED
|
@@ -18,6 +18,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
18
18
|
|
|
19
19
|
## ✅ Closed Issues
|
|
20
20
|
|
|
21
|
+
## [v1.5.0-beta.9] - 2026-09-14
|
|
22
|
+
|
|
23
|
+
<!-- Add new changes here. Running `node scripts/versioning.mjs apply` will
|
|
24
|
+
move this section to a versioned entry and reset the template below. -->
|
|
25
|
+
|
|
26
|
+
## 🆕 What's New
|
|
27
|
+
|
|
28
|
+
- **TUI delegations nativas (gated):** painel Delegations renderiza N delegações native + M pantheon com tag [native], live task em tempo real, click navega para sessão. Implementado em `src/plugins/tui/src/index.tsx` com dist rebuildado.
|
|
29
|
+
- **93 testes TUI delegations:** suite `tests/pantheon/tui-delegations.test.ts` com 93/93 passando (tsc + biome OK).
|
|
30
|
+
- **E2E beta.8 PASS (referência):** baseline E2E da beta.8 mantido como referência de estabilidade para beta.9.
|
|
31
|
+
|
|
32
|
+
## 🐞 Fixed
|
|
33
|
+
|
|
34
|
+
- Removido diretório `testyes/` (artefato de teste).
|
|
35
|
+
- Truncado `hooks.log` para baseline limpo de beta.9.
|
|
36
|
+
|
|
21
37
|
## [v1.5.0-beta.8] - 2026-09-13
|
|
22
38
|
|
|
23
39
|
<!-- Add new changes here. Running `node scripts/versioning.mjs apply` will
|
package/package.json
CHANGED
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pantheon",
|
|
3
3
|
"publisher": "ils15",
|
|
4
|
-
"version": "1.5.0-beta.
|
|
4
|
+
"version": "1.5.0-beta.9",
|
|
5
5
|
"displayName": "Pantheon",
|
|
6
6
|
"description": "Conductor-Delegate orchestration platform with 14 specialized agents, 21 skills, and OpenCode support.",
|
|
7
7
|
"agents": "./src/agents",
|
package/pyproject.toml
CHANGED
|
@@ -171,7 +171,7 @@ type LiveDelegationEntry = {
|
|
|
171
171
|
partID: string;
|
|
172
172
|
/** Parent session the delegation was launched from. */
|
|
173
173
|
sessionID: string;
|
|
174
|
-
tool: 'pantheon_delegate' | 'pantheon_delegation_read';
|
|
174
|
+
tool: 'pantheon_delegate' | 'pantheon_delegation_read' | 'task';
|
|
175
175
|
/** Agent name (from the delegate input args). */
|
|
176
176
|
agent: string;
|
|
177
177
|
description: string;
|
|
@@ -190,7 +190,7 @@ type ParsedDelegationToolPart = {
|
|
|
190
190
|
callID: string;
|
|
191
191
|
partID: string;
|
|
192
192
|
sessionID: string;
|
|
193
|
-
tool: 'pantheon_delegate' | 'pantheon_delegation_read';
|
|
193
|
+
tool: 'pantheon_delegate' | 'pantheon_delegation_read' | 'task';
|
|
194
194
|
/** null for read parts (no agent arg — the id targets an existing job). */
|
|
195
195
|
agent: string | null;
|
|
196
196
|
description: string;
|
|
@@ -202,7 +202,9 @@ type ParsedDelegationToolPart = {
|
|
|
202
202
|
};
|
|
203
203
|
/** Extract the tool name + args from a `message.part.updated` part and
|
|
204
204
|
* reduce it to what the panel needs. Returns null for anything that is
|
|
205
|
-
* not a pantheon delegation tool part
|
|
205
|
+
* not a pantheon delegation tool part, the native `task` subagent tool
|
|
206
|
+
* (same parentID === caller mechanism — its children render `[native]`),
|
|
207
|
+
* or is missing its callID. */
|
|
206
208
|
declare function parseDelegationToolPart(part: DelegationToolPart, now?: number): ParsedDelegationToolPart | null;
|
|
207
209
|
/** Apply one tool part to the live map. Returns true when the map changed.
|
|
208
210
|
* Pure w.r.t. I/O — only mutates `map`. */
|
|
@@ -210,11 +212,14 @@ declare function reduceDelegationToolPart(map: Map<string, LiveDelegationEntry>,
|
|
|
210
212
|
/** Remove a live entry by part id (message.part.removed) or call id.
|
|
211
213
|
* Returns true when something was removed. */
|
|
212
214
|
declare function removeDelegationEntry(map: Map<string, LiveDelegationEntry>, partIDOrCallID: string): boolean;
|
|
213
|
-
/** Collect pantheon delegation tool parts from a session's messages.
|
|
215
|
+
/** Collect pantheon delegation + native task tool parts from a session's messages.
|
|
214
216
|
* Messages may carry their parts inline (duck-typed `msg.parts`); when
|
|
215
217
|
* they don't, the optional `getParts(messageID)` callback is used (the TUI
|
|
216
|
-
* SDK exposes `api.state.part(messageID)`).
|
|
217
|
-
*
|
|
218
|
+
* SDK exposes `api.state.part(messageID)`). The native `task` tool spawns a
|
|
219
|
+
* child session with parentID = caller — the same mechanism as
|
|
220
|
+
* pantheon_delegate — so its parts feed the live-map as the native signal
|
|
221
|
+
* (rows render `[native]` via the children channel). Pure w.r.t. I/O — used
|
|
222
|
+
* by the mount re-scan to re-seed the live map after compaction/attach. */
|
|
218
223
|
declare function collectDelegationToolParts(messages: readonly {
|
|
219
224
|
id?: string;
|
|
220
225
|
parts?: unknown[];
|
|
@@ -318,6 +323,18 @@ declare function childStatusToState(status: string | undefined): 'running' | 'co
|
|
|
318
323
|
* panel renders the tag with a distinct style so native task() children are
|
|
319
324
|
* visually separable from pantheon_delegate jobs. */
|
|
320
325
|
declare function delegationTag(entry: DelegationEntry): string;
|
|
326
|
+
/** Split a display list into native task() rows vs pantheon_delegate rows.
|
|
327
|
+
* Native = source 'children-only' (no board report); everything else counts
|
|
328
|
+
* as pantheon. Pure — powers the header breakdown + the hooks.log line. */
|
|
329
|
+
declare function countDelegationSources(entries: readonly DelegationEntry[]): {
|
|
330
|
+
native: number;
|
|
331
|
+
pantheon: number;
|
|
332
|
+
total: number;
|
|
333
|
+
};
|
|
334
|
+
/** Diagnostic hooks.log line for a panel re-fetch, with the children
|
|
335
|
+
* breakdown (pantheon = children WITH a board report, native = children
|
|
336
|
+
* WITHOUT one). Pure — the View logs the returned string verbatim. */
|
|
337
|
+
declare function formatPanelLogLine(children: number, pantheon: number, native: number, md: number, events: number): string;
|
|
321
338
|
/** Turn child sessions (PRIMARY) enriched with md reports into the display
|
|
322
339
|
* list. One entry per child id (duplicates across re-fetches collapse).
|
|
323
340
|
* The md report is matched by `Task ID` (== child.id) and supplies alias,
|
|
@@ -326,10 +343,14 @@ declare function delegationTag(entry: DelegationEntry): string;
|
|
|
326
343
|
* itself (fallback 'agent'), state derived from its status, startedAt from
|
|
327
344
|
* time.created. A report-less child is a NATIVE task() child (every
|
|
328
345
|
* child of the current session — pantheon_delegate OR the native `task()`
|
|
329
|
-
* tool — carries parentID = caller), so it gets source 'children-only'
|
|
330
|
-
* and the `[native]` tag instead of a
|
|
346
|
+
* tool — carries parentID = caller), so it gets source 'children-only',
|
|
347
|
+
* the internal alias 'native-task' and the `[native]` tag instead of a
|
|
348
|
+
* board alias. The 'task nativa' description fallback keeps the row
|
|
349
|
+
* non-empty when the child carries no title.
|
|
331
350
|
* Terminal md state wins over the derived state; a running md defers to
|
|
332
|
-
* the child's live status.
|
|
351
|
+
* the child's live status. A running child is NEVER archived — it always
|
|
352
|
+
* lands in the active split (splitDelegationList active = running/retry).
|
|
353
|
+
* Sorted running-first (compareDelegationEntries).
|
|
333
354
|
* Pure — no I/O. */
|
|
334
355
|
declare function childrenToDelegationEntries(children: readonly ChildDelegationLike[] | undefined, md: readonly DelegationEntry[], now?: number): DelegationEntry[];
|
|
335
356
|
/** Navigate the TUI to a child session (click/Enter on a delegation row).
|
|
@@ -356,5 +377,5 @@ declare const plugin: TuiPluginModule & {
|
|
|
356
377
|
setup: () => Promise<void>;
|
|
357
378
|
};
|
|
358
379
|
//#endregion
|
|
359
|
-
export { ChildDelegationLike, DelegationActivity, DelegationEntry, DelegationToolPart, IDLE_SILENCE_MS, LiveDelegationEntry, LiveDelegationStore, ParsedDelegationToolPart, STALE_RUNNING_THRESHOLD_MS, ToolActivity, TuiSessionSources, buildChildrenPath, childStatusToState, childrenToDelegationEntries, collectDelegationToolParts, compareDelegationEntries, plugin as default, delegationActivity, delegationActivityLabel, delegationElapsed, delegationSpinnerFrame, delegationTag, extractToolActivity, fmtElapsed, isValidSessionId, latestToolActivityFor, markStaleIfRunning, mergeChildDelegationSources, mergeDelegationSources, navigateToDelegationSession, panelLogDir, parseDelegationMarkdown, parseDelegationToolPart, readAllDelegationEntries, readDelegationEntries, reduceDelegationToolPart, removeDelegationEntry, resolveCurrentSessionID, resolveDelegationsDir, safeSessionPath, seedLiveDelegationMap, splitDelegationList, toDelegationEntry, trackToolActivity, tuiLogPath, visibleDelegationList };
|
|
380
|
+
export { ChildDelegationLike, DelegationActivity, DelegationEntry, DelegationToolPart, IDLE_SILENCE_MS, LiveDelegationEntry, LiveDelegationStore, ParsedDelegationToolPart, STALE_RUNNING_THRESHOLD_MS, ToolActivity, TuiSessionSources, buildChildrenPath, childStatusToState, childrenToDelegationEntries, collectDelegationToolParts, compareDelegationEntries, countDelegationSources, plugin as default, delegationActivity, delegationActivityLabel, delegationElapsed, delegationSpinnerFrame, delegationTag, extractToolActivity, fmtElapsed, formatPanelLogLine, isValidSessionId, latestToolActivityFor, markStaleIfRunning, mergeChildDelegationSources, mergeDelegationSources, navigateToDelegationSession, panelLogDir, parseDelegationMarkdown, parseDelegationToolPart, readAllDelegationEntries, readDelegationEntries, reduceDelegationToolPart, removeDelegationEntry, resolveCurrentSessionID, resolveDelegationsDir, safeSessionPath, seedLiveDelegationMap, splitDelegationList, toDelegationEntry, trackToolActivity, tuiLogPath, visibleDelegationList };
|
|
360
381
|
//# sourceMappingURL=tui.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tui.d.ts","names":[],"sources":["../src/index.tsx"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"tui.d.ts","names":[],"sources":["../src/index.tsx"],"mappings":";;KAu7BY;;EAEV;;EAEA;;;;EAIA;;EAEA;EACA;;EAUA;;EAEA;EACA;EACA;;EAEA;;;;EAIA;;;;;;;;;;;iBAsBc,wBACd,aACA,oBACA,qBACC;;;;;;iBAkHmB,sBAAsB,cAAc,QAAQ;;;;;;;;iBAwC5C,yBAAyB,eAAe,QAAQ;;;;;;;;iBAWtD,sBACd;EAAS;EAAoB;eAC7B;;;;;;iBAYc,YAAY;;;iBAMZ,WAAW;;;;iBAOX,yBAAyB,GAAG,iBAAiB,GAAG;;;;;iBAahD,oBACd,cAAc,mBACd,oBACA,cACA;EACG,QAAQ;EAAmB,QAAQ;EAAmB,UAAU;;;;;iBAerD,sBACd,cAAc,mBACd,sBACA,cACA,4BACC;;cAMU;;;;cAKA;;;;;;;;;iBAUG,mBACd,OAAO,iBACP,aACA,uBACC;;;iBAWa,WAAW;;;iBAcX,kBAAkB,OAAO,iBAAiB;;;KAQ9C;iBAQI,mBAAmB,OAAO,kBAAkB;iBAc5C,wBAAwB,OAAO;;iBAqB/B,uBAAuB;KAY3B;EACV;EACA;EACA;;;;;iBA0Bc,oBACd;EACE;EACA;EACA;EACA;IAAU;IAAiB,QAAQ;;GAErC;EACG;EAAmB,UAAU;;;iBAYlB,kBACd,KAAK,YAAY,eACjB,mBACA,UAAU;;iBAWI,sBACd,KAAK,YAAY,eACjB,+BACA,cACA,iBACC;;;;;;;iBAca,4BACd,mBAAmB,mBACnB,eAAe,wBACd;;KAqFS;EACV;EACA;EACA;EACA;EACA;EACA;IACE;IACA,QAAQ;IACR;IACA;IACA;MAAS;MAAgB;;;;;KAKjB;;EAEV;;EAEA;;EAEA;EACA;;EAEA;EACA;;EAEA;;EAEA;EACA;EACA;EACA;;EAEA;;;KAIU;EACV;EACA;EACA;EACA;;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;iBAec,wBACd,MAAM,oBACN,eACC;;;iBAgGa,yBACd,KAAK,YAAY,sBACjB,MAAM,oBACN;;;iBAiGc,sBACd,KAAK,YAAY,sBACjB;;;;;;;;;iBAoBc,2BACd;EAAqB;EAAa;iBAClC,YAAY,uDACX;;;;;iBA6Ba,sBACd,KAAK,YAAY,sBACjB,gBAAgB,sBAChB;;;iBAWc,kBAAkB,MAAM,sBAAsB;;;;;;iBAqB9C,uBACd,eAAe,uBACf,aAAa,oBACZ;;;;;;;;;iBA8Da,iBAAiB,cAAc;;;;KAOnC;;EAEV;EACA;;IAEE;MAAU;;;IAEV;MAAU;QAAY;QAAe,SAAS;;;;;;;;;;iBASlC,wBAAwB,SAAS;;;;;;;;iBAmBjC,gBAAgB;EAAgB;IAAQ;;;;;;;iBASxC,kBAAkB;EAAkC;IAAQ;;;;KAKhE;;EAEV;;EAEA;;;EAGA;;;EAGA;EACA;IACE;IACA;;;;;;;iBAQY,mBAAmB;;;;;;;iBAYnB,cAAc,OAAO;;;;iBAOrB,uBAAuB,kBAAkB;EACvD;EACA;EACA;;;;;iBAUc,mBACd,kBACA,kBACA,gBACA,YACA;;;;;;;;;;;;;;;;;;iBAsBc,4BACd,mBAAmB,mCACnB,aAAa,mBACb,eACC;;;;;;iBA4Ca,4BACd;EAAS,YAAY,cAAc,SAAS;eAC5C;;;;;KAiMU;EACV,KAAK,YAAY;;EAEjB;;EAEA;;cAugBI,QAAQ;EAAoB;EAAY,aAAa"}
|
|
@@ -164,7 +164,7 @@ async function detectVersion(api) {
|
|
|
164
164
|
if (ver) return ver;
|
|
165
165
|
}
|
|
166
166
|
} catch {}
|
|
167
|
-
return "1.5.0-beta.
|
|
167
|
+
return "1.5.0-beta.9";
|
|
168
168
|
}
|
|
169
169
|
/**
|
|
170
170
|
* usage-bar — AI subscription usage gauge for the opencode TUI.
|
|
@@ -1095,10 +1095,12 @@ const DELEGATE_TASKID_PATTERN = /\(task\s+([a-z0-9_]+)\)/i;
|
|
|
1095
1095
|
const READ_ALIAS_PATTERN = /^[a-z]{2,8}-\d+$/i;
|
|
1096
1096
|
/** Extract the tool name + args from a `message.part.updated` part and
|
|
1097
1097
|
* reduce it to what the panel needs. Returns null for anything that is
|
|
1098
|
-
* not a pantheon delegation tool part
|
|
1098
|
+
* not a pantheon delegation tool part, the native `task` subagent tool
|
|
1099
|
+
* (same parentID === caller mechanism — its children render `[native]`),
|
|
1100
|
+
* or is missing its callID. */
|
|
1099
1101
|
function parseDelegationToolPart(part, now = Date.now()) {
|
|
1100
1102
|
if (part.type !== "tool") return null;
|
|
1101
|
-
if (part.tool !== "pantheon_delegate" && part.tool !== "pantheon_delegation_read") return null;
|
|
1103
|
+
if (part.tool !== "pantheon_delegate" && part.tool !== "pantheon_delegation_read" && part.tool !== "task") return null;
|
|
1102
1104
|
const callID = part.callID;
|
|
1103
1105
|
if (callID === void 0 || callID === "") return null;
|
|
1104
1106
|
const sessionID = part.sessionID ?? "";
|
|
@@ -1130,8 +1132,8 @@ function parseDelegationToolPart(part, now = Date.now()) {
|
|
|
1130
1132
|
endAt
|
|
1131
1133
|
};
|
|
1132
1134
|
}
|
|
1133
|
-
const agent = typeof input.agent === "string" ? input.agent : "agent";
|
|
1134
|
-
const description = typeof input.description === "string" ? input.description : "";
|
|
1135
|
+
const agent = typeof input.agent === "string" ? input.agent : typeof input.subagent_type === "string" ? input.subagent_type : "agent";
|
|
1136
|
+
const description = typeof input.description === "string" ? input.description : typeof input.prompt === "string" ? input.prompt.slice(0, 120) : "";
|
|
1135
1137
|
let alias = null;
|
|
1136
1138
|
let taskID = null;
|
|
1137
1139
|
if (status === "completed") {
|
|
@@ -1143,7 +1145,7 @@ function parseDelegationToolPart(part, now = Date.now()) {
|
|
|
1143
1145
|
callID,
|
|
1144
1146
|
partID: part.id ?? "",
|
|
1145
1147
|
sessionID,
|
|
1146
|
-
tool: "pantheon_delegate",
|
|
1148
|
+
tool: part.tool === "task" ? "task" : "pantheon_delegate",
|
|
1147
1149
|
agent,
|
|
1148
1150
|
description,
|
|
1149
1151
|
status,
|
|
@@ -1192,7 +1194,7 @@ function reduceDelegationToolPart(map, part, now = Date.now()) {
|
|
|
1192
1194
|
callID: parsed.callID,
|
|
1193
1195
|
partID: parsed.partID,
|
|
1194
1196
|
sessionID: parsed.sessionID,
|
|
1195
|
-
tool:
|
|
1197
|
+
tool: parsed.tool,
|
|
1196
1198
|
agent: parsed.agent ?? "agent",
|
|
1197
1199
|
description: parsed.description,
|
|
1198
1200
|
alias: existing?.alias ?? null,
|
|
@@ -1209,7 +1211,7 @@ function reduceDelegationToolPart(map, part, now = Date.now()) {
|
|
|
1209
1211
|
callID: parsed.callID,
|
|
1210
1212
|
partID: parsed.partID,
|
|
1211
1213
|
sessionID: parsed.sessionID,
|
|
1212
|
-
tool:
|
|
1214
|
+
tool: parsed.tool,
|
|
1213
1215
|
agent: parsed.agent ?? "agent",
|
|
1214
1216
|
description: parsed.description,
|
|
1215
1217
|
alias: parsed.alias,
|
|
@@ -1254,11 +1256,14 @@ function removeDelegationEntry(map, partIDOrCallID) {
|
|
|
1254
1256
|
}
|
|
1255
1257
|
return false;
|
|
1256
1258
|
}
|
|
1257
|
-
/** Collect pantheon delegation tool parts from a session's messages.
|
|
1259
|
+
/** Collect pantheon delegation + native task tool parts from a session's messages.
|
|
1258
1260
|
* Messages may carry their parts inline (duck-typed `msg.parts`); when
|
|
1259
1261
|
* they don't, the optional `getParts(messageID)` callback is used (the TUI
|
|
1260
|
-
* SDK exposes `api.state.part(messageID)`).
|
|
1261
|
-
*
|
|
1262
|
+
* SDK exposes `api.state.part(messageID)`). The native `task` tool spawns a
|
|
1263
|
+
* child session with parentID = caller — the same mechanism as
|
|
1264
|
+
* pantheon_delegate — so its parts feed the live-map as the native signal
|
|
1265
|
+
* (rows render `[native]` via the children channel). Pure w.r.t. I/O — used
|
|
1266
|
+
* by the mount re-scan to re-seed the live map after compaction/attach. */
|
|
1262
1267
|
function collectDelegationToolParts(messages, getParts) {
|
|
1263
1268
|
const out = [];
|
|
1264
1269
|
for (const msg of messages ?? []) {
|
|
@@ -1268,7 +1273,7 @@ function collectDelegationToolParts(messages, getParts) {
|
|
|
1268
1273
|
if (!parts) continue;
|
|
1269
1274
|
for (const raw of parts) {
|
|
1270
1275
|
const part = raw;
|
|
1271
|
-
if (part?.type === "tool" && (part.tool === "pantheon_delegate" || part.tool === "pantheon_delegation_read")) out.push(part);
|
|
1276
|
+
if (part?.type === "tool" && (part.tool === "pantheon_delegate" || part.tool === "pantheon_delegation_read" || part.tool === "task")) out.push(part);
|
|
1272
1277
|
}
|
|
1273
1278
|
}
|
|
1274
1279
|
return out;
|
|
@@ -1393,6 +1398,24 @@ function childStatusToState(status) {
|
|
|
1393
1398
|
function delegationTag(entry) {
|
|
1394
1399
|
return entry.source === "children-only" ? "[native]" : `[pantheon:${entry.alias}]`;
|
|
1395
1400
|
}
|
|
1401
|
+
/** Split a display list into native task() rows vs pantheon_delegate rows.
|
|
1402
|
+
* Native = source 'children-only' (no board report); everything else counts
|
|
1403
|
+
* as pantheon. Pure — powers the header breakdown + the hooks.log line. */
|
|
1404
|
+
function countDelegationSources(entries) {
|
|
1405
|
+
let native = 0;
|
|
1406
|
+
for (const e of entries) if (e.source === "children-only") native++;
|
|
1407
|
+
return {
|
|
1408
|
+
native,
|
|
1409
|
+
pantheon: entries.length - native,
|
|
1410
|
+
total: entries.length
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
/** Diagnostic hooks.log line for a panel re-fetch, with the children
|
|
1414
|
+
* breakdown (pantheon = children WITH a board report, native = children
|
|
1415
|
+
* WITHOUT one). Pure — the View logs the returned string verbatim. */
|
|
1416
|
+
function formatPanelLogLine(children, pantheon, native, md, events) {
|
|
1417
|
+
return `panel: children=${children}(pantheon=${pantheon} native=${native}) md=${md} events=${events}`;
|
|
1418
|
+
}
|
|
1396
1419
|
/** Turn child sessions (PRIMARY) enriched with md reports into the display
|
|
1397
1420
|
* list. One entry per child id (duplicates across re-fetches collapse).
|
|
1398
1421
|
* The md report is matched by `Task ID` (== child.id) and supplies alias,
|
|
@@ -1401,10 +1424,14 @@ function delegationTag(entry) {
|
|
|
1401
1424
|
* itself (fallback 'agent'), state derived from its status, startedAt from
|
|
1402
1425
|
* time.created. A report-less child is a NATIVE task() child (every
|
|
1403
1426
|
* child of the current session — pantheon_delegate OR the native `task()`
|
|
1404
|
-
* tool — carries parentID = caller), so it gets source 'children-only'
|
|
1405
|
-
* and the `[native]` tag instead of a
|
|
1427
|
+
* tool — carries parentID = caller), so it gets source 'children-only',
|
|
1428
|
+
* the internal alias 'native-task' and the `[native]` tag instead of a
|
|
1429
|
+
* board alias. The 'task nativa' description fallback keeps the row
|
|
1430
|
+
* non-empty when the child carries no title.
|
|
1406
1431
|
* Terminal md state wins over the derived state; a running md defers to
|
|
1407
|
-
* the child's live status.
|
|
1432
|
+
* the child's live status. A running child is NEVER archived — it always
|
|
1433
|
+
* lands in the active split (splitDelegationList active = running/retry).
|
|
1434
|
+
* Sorted running-first (compareDelegationEntries).
|
|
1408
1435
|
* Pure — no I/O. */
|
|
1409
1436
|
function childrenToDelegationEntries(children, md, now = Date.now()) {
|
|
1410
1437
|
const byTaskID = /* @__PURE__ */ new Map();
|
|
@@ -1417,7 +1444,7 @@ function childrenToDelegationEntries(children, md, now = Date.now()) {
|
|
|
1417
1444
|
const mdEntry = byTaskID.get(child.id);
|
|
1418
1445
|
const state = mdEntry !== void 0 && mdEntry.state !== "running" ? mdEntry.state : childStatusToState(child.status);
|
|
1419
1446
|
out.push({
|
|
1420
|
-
alias: mdEntry?.alias ?? "task",
|
|
1447
|
+
alias: mdEntry?.alias ?? "native-task",
|
|
1421
1448
|
sessionID: mdEntry?.sessionID ?? "",
|
|
1422
1449
|
taskID: child.id,
|
|
1423
1450
|
agent: mdEntry?.agent ?? child.agent ?? "agent",
|
|
@@ -1425,7 +1452,7 @@ function childrenToDelegationEntries(children, md, now = Date.now()) {
|
|
|
1425
1452
|
startedAt: mdEntry?.startedAt ?? child.time?.created ?? now,
|
|
1426
1453
|
updatedAt: mdEntry?.updatedAt ?? (state === "running" || state === "stale-running" ? null : child.time?.updated ?? null),
|
|
1427
1454
|
timedOut: mdEntry?.timedOut ?? false,
|
|
1428
|
-
description: mdEntry !== void 0 && mdEntry.description !== "" ? mdEntry.description : child.title
|
|
1455
|
+
description: mdEntry !== void 0 && mdEntry.description !== "" ? mdEntry.description : child.title !== void 0 && child.title !== "" ? child.title : "task nativa",
|
|
1429
1456
|
source: mdEntry !== void 0 ? "md" : "children-only"
|
|
1430
1457
|
});
|
|
1431
1458
|
}
|
|
@@ -1549,7 +1576,7 @@ function DelegationRow(props) {
|
|
|
1549
1576
|
},
|
|
1550
1577
|
children: (a) => (() => {
|
|
1551
1578
|
var _el$19 = createElement("text");
|
|
1552
|
-
insert(_el$19, () => ` \u21b3 ${a().tool}${a().summary !== "" ?
|
|
1579
|
+
insert(_el$19, () => ` \u21b3 ${a().tool}${a().summary !== "" ? ` ${a().summary}` : ""}`);
|
|
1553
1580
|
effect((_$p) => setProp(_el$19, "fg", theme().textMuted, _$p));
|
|
1554
1581
|
return _el$19;
|
|
1555
1582
|
})()
|
|
@@ -1647,7 +1674,7 @@ function View(props) {
|
|
|
1647
1674
|
});
|
|
1648
1675
|
if (sessionID === null) {
|
|
1649
1676
|
setChildDelegations(md);
|
|
1650
|
-
panelLog.info(
|
|
1677
|
+
panelLog.info(`${formatPanelLogLine(0, 0, 0, md.length, eventRefreshCount)} (no sessionID — history shown)`);
|
|
1651
1678
|
return;
|
|
1652
1679
|
}
|
|
1653
1680
|
let children = [];
|
|
@@ -1674,7 +1701,8 @@ function View(props) {
|
|
|
1674
1701
|
for (const [k, v] of props.liveStore.map) if (v.alias === null && v.taskID === null && Date.now() - v.startedAt > 3e4) props.liveStore.map.delete(k);
|
|
1675
1702
|
const liveEntries = [...props.liveStore.map.values()].filter((entry) => entry.sessionID === sessionID);
|
|
1676
1703
|
setChildDelegations(mergeChildDelegationSources(childEntries, liveEntries));
|
|
1677
|
-
|
|
1704
|
+
const counts = countDelegationSources(childEntries);
|
|
1705
|
+
panelLog.info(formatPanelLogLine(children.length, counts.pantheon, counts.native, md.length, eventRefreshCount));
|
|
1678
1706
|
} finally {
|
|
1679
1707
|
delegationsInflight = null;
|
|
1680
1708
|
}
|
|
@@ -1683,6 +1711,7 @@ function View(props) {
|
|
|
1683
1711
|
};
|
|
1684
1712
|
const delegationSplit = createMemo(() => splitDelegationList(childDelegations()));
|
|
1685
1713
|
const visibleDelegations = createMemo(() => [...delegationSplit().active, ...delegationSplit().recent]);
|
|
1714
|
+
const delegationCounts = createMemo(() => countDelegationSources(visibleDelegations()));
|
|
1686
1715
|
const [archivedPage, setArchivedPage] = createSignal(archivedUiState.page);
|
|
1687
1716
|
const clampArchivedPage = (page, pages) => {
|
|
1688
1717
|
const clamped = Math.max(0, Math.min(page, Math.max(0, pages - 1)));
|
|
@@ -1852,7 +1881,7 @@ function View(props) {
|
|
|
1852
1881
|
setProp(_el$28, "onMouseDown", () => setShowDelegations((x) => !x));
|
|
1853
1882
|
setProp(_el$29, "attributes", 1);
|
|
1854
1883
|
insert(_el$29, () => `${showDelegations() ? "▼" : "▶"} Delegations`);
|
|
1855
|
-
insert(_el$30, () => ` (${String(
|
|
1884
|
+
insert(_el$30, () => ` (${String(delegationCounts().native)} native + ${String(delegationCounts().pantheon)} pantheon)`);
|
|
1856
1885
|
insert(_el$22, createComponent(Show, {
|
|
1857
1886
|
get when() {
|
|
1858
1887
|
return showDelegations();
|
|
@@ -2033,6 +2062,6 @@ const plugin = {
|
|
|
2033
2062
|
setup: async () => {}
|
|
2034
2063
|
};
|
|
2035
2064
|
//#endregion
|
|
2036
|
-
export { IDLE_SILENCE_MS, STALE_RUNNING_THRESHOLD_MS, buildChildrenPath, childStatusToState, childrenToDelegationEntries, collectDelegationToolParts, compareDelegationEntries, plugin as default, delegationActivity, delegationActivityLabel, delegationElapsed, delegationSpinnerFrame, delegationTag, extractToolActivity, fmtElapsed, isValidSessionId, latestToolActivityFor, markStaleIfRunning, mergeChildDelegationSources, mergeDelegationSources, navigateToDelegationSession, panelLogDir, parseDelegationMarkdown, parseDelegationToolPart, readAllDelegationEntries, readDelegationEntries, reduceDelegationToolPart, removeDelegationEntry, resolveCurrentSessionID, resolveDelegationsDir, safeSessionPath, seedLiveDelegationMap, splitDelegationList, toDelegationEntry, trackToolActivity, tuiLogPath, visibleDelegationList };
|
|
2065
|
+
export { IDLE_SILENCE_MS, STALE_RUNNING_THRESHOLD_MS, buildChildrenPath, childStatusToState, childrenToDelegationEntries, collectDelegationToolParts, compareDelegationEntries, countDelegationSources, plugin as default, delegationActivity, delegationActivityLabel, delegationElapsed, delegationSpinnerFrame, delegationTag, extractToolActivity, fmtElapsed, formatPanelLogLine, isValidSessionId, latestToolActivityFor, markStaleIfRunning, mergeChildDelegationSources, mergeDelegationSources, navigateToDelegationSession, panelLogDir, parseDelegationMarkdown, parseDelegationToolPart, readAllDelegationEntries, readDelegationEntries, reduceDelegationToolPart, removeDelegationEntry, resolveCurrentSessionID, resolveDelegationsDir, safeSessionPath, seedLiveDelegationMap, splitDelegationList, toDelegationEntry, trackToolActivity, tuiLogPath, visibleDelegationList };
|
|
2037
2066
|
|
|
2038
2067
|
//# sourceMappingURL=tui.js.map
|
|
@@ -921,9 +921,12 @@ async function setupUsageBar(api: TuiPluginApi) {
|
|
|
921
921
|
* children; a 1s safety poll (oh-my-opencode-slim pattern) re-fetches
|
|
922
922
|
* children + re-reads the md so the panel updates EVEN without events.
|
|
923
923
|
*
|
|
924
|
-
* 4. DIAGNOSTICS — every re-fetch logs
|
|
924
|
+
* 4. DIAGNOSTICS — every re-fetch logs
|
|
925
|
+
* "panel: children=N(pantheon=M native=K) md=N events=N"
|
|
925
926
|
* to `.pantheon/logs/hooks.log` (silence-by-default, createPantheonLogger
|
|
926
|
-
* policy) so the (0) symptom is diagnosable from disk
|
|
927
|
+
* policy) so the (0) symptom is diagnosable from disk: children>0 means
|
|
928
|
+
* the source works, pantheon/native splits board vs task() children.
|
|
929
|
+
* A children fetch
|
|
927
930
|
* failure logs "panel: error <msg>" (previously silenced), and a missing
|
|
928
931
|
* current session (null/placeholder sessionID) logs "panel: children=0
|
|
929
932
|
* md=N ... (no sessionID — history shown)" — the md HISTORY renders
|
|
@@ -1509,9 +1512,11 @@ export function mergeChildDelegationSources(
|
|
|
1509
1512
|
* Source of truth for jobs born while opencode is open. The TUI event bus
|
|
1510
1513
|
* (api.event.on) delivers `message.part.updated` for every part change;
|
|
1511
1514
|
* we only care about tool parts whose `tool` is pantheon_delegate (job
|
|
1512
|
-
* launch)
|
|
1513
|
-
*
|
|
1514
|
-
*
|
|
1515
|
+
* launch), the native `task` subagent tool (same parentID === caller
|
|
1516
|
+
* mechanism — its rows render `[native]`), or pantheon_delegation_read
|
|
1517
|
+
* (blocking until terminal → closes the entry). Shape-adapted from the
|
|
1518
|
+
* SDK v2 ToolPart but duck-typed so the pure helpers are testable without
|
|
1519
|
+
* the SDK. */
|
|
1515
1520
|
|
|
1516
1521
|
/** Duck-typed subset of a tool part (SDK v2 `ToolPart` / `ToolState`). */
|
|
1517
1522
|
export type DelegationToolPart = {
|
|
@@ -1537,7 +1542,7 @@ export type LiveDelegationEntry = {
|
|
|
1537
1542
|
partID: string
|
|
1538
1543
|
/** Parent session the delegation was launched from. */
|
|
1539
1544
|
sessionID: string
|
|
1540
|
-
tool: 'pantheon_delegate' | 'pantheon_delegation_read'
|
|
1545
|
+
tool: 'pantheon_delegate' | 'pantheon_delegation_read' | 'task'
|
|
1541
1546
|
/** Agent name (from the delegate input args). */
|
|
1542
1547
|
agent: string
|
|
1543
1548
|
description: string
|
|
@@ -1557,7 +1562,7 @@ export type ParsedDelegationToolPart = {
|
|
|
1557
1562
|
callID: string
|
|
1558
1563
|
partID: string
|
|
1559
1564
|
sessionID: string
|
|
1560
|
-
tool: 'pantheon_delegate' | 'pantheon_delegation_read'
|
|
1565
|
+
tool: 'pantheon_delegate' | 'pantheon_delegation_read' | 'task'
|
|
1561
1566
|
/** null for read parts (no agent arg — the id targets an existing job). */
|
|
1562
1567
|
agent: string | null
|
|
1563
1568
|
description: string
|
|
@@ -1577,13 +1582,20 @@ const READ_ALIAS_PATTERN = /^[a-z]{2,8}-\d+$/i
|
|
|
1577
1582
|
|
|
1578
1583
|
/** Extract the tool name + args from a `message.part.updated` part and
|
|
1579
1584
|
* reduce it to what the panel needs. Returns null for anything that is
|
|
1580
|
-
* not a pantheon delegation tool part
|
|
1585
|
+
* not a pantheon delegation tool part, the native `task` subagent tool
|
|
1586
|
+
* (same parentID === caller mechanism — its children render `[native]`),
|
|
1587
|
+
* or is missing its callID. */
|
|
1581
1588
|
export function parseDelegationToolPart(
|
|
1582
1589
|
part: DelegationToolPart,
|
|
1583
1590
|
now = Date.now(),
|
|
1584
1591
|
): ParsedDelegationToolPart | null {
|
|
1585
1592
|
if (part.type !== 'tool') return null
|
|
1586
|
-
if (
|
|
1593
|
+
if (
|
|
1594
|
+
part.tool !== 'pantheon_delegate' &&
|
|
1595
|
+
part.tool !== 'pantheon_delegation_read' &&
|
|
1596
|
+
part.tool !== 'task'
|
|
1597
|
+
)
|
|
1598
|
+
return null
|
|
1587
1599
|
const callID = part.callID
|
|
1588
1600
|
if (callID === undefined || callID === '') return null
|
|
1589
1601
|
const sessionID = part.sessionID ?? ''
|
|
@@ -1618,10 +1630,22 @@ export function parseDelegationToolPart(
|
|
|
1618
1630
|
}
|
|
1619
1631
|
}
|
|
1620
1632
|
|
|
1621
|
-
const agent =
|
|
1622
|
-
|
|
1633
|
+
const agent =
|
|
1634
|
+
typeof input.agent === 'string'
|
|
1635
|
+
? input.agent
|
|
1636
|
+
: typeof input.subagent_type === 'string'
|
|
1637
|
+
? (input.subagent_type as string)
|
|
1638
|
+
: 'agent'
|
|
1639
|
+
const description =
|
|
1640
|
+
typeof input.description === 'string'
|
|
1641
|
+
? input.description
|
|
1642
|
+
: typeof input.prompt === 'string'
|
|
1643
|
+
? (input.prompt as string).slice(0, 120)
|
|
1644
|
+
: ''
|
|
1623
1645
|
// The alias/taskID only exist once the delegate tool COMPLETES (they are
|
|
1624
|
-
// returned in its output); a running/pending part has neither.
|
|
1646
|
+
// returned in its output); a running/pending part has neither. Native
|
|
1647
|
+
// `task` parts never carry a board alias — the children channel supplies
|
|
1648
|
+
// the child session id, so the row renders `[native]`.
|
|
1625
1649
|
let alias: string | null = null
|
|
1626
1650
|
let taskID: string | null = null
|
|
1627
1651
|
if (status === 'completed') {
|
|
@@ -1633,7 +1657,7 @@ export function parseDelegationToolPart(
|
|
|
1633
1657
|
callID,
|
|
1634
1658
|
partID: part.id ?? '',
|
|
1635
1659
|
sessionID,
|
|
1636
|
-
tool: 'pantheon_delegate',
|
|
1660
|
+
tool: part.tool === 'task' ? 'task' : 'pantheon_delegate',
|
|
1637
1661
|
agent,
|
|
1638
1662
|
description,
|
|
1639
1663
|
status,
|
|
@@ -1691,9 +1715,11 @@ export function reduceDelegationToolPart(
|
|
|
1691
1715
|
return changed
|
|
1692
1716
|
}
|
|
1693
1717
|
|
|
1694
|
-
// Delegate tool: pending/running →
|
|
1695
|
-
// delegate tool only means the job was
|
|
1696
|
-
// we pick up alias + taskID from the
|
|
1718
|
+
// Delegate tool (pantheon_delegate AND native task): pending/running →
|
|
1719
|
+
// job launched (running). A COMPLETED delegate tool only means the job was
|
|
1720
|
+
// registered — it stays running and we pick up alias + taskID from the
|
|
1721
|
+
// output. Native `task` parts never yield a board alias, so their rows
|
|
1722
|
+
// stay `[native]` until the children channel resolves them.
|
|
1697
1723
|
const existing = map.get(parsed.callID)
|
|
1698
1724
|
if (parsed.status === 'error') {
|
|
1699
1725
|
if (existing !== undefined && existing.state === 'error' && existing.updatedAt === parsed.endAt)
|
|
@@ -1702,7 +1728,7 @@ export function reduceDelegationToolPart(
|
|
|
1702
1728
|
callID: parsed.callID,
|
|
1703
1729
|
partID: parsed.partID,
|
|
1704
1730
|
sessionID: parsed.sessionID,
|
|
1705
|
-
tool:
|
|
1731
|
+
tool: parsed.tool,
|
|
1706
1732
|
agent: parsed.agent ?? 'agent',
|
|
1707
1733
|
description: parsed.description,
|
|
1708
1734
|
alias: existing?.alias ?? null,
|
|
@@ -1719,7 +1745,7 @@ export function reduceDelegationToolPart(
|
|
|
1719
1745
|
callID: parsed.callID,
|
|
1720
1746
|
partID: parsed.partID,
|
|
1721
1747
|
sessionID: parsed.sessionID,
|
|
1722
|
-
tool:
|
|
1748
|
+
tool: parsed.tool,
|
|
1723
1749
|
agent: parsed.agent ?? 'agent',
|
|
1724
1750
|
description: parsed.description,
|
|
1725
1751
|
alias: parsed.alias,
|
|
@@ -1772,11 +1798,14 @@ export function removeDelegationEntry(
|
|
|
1772
1798
|
return false
|
|
1773
1799
|
}
|
|
1774
1800
|
|
|
1775
|
-
/** Collect pantheon delegation tool parts from a session's messages.
|
|
1801
|
+
/** Collect pantheon delegation + native task tool parts from a session's messages.
|
|
1776
1802
|
* Messages may carry their parts inline (duck-typed `msg.parts`); when
|
|
1777
1803
|
* they don't, the optional `getParts(messageID)` callback is used (the TUI
|
|
1778
|
-
* SDK exposes `api.state.part(messageID)`).
|
|
1779
|
-
*
|
|
1804
|
+
* SDK exposes `api.state.part(messageID)`). The native `task` tool spawns a
|
|
1805
|
+
* child session with parentID = caller — the same mechanism as
|
|
1806
|
+
* pantheon_delegate — so its parts feed the live-map as the native signal
|
|
1807
|
+
* (rows render `[native]` via the children channel). Pure w.r.t. I/O — used
|
|
1808
|
+
* by the mount re-scan to re-seed the live map after compaction/attach. */
|
|
1780
1809
|
export function collectDelegationToolParts(
|
|
1781
1810
|
messages: readonly { id?: string; parts?: unknown[] }[] | undefined,
|
|
1782
1811
|
getParts?: (messageID: string) => readonly unknown[] | undefined,
|
|
@@ -1794,7 +1823,9 @@ export function collectDelegationToolParts(
|
|
|
1794
1823
|
const part = raw as DelegationToolPart
|
|
1795
1824
|
if (
|
|
1796
1825
|
part?.type === 'tool' &&
|
|
1797
|
-
(part.tool === 'pantheon_delegate' ||
|
|
1826
|
+
(part.tool === 'pantheon_delegate' ||
|
|
1827
|
+
part.tool === 'pantheon_delegation_read' ||
|
|
1828
|
+
part.tool === 'task')
|
|
1798
1829
|
) {
|
|
1799
1830
|
out.push(part)
|
|
1800
1831
|
}
|
|
@@ -2000,6 +2031,32 @@ export function delegationTag(entry: DelegationEntry): string {
|
|
|
2000
2031
|
return entry.source === 'children-only' ? '[native]' : `[pantheon:${entry.alias}]`
|
|
2001
2032
|
}
|
|
2002
2033
|
|
|
2034
|
+
/** Split a display list into native task() rows vs pantheon_delegate rows.
|
|
2035
|
+
* Native = source 'children-only' (no board report); everything else counts
|
|
2036
|
+
* as pantheon. Pure — powers the header breakdown + the hooks.log line. */
|
|
2037
|
+
export function countDelegationSources(entries: readonly DelegationEntry[]): {
|
|
2038
|
+
native: number
|
|
2039
|
+
pantheon: number
|
|
2040
|
+
total: number
|
|
2041
|
+
} {
|
|
2042
|
+
let native = 0
|
|
2043
|
+
for (const e of entries) if (e.source === 'children-only') native++
|
|
2044
|
+
return { native, pantheon: entries.length - native, total: entries.length }
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
/** Diagnostic hooks.log line for a panel re-fetch, with the children
|
|
2048
|
+
* breakdown (pantheon = children WITH a board report, native = children
|
|
2049
|
+
* WITHOUT one). Pure — the View logs the returned string verbatim. */
|
|
2050
|
+
export function formatPanelLogLine(
|
|
2051
|
+
children: number,
|
|
2052
|
+
pantheon: number,
|
|
2053
|
+
native: number,
|
|
2054
|
+
md: number,
|
|
2055
|
+
events: number,
|
|
2056
|
+
): string {
|
|
2057
|
+
return `panel: children=${children}(pantheon=${pantheon} native=${native}) md=${md} events=${events}`
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2003
2060
|
/** Turn child sessions (PRIMARY) enriched with md reports into the display
|
|
2004
2061
|
* list. One entry per child id (duplicates across re-fetches collapse).
|
|
2005
2062
|
* The md report is matched by `Task ID` (== child.id) and supplies alias,
|
|
@@ -2008,10 +2065,14 @@ export function delegationTag(entry: DelegationEntry): string {
|
|
|
2008
2065
|
* itself (fallback 'agent'), state derived from its status, startedAt from
|
|
2009
2066
|
* time.created. A report-less child is a NATIVE task() child (every
|
|
2010
2067
|
* child of the current session — pantheon_delegate OR the native `task()`
|
|
2011
|
-
* tool — carries parentID = caller), so it gets source 'children-only'
|
|
2012
|
-
* and the `[native]` tag instead of a
|
|
2068
|
+
* tool — carries parentID = caller), so it gets source 'children-only',
|
|
2069
|
+
* the internal alias 'native-task' and the `[native]` tag instead of a
|
|
2070
|
+
* board alias. The 'task nativa' description fallback keeps the row
|
|
2071
|
+
* non-empty when the child carries no title.
|
|
2013
2072
|
* Terminal md state wins over the derived state; a running md defers to
|
|
2014
|
-
* the child's live status.
|
|
2073
|
+
* the child's live status. A running child is NEVER archived — it always
|
|
2074
|
+
* lands in the active split (splitDelegationList active = running/retry).
|
|
2075
|
+
* Sorted running-first (compareDelegationEntries).
|
|
2015
2076
|
* Pure — no I/O. */
|
|
2016
2077
|
export function childrenToDelegationEntries(
|
|
2017
2078
|
children: readonly ChildDelegationLike[] | undefined,
|
|
@@ -2033,7 +2094,7 @@ export function childrenToDelegationEntries(
|
|
|
2033
2094
|
? mdEntry.state
|
|
2034
2095
|
: childStatusToState(child.status)
|
|
2035
2096
|
out.push({
|
|
2036
|
-
alias: mdEntry?.alias ?? 'task',
|
|
2097
|
+
alias: mdEntry?.alias ?? 'native-task',
|
|
2037
2098
|
sessionID: mdEntry?.sessionID ?? '',
|
|
2038
2099
|
taskID: child.id,
|
|
2039
2100
|
agent: mdEntry?.agent ?? child.agent ?? 'agent',
|
|
@@ -2046,7 +2107,9 @@ export function childrenToDelegationEntries(
|
|
|
2046
2107
|
description:
|
|
2047
2108
|
mdEntry !== undefined && mdEntry.description !== ''
|
|
2048
2109
|
? mdEntry.description
|
|
2049
|
-
:
|
|
2110
|
+
: child.title !== undefined && child.title !== ''
|
|
2111
|
+
? child.title
|
|
2112
|
+
: 'task nativa',
|
|
2050
2113
|
source: mdEntry !== undefined ? 'md' : 'children-only',
|
|
2051
2114
|
})
|
|
2052
2115
|
}
|
|
@@ -2241,7 +2304,7 @@ function DelegationRow(props: {
|
|
|
2241
2304
|
{(a) => (
|
|
2242
2305
|
<text
|
|
2243
2306
|
fg={theme().textMuted}
|
|
2244
|
-
>{` \u21b3 ${a().tool}${a().summary !== '' ?
|
|
2307
|
+
>{` \u21b3 ${a().tool}${a().summary !== '' ? ` ${a().summary}` : ''}`}</text>
|
|
2245
2308
|
)}
|
|
2246
2309
|
</Show>
|
|
2247
2310
|
</box>
|
|
@@ -2370,9 +2433,10 @@ function View(props: {
|
|
|
2370
2433
|
// remains on the 1s ticker to avoid unnecessary work.
|
|
2371
2434
|
const [animationNow, setAnimationNow] = createSignal(Date.now())
|
|
2372
2435
|
// Cumulative count of event-triggered refreshes — logged with every fetch
|
|
2373
|
-
// ("panel: children=N md=N events=N") so the (0)
|
|
2374
|
-
// children>0 means the source works; events=0
|
|
2375
|
-
// delivering, and the 1s poll is what keeps
|
|
2436
|
+
// ("panel: children=N(pantheon=M native=K) md=N events=N") so the (0)
|
|
2437
|
+
// symptom is diagnosable: children>0 means the source works; events=0
|
|
2438
|
+
// means the event bus isn't delivering, and the 1s poll is what keeps
|
|
2439
|
+
// the panel honest.
|
|
2376
2440
|
let eventRefreshCount = 0
|
|
2377
2441
|
// In-flight guard: overlap between the poll, events and the version effect
|
|
2378
2442
|
// collapses into one fetch — no duplicate work, no duplicate entries.
|
|
@@ -2404,7 +2468,7 @@ function View(props: {
|
|
|
2404
2468
|
if (sessionID === null) {
|
|
2405
2469
|
setChildDelegations(md)
|
|
2406
2470
|
panelLog.info(
|
|
2407
|
-
|
|
2471
|
+
`${formatPanelLogLine(0, 0, 0, md.length, eventRefreshCount)} (no sessionID — history shown)`,
|
|
2408
2472
|
)
|
|
2409
2473
|
return
|
|
2410
2474
|
}
|
|
@@ -2447,8 +2511,15 @@ function View(props: {
|
|
|
2447
2511
|
)
|
|
2448
2512
|
setChildDelegations(mergeChildDelegationSources(childEntries, liveEntries))
|
|
2449
2513
|
// 4. diagnostic line — every re-fetch (silence-by-default, hooks.log).
|
|
2514
|
+
const counts = countDelegationSources(childEntries)
|
|
2450
2515
|
panelLog.info(
|
|
2451
|
-
|
|
2516
|
+
formatPanelLogLine(
|
|
2517
|
+
children.length,
|
|
2518
|
+
counts.pantheon,
|
|
2519
|
+
counts.native,
|
|
2520
|
+
md.length,
|
|
2521
|
+
eventRefreshCount,
|
|
2522
|
+
),
|
|
2452
2523
|
)
|
|
2453
2524
|
} finally {
|
|
2454
2525
|
delegationsInflight = null
|
|
@@ -2464,6 +2535,9 @@ function View(props: {
|
|
|
2464
2535
|
...delegationSplit().active,
|
|
2465
2536
|
...delegationSplit().recent,
|
|
2466
2537
|
])
|
|
2538
|
+
// Header breakdown: native task() rows vs pantheon_delegate rows, so
|
|
2539
|
+
// Sessions(3) with 3 native children reads "Delegations (3 native + 0 pantheon)".
|
|
2540
|
+
const delegationCounts = createMemo(() => countDelegationSources(visibleDelegations()))
|
|
2467
2541
|
const [archivedPage, setArchivedPage] = createSignal(archivedUiState.page)
|
|
2468
2542
|
const clampArchivedPage = (page: number, pages: number) => {
|
|
2469
2543
|
const clamped = Math.max(0, Math.min(page, Math.max(0, pages - 1)))
|
|
@@ -2612,9 +2686,11 @@ function View(props: {
|
|
|
2612
2686
|
{/* ── Delegations (session.children primary + .pantheon/delegations md enrichment) ── */}
|
|
2613
2687
|
<box onMouseDown={() => setShowDelegations((x) => !x)}>
|
|
2614
2688
|
<text fg={theme().text} attributes={1}>
|
|
2615
|
-
{`${showDelegations() ? '
|
|
2689
|
+
{`${showDelegations() ? '▼' : '▶'} Delegations`}
|
|
2616
2690
|
</text>
|
|
2617
|
-
<text
|
|
2691
|
+
<text
|
|
2692
|
+
fg={theme().textMuted}
|
|
2693
|
+
>{` (${String(delegationCounts().native)} native + ${String(delegationCounts().pantheon)} pantheon)`}</text>
|
|
2618
2694
|
</box>
|
|
2619
2695
|
<Show when={showDelegations()}>
|
|
2620
2696
|
<Show
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pantheon-tui",
|
|
3
|
-
"version": "1.5.0-beta.
|
|
3
|
+
"version": "1.5.0-beta.9",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pantheon-tui",
|
|
9
|
-
"version": "1.5.0-beta.
|
|
9
|
+
"version": "1.5.0-beta.9",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@opencode-ai/plugin": ">=1.14.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pantheon-tui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.5.0-beta.
|
|
4
|
+
"version": "1.5.0-beta.9",
|
|
5
5
|
"description": "Pantheon TUI plugin — sidebar (version, preset, sessions, real-time delegations) + AI subscription usage gauges incl. OpenCode Go/Zen dollar limits (vendored, MIT)",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"exports": {
|
|
@@ -921,9 +921,12 @@ async function setupUsageBar(api: TuiPluginApi) {
|
|
|
921
921
|
* children; a 1s safety poll (oh-my-opencode-slim pattern) re-fetches
|
|
922
922
|
* children + re-reads the md so the panel updates EVEN without events.
|
|
923
923
|
*
|
|
924
|
-
* 4. DIAGNOSTICS — every re-fetch logs
|
|
924
|
+
* 4. DIAGNOSTICS — every re-fetch logs
|
|
925
|
+
* "panel: children=N(pantheon=M native=K) md=N events=N"
|
|
925
926
|
* to `.pantheon/logs/hooks.log` (silence-by-default, createPantheonLogger
|
|
926
|
-
* policy) so the (0) symptom is diagnosable from disk
|
|
927
|
+
* policy) so the (0) symptom is diagnosable from disk: children>0 means
|
|
928
|
+
* the source works, pantheon/native splits board vs task() children.
|
|
929
|
+
* A children fetch
|
|
927
930
|
* failure logs "panel: error <msg>" (previously silenced), and a missing
|
|
928
931
|
* current session (null/placeholder sessionID) logs "panel: children=0
|
|
929
932
|
* md=N ... (no sessionID — history shown)" — the md HISTORY renders
|
|
@@ -1509,9 +1512,11 @@ export function mergeChildDelegationSources(
|
|
|
1509
1512
|
* Source of truth for jobs born while opencode is open. The TUI event bus
|
|
1510
1513
|
* (api.event.on) delivers `message.part.updated` for every part change;
|
|
1511
1514
|
* we only care about tool parts whose `tool` is pantheon_delegate (job
|
|
1512
|
-
* launch)
|
|
1513
|
-
*
|
|
1514
|
-
*
|
|
1515
|
+
* launch), the native `task` subagent tool (same parentID === caller
|
|
1516
|
+
* mechanism — its rows render `[native]`), or pantheon_delegation_read
|
|
1517
|
+
* (blocking until terminal → closes the entry). Shape-adapted from the
|
|
1518
|
+
* SDK v2 ToolPart but duck-typed so the pure helpers are testable without
|
|
1519
|
+
* the SDK. */
|
|
1515
1520
|
|
|
1516
1521
|
/** Duck-typed subset of a tool part (SDK v2 `ToolPart` / `ToolState`). */
|
|
1517
1522
|
export type DelegationToolPart = {
|
|
@@ -1537,7 +1542,7 @@ export type LiveDelegationEntry = {
|
|
|
1537
1542
|
partID: string
|
|
1538
1543
|
/** Parent session the delegation was launched from. */
|
|
1539
1544
|
sessionID: string
|
|
1540
|
-
tool: 'pantheon_delegate' | 'pantheon_delegation_read'
|
|
1545
|
+
tool: 'pantheon_delegate' | 'pantheon_delegation_read' | 'task'
|
|
1541
1546
|
/** Agent name (from the delegate input args). */
|
|
1542
1547
|
agent: string
|
|
1543
1548
|
description: string
|
|
@@ -1557,7 +1562,7 @@ export type ParsedDelegationToolPart = {
|
|
|
1557
1562
|
callID: string
|
|
1558
1563
|
partID: string
|
|
1559
1564
|
sessionID: string
|
|
1560
|
-
tool: 'pantheon_delegate' | 'pantheon_delegation_read'
|
|
1565
|
+
tool: 'pantheon_delegate' | 'pantheon_delegation_read' | 'task'
|
|
1561
1566
|
/** null for read parts (no agent arg — the id targets an existing job). */
|
|
1562
1567
|
agent: string | null
|
|
1563
1568
|
description: string
|
|
@@ -1577,13 +1582,20 @@ const READ_ALIAS_PATTERN = /^[a-z]{2,8}-\d+$/i
|
|
|
1577
1582
|
|
|
1578
1583
|
/** Extract the tool name + args from a `message.part.updated` part and
|
|
1579
1584
|
* reduce it to what the panel needs. Returns null for anything that is
|
|
1580
|
-
* not a pantheon delegation tool part
|
|
1585
|
+
* not a pantheon delegation tool part, the native `task` subagent tool
|
|
1586
|
+
* (same parentID === caller mechanism — its children render `[native]`),
|
|
1587
|
+
* or is missing its callID. */
|
|
1581
1588
|
export function parseDelegationToolPart(
|
|
1582
1589
|
part: DelegationToolPart,
|
|
1583
1590
|
now = Date.now(),
|
|
1584
1591
|
): ParsedDelegationToolPart | null {
|
|
1585
1592
|
if (part.type !== 'tool') return null
|
|
1586
|
-
if (
|
|
1593
|
+
if (
|
|
1594
|
+
part.tool !== 'pantheon_delegate' &&
|
|
1595
|
+
part.tool !== 'pantheon_delegation_read' &&
|
|
1596
|
+
part.tool !== 'task'
|
|
1597
|
+
)
|
|
1598
|
+
return null
|
|
1587
1599
|
const callID = part.callID
|
|
1588
1600
|
if (callID === undefined || callID === '') return null
|
|
1589
1601
|
const sessionID = part.sessionID ?? ''
|
|
@@ -1618,10 +1630,22 @@ export function parseDelegationToolPart(
|
|
|
1618
1630
|
}
|
|
1619
1631
|
}
|
|
1620
1632
|
|
|
1621
|
-
const agent =
|
|
1622
|
-
|
|
1633
|
+
const agent =
|
|
1634
|
+
typeof input.agent === 'string'
|
|
1635
|
+
? input.agent
|
|
1636
|
+
: typeof input.subagent_type === 'string'
|
|
1637
|
+
? (input.subagent_type as string)
|
|
1638
|
+
: 'agent'
|
|
1639
|
+
const description =
|
|
1640
|
+
typeof input.description === 'string'
|
|
1641
|
+
? input.description
|
|
1642
|
+
: typeof input.prompt === 'string'
|
|
1643
|
+
? (input.prompt as string).slice(0, 120)
|
|
1644
|
+
: ''
|
|
1623
1645
|
// The alias/taskID only exist once the delegate tool COMPLETES (they are
|
|
1624
|
-
// returned in its output); a running/pending part has neither.
|
|
1646
|
+
// returned in its output); a running/pending part has neither. Native
|
|
1647
|
+
// `task` parts never carry a board alias — the children channel supplies
|
|
1648
|
+
// the child session id, so the row renders `[native]`.
|
|
1625
1649
|
let alias: string | null = null
|
|
1626
1650
|
let taskID: string | null = null
|
|
1627
1651
|
if (status === 'completed') {
|
|
@@ -1633,7 +1657,7 @@ export function parseDelegationToolPart(
|
|
|
1633
1657
|
callID,
|
|
1634
1658
|
partID: part.id ?? '',
|
|
1635
1659
|
sessionID,
|
|
1636
|
-
tool: 'pantheon_delegate',
|
|
1660
|
+
tool: part.tool === 'task' ? 'task' : 'pantheon_delegate',
|
|
1637
1661
|
agent,
|
|
1638
1662
|
description,
|
|
1639
1663
|
status,
|
|
@@ -1691,9 +1715,11 @@ export function reduceDelegationToolPart(
|
|
|
1691
1715
|
return changed
|
|
1692
1716
|
}
|
|
1693
1717
|
|
|
1694
|
-
// Delegate tool: pending/running →
|
|
1695
|
-
// delegate tool only means the job was
|
|
1696
|
-
// we pick up alias + taskID from the
|
|
1718
|
+
// Delegate tool (pantheon_delegate AND native task): pending/running →
|
|
1719
|
+
// job launched (running). A COMPLETED delegate tool only means the job was
|
|
1720
|
+
// registered — it stays running and we pick up alias + taskID from the
|
|
1721
|
+
// output. Native `task` parts never yield a board alias, so their rows
|
|
1722
|
+
// stay `[native]` until the children channel resolves them.
|
|
1697
1723
|
const existing = map.get(parsed.callID)
|
|
1698
1724
|
if (parsed.status === 'error') {
|
|
1699
1725
|
if (existing !== undefined && existing.state === 'error' && existing.updatedAt === parsed.endAt)
|
|
@@ -1702,7 +1728,7 @@ export function reduceDelegationToolPart(
|
|
|
1702
1728
|
callID: parsed.callID,
|
|
1703
1729
|
partID: parsed.partID,
|
|
1704
1730
|
sessionID: parsed.sessionID,
|
|
1705
|
-
tool:
|
|
1731
|
+
tool: parsed.tool,
|
|
1706
1732
|
agent: parsed.agent ?? 'agent',
|
|
1707
1733
|
description: parsed.description,
|
|
1708
1734
|
alias: existing?.alias ?? null,
|
|
@@ -1719,7 +1745,7 @@ export function reduceDelegationToolPart(
|
|
|
1719
1745
|
callID: parsed.callID,
|
|
1720
1746
|
partID: parsed.partID,
|
|
1721
1747
|
sessionID: parsed.sessionID,
|
|
1722
|
-
tool:
|
|
1748
|
+
tool: parsed.tool,
|
|
1723
1749
|
agent: parsed.agent ?? 'agent',
|
|
1724
1750
|
description: parsed.description,
|
|
1725
1751
|
alias: parsed.alias,
|
|
@@ -1772,11 +1798,14 @@ export function removeDelegationEntry(
|
|
|
1772
1798
|
return false
|
|
1773
1799
|
}
|
|
1774
1800
|
|
|
1775
|
-
/** Collect pantheon delegation tool parts from a session's messages.
|
|
1801
|
+
/** Collect pantheon delegation + native task tool parts from a session's messages.
|
|
1776
1802
|
* Messages may carry their parts inline (duck-typed `msg.parts`); when
|
|
1777
1803
|
* they don't, the optional `getParts(messageID)` callback is used (the TUI
|
|
1778
|
-
* SDK exposes `api.state.part(messageID)`).
|
|
1779
|
-
*
|
|
1804
|
+
* SDK exposes `api.state.part(messageID)`). The native `task` tool spawns a
|
|
1805
|
+
* child session with parentID = caller — the same mechanism as
|
|
1806
|
+
* pantheon_delegate — so its parts feed the live-map as the native signal
|
|
1807
|
+
* (rows render `[native]` via the children channel). Pure w.r.t. I/O — used
|
|
1808
|
+
* by the mount re-scan to re-seed the live map after compaction/attach. */
|
|
1780
1809
|
export function collectDelegationToolParts(
|
|
1781
1810
|
messages: readonly { id?: string; parts?: unknown[] }[] | undefined,
|
|
1782
1811
|
getParts?: (messageID: string) => readonly unknown[] | undefined,
|
|
@@ -1794,7 +1823,9 @@ export function collectDelegationToolParts(
|
|
|
1794
1823
|
const part = raw as DelegationToolPart
|
|
1795
1824
|
if (
|
|
1796
1825
|
part?.type === 'tool' &&
|
|
1797
|
-
(part.tool === 'pantheon_delegate' ||
|
|
1826
|
+
(part.tool === 'pantheon_delegate' ||
|
|
1827
|
+
part.tool === 'pantheon_delegation_read' ||
|
|
1828
|
+
part.tool === 'task')
|
|
1798
1829
|
) {
|
|
1799
1830
|
out.push(part)
|
|
1800
1831
|
}
|
|
@@ -2000,6 +2031,32 @@ export function delegationTag(entry: DelegationEntry): string {
|
|
|
2000
2031
|
return entry.source === 'children-only' ? '[native]' : `[pantheon:${entry.alias}]`
|
|
2001
2032
|
}
|
|
2002
2033
|
|
|
2034
|
+
/** Split a display list into native task() rows vs pantheon_delegate rows.
|
|
2035
|
+
* Native = source 'children-only' (no board report); everything else counts
|
|
2036
|
+
* as pantheon. Pure — powers the header breakdown + the hooks.log line. */
|
|
2037
|
+
export function countDelegationSources(entries: readonly DelegationEntry[]): {
|
|
2038
|
+
native: number
|
|
2039
|
+
pantheon: number
|
|
2040
|
+
total: number
|
|
2041
|
+
} {
|
|
2042
|
+
let native = 0
|
|
2043
|
+
for (const e of entries) if (e.source === 'children-only') native++
|
|
2044
|
+
return { native, pantheon: entries.length - native, total: entries.length }
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
/** Diagnostic hooks.log line for a panel re-fetch, with the children
|
|
2048
|
+
* breakdown (pantheon = children WITH a board report, native = children
|
|
2049
|
+
* WITHOUT one). Pure — the View logs the returned string verbatim. */
|
|
2050
|
+
export function formatPanelLogLine(
|
|
2051
|
+
children: number,
|
|
2052
|
+
pantheon: number,
|
|
2053
|
+
native: number,
|
|
2054
|
+
md: number,
|
|
2055
|
+
events: number,
|
|
2056
|
+
): string {
|
|
2057
|
+
return `panel: children=${children}(pantheon=${pantheon} native=${native}) md=${md} events=${events}`
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2003
2060
|
/** Turn child sessions (PRIMARY) enriched with md reports into the display
|
|
2004
2061
|
* list. One entry per child id (duplicates across re-fetches collapse).
|
|
2005
2062
|
* The md report is matched by `Task ID` (== child.id) and supplies alias,
|
|
@@ -2008,10 +2065,14 @@ export function delegationTag(entry: DelegationEntry): string {
|
|
|
2008
2065
|
* itself (fallback 'agent'), state derived from its status, startedAt from
|
|
2009
2066
|
* time.created. A report-less child is a NATIVE task() child (every
|
|
2010
2067
|
* child of the current session — pantheon_delegate OR the native `task()`
|
|
2011
|
-
* tool — carries parentID = caller), so it gets source 'children-only'
|
|
2012
|
-
* and the `[native]` tag instead of a
|
|
2068
|
+
* tool — carries parentID = caller), so it gets source 'children-only',
|
|
2069
|
+
* the internal alias 'native-task' and the `[native]` tag instead of a
|
|
2070
|
+
* board alias. The 'task nativa' description fallback keeps the row
|
|
2071
|
+
* non-empty when the child carries no title.
|
|
2013
2072
|
* Terminal md state wins over the derived state; a running md defers to
|
|
2014
|
-
* the child's live status.
|
|
2073
|
+
* the child's live status. A running child is NEVER archived — it always
|
|
2074
|
+
* lands in the active split (splitDelegationList active = running/retry).
|
|
2075
|
+
* Sorted running-first (compareDelegationEntries).
|
|
2015
2076
|
* Pure — no I/O. */
|
|
2016
2077
|
export function childrenToDelegationEntries(
|
|
2017
2078
|
children: readonly ChildDelegationLike[] | undefined,
|
|
@@ -2033,7 +2094,7 @@ export function childrenToDelegationEntries(
|
|
|
2033
2094
|
? mdEntry.state
|
|
2034
2095
|
: childStatusToState(child.status)
|
|
2035
2096
|
out.push({
|
|
2036
|
-
alias: mdEntry?.alias ?? 'task',
|
|
2097
|
+
alias: mdEntry?.alias ?? 'native-task',
|
|
2037
2098
|
sessionID: mdEntry?.sessionID ?? '',
|
|
2038
2099
|
taskID: child.id,
|
|
2039
2100
|
agent: mdEntry?.agent ?? child.agent ?? 'agent',
|
|
@@ -2046,7 +2107,9 @@ export function childrenToDelegationEntries(
|
|
|
2046
2107
|
description:
|
|
2047
2108
|
mdEntry !== undefined && mdEntry.description !== ''
|
|
2048
2109
|
? mdEntry.description
|
|
2049
|
-
:
|
|
2110
|
+
: child.title !== undefined && child.title !== ''
|
|
2111
|
+
? child.title
|
|
2112
|
+
: 'task nativa',
|
|
2050
2113
|
source: mdEntry !== undefined ? 'md' : 'children-only',
|
|
2051
2114
|
})
|
|
2052
2115
|
}
|
|
@@ -2241,7 +2304,7 @@ function DelegationRow(props: {
|
|
|
2241
2304
|
{(a) => (
|
|
2242
2305
|
<text
|
|
2243
2306
|
fg={theme().textMuted}
|
|
2244
|
-
>{` \u21b3 ${a().tool}${a().summary !== '' ?
|
|
2307
|
+
>{` \u21b3 ${a().tool}${a().summary !== '' ? ` ${a().summary}` : ''}`}</text>
|
|
2245
2308
|
)}
|
|
2246
2309
|
</Show>
|
|
2247
2310
|
</box>
|
|
@@ -2370,9 +2433,10 @@ function View(props: {
|
|
|
2370
2433
|
// remains on the 1s ticker to avoid unnecessary work.
|
|
2371
2434
|
const [animationNow, setAnimationNow] = createSignal(Date.now())
|
|
2372
2435
|
// Cumulative count of event-triggered refreshes — logged with every fetch
|
|
2373
|
-
// ("panel: children=N md=N events=N") so the (0)
|
|
2374
|
-
// children>0 means the source works; events=0
|
|
2375
|
-
// delivering, and the 1s poll is what keeps
|
|
2436
|
+
// ("panel: children=N(pantheon=M native=K) md=N events=N") so the (0)
|
|
2437
|
+
// symptom is diagnosable: children>0 means the source works; events=0
|
|
2438
|
+
// means the event bus isn't delivering, and the 1s poll is what keeps
|
|
2439
|
+
// the panel honest.
|
|
2376
2440
|
let eventRefreshCount = 0
|
|
2377
2441
|
// In-flight guard: overlap between the poll, events and the version effect
|
|
2378
2442
|
// collapses into one fetch — no duplicate work, no duplicate entries.
|
|
@@ -2404,7 +2468,7 @@ function View(props: {
|
|
|
2404
2468
|
if (sessionID === null) {
|
|
2405
2469
|
setChildDelegations(md)
|
|
2406
2470
|
panelLog.info(
|
|
2407
|
-
|
|
2471
|
+
`${formatPanelLogLine(0, 0, 0, md.length, eventRefreshCount)} (no sessionID — history shown)`,
|
|
2408
2472
|
)
|
|
2409
2473
|
return
|
|
2410
2474
|
}
|
|
@@ -2447,8 +2511,15 @@ function View(props: {
|
|
|
2447
2511
|
)
|
|
2448
2512
|
setChildDelegations(mergeChildDelegationSources(childEntries, liveEntries))
|
|
2449
2513
|
// 4. diagnostic line — every re-fetch (silence-by-default, hooks.log).
|
|
2514
|
+
const counts = countDelegationSources(childEntries)
|
|
2450
2515
|
panelLog.info(
|
|
2451
|
-
|
|
2516
|
+
formatPanelLogLine(
|
|
2517
|
+
children.length,
|
|
2518
|
+
counts.pantheon,
|
|
2519
|
+
counts.native,
|
|
2520
|
+
md.length,
|
|
2521
|
+
eventRefreshCount,
|
|
2522
|
+
),
|
|
2452
2523
|
)
|
|
2453
2524
|
} finally {
|
|
2454
2525
|
delegationsInflight = null
|
|
@@ -2464,6 +2535,9 @@ function View(props: {
|
|
|
2464
2535
|
...delegationSplit().active,
|
|
2465
2536
|
...delegationSplit().recent,
|
|
2466
2537
|
])
|
|
2538
|
+
// Header breakdown: native task() rows vs pantheon_delegate rows, so
|
|
2539
|
+
// Sessions(3) with 3 native children reads "Delegations (3 native + 0 pantheon)".
|
|
2540
|
+
const delegationCounts = createMemo(() => countDelegationSources(visibleDelegations()))
|
|
2467
2541
|
const [archivedPage, setArchivedPage] = createSignal(archivedUiState.page)
|
|
2468
2542
|
const clampArchivedPage = (page: number, pages: number) => {
|
|
2469
2543
|
const clamped = Math.max(0, Math.min(page, Math.max(0, pages - 1)))
|
|
@@ -2612,9 +2686,11 @@ function View(props: {
|
|
|
2612
2686
|
{/* ── Delegations (session.children primary + .pantheon/delegations md enrichment) ── */}
|
|
2613
2687
|
<box onMouseDown={() => setShowDelegations((x) => !x)}>
|
|
2614
2688
|
<text fg={theme().text} attributes={1}>
|
|
2615
|
-
{`${showDelegations() ? '
|
|
2689
|
+
{`${showDelegations() ? '▼' : '▶'} Delegations`}
|
|
2616
2690
|
</text>
|
|
2617
|
-
<text
|
|
2691
|
+
<text
|
|
2692
|
+
fg={theme().textMuted}
|
|
2693
|
+
>{` (${String(delegationCounts().native)} native + ${String(delegationCounts().pantheon)} pantheon)`}</text>
|
|
2618
2694
|
</box>
|
|
2619
2695
|
<Show when={showDelegations()}>
|
|
2620
2696
|
<Show
|