@aeondave/pi-persona 1.17.3 → 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 +44 -36
- package/CHANGELOG.md +76 -1
- package/README.md +23 -13
- package/docs/ARCHITECTURE.md +97 -37
- package/docs/COLLABORATION.md +110 -0
- package/docs/EXPERIENCE.md +33 -8
- package/docs/README.md +12 -0
- package/docs/REFERENCE.md +65 -14
- package/package.json +12 -6
- package/src/agents/agent.ts +6 -0
- package/src/bridge.ts +25 -8
- 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 +14 -4
- package/src/core/capabilities.ts +1 -1
- package/src/core/leg-thinking.ts +57 -0
- package/src/core/ownership.ts +1 -2
- package/src/core/pi-compat.ts +5 -12
- package/src/core/session-runs.ts +1 -1
- package/src/core/types.ts +1 -1
- package/src/engine/adapter.ts +83 -19
- package/src/engine/async.ts +49 -10
- package/src/engine/child.ts +10 -2
- package/src/engine/inproc.ts +87 -46
- package/src/engine/result-grant.ts +62 -0
- package/src/engine/session-tools.ts +6 -0
- package/src/engine/stream.ts +6 -0
- 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 +45 -17
- 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 +20 -21
- package/src/extension/hooks.ts +952 -938
- package/src/extension/identity.ts +41 -75
- package/src/extension/results.ts +136 -0
- package/src/extension/shared.ts +17 -0
- package/src/extension.ts +2963 -2873
- package/src/loader.ts +11 -3
- package/src/orchestration/flow-journal.ts +7 -1
- package/src/orchestration/flow-run.ts +122 -19
- package/src/orchestration/flow.ts +11 -0
- package/src/orchestration/roster.ts +71 -23
- package/src/orchestration/sdk.ts +44 -6
- 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 +1 -1
- 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/council.ts +40 -23
- package/src/tools/delegate-tool.ts +50 -14
- package/src/tools/delegate.ts +13 -6
- 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.ts +20 -3
- package/src/tools/flow.ts +24 -8
- package/src/tools/intercom-tool.ts +98 -14
- package/src/tools/models.ts +15 -0
- package/src/tools/monitor.ts +20 -1
- package/src/tools/read-result.ts +52 -0
- package/src/tools/timer.ts +23 -2
- package/src/ui/agent-overlay.ts +11 -3
- package/src/ui/agent-tree.ts +43 -1
- package/src/ui/literal-tool-result.ts +1 -1
- package/src/ui/model-picker.ts +1 -1
- package/src/ui/orchestration-launch.ts +49 -0
- package/src/ui/session-cards.ts +191 -14
package/src/ui/session-cards.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Operator-only presentation of existing session payloads. No delivery, storage or tool shells. */
|
|
2
2
|
import type { ExtensionAPI, Theme, ThemeBg, ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import { Text, truncateToWidth, type Component } from "@earendil-works/pi-tui";
|
|
3
|
+
import { Text, truncateToWidth, visibleWidth, type Component } from "@earendil-works/pi-tui";
|
|
4
4
|
import { formatDuration } from "../core/time.ts";
|
|
5
5
|
import { neutralizeMonitorUnicode } from "../core/monitor-output.ts";
|
|
6
6
|
import { createReportBody } from "./report-body.ts";
|
|
@@ -25,6 +25,14 @@ interface Card {
|
|
|
25
25
|
markdown?: boolean;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/** The delegate-card-family frame every surface in this file shares: a bold semantic
|
|
29
|
+
* `<Event> · <datum>` title, two-space-indented compact content lines, and the dim
|
|
30
|
+
* expand hint only when compact actually hides content. Payloads are never modified —
|
|
31
|
+
* this is projection-only (expanded still shows the complete sanitized payload). */
|
|
32
|
+
const INDENT = " ";
|
|
33
|
+
/** Compact budget: title + 5 body rows + optional hint keeps every collapsed card ≤ 7 rows. */
|
|
34
|
+
const COMPACT_BODY_ROWS = 4;
|
|
35
|
+
|
|
28
36
|
/** All seven surfaces share semantic chrome. Literal paths style at paint time; report bodies
|
|
29
37
|
* use native Markdown's invalidatable layout cache. Pi rebuilds callbacks on transcript invalidation. */
|
|
30
38
|
function createSessionCard(card: Card, expanded: boolean, theme: Theme, detailHint: () => string, outputPad = 0): Component {
|
|
@@ -45,12 +53,19 @@ function createSessionCard(card: Card, expanded: boolean, theme: Theme, detailHi
|
|
|
45
53
|
const padding = Math.min(Math.max(0, Math.floor(outputPad)), Math.max(0, Math.floor((width - 1) / 2)));
|
|
46
54
|
const innerWidth = Math.max(1, width - padding * 2);
|
|
47
55
|
const colors = COLORS[card.state];
|
|
48
|
-
|
|
56
|
+
// The indent only applies where a clipped content column still fits; every compact line
|
|
57
|
+
// is clipped to the remaining columns so indent + text never wrap or spill mid-word.
|
|
58
|
+
const bodyPad = innerWidth > INDENT.length ? INDENT : "";
|
|
59
|
+
const contentWidth = Math.max(1, innerWidth - bodyPad.length);
|
|
60
|
+
const preview = expanded ? undefined : card.preview(contentWidth);
|
|
49
61
|
const body = expanded ? card.full : preview?.text ?? "(no output)";
|
|
62
|
+
const bodyLines = sanitizeTerminalText(body || "(no output)").split("\n").map((line) => expanded
|
|
63
|
+
? theme.fg("toolOutput", line)
|
|
64
|
+
: theme.fg("toolOutput", `${bodyPad}${truncateToWidth(line, contentWidth)}`));
|
|
50
65
|
const lines = [
|
|
51
66
|
theme.fg(colors.fg, theme.bold(truncateToWidth(sanitizeTerminalText(card.title), innerWidth))),
|
|
52
|
-
...(card.metadata ? [theme.fg("dim", truncateToWidth(card.metadata
|
|
53
|
-
...
|
|
67
|
+
...(card.metadata ? [theme.fg("dim", truncateToWidth(`${bodyPad}${sanitizeTerminalText(card.metadata)}`, innerWidth))] : []),
|
|
68
|
+
...bodyLines,
|
|
54
69
|
...(preview?.truncated ? [theme.fg("dim", truncateToWidth(detailHint(), innerWidth))] : []),
|
|
55
70
|
];
|
|
56
71
|
// Text owns wrapping and full-row padding/background, including embedded foreground resets.
|
|
@@ -65,11 +80,15 @@ function preview(full: string, maxLines: number, maxColumns: number): Card["prev
|
|
|
65
80
|
return (width) => compactVisibleText(full, { maxLines, maxLineChars: Math.min(width, maxColumns) });
|
|
66
81
|
}
|
|
67
82
|
|
|
83
|
+
/** Single source of truth for the settlement head grammar, shared by state selection and the
|
|
84
|
+
* compact projection below. The generator is `buildCompletionReport` in src/engine/async.ts. */
|
|
85
|
+
const SETTLEMENT_HEAD = /^(?:\[pi-persona\] )?(\d+) async runs? settled — (\d+) done, (\d+) failed(?:, (\d+) stopped)?$/;
|
|
86
|
+
|
|
68
87
|
/** Only this exact runtime-owned aggregate is evidence of settlement. No keyword scan of output,
|
|
69
88
|
* asks, timers, monitor event bodies, or peers. Legacy/unknown follow-ups remain informational. */
|
|
70
89
|
function followUpState(full: string): { state: State; status: string } {
|
|
71
90
|
const first = full.split("\n", 1)[0] ?? "";
|
|
72
|
-
const summary =
|
|
91
|
+
const summary = SETTLEMENT_HEAD.exec(first);
|
|
73
92
|
if (summary) {
|
|
74
93
|
const total = Number(summary[1]);
|
|
75
94
|
const done = Number(summary[2]);
|
|
@@ -101,6 +120,149 @@ interface ResultEntry { label?: string; content?: string; ok?: boolean; stoppedA
|
|
|
101
120
|
interface ContentEntry { content?: string; kind?: "routine" | "stall" }
|
|
102
121
|
interface RunFailureEntry { id: string; label: string; agent: string; model?: string; durationMs?: number; error: string }
|
|
103
122
|
|
|
123
|
+
/** One run a settlement report names, projected to `<name> · <status>[ · <duration>]`. The
|
|
124
|
+
* display name already carries the model (`runDisplayName`: "label · model"). */
|
|
125
|
+
interface SettledRunLine { name: string; status: "done" | "failed" | "stopped"; duration?: string }
|
|
126
|
+
|
|
127
|
+
/** Boilerplate the settlement generator interleaves between the per-run lines. Every entry is
|
|
128
|
+
* drift-guarded in tests against live `buildCompletionReport` output (src/engine/async.ts):
|
|
129
|
+
* receipts header + `run-x: …` reference lines, the effort header, salvage `↩` block headers,
|
|
130
|
+
* the shared untrusted-fence header (core/fence.ts quoteUntrusted) and its `> `-quoted payload
|
|
131
|
+
* lines, the failed-leg guidance sentence, the truncation marker and retention/tail notes. */
|
|
132
|
+
const SETTLEMENT_SKIP: readonly RegExp[] = [
|
|
133
|
+
/^Terminal reports \(/,
|
|
134
|
+
/^run-\S+: /,
|
|
135
|
+
/^Thinking \(not provider cognition\):$/,
|
|
136
|
+
/^↩ /,
|
|
137
|
+
/^Sub-agent output \(untrusted data\):$/,
|
|
138
|
+
/^>/,
|
|
139
|
+
/^Handle each failure deliberately: /,
|
|
140
|
+
/^\[\.\.\. .*?\]$/,
|
|
141
|
+
/^⚠ /,
|
|
142
|
+
/^Run ids in this report: /,
|
|
143
|
+
];
|
|
144
|
+
const SETTLEMENT_DONE = /^✅ (\S+) \((.+?)\) done(?: in (.+?))?:$/;
|
|
145
|
+
const SETTLEMENT_STOPPED_SECTION = /^⏹ \d+ stopped:$/;
|
|
146
|
+
const SETTLEMENT_FAILED_SECTION = /^❌ \d+ failed:$/;
|
|
147
|
+
/** Quoted (fence) bullets carry the stopped/failed runs; the section header above them names the status. */
|
|
148
|
+
const SETTLEMENT_BULLET = /^> ?• (\S+) \((.+?)\)(?: after (.+?))?: /;
|
|
149
|
+
|
|
150
|
+
/** Project the compact per-run lines of a settlement report, or undefined when the body is not
|
|
151
|
+
* exactly the known generator shape (head line + recognized/skipped lines only). A duration is
|
|
152
|
+
* kept only when it is short (≤ 8 columns, e.g. "12s"/"2m 5s"); width clipping handles the rest. */
|
|
153
|
+
function parseSettlementRuns(full: string): { counts: string; runs: SettledRunLine[] } | undefined {
|
|
154
|
+
const lines = full.split("\n");
|
|
155
|
+
const head = SETTLEMENT_HEAD.exec(lines[0] ?? "");
|
|
156
|
+
if (!head) return undefined;
|
|
157
|
+
const done = Number(head[2]);
|
|
158
|
+
const failed = Number(head[3]);
|
|
159
|
+
const stopped = Number(head[4] ?? 0);
|
|
160
|
+
const counts = `${done} done, ${failed} failed${stopped > 0 ? `, ${stopped} stopped` : ""}`;
|
|
161
|
+
let section: "failed" | "stopped" | undefined;
|
|
162
|
+
const runs: SettledRunLine[] = [];
|
|
163
|
+
const seen = new Set<string>();
|
|
164
|
+
const add = (name: string | undefined, status: SettledRunLine["status"], duration: string | undefined) => {
|
|
165
|
+
if (!name) return;
|
|
166
|
+
const key = `${status}:${name}`;
|
|
167
|
+
if (seen.has(key)) return;
|
|
168
|
+
seen.add(key);
|
|
169
|
+
runs.push({ name, status, ...(duration !== undefined && visibleWidth(duration) <= 8 ? { duration } : {}) });
|
|
170
|
+
};
|
|
171
|
+
for (const line of lines.slice(1)) {
|
|
172
|
+
const bullet = SETTLEMENT_BULLET.exec(line);
|
|
173
|
+
if (bullet) {
|
|
174
|
+
if (section === undefined) return undefined;
|
|
175
|
+
add(bullet[2], section, bullet[3]);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (line.trim().length === 0 || SETTLEMENT_SKIP.some((skip) => skip.test(line))) continue;
|
|
179
|
+
const doneLine = SETTLEMENT_DONE.exec(line);
|
|
180
|
+
if (doneLine) {
|
|
181
|
+
section = undefined;
|
|
182
|
+
add(doneLine[2], "done", doneLine[3]);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (SETTLEMENT_STOPPED_SECTION.test(line)) { section = "stopped"; continue; }
|
|
186
|
+
if (SETTLEMENT_FAILED_SECTION.test(line)) { section = "failed"; continue; }
|
|
187
|
+
return undefined; // unknown shape → the caller falls back to the generic bounded preview
|
|
188
|
+
}
|
|
189
|
+
return { counts, runs };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Compact projection of a settlement report: counts line + one line per named run, capped at
|
|
193
|
+
* COMPACT_BODY_ROWS (a `… +N more` tail row replaces the overflow). Undefined run list = fallback. */
|
|
194
|
+
function settlementPreview(full: string): Card["preview"] | undefined {
|
|
195
|
+
const parsed = parseSettlementRuns(full);
|
|
196
|
+
if (!parsed || parsed.runs.length === 0) return undefined;
|
|
197
|
+
return () => {
|
|
198
|
+
const lines = [parsed.counts, ...parsed.runs.map((run) => `${run.name} · ${run.status}${run.duration ? ` · ${run.duration}` : ""}`)];
|
|
199
|
+
const visible = lines.length <= COMPACT_BODY_ROWS ? lines : [...lines.slice(0, COMPACT_BODY_ROWS - 1), `… +${lines.length - (COMPACT_BODY_ROWS - 1)} more`];
|
|
200
|
+
return { text: visible.join("\n"), truncated: true };
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
interface StatusRunLine { name: string; detail: string; queued: boolean }
|
|
205
|
+
|
|
206
|
+
/** Skip-list for the peek-digest boilerplate the status card's title/frame replaces. Each entry
|
|
207
|
+
* is drift-guarded in tests against live `buildPeekDigest`/`buildCheckIn` output (src/engine/async.ts):
|
|
208
|
+
* the check-in header, the cumulative-token footnote and the omitted-runs tail. The
|
|
209
|
+
* `Async runs: N (M running)` header is recognized below (the title carries its counts). */
|
|
210
|
+
const STATUS_SKIP: readonly RegExp[] = [
|
|
211
|
+
/^Async activity · (?:routine check-in|possible stall)$/,
|
|
212
|
+
/^Cumulative tokens show /,
|
|
213
|
+
/^… \d+ additional async runs omitted/,
|
|
214
|
+
];
|
|
215
|
+
const STATUS_RUNNING = /^\[([^\]]+)\] (.+?) — running \((?:(\d+) turns, )?(.+?) cumulative input \+ output tokens\)(.*)$/;
|
|
216
|
+
const STATUS_SETTLED = /^\[([^\]]+)\] (.+?) — (done|failed|stopped)(?: (?:in|after) (.+?))?(?::.*)?$/;
|
|
217
|
+
const STATUS_HEADER_COUNTS = /^Async runs: \d+ \((\d+) running\)$/;
|
|
218
|
+
|
|
219
|
+
/** Parse one peek digest into per-run compact lines (`<name> · 53t · 216k` for running legs,
|
|
220
|
+
* `<name> · done · 12s` for settled ones), or undefined when the shape is not the generator's. */
|
|
221
|
+
function parseStatusRuns(full: string): { running: number; queued: number; runs: StatusRunLine[] } | undefined {
|
|
222
|
+
let running: number | undefined;
|
|
223
|
+
let queued = 0;
|
|
224
|
+
const runs: StatusRunLine[] = [];
|
|
225
|
+
for (const line of full.split("\n")) {
|
|
226
|
+
if (line.trim().length === 0 || STATUS_SKIP.some((skip) => skip.test(line))) continue;
|
|
227
|
+
const head = STATUS_HEADER_COUNTS.exec(line);
|
|
228
|
+
if (head) {
|
|
229
|
+
if (running !== undefined) return undefined;
|
|
230
|
+
running = Number(head[1]);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const runLine = STATUS_RUNNING.exec(line);
|
|
234
|
+
if (runLine) {
|
|
235
|
+
const [, , name, turns, tokens, suffix] = runLine;
|
|
236
|
+
const isQueued = /· queued \(/.test(suffix ?? "");
|
|
237
|
+
if (isQueued) queued++;
|
|
238
|
+
const usage = `${tokens ?? "0"} cumulative input + output tokens`;
|
|
239
|
+
const detail = isQueued ? `queued · ${usage}` : `${turns !== undefined ? `${turns} turns · ` : ""}${usage}`;
|
|
240
|
+
runs.push({ name: name ?? "", detail, queued: isQueued });
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const settledLine = STATUS_SETTLED.exec(line);
|
|
244
|
+
if (settledLine) {
|
|
245
|
+
const [, , name, status, duration] = settledLine;
|
|
246
|
+
runs.push({ name: name ?? "", detail: duration !== undefined ? `${status} · ${duration}` : status ?? "", queued: false });
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
return undefined; // unknown shape → the caller falls back to the generic bounded preview
|
|
250
|
+
}
|
|
251
|
+
if (running === undefined || runs.length === 0) return undefined;
|
|
252
|
+
return { running, queued, runs };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Compact projection of the periodic status digest, capped like the settlement preview. */
|
|
256
|
+
function statusPreview(full: string): Card["preview"] | undefined {
|
|
257
|
+
const parsed = parseStatusRuns(full);
|
|
258
|
+
if (!parsed) return undefined;
|
|
259
|
+
return () => {
|
|
260
|
+
const lines = parsed.runs.map((run) => `${run.name} · ${run.detail}`);
|
|
261
|
+
const visible = lines.length <= COMPACT_BODY_ROWS ? lines : [...lines.slice(0, COMPACT_BODY_ROWS - 1), `… +${lines.length - (COMPACT_BODY_ROWS - 1)} more`];
|
|
262
|
+
return { text: visible.join("\n"), truncated: true };
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
104
266
|
export interface SessionCardOptions {
|
|
105
267
|
detailHint: () => string;
|
|
106
268
|
/** Existing bounded/fenced runtime formatter, injected to keep UI independent of tool wiring. */
|
|
@@ -119,7 +281,12 @@ export function registerSessionCards(pi: Pick<ExtensionAPI, "registerMessageRend
|
|
|
119
281
|
// Only the validated settlement envelope is a pi-persona report. Unknown is NOT
|
|
120
282
|
// Markdown: monitor stdout/stderr, timers and operational notices share this type.
|
|
121
283
|
const markdown = customType === "exocom_received" || status !== "info";
|
|
122
|
-
|
|
284
|
+
const projected = customType === "pi-persona" ? settlementPreview(full) : undefined;
|
|
285
|
+
return sessionCard({
|
|
286
|
+
title: customType === "pi-persona" ? `Update · ${status}` : "Exocom · inbound",
|
|
287
|
+
state, full, markdown,
|
|
288
|
+
preview: projected ?? preview(full, 4, 100),
|
|
289
|
+
}, expanded, theme, outputPad);
|
|
123
290
|
});
|
|
124
291
|
}
|
|
125
292
|
pi.registerEntryRenderer(MONITOR_LOG_ENTRY, (entry, { expanded }, theme) => {
|
|
@@ -127,8 +294,9 @@ export function registerSessionCards(pi: Pick<ExtensionAPI, "registerMessageRend
|
|
|
127
294
|
if (!record) return undefined;
|
|
128
295
|
const { state, status } = monitorState(record);
|
|
129
296
|
return sessionCard({
|
|
130
|
-
title: `Monitor
|
|
131
|
-
state,
|
|
297
|
+
title: `Monitor · ${compactInlineText(record.entry.label)}`,
|
|
298
|
+
state, metadata: `${record.outcome} · ${status} · ${compactInlineText(record.entry.id)}`,
|
|
299
|
+
full: sanitizeTerminalText(options.monitorLog(record)),
|
|
132
300
|
preview: (width) => ({ text: `${compactInlineText(neutralizeMonitorUnicode(record.text), { maxChars: Math.min(width, 160) })}\n${compactInlineText(record.entry.id)} · monitor result`, truncated: true }),
|
|
133
301
|
}, expanded, theme);
|
|
134
302
|
});
|
|
@@ -137,7 +305,7 @@ export function registerSessionCards(pi: Pick<ExtensionAPI, "registerMessageRend
|
|
|
137
305
|
if (!data) return undefined;
|
|
138
306
|
const metadata = [compactInlineText(data.agent, { maxChars: 48 }), ...(data.model ? [compactInlineText(data.model, { maxChars: 96 })] : []), ...(data.durationMs !== undefined ? [formatDuration(data.durationMs)] : [])].join(" · ");
|
|
139
307
|
return sessionCard({
|
|
140
|
-
title:
|
|
308
|
+
title: `Failed · ${compactInlineText(data.label || data.agent, { maxChars: 80 })}`, state: "error", metadata,
|
|
141
309
|
full: `Run ${compactInlineText(data.id)}\n${sanitizeTerminalText(data.error)}`,
|
|
142
310
|
preview: (width) => ({ text: compactVisibleText(summarizeFailure(data.error), { maxLines: 2, maxLineChars: Math.min(width, 100) }).text, truncated: true }),
|
|
143
311
|
}, expanded, theme);
|
|
@@ -150,8 +318,9 @@ export function registerSessionCards(pi: Pick<ExtensionAPI, "registerMessageRend
|
|
|
150
318
|
const state: State = stopped ? "attention" : data.ok === true ? "success" : data.ok === false ? "error" : "neutral";
|
|
151
319
|
const status = stopped ? `stopped · checkpoint ${compactInlineText(data.stoppedAtGate ?? "", { maxChars: 32 })}` : data.ok === true ? "complete" : data.ok === false ? "failed" : "unknown";
|
|
152
320
|
const cause = compactVisibleText([data.failureKind, data.error].filter(Boolean).join(" · "), { maxLines: 1, maxLineChars: 80 }).text;
|
|
321
|
+
const label = compactInlineText(data.label ?? "", { maxChars: 96 });
|
|
153
322
|
return sessionCard({
|
|
154
|
-
title: `${
|
|
323
|
+
title: `${label || "Result"} · ${status}${state === "error" && cause !== "(no output)" ? ` · ${cause}` : ""}`,
|
|
155
324
|
state, full, markdown: true, preview: preview(full, 3, 100),
|
|
156
325
|
}, expanded, theme);
|
|
157
326
|
});
|
|
@@ -159,14 +328,17 @@ export function registerSessionCards(pi: Pick<ExtensionAPI, "registerMessageRend
|
|
|
159
328
|
if (!entry.data) return undefined;
|
|
160
329
|
const full = sanitizeTerminalText(entry.data.content || "⟢ pi-persona · checkpoint");
|
|
161
330
|
const lines = full.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
162
|
-
const header = compactInlineText(lines[0]?.replace(/^⟢\s*/, "") ?? "", { maxChars: 96 }) || "
|
|
331
|
+
const header = compactInlineText(lines[0]?.replace(/^⟢\s*/, "") ?? "", { maxChars: 96 }) || "checkpoint";
|
|
332
|
+
// Drop the generator's own "pi-persona · checkpoint" boilerplate from the datum; a bare
|
|
333
|
+
// checkpoint header leaves the title as just the event word.
|
|
334
|
+
const datum = header === "checkpoint" ? "" : header.replace(/^pi-persona · checkpoint\b\??/, "").replace(/^pi-persona · /, "").trim();
|
|
163
335
|
const details = lines.slice(1);
|
|
164
336
|
const trigger = details.find((line) => line.startsWith("Trigger:"));
|
|
165
337
|
const action = details.find((line) => /^(?:Action|Verify|Otherwise):/.test(line));
|
|
166
338
|
const selected = [trigger, action].filter((line): line is string => line !== undefined);
|
|
167
339
|
const chosen = selected.length > 0 ? selected : details.slice(0, 2);
|
|
168
340
|
return sessionCard({
|
|
169
|
-
title:
|
|
341
|
+
title: datum ? `Checkpoint · ${datum}` : "Checkpoint", state: "attention", full,
|
|
170
342
|
preview: (width) => {
|
|
171
343
|
const result = preview(chosen.join("\n"), 2, 100)(width);
|
|
172
344
|
return { text: result.text, truncated: result.truncated || details.some((line) => !chosen.includes(line)) };
|
|
@@ -177,10 +349,15 @@ export function registerSessionCards(pi: Pick<ExtensionAPI, "registerMessageRend
|
|
|
177
349
|
if (!entry.data) return undefined;
|
|
178
350
|
const full = sanitizeTerminalText(entry.data.content || "Async activity · routine check-in");
|
|
179
351
|
const stall = entry.data.kind === "stall";
|
|
352
|
+
const event = stall ? "Stall" : "Status";
|
|
180
353
|
const digest = full.split("\n").slice(1).filter((line) => line.trim().length > 0);
|
|
354
|
+
const parsed = parseStatusRuns(full);
|
|
181
355
|
return sessionCard({
|
|
182
|
-
title:
|
|
183
|
-
|
|
356
|
+
title: parsed
|
|
357
|
+
? `${event} · ${parsed.running} running${parsed.queued > 0 ? ` · ${parsed.queued} queued` : ""}`
|
|
358
|
+
: `${event} · check-in`,
|
|
359
|
+
state: stall ? "attention" : "neutral", full,
|
|
360
|
+
preview: parsed ? statusPreview(full)! : (width) => {
|
|
184
361
|
const result = preview(digest.slice(0, 2).join("\n") || full, 2, 140)(width);
|
|
185
362
|
return { text: result.text, truncated: result.truncated || digest.length > 2 };
|
|
186
363
|
},
|