@aeondave/pi-persona 1.15.2 → 1.16.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/AGENTS.md +30 -6
- package/CHANGELOG.md +82 -0
- package/README.md +27 -8
- package/SECURITY.md +42 -0
- package/docs/ARCHITECTURE.md +52 -7
- package/docs/EXPERIENCE.md +21 -5
- package/docs/README.md +2 -0
- package/docs/REFERENCE.md +84 -14
- package/docs/STRATEGIES.md +58 -31
- package/docs/TELEMETRY.md +8 -0
- package/package.json +11 -9
- package/src/bus/broker/paths.ts +3 -2
- package/src/core/brief.ts +1 -1
- package/src/core/nudge.ts +3 -4
- package/src/core/pi-compat.ts +3 -3
- package/src/engine/adapter.ts +11 -6
- package/src/engine/async.ts +12 -6
- package/src/engine/inproc.ts +14 -6
- package/src/extension/engine.ts +25 -4
- package/src/extension/hooks.ts +68 -21
- package/src/extension.ts +209 -49
- package/src/orchestration/model-retry.ts +32 -61
- package/src/orchestration/params.ts +27 -1
- package/src/orchestration/roster.ts +80 -3
- package/src/orchestration/sdk.ts +150 -36
- package/src/orchestration/strategies/compete.ts +14 -8
- package/src/orchestration/strategies/council-rounds.ts +8 -1
- package/src/orchestration/strategies/critic-loop.ts +25 -11
- package/src/orchestration/strategies/judge.ts +18 -9
- package/src/orchestration/strategies/magi.ts +42 -93
- package/src/orchestration/strategies/map.ts +41 -19
- package/src/orchestration/strategies/pipeline.ts +2 -2
- package/src/orchestration/strategies/synthesize.ts +18 -11
- package/src/orchestration/types.ts +15 -0
- package/src/persona/model-participants.ts +240 -0
- package/src/persona/orchestrate.ts +44 -2
- package/src/persona/persona.ts +86 -14
- package/src/telemetry/contract.ts +1 -1
- package/src/telemetry/producer.ts +241 -8
- package/src/tools/council.ts +36 -8
- package/src/tools/delegate-tool.ts +162 -24
- package/src/tools/intercom-tool.ts +119 -32
- package/src/ui/agent-overlay.ts +15 -6
- package/src/ui/agent-tree.ts +39 -2
package/src/ui/agent-overlay.ts
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
wrapTextWithAnsi,
|
|
24
24
|
} from "@earendil-works/pi-tui";
|
|
25
25
|
|
|
26
|
-
import { type AgentNode, type AgentTree, type FlatRow, flattenTree, GLYPH, runningAnnotation } from "./agent-tree.ts";
|
|
26
|
+
import { type AgentNode, type AgentTree, type FlatRow, deriveWaitingNodeIds, flattenTree, GLYPH, runningAnnotation } from "./agent-tree.ts";
|
|
27
27
|
import { LiveClock } from "./live-clock.ts";
|
|
28
28
|
import { visibleWindow } from "./model-picker.ts";
|
|
29
29
|
import { compactInlineText, OPEN_SEQUENCE_TAIL, sanitizeTerminalText } from "./presentation.ts";
|
|
@@ -54,6 +54,7 @@ export interface AgentRowInput {
|
|
|
54
54
|
inner: number;
|
|
55
55
|
now: number;
|
|
56
56
|
stallMs: number;
|
|
57
|
+
waitingForSupervisor?: boolean;
|
|
57
58
|
theme: Theme;
|
|
58
59
|
}
|
|
59
60
|
|
|
@@ -68,7 +69,7 @@ export interface AgentRowInput {
|
|
|
68
69
|
export function composeAgentRow(input: AgentRowInput): string {
|
|
69
70
|
const { node, depth, selected, inner, now, stallMs, theme: t } = input;
|
|
70
71
|
const indent = " ".repeat(depth);
|
|
71
|
-
const clock = runningAnnotation(node, now, stallMs);
|
|
72
|
+
const clock = runningAnnotation(node, now, stallMs, input.waitingForSupervisor);
|
|
72
73
|
// Reserve the clock's exact width plus its widest separator (" · ") up front — this portion
|
|
73
74
|
// of the row is never truncated.
|
|
74
75
|
const clockCols = clock ? visibleWidth(clock) + visibleWidth(" · ") : 0;
|
|
@@ -98,6 +99,8 @@ export interface AgentOverlayActions {
|
|
|
98
99
|
onSteer?: (nodeId: string, text: string) => boolean;
|
|
99
100
|
canSteer?: (nodeId: string) => boolean;
|
|
100
101
|
canStop?: (nodeId: string) => boolean;
|
|
102
|
+
/** Live predicate for the exact engine handle behind a row. */
|
|
103
|
+
isWaitingForSupervisor?: (nodeId: string) => boolean;
|
|
101
104
|
/** Stall threshold for the ⚠ badge; 0/omitted ⇒ no badge. */
|
|
102
105
|
stallMs?: number;
|
|
103
106
|
/** Clock; tests inject a fixed one. */
|
|
@@ -202,8 +205,11 @@ export class AgentOverlay extends Container {
|
|
|
202
205
|
return Math.max(6, termRows() - 10);
|
|
203
206
|
}
|
|
204
207
|
|
|
205
|
-
private
|
|
206
|
-
|
|
208
|
+
private waitingNodeIds(nodes: readonly AgentNode[] = this.tree.snapshot()): Set<string> {
|
|
209
|
+
return deriveWaitingNodeIds(nodes, (id) => this.actions.isWaitingForSupervisor?.(id) === true);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private leafRows(snap: AgentNode[] = this.tree.snapshot()): FlatRow[] {
|
|
207
213
|
const parents = new Set(snap.map((n) => n.parentId).filter((p): p is string => p !== undefined));
|
|
208
214
|
return flattenTree(snap).filter((r) => !parents.has(r.node.id));
|
|
209
215
|
}
|
|
@@ -256,8 +262,9 @@ export class AgentOverlay extends Container {
|
|
|
256
262
|
|
|
257
263
|
private renderList(): void {
|
|
258
264
|
const t = this.theme;
|
|
259
|
-
const
|
|
260
|
-
const
|
|
265
|
+
const nodes = this.tree.snapshot();
|
|
266
|
+
const rows = flattenTree(nodes);
|
|
267
|
+
const selected = this.selectedLeaf(this.leafRows(nodes))?.node;
|
|
261
268
|
this.addChild(new Text(t.fg("accent", t.bold("Agents")), 1, 0));
|
|
262
269
|
this.addChild(new Spacer(1));
|
|
263
270
|
if (rows.length === 0) {
|
|
@@ -265,6 +272,7 @@ export class AgentOverlay extends Container {
|
|
|
265
272
|
} else {
|
|
266
273
|
// Scroll window over the rows (a big fan-out must not overflow the screen);
|
|
267
274
|
// the window follows the selection, ▲/▼ markers show what's off-screen.
|
|
275
|
+
const waitingIds = this.waitingNodeIds(nodes);
|
|
268
276
|
const vp = this.listViewport();
|
|
269
277
|
const selIdx = Math.max(0, rows.findIndex((r) => r.node.id === selected?.id));
|
|
270
278
|
this.listScroll = visibleWindow(selIdx, vp, rows.length, this.listScroll);
|
|
@@ -278,6 +286,7 @@ export class AgentOverlay extends Container {
|
|
|
278
286
|
inner: this.inner(),
|
|
279
287
|
now: this.now(),
|
|
280
288
|
stallMs: this.actions.stallMs ?? 0,
|
|
289
|
+
waitingForSupervisor: waitingIds.has(row.node.id),
|
|
281
290
|
theme: t,
|
|
282
291
|
});
|
|
283
292
|
this.addChild(new Text(composed, 1, 0));
|
package/src/ui/agent-tree.ts
CHANGED
|
@@ -65,6 +65,8 @@ export interface RenderOptions {
|
|
|
65
65
|
now?: number;
|
|
66
66
|
/** Quiet time after which a running row shows the stall badge; 0/omitted ⇒ never. */
|
|
67
67
|
stallMs?: number;
|
|
68
|
+
/** Live ids whose exact engine handle currently has a pending supervisor ask. */
|
|
69
|
+
waitingForSupervisor?: ReadonlySet<string>;
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
/** Whether `detail` is the literal "queued" marker a seeded-but-not-yet-live leg carries — shared
|
|
@@ -78,14 +80,43 @@ export function isQueuedMarker(detail: string | undefined): boolean {
|
|
|
78
80
|
* for `stallMs`. Undefined for settled nodes, nodes without clock data, or a node still carrying
|
|
79
81
|
* the "queued" marker — it was seeded ahead of the concurrency gate and has not started running
|
|
80
82
|
* yet, so its clock (stamped at seed time) has nothing true to report. Pure. */
|
|
81
|
-
export function runningAnnotation(node: Pick<AgentNode, "status" | "startedAt" | "lastAdvanceAt" | "detail">, now: number, stallMs: number): string | undefined {
|
|
83
|
+
export function runningAnnotation(node: Pick<AgentNode, "status" | "startedAt" | "lastAdvanceAt" | "detail">, now: number, stallMs: number, waitingForSupervisor = false): string | undefined {
|
|
82
84
|
if (node.status !== "running" || node.startedAt === undefined) return undefined;
|
|
83
85
|
if (isQueuedMarker(node.detail)) return undefined;
|
|
86
|
+
if (waitingForSupervisor) return "waiting for supervisor";
|
|
84
87
|
const quietSince = node.lastAdvanceAt ?? node.startedAt;
|
|
85
88
|
if (stallMs > 0 && now - quietSince >= stallMs) return `⚠ stalled ${formatDuration(now - quietSince)}`;
|
|
86
89
|
return formatDuration(now - node.startedAt);
|
|
87
90
|
}
|
|
88
91
|
|
|
92
|
+
/** Derive waiting rows from exact live-handle predicates. A running parent is waiting only when
|
|
93
|
+
* every live leaf below it is waiting; a progressing or stalled sibling keeps the parent honest. */
|
|
94
|
+
export function deriveWaitingNodeIds(nodes: readonly AgentNode[], isWaiting: (id: string) => boolean): Set<string> {
|
|
95
|
+
const children = new Map<string, AgentNode[]>();
|
|
96
|
+
for (const node of nodes) {
|
|
97
|
+
if (node.parentId === undefined) continue;
|
|
98
|
+
const list = children.get(node.parentId) ?? [];
|
|
99
|
+
list.push(node);
|
|
100
|
+
children.set(node.parentId, list);
|
|
101
|
+
}
|
|
102
|
+
const waitingLeaves = new Map<string, { hasLive: boolean; allWaiting: boolean }>();
|
|
103
|
+
const visit = (node: AgentNode): { hasLive: boolean; allWaiting: boolean } => {
|
|
104
|
+
if (node.status === "running" && isQueuedMarker(node.detail)) return { hasLive: false, allWaiting: false };
|
|
105
|
+
const descendants = (children.get(node.id) ?? []).map(visit).filter((state) => state.hasLive);
|
|
106
|
+
if (node.status !== "running") return { hasLive: descendants.length > 0, allWaiting: descendants.every((state) => state.allWaiting) };
|
|
107
|
+
if (descendants.length === 0) {
|
|
108
|
+
const waiting = isWaiting(node.id);
|
|
109
|
+
if (waiting) waitingLeaves.set(node.id, { hasLive: true, allWaiting: true });
|
|
110
|
+
return { hasLive: true, allWaiting: waiting };
|
|
111
|
+
}
|
|
112
|
+
const allWaiting = descendants.every((state) => state.allWaiting);
|
|
113
|
+
if (allWaiting) waitingLeaves.set(node.id, { hasLive: true, allWaiting: true });
|
|
114
|
+
return { hasLive: true, allWaiting };
|
|
115
|
+
};
|
|
116
|
+
for (const node of nodes) if (node.parentId === undefined) visit(node);
|
|
117
|
+
return new Set(waitingLeaves.keys());
|
|
118
|
+
}
|
|
119
|
+
|
|
89
120
|
/** The tree patch for one progress snapshot: every snapshot is an advance; activity wins over
|
|
90
121
|
* a bare token count as the visible detail. One helper for every progress site. */
|
|
91
122
|
export function progressPatch(snap: { output?: string; activity?: string; tokens?: number }, now: number): AgentNodePatch {
|
|
@@ -121,7 +152,7 @@ export function renderAgentTree(nodes: AgentNode[], opts: RenderOptions = {}): s
|
|
|
121
152
|
const branch = isRoot ? "" : isLast ? "└─ " : "├─ ";
|
|
122
153
|
const label = safeInline(node.label) || safeInline(node.id) || "agent";
|
|
123
154
|
const detailText = node.detail ? safeInline(node.detail) : "";
|
|
124
|
-
const clock = opts.now !== undefined ? runningAnnotation(node, opts.now, opts.stallMs ?? 0) : undefined;
|
|
155
|
+
const clock = opts.now !== undefined ? runningAnnotation(node, opts.now, opts.stallMs ?? 0, opts.waitingForSupervisor?.has(node.id) ?? false) : undefined;
|
|
125
156
|
const tail = [detailText, clock].filter((s): s is string => Boolean(s)).join(" · ");
|
|
126
157
|
const detail = tail ? ` ${tail}` : "";
|
|
127
158
|
lines.push(`${prefix}${branch}${GLYPH[node.status]} ${label}${detail}`);
|
|
@@ -246,6 +277,12 @@ export class AgentTree {
|
|
|
246
277
|
update(id: string, patch: AgentNodePatch): void {
|
|
247
278
|
const node = this.nodes.find((n) => n.id === id);
|
|
248
279
|
if (!node) return;
|
|
280
|
+
// Parent progress clocks are refreshed on every child tick. They never emit a change;
|
|
281
|
+
// avoid cloning/comparing the whole node for that common clock-only path.
|
|
282
|
+
if (Object.keys(patch).every((key) => CLOCK_KEYS.has(key as keyof AgentNode))) {
|
|
283
|
+
applyPatch(node, patch);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
249
286
|
const before = { ...node };
|
|
250
287
|
applyPatch(node, patch);
|
|
251
288
|
if (!sameNode(before, node)) this.emit({ type: "updated", node: { ...node } });
|