@aeondave/pi-persona 1.17.1 → 1.17.4
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 +103 -64
- package/CHANGELOG.md +310 -1
- package/README.md +37 -23
- package/agents/architect.md +21 -0
- package/agents/balthasar.md +17 -22
- package/agents/casper.md +17 -23
- package/agents/evidence-verifier.md +31 -16
- package/agents/melchior.md +17 -22
- package/agents/operator.md +15 -55
- package/agents/research.md +14 -48
- package/agents/reviewer.md +20 -39
- package/agents/scout.md +14 -15
- package/agents/verifier.md +15 -16
- package/docs/ARCHITECTURE.md +280 -92
- package/docs/COLLABORATION.md +110 -0
- package/docs/EXPERIENCE.md +52 -17
- package/docs/MONITORS.md +62 -3
- package/docs/README.md +12 -0
- package/docs/REFERENCE.md +139 -38
- package/docs/SPINE.md +2 -2
- package/docs/STRATEGIES.md +23 -8
- package/docs/TELEMETRY.md +1 -0
- package/flows/deep-review.flow.json +1 -1
- package/package.json +15 -8
- package/personas/audit.md +28 -17
- package/personas/dev.md +30 -32
- package/personas/elite.md +223 -221
- package/personas/judge.md +25 -19
- package/personas/magi.md +26 -29
- package/personas/planner.md +31 -24
- package/personas/researcher.md +25 -26
- package/personas/swarm.md +35 -25
- package/personas/verify.md +30 -19
- package/src/agents/agent.ts +26 -0
- package/src/bridge.ts +28 -10
- package/src/bus/broker/client.ts +119 -21
- package/src/bus/broker/host.ts +123 -8
- package/src/bus/broker/messages.ts +31 -4
- package/src/bus/contact.ts +113 -38
- package/src/bus/peers.ts +15 -0
- package/src/bus/result-reader.ts +88 -0
- package/src/core/brief.ts +436 -353
- package/src/core/capabilities.ts +1 -1
- package/src/core/config.ts +15 -10
- package/src/core/leg-thinking.ts +57 -0
- package/src/core/model-catalog.ts +171 -0
- package/src/core/model-tier.ts +89 -0
- package/src/core/models.ts +13 -4
- package/src/core/monitor-output.ts +85 -0
- package/src/core/monitor.ts +34 -2
- package/src/core/nudge.ts +176 -23
- package/src/core/ownership.ts +1 -2
- package/src/core/pi-compat.ts +5 -12
- package/src/core/seed.ts +99 -6
- package/src/core/session-runs.ts +87 -0
- package/src/core/settings.ts +1 -1
- package/src/core/timer.ts +34 -9
- package/src/core/types.ts +1 -1
- package/src/engine/adapter.ts +95 -27
- package/src/engine/async.ts +213 -155
- package/src/engine/child.ts +12 -4
- package/src/engine/host-module.ts +12 -0
- package/src/engine/inproc.ts +99 -53
- package/src/engine/result-grant.ts +62 -0
- package/src/engine/session-tools.ts +6 -0
- package/src/engine/stream.ts +10 -20
- package/src/engine/worktree.ts +17 -7
- package/src/exocom/collaboration-delivery.ts +29 -0
- package/src/exocom/collaboration.ts +148 -0
- package/src/exocom/envelope.ts +43 -4
- package/src/exocom/gate.ts +7 -2
- package/src/exocom/install.ts +47 -18
- package/src/exocom/ledger.ts +58 -8
- package/src/exocom/plane.ts +32 -1
- package/src/exocom/profile.ts +40 -0
- package/src/exocom/registry.ts +5 -0
- package/src/exocom/status.ts +11 -1
- package/src/exocom/write-guard.ts +7 -0
- package/src/extension/broker-host.ts +61 -9
- package/src/extension/engine.ts +54 -28
- package/src/extension/hooks.ts +952 -868
- package/src/extension/identity.ts +41 -75
- package/src/extension/persona-model-settings.ts +31 -13
- package/src/extension/results.ts +136 -0
- package/src/extension/shared.ts +32 -7
- package/src/extension.ts +2965 -2611
- package/src/loader.ts +11 -3
- package/src/monitor/archive.ts +235 -0
- package/src/monitor/session.ts +21 -1
- package/src/orchestration/flow-journal.ts +33 -6
- package/src/orchestration/flow-run.ts +160 -32
- package/src/orchestration/flow.ts +20 -2
- package/src/orchestration/reducers.ts +9 -5
- package/src/orchestration/render.ts +1 -1
- package/src/orchestration/roster.ts +71 -23
- package/src/orchestration/sdk.ts +61 -11
- package/src/orchestration/strategies/compete.ts +5 -1
- package/src/orchestration/strategies/council-rounds.ts +2 -0
- package/src/orchestration/strategies/critic-loop.ts +3 -0
- package/src/orchestration/strategies/debate.ts +2 -0
- package/src/orchestration/strategies/fanout.ts +3 -2
- package/src/orchestration/strategies/judge.ts +5 -1
- package/src/orchestration/strategies/magi.ts +40 -23
- package/src/orchestration/strategies/map.ts +13 -3
- package/src/orchestration/strategies/pair.ts +2 -0
- package/src/orchestration/strategies/pipeline.ts +2 -0
- package/src/orchestration/strategies/synthesize.ts +27 -7
- package/src/orchestration/strategy.ts +31 -0
- package/src/orchestration/types.ts +10 -0
- package/src/persona/model-participants.ts +20 -7
- package/src/persona/orchestrate.ts +2 -0
- package/src/persona/persona.ts +8 -4
- package/src/results/archive.ts +281 -0
- package/src/results/record.ts +166 -0
- package/src/results/retrieval.ts +142 -0
- package/src/results/types.ts +128 -0
- package/src/telemetry/contract.ts +1 -1
- package/src/tools/background-run.ts +151 -0
- package/src/tools/council.ts +108 -66
- package/src/tools/delegate-tool.ts +349 -283
- package/src/tools/delegate.ts +35 -216
- package/src/tools/exocom-collaboration-input.ts +168 -0
- package/src/tools/exocom-collaboration.ts +315 -0
- package/src/tools/exocom-profile.ts +102 -0
- package/src/tools/exocom-work.ts +18 -53
- package/src/tools/exocom.ts +35 -22
- package/src/tools/flow.ts +103 -30
- package/src/tools/intercom-tool.ts +202 -128
- package/src/tools/intercom.ts +1 -1
- package/src/tools/models.ts +34 -9
- package/src/tools/monitor.ts +58 -6
- package/src/tools/read-result.ts +52 -0
- package/src/tools/timer.ts +34 -15
- package/src/ui/agent-overlay.ts +65 -16
- package/src/ui/agent-tree.ts +127 -19
- package/src/ui/compact-widget.ts +25 -0
- package/src/ui/failure-summary.ts +55 -0
- package/src/ui/literal-tool-result.ts +24 -0
- package/src/ui/model-picker.ts +158 -81
- package/src/ui/orchestration-launch.ts +49 -0
- package/src/ui/persona-settings.ts +9 -3
- package/src/ui/presentation.ts +24 -0
- package/src/ui/report-body.ts +63 -0
- package/src/ui/session-cards.ts +366 -0
- package/src/ui/wake-rows.ts +262 -0
- package/teams.yaml +5 -1
package/src/ui/agent-tree.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { compactTokens } from "../core/format.ts";
|
|
2
|
+
import { effortFullName, effortSigla } from "../core/leg-thinking.ts";
|
|
2
3
|
import { formatDuration } from "../core/time.ts";
|
|
4
|
+
import type { ThinkingLevel } from "../core/types.ts";
|
|
5
|
+
import type { ChildUsage } from "../engine/stream.ts";
|
|
3
6
|
import { compactInlineText } from "./presentation.ts";
|
|
7
|
+
import { formatUsage } from "./usage.ts";
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
10
|
* The unified agent tree — a single live model of *every* agent in flight,
|
|
@@ -11,7 +15,7 @@ import { compactInlineText } from "./presentation.ts";
|
|
|
11
15
|
*/
|
|
12
16
|
|
|
13
17
|
export type AgentNodeStatus = "running" | "done" | "failed" | "stopped";
|
|
14
|
-
export type AgentNodeKind = "subagent" | "delegate" | "council" | "flow" | "phase";
|
|
18
|
+
export type AgentNodeKind = "subagent" | "delegate" | "council" | "flow" | "phase" | "monitor" | "timer";
|
|
15
19
|
|
|
16
20
|
export interface AgentNode {
|
|
17
21
|
id: string;
|
|
@@ -23,6 +27,13 @@ export interface AgentNode {
|
|
|
23
27
|
/** Configured agent definition (operator/reviewer/…), if known. */
|
|
24
28
|
agent?: string;
|
|
25
29
|
model?: string;
|
|
30
|
+
/** Requested per-leg thinking effort, known at launch (the spec-level value the launch row
|
|
31
|
+
* carried). Rendered as the compact sigla right after the model; absent/off render nothing. */
|
|
32
|
+
thinking?: ThinkingLevel;
|
|
33
|
+
/** Observed effective Pi effort from the terminal result. Set ONLY from a real
|
|
34
|
+
* AgentResult.thinkingEffective (never invented); when it diverges from `thinking`, the row
|
|
35
|
+
* renders the honest `xh→h` form. */
|
|
36
|
+
thinkingEffective?: ThinkingLevel;
|
|
26
37
|
/** A short trailing annotation (usage, current activity), shown after the label. */
|
|
27
38
|
detail: string | undefined;
|
|
28
39
|
/** The agent's output buffer, shown when the user drills into the node. */
|
|
@@ -31,6 +42,8 @@ export interface AgentNode {
|
|
|
31
42
|
* Excluded from change notifications — the live clock paints them. */
|
|
32
43
|
startedAt?: number;
|
|
33
44
|
lastAdvanceAt?: number;
|
|
45
|
+
/** Epoch ms when a wake source is due: a monitor's deadline or a timer's fire time. Its countdown replaces the elapsed/stall clock. Set once by the source; a change notifies (it is not a clock key). */
|
|
46
|
+
dueAt?: number;
|
|
34
47
|
}
|
|
35
48
|
|
|
36
49
|
export type AgentTreeChange =
|
|
@@ -60,6 +73,21 @@ export function flattenTree(nodes: AgentNode[]): FlatRow[] {
|
|
|
60
73
|
/** Status → glyph, shared by every agent surface (tree, overlay). */
|
|
61
74
|
export const GLYPH: Record<AgentNodeStatus, string> = { running: "⏳", done: "✓", failed: "✗", stopped: "■" };
|
|
62
75
|
|
|
76
|
+
/** A wake source is a row that is not an agent (never counted, steered, or stopped in bulk). */
|
|
77
|
+
export function isWakeSource(node: Pick<AgentNode, "kind">): boolean {
|
|
78
|
+
return node.kind === "monitor" || node.kind === "timer";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The kind-aware glyph shared by every surface (tree, overlay): a running timer is ⏰, a running
|
|
82
|
+
* monitor ◉; everything else, settled wake rows included, uses the ordinary status glyph. */
|
|
83
|
+
export function nodeGlyph(node: Pick<AgentNode, "status" | "kind">): string {
|
|
84
|
+
if (node.status === "running") {
|
|
85
|
+
if (node.kind === "timer") return "⏰";
|
|
86
|
+
if (node.kind === "monitor") return "◉";
|
|
87
|
+
}
|
|
88
|
+
return GLYPH[node.status];
|
|
89
|
+
}
|
|
90
|
+
|
|
63
91
|
export interface RenderOptions {
|
|
64
92
|
/** Current time; omitted ⇒ no elapsed/stall annotation (pure renders stay byte-identical). */
|
|
65
93
|
now?: number;
|
|
@@ -69,6 +97,28 @@ export interface RenderOptions {
|
|
|
69
97
|
waitingForSupervisor?: ReadonlySet<string>;
|
|
70
98
|
}
|
|
71
99
|
|
|
100
|
+
/** The frozen compact effort tag for a tree/overlay row: ` · xh` after the model, or ` · xh→h`
|
|
101
|
+
* when the terminal result reported a DIFFERENT observed effective level. `off`/absent render
|
|
102
|
+
* nothing — absence means off, and an observation is never invented: the divergence form shows
|
|
103
|
+
* only when both sides are known and genuinely differ (an observed `off` never leaves a dangling
|
|
104
|
+
* `→`). Pure. */
|
|
105
|
+
export function effortTag(requested: ThinkingLevel | undefined, effective?: ThinkingLevel): string {
|
|
106
|
+
const req = requested !== undefined ? effortSigla(requested) : "";
|
|
107
|
+
if (!req) return "";
|
|
108
|
+
const obs = effective !== undefined ? effortSigla(effective) : "";
|
|
109
|
+
return obs && obs !== req ? ` · ${req}→${obs}` : ` · ${req}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The F9 drill-in line for a leg's effort — FULL names, the drill-in has the room (the compact
|
|
113
|
+
* siglas live only on the rows): `thinking: requested xhigh → observed high`. Same honesty rules
|
|
114
|
+
* as {@link effortTag}; the line is omitted entirely when nothing was requested. Pure. */
|
|
115
|
+
export function thinkingDetailLine(requested: ThinkingLevel | undefined, effective?: ThinkingLevel): string {
|
|
116
|
+
const req = requested !== undefined ? effortFullName(requested) : "";
|
|
117
|
+
if (!req) return "";
|
|
118
|
+
const obs = effective !== undefined && effective !== requested ? effortFullName(effective) : "";
|
|
119
|
+
return obs ? `thinking: requested ${req} → observed ${obs}` : `thinking: requested ${req}`;
|
|
120
|
+
}
|
|
121
|
+
|
|
72
122
|
/** Whether `detail` is the literal "queued" marker a seeded-but-not-yet-live leg carries — shared
|
|
73
123
|
* by `runningAnnotation` (suppresses the clock) and `telemetryStatus` (`extension.ts`, reports the
|
|
74
124
|
* `queued` state). One predicate so the two readings of the marker cannot drift apart. */
|
|
@@ -79,11 +129,20 @@ export function isQueuedMarker(detail: string | undefined): boolean {
|
|
|
79
129
|
/** "1m 15s" while a running node keeps advancing; "⚠ stalled 1m 30s" once it has been quiet
|
|
80
130
|
* for `stallMs`. Undefined for settled nodes, nodes without clock data, or a node still carrying
|
|
81
131
|
* the "queued" marker — it was seeded ahead of the concurrency gate and has not started running
|
|
82
|
-
* yet, so its clock (stamped at seed time) has nothing true to report.
|
|
83
|
-
|
|
132
|
+
* yet, so its clock (stamped at seed time) has nothing true to report. A wake-source row (one with
|
|
133
|
+
* a `dueAt`) counts down instead — "fires in 49m 12s" for a timer, "12m 3s · deadline in 1h 47m"
|
|
134
|
+
* for a monitor — and NEVER shows the stall badge: a quiet monitor is waiting for its event,
|
|
135
|
+
* not stalled. Pure. */
|
|
136
|
+
export function runningAnnotation(node: Pick<AgentNode, "status" | "startedAt" | "lastAdvanceAt" | "detail" | "kind" | "dueAt">, now: number, stallMs: number, waitingForSupervisor = false): string | undefined {
|
|
84
137
|
if (node.status !== "running" || node.startedAt === undefined) return undefined;
|
|
85
138
|
if (isQueuedMarker(node.detail)) return undefined;
|
|
86
139
|
if (waitingForSupervisor) return "waiting for supervisor";
|
|
140
|
+
if (node.dueAt !== undefined) {
|
|
141
|
+
const left = node.dueAt - now;
|
|
142
|
+
if (node.kind === "timer") return left > 0 ? `fires in ${formatDuration(left)}` : "firing…";
|
|
143
|
+
const elapsed = formatDuration(now - node.startedAt);
|
|
144
|
+
return left > 0 ? `${elapsed} · deadline in ${formatDuration(left)}` : `${elapsed} · deadline reached`;
|
|
145
|
+
}
|
|
87
146
|
const quietSince = node.lastAdvanceAt ?? node.startedAt;
|
|
88
147
|
if (stallMs > 0 && now - quietSince >= stallMs) return `⚠ stalled ${formatDuration(now - quietSince)}`;
|
|
89
148
|
return formatDuration(now - node.startedAt);
|
|
@@ -118,12 +177,13 @@ export function deriveWaitingNodeIds(nodes: readonly AgentNode[], isWaiting: (id
|
|
|
118
177
|
}
|
|
119
178
|
|
|
120
179
|
/** The tree patch for one progress snapshot: every snapshot is an advance; activity wins over
|
|
121
|
-
*
|
|
122
|
-
export function progressPatch(snap: { output?: string; activity?: string; tokens?: number }, now: number): AgentNodePatch {
|
|
180
|
+
* usage, formatted like a completed row. Legacy aggregate-only progress stays a neutral count. */
|
|
181
|
+
export function progressPatch(snap: { output?: string; activity?: string; tokens?: number; usage?: ChildUsage }, now: number): AgentNodePatch {
|
|
123
182
|
const patch: AgentNodePatch = { lastAdvanceAt: now };
|
|
124
183
|
if (snap.output) patch.output = snap.output;
|
|
125
184
|
if (snap.activity) patch.detail = snap.activity;
|
|
126
|
-
else if (snap.
|
|
185
|
+
else if (snap.usage) patch.detail = formatUsage(snap.usage);
|
|
186
|
+
else if (snap.tokens) patch.detail = compactTokens(snap.tokens);
|
|
127
187
|
return patch;
|
|
128
188
|
}
|
|
129
189
|
|
|
@@ -150,12 +210,15 @@ export function renderAgentTree(nodes: AgentNode[], opts: RenderOptions = {}): s
|
|
|
150
210
|
const isRoot = parentId === undefined;
|
|
151
211
|
const isLast = i === kids.length - 1;
|
|
152
212
|
const branch = isRoot ? "" : isLast ? "└─ " : "├─ ";
|
|
153
|
-
const
|
|
213
|
+
const base = safeInline(node.label) || safeInline(node.id) || "agent";
|
|
214
|
+
// The compact effort sigla rides AFTER the label clamp — the existing per-part clipping is
|
|
215
|
+
// untouched; the appended field is at most ` · xh→h` wide.
|
|
216
|
+
const label = `${base}${effortTag(node.thinking, node.thinkingEffective)}`;
|
|
154
217
|
const detailText = node.detail ? safeInline(node.detail) : "";
|
|
155
218
|
const clock = opts.now !== undefined ? runningAnnotation(node, opts.now, opts.stallMs ?? 0, opts.waitingForSupervisor?.has(node.id) ?? false) : undefined;
|
|
156
219
|
const tail = [detailText, clock].filter((s): s is string => Boolean(s)).join(" · ");
|
|
157
220
|
const detail = tail ? ` ${tail}` : "";
|
|
158
|
-
lines.push(`${prefix}${branch}${
|
|
221
|
+
lines.push(`${prefix}${branch}${nodeGlyph(node)} ${label}${detail}`);
|
|
159
222
|
const childPrefix = isRoot ? "" : `${prefix}${isLast ? " " : "│ "}`;
|
|
160
223
|
walk(node.id, childPrefix);
|
|
161
224
|
});
|
|
@@ -165,14 +228,41 @@ export function renderAgentTree(nodes: AgentNode[], opts: RenderOptions = {}): s
|
|
|
165
228
|
return lines;
|
|
166
229
|
}
|
|
167
230
|
|
|
231
|
+
/** One digest line for the running wake sources (monitors, timers) that fell below the summary
|
|
232
|
+
* fold: counts by kind ("◉ 1 monitor · ⏰ 2 timers"), plus the countdown to the soonest finite
|
|
233
|
+
* `dueAt` when a clock is given. A monitor armed after a fan-out would otherwise hide behind the
|
|
234
|
+
* "… +N more" line — exactly the "armed but nothing visible" case wake rows exist to fix. Pure. */
|
|
235
|
+
function wakeDigestLine(hidden: readonly AgentNode[], now: number | undefined): string {
|
|
236
|
+
const monitors = hidden.filter((node) => node.kind === "monitor").length;
|
|
237
|
+
const timers = hidden.filter((node) => node.kind === "timer").length;
|
|
238
|
+
const parts: string[] = [];
|
|
239
|
+
if (monitors > 0) parts.push(`${nodeGlyph({ status: "running", kind: "monitor" })} ${monitors} monitor${monitors === 1 ? "" : "s"}`);
|
|
240
|
+
if (timers > 0) parts.push(`${nodeGlyph({ status: "running", kind: "timer" })} ${timers} timer${timers === 1 ? "" : "s"}`);
|
|
241
|
+
if (now !== undefined) {
|
|
242
|
+
const due = hidden.map((node) => node.dueAt).filter((at): at is number => at !== undefined && Number.isFinite(at));
|
|
243
|
+
if (due.length > 0) {
|
|
244
|
+
const left = Math.min(...due) - now;
|
|
245
|
+
parts.push(left > 0 ? `next due in ${formatDuration(left)}` : "next due now");
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return parts.join(" · ");
|
|
249
|
+
}
|
|
250
|
+
|
|
168
251
|
/**
|
|
169
252
|
* Bounded above-editor digest. Large fan-outs belong in the scrollable F9
|
|
170
253
|
* overlay; they must not push the editor and conversation off the screen.
|
|
254
|
+
*
|
|
255
|
+
* Below the fold, two kinds of row stay visible as one line each, because an operator must not
|
|
256
|
+
* have to open F9 to learn them: failures (`✗ N failed: …`) and RUNNING wake sources (`◉ 1 monitor
|
|
257
|
+
* · ⏰ 2 timers · next due in 4m 12s`). The layout when overflowing is
|
|
258
|
+
* `[...rows, failureLine?, wakeDigest?, overflowLine]`, every line inside `maxRows`; when the budget
|
|
259
|
+
* cannot hold them all the lowest-priority line goes first (failure > wake digest > overflow).
|
|
171
260
|
*/
|
|
172
261
|
export function renderAgentTreeSummary(nodes: AgentNode[], maxRows = 8, opts: RenderOptions = {}): string[] {
|
|
173
262
|
const rows = renderAgentTree(nodes, opts);
|
|
174
263
|
const limit = Math.max(1, Math.floor(maxRows));
|
|
175
264
|
if (rows.length <= limit) return rows;
|
|
265
|
+
let failureLine: string | undefined;
|
|
176
266
|
const failed = nodes.filter((node) => node.status === "failed");
|
|
177
267
|
if (failed.length > 0) {
|
|
178
268
|
const names = failed.slice(0, 2).map((node) => {
|
|
@@ -181,20 +271,26 @@ export function renderAgentTreeSummary(nodes: AgentNode[], maxRows = 8, opts: Re
|
|
|
181
271
|
return detail ? `${label} (${detail})` : label;
|
|
182
272
|
});
|
|
183
273
|
const extra = failed.length - names.length;
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
274
|
+
failureLine = `✗ ${failed.length} failed: ${names.join(", ")}${extra > 0 ? `, +${extra} more` : ""}`;
|
|
275
|
+
}
|
|
276
|
+
// `flattenTree(nodes)[i]` is the node behind `rows[i]`: `renderAgentTree` and `flattenTree` walk the
|
|
277
|
+
// same parent→children order, so a row's flat index doubles as its position in the rendered list.
|
|
278
|
+
const flat = flattenTree(nodes);
|
|
279
|
+
const hiddenWake = (from: number): AgentNode[] =>
|
|
280
|
+
flat.slice(from).map((row) => row.node).filter((node) => isWakeSource(node) && node.status === "running");
|
|
281
|
+
let visibleCount = Math.max(0, limit - (failureLine !== undefined ? 2 : 1));
|
|
282
|
+
let hidden = hiddenWake(visibleCount);
|
|
283
|
+
if (hidden.length > 0) {
|
|
284
|
+
// The digest takes a slot of its own; one fewer visible row can only hide more wake rows.
|
|
285
|
+
visibleCount = Math.max(0, visibleCount - 1);
|
|
286
|
+
hidden = hiddenWake(visibleCount);
|
|
192
287
|
}
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
...
|
|
288
|
+
const tail = [
|
|
289
|
+
...(failureLine !== undefined ? [failureLine] : []),
|
|
290
|
+
...(hidden.length > 0 ? [wakeDigestLine(hidden, opts.now)] : []),
|
|
196
291
|
`… +${rows.length - visibleCount} more · F9 or /agents for the full live tree`,
|
|
197
292
|
];
|
|
293
|
+
return [...rows.slice(0, visibleCount), ...tail.slice(0, Math.max(0, limit - visibleCount))];
|
|
198
294
|
}
|
|
199
295
|
|
|
200
296
|
export interface AddNodeInput {
|
|
@@ -205,10 +301,13 @@ export interface AddNodeInput {
|
|
|
205
301
|
kind?: AgentNodeKind;
|
|
206
302
|
agent?: string;
|
|
207
303
|
model?: string;
|
|
304
|
+
thinking?: ThinkingLevel;
|
|
305
|
+
thinkingEffective?: ThinkingLevel;
|
|
208
306
|
detail?: string;
|
|
209
307
|
output?: string;
|
|
210
308
|
startedAt?: number;
|
|
211
309
|
lastAdvanceAt?: number;
|
|
310
|
+
dueAt?: number;
|
|
212
311
|
}
|
|
213
312
|
|
|
214
313
|
export interface AgentNodePatch {
|
|
@@ -216,10 +315,13 @@ export interface AgentNodePatch {
|
|
|
216
315
|
kind?: AgentNodeKind;
|
|
217
316
|
agent?: string;
|
|
218
317
|
model?: string;
|
|
318
|
+
thinking?: ThinkingLevel;
|
|
319
|
+
thinkingEffective?: ThinkingLevel;
|
|
219
320
|
detail?: string;
|
|
220
321
|
output?: string;
|
|
221
322
|
startedAt?: number;
|
|
222
323
|
lastAdvanceAt?: number;
|
|
324
|
+
dueAt?: number;
|
|
223
325
|
}
|
|
224
326
|
|
|
225
327
|
/** Apply the fields `add`'s upsert branch and `update` both accept, in place — the one place
|
|
@@ -229,10 +331,13 @@ function applyPatch(node: AgentNode, patch: AgentNodePatch): void {
|
|
|
229
331
|
if (patch.kind !== undefined) node.kind = patch.kind;
|
|
230
332
|
if (patch.agent !== undefined) node.agent = patch.agent;
|
|
231
333
|
if (patch.model !== undefined) node.model = patch.model;
|
|
334
|
+
if (patch.thinking !== undefined) node.thinking = patch.thinking;
|
|
335
|
+
if (patch.thinkingEffective !== undefined) node.thinkingEffective = patch.thinkingEffective;
|
|
232
336
|
if (patch.detail !== undefined) node.detail = patch.detail;
|
|
233
337
|
if (patch.output !== undefined) node.output = patch.output;
|
|
234
338
|
if (patch.startedAt !== undefined) node.startedAt = patch.startedAt;
|
|
235
339
|
if (patch.lastAdvanceAt !== undefined) node.lastAdvanceAt = patch.lastAdvanceAt;
|
|
340
|
+
if (patch.dueAt !== undefined) node.dueAt = patch.dueAt;
|
|
236
341
|
}
|
|
237
342
|
|
|
238
343
|
/** A small mutable registry with change notification. The extension owns one. */
|
|
@@ -268,7 +373,10 @@ export class AgentTree {
|
|
|
268
373
|
...(input.kind !== undefined ? { kind: input.kind } : {}),
|
|
269
374
|
...(input.agent !== undefined ? { agent: input.agent } : {}),
|
|
270
375
|
...(input.model !== undefined ? { model: input.model } : {}),
|
|
376
|
+
...(input.thinking !== undefined ? { thinking: input.thinking } : {}),
|
|
377
|
+
...(input.thinkingEffective !== undefined ? { thinkingEffective: input.thinkingEffective } : {}),
|
|
271
378
|
...(input.output !== undefined ? { output: input.output } : {}),
|
|
379
|
+
...(input.dueAt !== undefined ? { dueAt: input.dueAt } : {}),
|
|
272
380
|
};
|
|
273
381
|
this.nodes.push(node);
|
|
274
382
|
this.emit({ type: "added", node: { ...node } });
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Component, sliceByColumn, visibleWidth } from "@earendil-works/pi-tui";
|
|
2
|
+
import { sanitizeTerminalText } from "./presentation.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Public setWidget factory for already-budgeted, raw-text rows. Unlike the host's
|
|
6
|
+
* string[]/Text path this never wraps: one source row occupies at most one terminal
|
|
7
|
+
* row. Right-edge clipping preserves tree indentation and leading semantic markers;
|
|
8
|
+
* F9/exocom_list remain the full-detail surfaces.
|
|
9
|
+
*
|
|
10
|
+
* Snapshot and sanitize once per publication, without trimming/collapsing indentation.
|
|
11
|
+
* No theme colors are captured; each render uses its supplied width (including resize).
|
|
12
|
+
*/
|
|
13
|
+
export function compactWidget(rows: readonly string[]): () => Component {
|
|
14
|
+
const lines = rows.map((row) => sanitizeTerminalText(row.replace(/\t/g, " ")).replace(/[\n\u2028\u2029]/g, " "));
|
|
15
|
+
return () => ({
|
|
16
|
+
render(width: number): string[] {
|
|
17
|
+
const columns = Math.max(0, Math.floor(width));
|
|
18
|
+
if (columns === 0) return [];
|
|
19
|
+
return lines.map((line) => visibleWidth(line) <= columns
|
|
20
|
+
? line
|
|
21
|
+
: `${sliceByColumn(line, 0, columns - 1, true)}…`);
|
|
22
|
+
},
|
|
23
|
+
invalidate(): void { /* No width/theme-dependent cache. */ },
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { compactInlineText, sanitizeTerminalText } from "./presentation.ts";
|
|
2
|
+
|
|
3
|
+
const MAX_INSPECTED_CHARS = 16_384;
|
|
4
|
+
const MAX_SUMMARY_COLUMNS = 140;
|
|
5
|
+
|
|
6
|
+
/** Display-only cause preview. The caller retains the original error for diagnostics. */
|
|
7
|
+
export function summarizeFailure(error: string): string {
|
|
8
|
+
let source = sanitizeTerminalText(error.slice(0, MAX_INSPECTED_CHARS)).trim();
|
|
9
|
+
// Omit only the runtime's identity/model wrapper, not meaningful tags such as [BLOCKED].
|
|
10
|
+
const wrapperEnd = source.startsWith("[") ? source.indexOf("]") : -1;
|
|
11
|
+
if (wrapperEnd > 0 && wrapperEnd <= 512 && source.slice(1, wrapperEnd).includes(" · ")) source = source.slice(wrapperEnd + 1).trim();
|
|
12
|
+
const reason = sanitizeTerminalText(jsonCause(source) ?? source).replace(/\s+/g, " ").trim();
|
|
13
|
+
if (/^(?:(?:the|this)\s+)?(?:['"`][^'"`]{1,200}['"`]\s+)?model\s+is\s+not\s+supported\s+when\s+using\s+Codex\s+with\s+a\s+ChatGPT\s+account[.!]?$/i.test(reason)) {
|
|
14
|
+
return "Model not supported by this ChatGPT account.";
|
|
15
|
+
}
|
|
16
|
+
return compactInlineText(reason, { maxChars: MAX_SUMMARY_COLUMNS });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Scan one bounded object, respecting JSON strings; never retry parsing at every brace. */
|
|
20
|
+
function jsonCause(source: string): string | undefined {
|
|
21
|
+
const start = source.indexOf("{");
|
|
22
|
+
if (start < 0) return undefined;
|
|
23
|
+
let depth = 0;
|
|
24
|
+
let quoted = false;
|
|
25
|
+
let escaped = false;
|
|
26
|
+
for (let index = start; index < source.length; index++) {
|
|
27
|
+
const char = source[index];
|
|
28
|
+
if (quoted) {
|
|
29
|
+
if (escaped) escaped = false;
|
|
30
|
+
else if (char === "\\") escaped = true;
|
|
31
|
+
else if (char === '"') quoted = false;
|
|
32
|
+
} else if (char === '"') quoted = true;
|
|
33
|
+
else if (char === "{") depth++;
|
|
34
|
+
else if (char === "}" && --depth === 0) {
|
|
35
|
+
try { return objectCause(JSON.parse(source.slice(start, index + 1)) as unknown); }
|
|
36
|
+
catch { return undefined; }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function objectCause(value: unknown): string | undefined {
|
|
43
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
44
|
+
const object = value as Record<string, unknown>;
|
|
45
|
+
for (const field of [object.detail, object.message, object.error]) {
|
|
46
|
+
if (typeof field === "string" && field.trim()) return field;
|
|
47
|
+
}
|
|
48
|
+
if (object.error !== null && typeof object.error === "object" && !Array.isArray(object.error)) {
|
|
49
|
+
const error = object.error as Record<string, unknown>;
|
|
50
|
+
for (const field of [error.message, error.detail]) {
|
|
51
|
+
if (typeof field === "string" && field.trim()) return field;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** One compact renderer for literal control receipts and diagnostic fallbacks. Pi owns the shell. */
|
|
2
|
+
import { keyHint, type Theme } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
4
|
+
import { compactVisibleText, sanitizeTerminalText } from "./presentation.ts";
|
|
5
|
+
|
|
6
|
+
interface LiteralResult {
|
|
7
|
+
content: readonly { type: string; text?: string }[];
|
|
8
|
+
/** Direct internal renderer calls carry this; Pi passes it in the render context. */
|
|
9
|
+
isError?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function renderLiteralToolResult(
|
|
13
|
+
result: LiteralResult,
|
|
14
|
+
{ expanded }: { expanded: boolean },
|
|
15
|
+
theme: Theme,
|
|
16
|
+
context?: { isError: boolean },
|
|
17
|
+
): Text {
|
|
18
|
+
const full = sanitizeTerminalText(result.content.filter((part) => part.type === "text").map((part) => part.text ?? "").join("\n")) || "(no output)";
|
|
19
|
+
const failed = context?.isError ?? result.isError ?? false;
|
|
20
|
+
const preview = expanded ? undefined : compactVisibleText(full, { maxLines: 4, maxLineChars: 100 });
|
|
21
|
+
const heading = failed ? `${theme.fg("error", theme.bold("failed"))}\n` : "";
|
|
22
|
+
const hint = preview?.truncated ? `\n${theme.fg("dim", keyHint("app.tools.expand", "to expand"))}` : "";
|
|
23
|
+
return new Text(`${heading}${theme.fg("toolOutput", preview?.text ?? full)}${hint}`, 0, 0);
|
|
24
|
+
}
|