@kushalkhemka/opensec-pi-subagents 0.20.3 → 0.20.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/CHANGELOG.md +36 -0
- package/README.md +26 -3
- package/dist/index.js +22 -52
- package/dist/session-handoff.d.ts +1 -1
- package/dist/session-handoff.js +1 -1
- package/dist/settings.d.ts +1 -15
- package/dist/settings.js +1 -6
- package/dist/types.d.ts +0 -14
- package/dist/ui/conversation-viewer.d.ts +27 -89
- package/dist/ui/conversation-viewer.js +119 -374
- package/dist/ui/fleet-list.d.ts +8 -22
- package/dist/ui/fleet-list.js +6 -15
- package/dist/ui/viewer-keys.d.ts +10 -1
- package/dist/ui/viewer-keys.js +5 -0
- package/dist/ui/viewer-transcript.d.ts +152 -0
- package/dist/ui/viewer-transcript.js +721 -0
- package/package.json +1 -1
- package/src/index.ts +25 -60
- package/src/session-handoff.ts +1 -1
- package/src/settings.ts +2 -20
- package/src/types.ts +0 -15
- package/src/ui/conversation-viewer.ts +116 -386
- package/src/ui/fleet-list.ts +6 -14
- package/src/ui/viewer-keys.ts +13 -1
- package/src/ui/viewer-transcript.ts +779 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.20.4] - 2026-10-16
|
|
11
|
+
|
|
12
|
+
The conversation viewer draws a subagent's transcript with Pi's own chat components, so it looks exactly like Pi's
|
|
13
|
+
main session.
|
|
14
|
+
|
|
15
|
+
> **Possibly breaking:** the `viewerMarkdown` setting (`/agents → Settings → Viewer markdown`) and the viewer's `m`
|
|
16
|
+
> key are removed: Pi's components decide how each message renders. A `viewerMarkdown` key left in `subagents.json`
|
|
17
|
+
> is ignored.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **The conversation viewer (regular overlay and fullscreen observer) renders like Pi's main session.** It builds
|
|
22
|
+
`UserMessageComponent`, `AssistantMessageComponent` (thinking and text), `ToolExecutionComponent` (each tool's own
|
|
23
|
+
call and result renderers from the subagent's session: edit diffs, read previews, bash command, output, exit status
|
|
24
|
+
and duration, running/done/error state, arguments), `BashExecutionComponent`, `CompactionSummaryMessageComponent`,
|
|
25
|
+
`BranchSummaryMessageComponent`, `SkillInvocationMessageComponent` and `CustomMessageComponent` from the session's
|
|
26
|
+
messages as Pi's interactive mode does, with the same spacing and collapsed defaults. Streaming messages, tool
|
|
27
|
+
progress and results update live. It replaces the viewer's own `[User]` / `[Assistant]` / `[Tool: name]` /
|
|
28
|
+
`[Result]` rendering and its 16k-character result cap (Pi's renderers truncate collapsed output themselves).
|
|
29
|
+
- **`ctrl+o` expands and collapses tool output in the viewer**, as in Pi; it follows a rebinding of Pi's
|
|
30
|
+
`app.tools.expand`, and the footer shows it when there is room. Pi's `hideThinkingBlock`, `outputPad` and
|
|
31
|
+
`markdown.codeBlockIndent` settings apply.
|
|
32
|
+
- Each component is built once and its rendered lines are cached per width and expand state; a frame re-renders only
|
|
33
|
+
the streamed message and tools that changed, scrolling never re-renders, and only visible rows are assembled.
|
|
34
|
+
Without a JIT (Bun with `BUN_JSC_useJIT=0`, standing in for Pi-Bolt), at 500 messages: a frame 6 µs, a scroll step
|
|
35
|
+
15 µs, a streaming delta 0.70 ms (was 0.92 ms), a tool call finishing 0.26 ms (was 0.31 ms), the first frame 121 ms
|
|
36
|
+
(was 160 ms). A new user message costs 0.27 ms (was 0.17 ms): Pi's user message is Markdown on a background.
|
|
37
|
+
- Where the viewer differs from Pi's main session: images in tool results show as Pi's text placeholder, since an
|
|
38
|
+
inline image cannot be clipped to a scrolled overlay; Mermaid blocks are not drawn as diagrams (Pi's Mermaid
|
|
39
|
+
transformer is not exported); OSC 133 prompt marks are not emitted inside the overlay; clicking a tool or thinking
|
|
40
|
+
block does not toggle it (use `ctrl+o`).
|
|
41
|
+
|
|
42
|
+
### Removed
|
|
43
|
+
|
|
44
|
+
- The `viewerMarkdown` setting and the viewer's `m` key (see above).
|
|
45
|
+
|
|
10
46
|
## [0.20.3] - 2026-10-16
|
|
11
47
|
|
|
12
48
|
Background agents survive a session switch, subagents run on the parent's model by default, and nested and workflow
|
package/README.md
CHANGED
|
@@ -108,6 +108,28 @@ appears under one of them after 2 s. In the conversation viewer (FleetView, `/ag
|
|
|
108
108
|
run moves from the foreground to the background concurrency pool, over the limit if the pool is full.
|
|
109
109
|
- Nested agents, workflow agents, queued spawns and resumed runs cannot be handed off.
|
|
110
110
|
|
|
111
|
+
### Conversation viewer
|
|
112
|
+
|
|
113
|
+
Opening an agent (FleetView, `/agents`, the workflow inspector's `c`) shows its conversation drawn by Pi's own chat
|
|
114
|
+
components, exactly as Pi draws the main session: user and assistant messages with thinking blocks, each tool call
|
|
115
|
+
with its arguments and its tool's own result rendering (edit diffs, read previews, bash command, output, exit status
|
|
116
|
+
and duration, running/done/error state), `!` bash runs, compaction and branch summaries, skill blocks and extension
|
|
117
|
+
messages. Tool renderers come from the subagent's own session, so an extension tool draws as it does in Pi. A message
|
|
118
|
+
that is still streaming and a tool that is still running update live.
|
|
119
|
+
|
|
120
|
+
- `ctrl+o` expands and collapses tool output, as in Pi (it follows a rebinding of Pi's `app.tools.expand`).
|
|
121
|
+
- Pi's `hideThinkingBlock`, `outputPad` and `markdown.codeBlockIndent` settings apply, read when the viewer opens.
|
|
122
|
+
- Agent output is sanitized before Pi's components see it: escape and control sequences in messages, tool arguments
|
|
123
|
+
and results are removed (Pi's own styling is kept).
|
|
124
|
+
- Not the same as the main session: images in tool results show as Pi's text placeholder (an inline image cannot be
|
|
125
|
+
clipped to a scrolled overlay), Mermaid blocks are not drawn as diagrams (Pi's Mermaid renderer is not available to
|
|
126
|
+
extensions), and clicking a tool block or thinking block does not toggle it (use `ctrl+o`).
|
|
127
|
+
- The `viewerMarkdown` setting and the viewer's `m` key were removed in 0.20.4; a `viewerMarkdown` key left in
|
|
128
|
+
`subagents.json` is ignored.
|
|
129
|
+
- Cost: each message's component is built once and its rendered lines are kept; a frame re-renders only the streamed
|
|
130
|
+
message and tools that changed, and scrolling never re-renders. Without a JIT (Pi-Bolt) a frame takes about 5 µs and
|
|
131
|
+
a scroll step about 15 µs at any transcript length.
|
|
132
|
+
|
|
111
133
|
### Fullscreen conversation observer
|
|
112
134
|
|
|
113
135
|
In Pi's fullscreen mode (`/settings → TUI mode → fullscreen`, Pi's default), an agent opened from FleetView, `/agents`
|
|
@@ -118,14 +140,15 @@ or pauses an agent, and `Esc` returns to the previous view (while composing, the
|
|
|
118
140
|
- A one-column scrollbar in Pi's style that follows Pi's `/settings → Fullscreen scrollbar`: `auto` appears while
|
|
119
141
|
scrolling or hovering and hides after a second, `always` stays, `hidden` removes it and its mouse target. Drag the
|
|
120
142
|
thumb, or click the track to page. The column is kept free in `auto` mode too, so the bar never re-wraps the text.
|
|
121
|
-
- Clickable footer controls (steer, send to background, stop with two clicks,
|
|
143
|
+
- Clickable footer controls (steer, send to background, stop with two clicks, expand tool output, close).
|
|
122
144
|
- While scrolled away, the divider above the footer shows `[ ↓ N new messages · Ctrl+End ]` (conversation messages,
|
|
123
145
|
not streaming updates); click it or press `Ctrl+End` to jump back and follow, without losing a steering draft.
|
|
124
146
|
- Mouse events are consumed, so they never scroll, select or click the main view underneath. Text selection, copying
|
|
125
147
|
and switching agents without leaving the observer are not implemented.
|
|
126
148
|
- Cost: built on the viewer's incremental transcript, so scrolling, wheel and drags reuse cached lines, a frame checks
|
|
127
|
-
only
|
|
128
|
-
(Pi-Bolt), a frame or a scroll step costs well under a
|
|
149
|
+
only what changed and draws only the visible rows, and nothing ticks while it is idle (a running bash tool's elapsed
|
|
150
|
+
time redraws its block once a second, as in Pi). Without a JIT (Pi-Bolt), a frame or a scroll step costs well under a
|
|
151
|
+
millisecond whatever the transcript's length.
|
|
129
152
|
|
|
130
153
|
### Resuming agents
|
|
131
154
|
|
package/dist/index.js
CHANGED
|
@@ -541,14 +541,6 @@ export default function (pi) {
|
|
|
541
541
|
let showModel = false;
|
|
542
542
|
function isShowModelEnabled() { return showModel; }
|
|
543
543
|
function setShowModel(b) { showModel = b; widget.update(); }
|
|
544
|
-
/**
|
|
545
|
-
* How much of the conversation viewer renders as Markdown. Read through a
|
|
546
|
-
* getter by the viewer rather than captured like `showCost`, because the
|
|
547
|
-
* viewer's `m` key writes back here while the overlay is on screen.
|
|
548
|
-
*/
|
|
549
|
-
let viewerMarkdown = "assistant";
|
|
550
|
-
function getViewerMarkdown() { return viewerMarkdown; }
|
|
551
|
-
function setViewerMarkdown(mode) { viewerMarkdown = mode; }
|
|
552
544
|
/** The `backgroundKey` setting as written; undefined means the default. Persisted as written. */
|
|
553
545
|
let backgroundKeySetting;
|
|
554
546
|
function setBackgroundKey(key) { backgroundKeySetting = key; }
|
|
@@ -557,16 +549,6 @@ export default function (pi) {
|
|
|
557
549
|
const key = backgroundKeySetting ?? DEFAULT_BACKGROUND_KEY;
|
|
558
550
|
return key === "off" ? undefined : key;
|
|
559
551
|
}
|
|
560
|
-
/**
|
|
561
|
-
* The viewer's `m` key, from either entry point: set the mode and persist it,
|
|
562
|
-
* so the key and `/agents → Settings` stay one setting rather than one per
|
|
563
|
-
* entry point. `ctx` carries only the warning a failed write notifies with,
|
|
564
|
-
* and the fleet list may be acting without one.
|
|
565
|
-
*/
|
|
566
|
-
function chooseViewerMarkdown(mode, ctx) {
|
|
567
|
-
setViewerMarkdown(mode);
|
|
568
|
-
persistSettings(ctx, `Viewer markdown set to ${mode}`);
|
|
569
|
-
}
|
|
570
552
|
const pendingUsage = new PendingUsagePool();
|
|
571
553
|
/**
|
|
572
554
|
* Set once this activation's session is shutting down (`/new`, `/resume`,
|
|
@@ -1694,10 +1676,28 @@ export default function (pi) {
|
|
|
1694
1676
|
return "auto";
|
|
1695
1677
|
}
|
|
1696
1678
|
}
|
|
1679
|
+
/**
|
|
1680
|
+
* Pi's chat settings the conversation viewer draws with, read from the main
|
|
1681
|
+
* session's live settings and normalized as Pi's SettingsManager getters do,
|
|
1682
|
+
* so a subagent's transcript looks like the main one.
|
|
1683
|
+
*/
|
|
1684
|
+
function getViewerRenderOptions() {
|
|
1685
|
+
try {
|
|
1686
|
+
const settings = pi.getSettings();
|
|
1687
|
+
return {
|
|
1688
|
+
hideThinkingBlock: settings.hideThinkingBlock ?? false,
|
|
1689
|
+
outputPad: settings.outputPad === 0 ? 0 : 1,
|
|
1690
|
+
codeBlockIndent: settings.markdown?.codeBlockIndent ?? " ",
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
catch {
|
|
1694
|
+
return {};
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
1697
|
// Claude Code-style FleetView: navigable list of main + subagents below the editor.
|
|
1698
|
-
// The
|
|
1699
|
-
//
|
|
1700
|
-
const fleet = new FleetList(manager, agentActivity, isShowCostEnabled,
|
|
1698
|
+
// The render options keep a conversation overlay opened here identical to one
|
|
1699
|
+
// opened from `/agents`.
|
|
1700
|
+
const fleet = new FleetList(manager, agentActivity, isShowCostEnabled, getViewerRenderOptions, refreshTicker, backgroundActionFor, getFullscreenScrollbar);
|
|
1701
1701
|
let fleetViewEnabled = true;
|
|
1702
1702
|
function isFleetViewEnabled() { return fleetViewEnabled; }
|
|
1703
1703
|
function setFleetViewEnabled(b) { fleetViewEnabled = b; fleet.setEnabled(b); }
|
|
@@ -2015,7 +2015,6 @@ export default function (pi) {
|
|
|
2015
2015
|
setReportUsage,
|
|
2016
2016
|
setShowCost,
|
|
2017
2017
|
setShowModel,
|
|
2018
|
-
setViewerMarkdown,
|
|
2019
2018
|
setBackgroundKey,
|
|
2020
2019
|
};
|
|
2021
2020
|
applyAndEmitLoaded(settingsAppliers, (event, payload) => pi.events.emit(event, payload), process.cwd(), projectTrusted);
|
|
@@ -3730,7 +3729,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
3730
3729
|
if (manager.abort(record.id)) {
|
|
3731
3730
|
ctx.ui.notify(`Stopped "${record.description}".`, "info");
|
|
3732
3731
|
}
|
|
3733
|
-
}, keybindings, (message) => manager.steer(record.id, message), showCost,
|
|
3732
|
+
}, keybindings, (message) => manager.steer(record.id, message), showCost, backgroundActionFor(record, message => ctx.ui.notify(message, "info")), getFullscreenScrollbar(), getViewerRenderOptions());
|
|
3734
3733
|
}, {
|
|
3735
3734
|
overlay: true,
|
|
3736
3735
|
overlayOptions: () => overlayOptions,
|
|
@@ -4122,7 +4121,6 @@ Write the file using the write tool. Only write the file, nothing else.`;
|
|
|
4122
4121
|
reportUsage: isReportUsageEnabled(),
|
|
4123
4122
|
showCost: isShowCostEnabled(),
|
|
4124
4123
|
showModel: isShowModelEnabled(),
|
|
4125
|
-
viewerMarkdown: getViewerMarkdown(),
|
|
4126
4124
|
// As written: undefined (the default) stays out of the file.
|
|
4127
4125
|
backgroundKey: backgroundKeySetting,
|
|
4128
4126
|
};
|
|
@@ -4292,13 +4290,6 @@ Write the file using the write tool. Only write the file, nothing else.`;
|
|
|
4292
4290
|
currentValue: isShowModelEnabled() ? "on" : "off",
|
|
4293
4291
|
values: ["on", "off"],
|
|
4294
4292
|
},
|
|
4295
|
-
{
|
|
4296
|
-
id: "viewerMarkdown",
|
|
4297
|
-
label: "Viewer markdown",
|
|
4298
|
-
description: "How much of the conversation viewer renders as Markdown. assistant = assistant text only (default); all = tool results too, for tools that emit Markdown — accepting that a Markdown pass over a diff or a log eats `#` comments, swallows a `---` line and re-fences indented output; off = everything verbatim. `m` in the viewer cycles the same setting (footer: raw / md / md+).",
|
|
4299
|
-
currentValue: getViewerMarkdown(),
|
|
4300
|
-
values: ["off", "assistant", "all"],
|
|
4301
|
-
},
|
|
4302
4293
|
{
|
|
4303
4294
|
id: "fleetView",
|
|
4304
4295
|
label: "Fleet view",
|
|
@@ -4485,10 +4476,6 @@ Write the file using the write tool. Only write the file, nothing else.`;
|
|
|
4485
4476
|
setShowModel(enabled);
|
|
4486
4477
|
notifyApplied(ctx, `Model display ${enabled ? "enabled" : "disabled"}`);
|
|
4487
4478
|
}
|
|
4488
|
-
else if (id === "viewerMarkdown") {
|
|
4489
|
-
setViewerMarkdown(value);
|
|
4490
|
-
notifyApplied(ctx, `Viewer markdown set to ${value}`);
|
|
4491
|
-
}
|
|
4492
4479
|
else if (id === "fleetView") {
|
|
4493
4480
|
const enabled = value === "on";
|
|
4494
4481
|
setFleetViewEnabled(enabled);
|
|
@@ -4590,23 +4577,6 @@ Write the file using the write tool. Only write the file, nothing else.`;
|
|
|
4590
4577
|
// the right toast. Successful saves show info; persistence failures downgrade
|
|
4591
4578
|
// to warning so users aren't silently reverted on restart. Event fires regardless
|
|
4592
4579
|
// of outcome so listeners see the in-memory change.
|
|
4593
|
-
/**
|
|
4594
|
-
* Persist + broadcast the settings, silent on success — for a change whose
|
|
4595
|
-
* feedback is the UI it just changed: the viewer's `m` key, where a
|
|
4596
|
-
* notification per press would talk over the overlay it is describing.
|
|
4597
|
-
*
|
|
4598
|
-
* A *failed* write still speaks. Every other settings path warns when the
|
|
4599
|
-
* value is session-only, and swallowing it here would leave a preference
|
|
4600
|
-
* looking persisted when the next session will not have it.
|
|
4601
|
-
*/
|
|
4602
|
-
function persistSettings(ctx, changeMsg) {
|
|
4603
|
-
const { message, level } = saveAndEmitChanged(snapshotSettings(), changeMsg, (event, payload) => pi.events.emit(event, payload));
|
|
4604
|
-
// `ctx` is absent only on the fleet path between sessions, where
|
|
4605
|
-
// `currentCtx` has been cleared and there is no UI to carry the warning to.
|
|
4606
|
-
// The write still happens.
|
|
4607
|
-
if (level === "warning")
|
|
4608
|
-
ctx?.ui.notify(message, level);
|
|
4609
|
-
}
|
|
4610
4580
|
function notifyApplied(ctx, successMsg) {
|
|
4611
4581
|
const { message, level } = saveAndEmitChanged(snapshotSettings(), successMsg, (event, payload) => pi.events.emit(event, payload));
|
|
4612
4582
|
ctx.ui.notify(message, level);
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
*/
|
|
36
36
|
import { type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
37
37
|
/** The package version this code ships in. `test/session-handoff.test.ts` keeps it equal to package.json. */
|
|
38
|
-
export declare const PACKAGE_VERSION = "0.20.
|
|
38
|
+
export declare const PACKAGE_VERSION = "0.20.4";
|
|
39
39
|
/** Bumped when the shape of a parked entry changes within one version (never in a release). */
|
|
40
40
|
export declare const HANDOFF_PROTOCOL = 1;
|
|
41
41
|
/** Writes an entry into the session `owner` if the caller can (it is the active one there); false otherwise. */
|
package/dist/session-handoff.js
CHANGED
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
import { existsSync } from "node:fs";
|
|
37
37
|
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
38
38
|
/** The package version this code ships in. `test/session-handoff.test.ts` keeps it equal to package.json. */
|
|
39
|
-
export const PACKAGE_VERSION = "0.20.
|
|
39
|
+
export const PACKAGE_VERSION = "0.20.4";
|
|
40
40
|
/** Bumped when the shape of a parked entry changes within one version (never in a release). */
|
|
41
41
|
export const HANDOFF_PROTOCOL = 1;
|
|
42
42
|
const HANDOFF_KEY = Symbol.for("pi-subagents:handoff:@kushalkhemka/opensec-pi-subagents");
|
package/dist/settings.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentMentionMode, JoinMode,
|
|
1
|
+
import type { AgentMentionMode, JoinMode, WidgetMode } from "./types.js";
|
|
2
2
|
export type ProjectTrustMode = "full" | "pi";
|
|
3
3
|
export interface SubagentsSettings {
|
|
4
4
|
maxConcurrent?: number;
|
|
@@ -310,19 +310,6 @@ export interface SubagentsSettings {
|
|
|
310
310
|
* narrow terminal.
|
|
311
311
|
*/
|
|
312
312
|
showModel?: boolean;
|
|
313
|
-
/**
|
|
314
|
-
* How much of the conversation viewer's transcript renders as Markdown.
|
|
315
|
-
* Defaults to `assistant`. Applied live — the viewer's `m` key cycles this
|
|
316
|
-
* same setting, so a choice made in the overlay persists like one made in
|
|
317
|
-
* `/agents → Settings`.
|
|
318
|
-
*
|
|
319
|
-
* Scoped rather than all-or-nothing because the two kinds of content have
|
|
320
|
-
* different contracts: assistant text is authored as Markdown, while a tool
|
|
321
|
-
* result is whatever bytes the tool produced. Rendering the latter as
|
|
322
|
-
* Markdown is lossy in ways that look like the tool misbehaved — see
|
|
323
|
-
* `ViewerMarkdownMode` for the specific rewrites — so `all` is opt-in.
|
|
324
|
-
*/
|
|
325
|
-
viewerMarkdown?: ViewerMarkdownMode;
|
|
326
313
|
/**
|
|
327
314
|
* The key that sends every running foreground `Agent` call to the background,
|
|
328
315
|
* as Claude Code's ctrl+b does. A pi key id (`ctrl+b`, `alt+b`, `ctrl+shift+b`,
|
|
@@ -363,7 +350,6 @@ export interface SettingsAppliers {
|
|
|
363
350
|
setReportUsage: (b: boolean) => void;
|
|
364
351
|
setShowCost: (b: boolean) => void;
|
|
365
352
|
setShowModel: (b: boolean) => void;
|
|
366
|
-
setViewerMarkdown: (mode: ViewerMarkdownMode) => void;
|
|
367
353
|
setBackgroundKey: (key: string) => void;
|
|
368
354
|
}
|
|
369
355
|
/** Emit callback — a subset of `pi.events.emit` to keep helpers testable. */
|
package/dist/settings.js
CHANGED
|
@@ -9,7 +9,6 @@ import { createDiscoveryCache } from "./discovery-cache.js";
|
|
|
9
9
|
const VALID_JOIN_MODES = new Set(["async", "group", "smart"]);
|
|
10
10
|
const VALID_TOOL_DESCRIPTION_MODES = new Set(["full", "compact", "custom"]);
|
|
11
11
|
const VALID_WIDGET_MODES = new Set(["all", "background", "off"]);
|
|
12
|
-
const VALID_VIEWER_MARKDOWN_MODES = new Set(["off", "assistant", "all"]);
|
|
13
12
|
const VALID_AGENT_MENTION_MODES = new Set(["model", "direct", "off"]);
|
|
14
13
|
// Sanity ceilings — prevent hand-edited configs from asking for values that
|
|
15
14
|
// make no operational sense (e.g. 1e6 concurrent subagents). Permissive enough
|
|
@@ -116,9 +115,7 @@ function sanitize(raw) {
|
|
|
116
115
|
if (typeof r.showModel === "boolean") {
|
|
117
116
|
out.showModel = r.showModel;
|
|
118
117
|
}
|
|
119
|
-
|
|
120
|
-
out.viewerMarkdown = r.viewerMarkdown;
|
|
121
|
-
}
|
|
118
|
+
// `viewerMarkdown` (until 0.20.3) is ignored: the viewer draws with Pi's own components now.
|
|
122
119
|
if (typeof r.backgroundKey === "string" && r.backgroundKey.trim()) {
|
|
123
120
|
out.backgroundKey = r.backgroundKey.trim().toLowerCase();
|
|
124
121
|
}
|
|
@@ -241,8 +238,6 @@ export function applySettings(s, appliers) {
|
|
|
241
238
|
appliers.setShowCost(s.showCost);
|
|
242
239
|
if (typeof s.showModel === "boolean")
|
|
243
240
|
appliers.setShowModel(s.showModel);
|
|
244
|
-
if (s.viewerMarkdown)
|
|
245
|
-
appliers.setViewerMarkdown(s.viewerMarkdown);
|
|
246
241
|
if (s.backgroundKey)
|
|
247
242
|
appliers.setBackgroundKey(s.backgroundKey);
|
|
248
243
|
if (typeof s.workflowsEnabled === "boolean")
|
package/dist/types.d.ts
CHANGED
|
@@ -100,20 +100,6 @@ export type JoinMode = 'async' | 'group' | 'smart';
|
|
|
100
100
|
* - `off`: hide the widget entirely.
|
|
101
101
|
*/
|
|
102
102
|
export type WidgetMode = 'all' | 'background' | 'off';
|
|
103
|
-
/**
|
|
104
|
-
* How much of the conversation viewer's transcript is rendered as Markdown.
|
|
105
|
-
* - `off`: every line wraps as literal text, as it did before the mode existed.
|
|
106
|
-
* - `assistant`: assistant text renders as Markdown; tool results stay verbatim
|
|
107
|
-
* and dim. The default, because assistant text *is* Markdown by contract
|
|
108
|
-
* while a tool result is arbitrary bytes — a Markdown pass over a log or a
|
|
109
|
-
* diff eats `#` from shell comments, swallows a `---` line into a setext
|
|
110
|
-
* heading, re-fences indented output and redraws `| a | b |` as a table.
|
|
111
|
-
* (Ordered-list renumbering is the one such rewrite actively suppressed —
|
|
112
|
-
* see `MARKDOWN_OPTIONS` — because it silently changes data, not layout.)
|
|
113
|
-
* - `all`: tool results render as Markdown too, for tools that genuinely emit
|
|
114
|
-
* it (#210's `ctx_execute`), accepting the rewrites above on ones that don't.
|
|
115
|
-
*/
|
|
116
|
-
export type ViewerMarkdownMode = 'off' | 'assistant' | 'all';
|
|
117
103
|
/**
|
|
118
104
|
* How `@handle message` starts an agent that is not already running.
|
|
119
105
|
* - `model`: inject Claude Code's `agent_mention` reminder and let the main
|
|
@@ -2,14 +2,19 @@
|
|
|
2
2
|
* conversation-viewer.ts — Live conversation overlay for viewing agent sessions.
|
|
3
3
|
*
|
|
4
4
|
* Displays a scrollable, live-updating view of an agent's conversation.
|
|
5
|
-
* Subscribes to session events for real-time streaming updates.
|
|
5
|
+
* Subscribes to session events for real-time streaming updates. The
|
|
6
|
+
* transcript is drawn with Pi's own chat components (`viewer-transcript.ts`),
|
|
7
|
+
* so it looks exactly like Pi's main session; the frame around it (header,
|
|
8
|
+
* footer, scrolling, steering) is the viewer's.
|
|
6
9
|
*/
|
|
7
10
|
import { type AgentSession } from "@earendil-works/pi-coding-agent";
|
|
8
11
|
import { type Component, type Focusable, type OverlayOptions, type ScrollViewScrollbar, type TUI, type TuiMouseEvent, type TuiMouseEventResult } from "@earendil-works/pi-tui";
|
|
9
|
-
import type { AgentRecord
|
|
12
|
+
import type { AgentRecord } from "../types.js";
|
|
10
13
|
import type { Theme } from "./agent-widget.js";
|
|
11
14
|
import { type AgentActivity } from "./agent-widget.js";
|
|
12
15
|
import { type ViewerKeybindings } from "./viewer-keys.js";
|
|
16
|
+
import { type ViewerRenderOptions } from "./viewer-transcript.js";
|
|
17
|
+
export type { ViewerRenderOptions } from "./viewer-transcript.js";
|
|
13
18
|
/** Height ceiling shared by the overlay's `maxHeight` and the viewer's internal viewport cap. */
|
|
14
19
|
export declare const VIEWPORT_HEIGHT_PCT = 70;
|
|
15
20
|
/**
|
|
@@ -18,18 +23,6 @@ export declare const VIEWPORT_HEIGHT_PCT = 70;
|
|
|
18
23
|
* setting that may need a restart to take effect.
|
|
19
24
|
*/
|
|
20
25
|
export declare function getConversationOverlayOptions(tui: Pick<TUI, "mode">): OverlayOptions;
|
|
21
|
-
/**
|
|
22
|
-
* Cap on a single tool result or bash output before the viewer elides the rest.
|
|
23
|
-
*
|
|
24
|
-
* The cap is not cosmetic — it bounds render cost. A message's lines are
|
|
25
|
-
* rebuilt whenever the width or Markdown mode changes, and the streaming tail
|
|
26
|
-
* on every frame it changes, so an uncapped 200 KB result costs ~6 ms per
|
|
27
|
-
* rebuild to parse as Markdown, against ~0.5 ms once capped (best of 5, width
|
|
28
|
-
* 76). 16 KB is roughly a screenful at every terminal size and still ~30x the
|
|
29
|
-
* 500 characters this replaces, which was small enough to cut most real
|
|
30
|
-
* results mid-sentence.
|
|
31
|
-
*/
|
|
32
|
-
export declare const RESULT_MAX_CHARS = 16000;
|
|
33
26
|
/** The `b` affordance: whether this run can be handed to the background now, and the handoff. */
|
|
34
27
|
export interface ViewerBackgroundAction {
|
|
35
28
|
available(): boolean;
|
|
@@ -52,17 +45,6 @@ export declare class ConversationViewer implements Component, Focusable {
|
|
|
52
45
|
* cannot change while it is on screen.
|
|
53
46
|
*/
|
|
54
47
|
private showCost;
|
|
55
|
-
/**
|
|
56
|
-
* The current `viewerMarkdown` setting. Read live rather than captured,
|
|
57
|
-
* unlike `showCost`: `m` changes it while the overlay is on screen.
|
|
58
|
-
* Omitted → `assistant`.
|
|
59
|
-
*/
|
|
60
|
-
private viewerMarkdown?;
|
|
61
|
-
/**
|
|
62
|
-
* Persist a mode chosen with `m`, so the key and `/agents → Settings` mean
|
|
63
|
-
* the same thing. Omitted → `m` still cycles, viewer-locally.
|
|
64
|
-
*/
|
|
65
|
-
private onMarkdownMode?;
|
|
66
48
|
/**
|
|
67
49
|
* Hand this run to the background. `available` is read live — it turns
|
|
68
50
|
* false the instant the handoff lands or the run ends. Omitted → no `b`
|
|
@@ -107,39 +89,13 @@ export declare class ConversationViewer implements Component, Focusable {
|
|
|
107
89
|
private keys;
|
|
108
90
|
/** Steering composer — present while the user is typing a message to the agent. */
|
|
109
91
|
private composer;
|
|
110
|
-
/** Resolved once: pi's Markdown theme is fixed for the life of the process. */
|
|
111
|
-
private readonly markdownTheme;
|
|
112
|
-
/** Set by the `m` key. Wins over the setting so `m` works without a persist hook. */
|
|
113
|
-
private markdownModeOverride;
|
|
114
|
-
/**
|
|
115
|
-
* One `Markdown` per message, so its own text/width cache does the work. A
|
|
116
|
-
* fresh instance per render would re-parse the whole transcript on every
|
|
117
|
-
* keystroke — the component caches, but only across calls to the same object.
|
|
118
|
-
* Weak so a compacted-away message doesn't pin its render.
|
|
119
|
-
*/
|
|
120
|
-
private readonly markdownCache;
|
|
121
|
-
/**
|
|
122
|
-
* The transcript, kept between frames. `blocks[i]` renders `messages[i]`;
|
|
123
|
-
* `lines` holds every block's lines (and separators) in order, and
|
|
124
|
-
* `blockStart[i]` is where block `i`'s part of it begins. All of it is valid
|
|
125
|
-
* for `cacheWidth` and `cacheMode` only.
|
|
126
|
-
*/
|
|
127
|
-
private blocks;
|
|
128
|
-
private blockStart;
|
|
129
|
-
private lines;
|
|
130
|
-
/** `lineWidths[i]` is the visible width of `lines[i]`, measured once when the line is built. */
|
|
131
|
-
private lineWidths;
|
|
132
92
|
/**
|
|
133
|
-
* The
|
|
134
|
-
*
|
|
135
|
-
* object only the unsettled tail needs checking — what keeps a frame's cost
|
|
136
|
-
* flat however long the transcript gets.
|
|
93
|
+
* The transcript drawn with Pi's components, its lines cached between
|
|
94
|
+
* frames. Undefined when Pi's theme is not initialized (see `plainLines`).
|
|
137
95
|
*/
|
|
138
|
-
private
|
|
139
|
-
|
|
140
|
-
private
|
|
141
|
-
/** Messages from this index on may still change in place; earlier ones are settled. */
|
|
142
|
-
private unsettledFrom;
|
|
96
|
+
private readonly transcript;
|
|
97
|
+
/** The plain fallback's lines and what they were built for. */
|
|
98
|
+
private plain;
|
|
143
99
|
/** Pending coalesced redraw for streaming events. */
|
|
144
100
|
private streamRenderTimer;
|
|
145
101
|
constructor(tui: TUI, session: AgentSession, record: AgentRecord, activity: AgentActivity | undefined, theme: Theme, done: (result: undefined) => void,
|
|
@@ -155,17 +111,6 @@ export declare class ConversationViewer implements Component, Focusable {
|
|
|
155
111
|
* cannot change while it is on screen.
|
|
156
112
|
*/
|
|
157
113
|
showCost?: boolean,
|
|
158
|
-
/**
|
|
159
|
-
* The current `viewerMarkdown` setting. Read live rather than captured,
|
|
160
|
-
* unlike `showCost`: `m` changes it while the overlay is on screen.
|
|
161
|
-
* Omitted → `assistant`.
|
|
162
|
-
*/
|
|
163
|
-
viewerMarkdown?: (() => ViewerMarkdownMode) | undefined,
|
|
164
|
-
/**
|
|
165
|
-
* Persist a mode chosen with `m`, so the key and `/agents → Settings` mean
|
|
166
|
-
* the same thing. Omitted → `m` still cycles, viewer-locally.
|
|
167
|
-
*/
|
|
168
|
-
onMarkdownMode?: ((mode: ViewerMarkdownMode) => void) | undefined,
|
|
169
114
|
/**
|
|
170
115
|
* Hand this run to the background. `available` is read live — it turns
|
|
171
116
|
* false the instant the handoff lands or the run ends. Omitted → no `b`
|
|
@@ -173,7 +118,9 @@ export declare class ConversationViewer implements Component, Focusable {
|
|
|
173
118
|
*/
|
|
174
119
|
onBackground?: ViewerBackgroundAction | undefined,
|
|
175
120
|
/** Pi's `fullscreenScrollbar` preference, captured when the viewer opens. */
|
|
176
|
-
scrollbarMode?: ScrollViewScrollbar
|
|
121
|
+
scrollbarMode?: ScrollViewScrollbar,
|
|
122
|
+
/** Pi's chat settings (thinking blocks, padding, code indent), read when the viewer opens. */
|
|
123
|
+
renderOptions?: ViewerRenderOptions);
|
|
177
124
|
handleInput(data: string): void;
|
|
178
125
|
/** Follow the conversation again and clear the new-message count (fullscreen `Ctrl+End`). */
|
|
179
126
|
private jumpToLatest;
|
|
@@ -217,12 +164,10 @@ export declare class ConversationViewer implements Component, Focusable {
|
|
|
217
164
|
* matches spawns nobody can hand off (the agent generator).
|
|
218
165
|
*/
|
|
219
166
|
private canSendToBackground;
|
|
220
|
-
/**
|
|
221
|
-
private
|
|
222
|
-
/**
|
|
223
|
-
private
|
|
224
|
-
/** Render `text` as Markdown, reusing this message's component instance. */
|
|
225
|
-
private markdownLines;
|
|
167
|
+
/** Expand or collapse every tool, bash and summary block, as Pi's `app.tools.expand`. */
|
|
168
|
+
private toggleExpand;
|
|
169
|
+
/** The expand toggle's footer control: the key bound to it and what it does next. */
|
|
170
|
+
private expandHint;
|
|
226
171
|
/** Steerable only when a steer handler exists and the agent is still active. */
|
|
227
172
|
private canSteer;
|
|
228
173
|
/** Open the inline steering composer and route subsequent input to it. */
|
|
@@ -236,23 +181,16 @@ export declare class ConversationViewer implements Component, Focusable {
|
|
|
236
181
|
buildContentLines(width: number): string[];
|
|
237
182
|
private contentLength;
|
|
238
183
|
/**
|
|
239
|
-
* The content area as cached transcript lines (`base`, owned by the
|
|
240
|
-
* do not mutate) plus the live activity
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
* by identity; messages from `unsettledFrom` on (the last one when the
|
|
244
|
-
* previous frame was drawn, and anything after it) are also compared by
|
|
245
|
-
* content, because older pi streams into the last message in place and a
|
|
246
|
-
* message can be edited and then pushed past before the next frame. Earlier
|
|
247
|
-
* messages are settled: pi never rewrites a message once another follows it,
|
|
248
|
-
* and compaction replaces the objects, which the identity check catches.
|
|
184
|
+
* The content area as cached transcript lines (`base`, owned by the
|
|
185
|
+
* transcript — do not mutate) with their widths, plus the live activity
|
|
186
|
+
* indicator (`tail`). Only blocks that changed are re-rendered, so a frame
|
|
187
|
+
* where nothing changed costs a few comparisons however long the transcript.
|
|
249
188
|
*/
|
|
250
189
|
private contentView;
|
|
251
190
|
/**
|
|
252
|
-
*
|
|
253
|
-
*
|
|
191
|
+
* The transcript as plain wrapped text, for a process whose Pi theme is not
|
|
192
|
+
* initialized (Pi's components cannot render there). Never the case in Pi's
|
|
193
|
+
* interactive mode; it keeps the viewer usable rather than throwing.
|
|
254
194
|
*/
|
|
255
|
-
private
|
|
256
|
-
/** One message's lines without its separator, clamped to `width`; undefined when it renders nothing. */
|
|
257
|
-
private messageLines;
|
|
195
|
+
private plainLines;
|
|
258
196
|
}
|