@esso0428/pi-subagents 0.15.0 → 0.15.2

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +2 -2
  3. package/dist/agent-manager.d.ts +8 -0
  4. package/dist/agent-manager.js +87 -19
  5. package/dist/agent-runner.js +85 -66
  6. package/dist/agent-types.js +45 -27
  7. package/dist/context.js +6 -2
  8. package/dist/cross-extension-rpc.js +9 -5
  9. package/dist/custom-agents.js +18 -15
  10. package/dist/default-agents.js +4 -1
  11. package/dist/enabled-models.js +16 -11
  12. package/dist/env.js +4 -1
  13. package/dist/group-join.js +5 -1
  14. package/dist/index.js +280 -221
  15. package/dist/invocation-config.js +6 -2
  16. package/dist/memory.js +34 -24
  17. package/dist/model-resolver.js +4 -1
  18. package/dist/nico-overrides.js +20 -14
  19. package/dist/output-file.js +21 -15
  20. package/dist/prompts.js +4 -1
  21. package/dist/schedule-store.js +21 -16
  22. package/dist/schedule.js +12 -8
  23. package/dist/settings.js +23 -15
  24. package/dist/skill-loader.js +23 -20
  25. package/dist/status-note.js +4 -1
  26. package/dist/types.d.ts +1 -0
  27. package/dist/types.js +4 -1
  28. package/dist/ui/agent-widget.js +37 -23
  29. package/dist/ui/conversation-viewer.js +43 -39
  30. package/dist/ui/fleet-list.js +30 -24
  31. package/dist/ui/markdown-result.d.ts +3 -0
  32. package/dist/ui/markdown-result.js +53 -0
  33. package/dist/ui/schedule-menu.js +4 -1
  34. package/dist/ui/viewer-keys.js +10 -7
  35. package/dist/usage.js +10 -4
  36. package/dist/worktree.js +31 -26
  37. package/package.json +1 -1
  38. package/src/agent-manager.ts +72 -0
  39. package/src/agent-runner.ts +11 -3
  40. package/src/index.ts +39 -16
  41. package/src/types.ts +1 -0
  42. package/src/ui/markdown-result.ts +56 -0
  43. package/test/agent-manager-history.test.ts +84 -0
  44. package/test/ui/markdown-result.test.ts +45 -0
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  /**
2
3
  * skill-loader.ts — Preload named skills.
3
4
  *
@@ -17,24 +18,26 @@
17
18
  *
18
19
  * Symlinks are rejected for security (deviation from Pi, which follows them).
19
20
  */
20
- import { existsSync, readdirSync } from "node:fs";
21
- import { homedir } from "node:os";
22
- import { join } from "node:path";
23
- import { getAgentDir } from "@earendil-works/pi-coding-agent";
24
- import { isSymlink, isUnsafeName, safeReadFile } from "./memory.js";
25
- export function preloadSkills(skillNames, cwd) {
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.preloadSkills = preloadSkills;
23
+ const node_fs_1 = require("node:fs");
24
+ const node_os_1 = require("node:os");
25
+ const node_path_1 = require("node:path");
26
+ const pi_coding_agent_1 = require("@earendil-works/pi-coding-agent");
27
+ const memory_js_1 = require("./memory.js");
28
+ function preloadSkills(skillNames, cwd) {
26
29
  return skillNames.map((name) => ({ name, content: loadSkillContent(name, cwd) }));
27
30
  }
28
31
  function loadSkillContent(name, cwd) {
29
- if (isUnsafeName(name)) {
32
+ if ((0, memory_js_1.isUnsafeName)(name)) {
30
33
  return `(Skill "${name}" skipped: name contains path traversal characters)`;
31
34
  }
32
35
  const roots = [
33
- join(cwd, ".pi", "skills"), // project — Pi standard
34
- join(cwd, ".agents", "skills"), // project — Agent Skills spec
35
- join(getAgentDir(), "skills"), // user — Pi standard
36
- join(homedir(), ".agents", "skills"), // user — Agent Skills spec
37
- join(homedir(), ".pi", "skills"), // legacy global, pre-Pi
36
+ (0, node_path_1.join)(cwd, ".pi", "skills"), // project — Pi standard
37
+ (0, node_path_1.join)(cwd, ".agents", "skills"), // project — Agent Skills spec
38
+ (0, node_path_1.join)((0, pi_coding_agent_1.getAgentDir)(), "skills"), // user — Pi standard
39
+ (0, node_path_1.join)((0, node_os_1.homedir)(), ".agents", "skills"), // user — Agent Skills spec
40
+ (0, node_path_1.join)((0, node_os_1.homedir)(), ".pi", "skills"), // legacy global, pre-Pi
38
41
  ];
39
42
  for (const root of roots) {
40
43
  const content = findInRoot(root, name);
@@ -44,16 +47,16 @@ function loadSkillContent(name, cwd) {
44
47
  return `(Skill "${name}" not found in .pi/skills/, .agents/skills/, or global skill locations)`;
45
48
  }
46
49
  function findInRoot(root, name) {
47
- if (isSymlink(root))
50
+ if ((0, memory_js_1.isSymlink)(root))
48
51
  return undefined; // reject symlinked roots entirely
49
- const flat = safeReadFile(join(root, `${name}.md`))?.trim();
52
+ const flat = (0, memory_js_1.safeReadFile)((0, node_path_1.join)(root, `${name}.md`))?.trim();
50
53
  if (flat !== undefined)
51
54
  return flat;
52
55
  return findSkillDirectory(root, name);
53
56
  }
54
57
  /** BFS under `root` for a directory named `name` containing `SKILL.md`. Pi-conforming filters. */
55
58
  function findSkillDirectory(root, name) {
56
- if (!existsSync(root))
59
+ if (!(0, node_fs_1.existsSync)(root))
57
60
  return undefined;
58
61
  const queue = [root];
59
62
  while (queue.length > 0) {
@@ -62,7 +65,7 @@ function findSkillDirectory(root, name) {
62
65
  continue;
63
66
  let entries;
64
67
  try {
65
- entries = readdirSync(current, { withFileTypes: true });
68
+ entries = (0, node_fs_1.readdirSync)(current, { withFileTypes: true });
66
69
  }
67
70
  catch {
68
71
  continue;
@@ -75,12 +78,12 @@ function findSkillDirectory(root, name) {
75
78
  if (entry.name.startsWith(".") || entry.name === "node_modules")
76
79
  continue;
77
80
  // Symlinked dirs already filtered by entry.isDirectory() — Dirent uses lstat semantics.
78
- const path = join(current, entry.name);
79
- const skillMd = join(path, "SKILL.md");
80
- const isSkillDir = existsSync(skillMd);
81
+ const path = (0, node_path_1.join)(current, entry.name);
82
+ const skillMd = (0, node_path_1.join)(path, "SKILL.md");
83
+ const isSkillDir = (0, node_fs_1.existsSync)(skillMd);
81
84
  if (isSkillDir) {
82
85
  if (entry.name === name) {
83
- const content = safeReadFile(skillMd)?.trim();
86
+ const content = (0, memory_js_1.safeReadFile)(skillMd)?.trim();
84
87
  if (content !== undefined)
85
88
  return content;
86
89
  }
@@ -1,6 +1,9 @@
1
+ "use strict";
1
2
  /**
2
3
  * status-note.ts — Parenthetical status note appended to agent result text.
3
4
  */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getStatusNote = getStatusNote;
4
7
  /**
5
8
  * Explicit parenthetical note for a non-normal terminal outcome, so the parent
6
9
  * agent can't mistake partial output for a completed result. Empty string for a
@@ -10,7 +13,7 @@
10
13
  * turn limit was hit) — the parent should treat human intervention differently
11
14
  * from a budget cutoff.
12
15
  */
13
- export function getStatusNote(status) {
16
+ function getStatusNote(status) {
14
17
  switch (status) {
15
18
  case "stopped":
16
19
  return " (STOPPED BY THE USER before completion — output is partial; the task was NOT finished)";
package/dist/types.d.ts CHANGED
@@ -150,6 +150,7 @@ export interface NotificationDetails {
150
150
  outputFile?: string;
151
151
  error?: string;
152
152
  resultPreview: string;
153
+ resultText?: string;
153
154
  /** Additional agents in a group notification. */
154
155
  others?: NotificationDetails[];
155
156
  }
package/dist/types.js CHANGED
@@ -1,5 +1,8 @@
1
+ "use strict";
1
2
  /**
2
3
  * types.ts — Type definitions for the subagent system.
3
4
  */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DEFAULT_AGENT_NAMES = void 0;
4
7
  /** Names of the three embedded default agents. */
5
- export const DEFAULT_AGENT_NAMES = ["general-purpose", "Explore", "Plan"];
8
+ exports.DEFAULT_AGENT_NAMES = ["general-purpose", "Explore", "Plan"];
@@ -1,19 +1,32 @@
1
+ "use strict";
1
2
  /**
2
3
  * agent-widget.ts — Persistent widget showing running/completed agents above the editor.
3
4
  *
4
5
  * Displays a tree of agents with animated spinners, live stats, and activity descriptions.
5
6
  * Uses the callback form of setWidget for themed rendering.
6
7
  */
7
- import { truncateToWidth } from "@earendil-works/pi-tui";
8
- import { getConfig } from "../agent-types.js";
9
- import { getLifetimeTotal, getSessionContextPercent } from "../usage.js";
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.AgentWidget = exports.ERROR_STATUSES = exports.SPINNER = void 0;
10
+ exports.fgPreservingNestedStyles = fgPreservingNestedStyles;
11
+ exports.formatTokens = formatTokens;
12
+ exports.formatSessionTokens = formatSessionTokens;
13
+ exports.formatTurns = formatTurns;
14
+ exports.formatMs = formatMs;
15
+ exports.formatDuration = formatDuration;
16
+ exports.getDisplayName = getDisplayName;
17
+ exports.getPromptModeLabel = getPromptModeLabel;
18
+ exports.buildInvocationTags = buildInvocationTags;
19
+ exports.describeActivity = describeActivity;
20
+ const pi_tui_1 = require("@earendil-works/pi-tui");
21
+ const agent_types_js_1 = require("../agent-types.js");
22
+ const usage_js_1 = require("../usage.js");
10
23
  // ---- Constants ----
11
24
  /** Maximum number of rendered lines before overflow collapse kicks in. */
12
25
  const MAX_WIDGET_LINES = 12;
13
26
  /** Braille spinner frames for animated running indicator. */
14
- export const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
27
+ exports.SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
15
28
  /** Statuses that indicate an error/non-success outcome (used for linger behavior and icon rendering). */
16
- export const ERROR_STATUSES = new Set(["error", "aborted", "steered", "stopped"]);
29
+ exports.ERROR_STATUSES = new Set(["error", "aborted", "steered", "stopped"]);
17
30
  /** Tool name → human-readable action for activity descriptions. */
18
31
  const TOOL_DISPLAY = {
19
32
  read: "reading",
@@ -26,13 +39,13 @@ const TOOL_DISPLAY = {
26
39
  };
27
40
  // ---- Formatting helpers ----
28
41
  /** Apply foreground styling while restoring it after nested foreground/full ANSI resets. */
29
- export function fgPreservingNestedStyles(theme, color, text) {
42
+ function fgPreservingNestedStyles(theme, color, text) {
30
43
  const styledEmpty = theme.fg(color, "");
31
44
  const styleStart = styledEmpty.replace(/\u001b\[(?:0|39)m/g, "");
32
45
  return theme.fg(color, text.replace(/\u001b\[(?:0|39)m/g, reset => `${reset}${styleStart}`));
33
46
  }
34
47
  /** Format a token count compactly: "33.8k token", "1.2M token". */
35
- export function formatTokens(count) {
48
+ function formatTokens(count) {
36
49
  if (count >= 1_000_000)
37
50
  return `${(count / 1_000_000).toFixed(1)}M token`;
38
51
  if (count >= 1_000)
@@ -49,7 +62,7 @@ export function formatTokens(count) {
49
62
  * "12.3k token (⇊2)" — compactions only (e.g. right after compact)
50
63
  * "12.3k token (45% · ⇊2)" — both
51
64
  */
52
- export function formatSessionTokens(tokens, percent, theme, compactions = 0) {
65
+ function formatSessionTokens(tokens, percent, theme, compactions = 0) {
53
66
  const tokenStr = formatTokens(tokens);
54
67
  const annot = [];
55
68
  if (percent !== null) {
@@ -64,30 +77,30 @@ export function formatSessionTokens(tokens, percent, theme, compactions = 0) {
64
77
  return `${tokenStr} (${annot.join(" · ")})`;
65
78
  }
66
79
  /** Format turn count with optional max limit: "↻5≤30" or "↻5". */
67
- export function formatTurns(turnCount, maxTurns) {
80
+ function formatTurns(turnCount, maxTurns) {
68
81
  return maxTurns != null ? `↻${turnCount}≤${maxTurns}` : `↻${turnCount}`;
69
82
  }
70
83
  /** Format milliseconds as human-readable duration. */
71
- export function formatMs(ms) {
84
+ function formatMs(ms) {
72
85
  return `${(ms / 1000).toFixed(1)}s`;
73
86
  }
74
87
  /** Format duration from start/completed timestamps. */
75
- export function formatDuration(startedAt, completedAt) {
88
+ function formatDuration(startedAt, completedAt) {
76
89
  if (completedAt)
77
90
  return formatMs(completedAt - startedAt);
78
91
  return `${formatMs(Date.now() - startedAt)} (running)`;
79
92
  }
80
93
  /** Get display name for any agent type (built-in or custom). */
81
- export function getDisplayName(type) {
82
- return getConfig(type).displayName;
94
+ function getDisplayName(type) {
95
+ return (0, agent_types_js_1.getConfig)(type).displayName;
83
96
  }
84
97
  /** Short label for prompt mode: "twin" for append, nothing for replace (the default). */
85
- export function getPromptModeLabel(type) {
86
- const config = getConfig(type);
98
+ function getPromptModeLabel(type) {
99
+ const config = (0, agent_types_js_1.getConfig)(type);
87
100
  return config.promptMode === "append" ? "twin" : undefined;
88
101
  }
89
102
  /** Mode label is not included — callers add it where they want it. */
90
- export function buildInvocationTags(invocation) {
103
+ function buildInvocationTags(invocation) {
91
104
  const tags = [];
92
105
  if (!invocation)
93
106
  return { tags };
@@ -113,7 +126,7 @@ function truncateLine(text, len = 60) {
113
126
  return line.slice(0, len) + "…";
114
127
  }
115
128
  /** Build a human-readable activity string from currently-running tools or response text. */
116
- export function describeActivity(activeTools, responseText) {
129
+ function describeActivity(activeTools, responseText) {
117
130
  if (activeTools.size > 0) {
118
131
  const groups = new Map();
119
132
  for (const toolName of activeTools.values()) {
@@ -138,7 +151,7 @@ export function describeActivity(activeTools, responseText) {
138
151
  return "thinking…";
139
152
  }
140
153
  // ---- Widget manager ----
141
- export class AgentWidget {
154
+ class AgentWidget {
142
155
  manager;
143
156
  agentActivity;
144
157
  mode;
@@ -217,7 +230,7 @@ export class AgentWidget {
217
230
  /** Check if a finished agent should still be shown in the widget. */
218
231
  shouldShowFinished(agentId, status) {
219
232
  const age = this.finishedTurnAge.get(agentId) ?? 0;
220
- const maxAge = ERROR_STATUSES.has(status) ? AgentWidget.ERROR_LINGER_TURNS : 1;
233
+ const maxAge = exports.ERROR_STATUSES.has(status) ? AgentWidget.ERROR_LINGER_TURNS : 1;
221
234
  return age < maxAge;
222
235
  }
223
236
  /** Record an agent as finished (call when agent completes). */
@@ -281,10 +294,10 @@ export class AgentWidget {
281
294
  if (!hasActive && !hasFinished)
282
295
  return [];
283
296
  const w = tui.terminal.columns;
284
- const truncate = (line) => truncateToWidth(line, w);
297
+ const truncate = (line) => (0, pi_tui_1.truncateToWidth)(line, w);
285
298
  const headingColor = hasActive ? "accent" : "dim";
286
299
  const headingIcon = hasActive ? "●" : "○";
287
- const frame = SPINNER[this.widgetFrame % SPINNER.length];
300
+ const frame = exports.SPINNER[this.widgetFrame % exports.SPINNER.length];
288
301
  // Build sections separately for overflow-aware assembly.
289
302
  // Each running agent = 2 lines (header + activity), finished = 1 line, queued = 1 line.
290
303
  const finishedLines = [];
@@ -299,8 +312,8 @@ export class AgentWidget {
299
312
  const elapsed = formatMs(Date.now() - a.startedAt);
300
313
  const bg = this.agentActivity.get(a.id);
301
314
  const toolUses = bg?.toolUses ?? a.toolUses;
302
- const tokens = getLifetimeTotal(bg?.lifetimeUsage);
303
- const contextPercent = getSessionContextPercent(bg?.session);
315
+ const tokens = (0, usage_js_1.getLifetimeTotal)(bg?.lifetimeUsage);
316
+ const contextPercent = (0, usage_js_1.getSessionContextPercent)(bg?.session);
304
317
  const tokenText = tokens > 0 ? formatSessionTokens(tokens, contextPercent, theme, a.compactionCount) : "";
305
318
  const parts = [];
306
319
  if (bg)
@@ -482,3 +495,4 @@ export class AgentWidget {
482
495
  this.lastStatusText = undefined;
483
496
  }
484
497
  }
498
+ exports.AgentWidget = AgentWidget;
@@ -1,20 +1,23 @@
1
+ "use strict";
1
2
  /**
2
3
  * conversation-viewer.ts — Live conversation overlay for viewing agent sessions.
3
4
  *
4
5
  * Displays a scrollable, live-updating view of an agent's conversation.
5
6
  * Subscribes to session events for real-time streaming updates.
6
7
  */
7
- import { Input, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
8
- import { extractText } from "../context.js";
9
- import { getLifetimeTotal, getSessionContextPercent } from "../usage.js";
10
- import { buildInvocationTags, describeActivity, fgPreservingNestedStyles, formatDuration, formatSessionTokens, getDisplayName, getPromptModeLabel } from "./agent-widget.js";
11
- import { createViewerKeys } from "./viewer-keys.js";
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.ConversationViewer = exports.VIEWPORT_HEIGHT_PCT = void 0;
10
+ const pi_tui_1 = require("@earendil-works/pi-tui");
11
+ const context_js_1 = require("../context.js");
12
+ const usage_js_1 = require("../usage.js");
13
+ const agent_widget_js_1 = require("./agent-widget.js");
14
+ const viewer_keys_js_1 = require("./viewer-keys.js");
12
15
  /** Base lines consumed by chrome: top border + header + header sep + footer sep + footer + bottom border. */
13
16
  const CHROME_LINES_BASE = 6;
14
17
  const MIN_VIEWPORT = 3;
15
18
  /** Height ceiling shared by the overlay's `maxHeight` and the viewer's internal viewport cap. */
16
- export const VIEWPORT_HEIGHT_PCT = 70;
17
- export class ConversationViewer {
19
+ exports.VIEWPORT_HEIGHT_PCT = 70;
20
+ class ConversationViewer {
18
21
  tui;
19
22
  session;
20
23
  record;
@@ -48,7 +51,7 @@ export class ConversationViewer {
48
51
  this.done = done;
49
52
  this.onStop = onStop;
50
53
  this.onSteer = onSteer;
51
- this.keys = createViewerKeys(keybindings);
54
+ this.keys = (0, viewer_keys_js_1.createViewerKeys)(keybindings);
52
55
  this.unsubscribe = session.subscribe(() => {
53
56
  if (this.closed)
54
57
  return;
@@ -63,7 +66,7 @@ export class ConversationViewer {
63
66
  this.tui.requestRender();
64
67
  return;
65
68
  }
66
- if (matchesKey(data, "escape") || matchesKey(data, "q")) {
69
+ if ((0, pi_tui_1.matchesKey)(data, "escape") || (0, pi_tui_1.matchesKey)(data, "q")) {
67
70
  this.closed = true;
68
71
  this.done(undefined);
69
72
  return;
@@ -71,14 +74,14 @@ export class ConversationViewer {
71
74
  // Enter opens the steering composer (only while the agent can still be
72
75
  // steered) — then type + Enter sends, Esc or an empty submit returns. When
73
76
  // not steerable, fall through so the key still disarms a pending stop.
74
- if (matchesKey(data, "enter") && this.canSteer()) {
77
+ if ((0, pi_tui_1.matchesKey)(data, "enter") && this.canSteer()) {
75
78
  this.stopArmed = false;
76
79
  this.openComposer();
77
80
  return;
78
81
  }
79
82
  // Stop/abort the agent (only while it can still be stopped). Two-press:
80
83
  // first "x" arms, second confirms — any other key disarms.
81
- if (matchesKey(data, "x")) {
84
+ if ((0, pi_tui_1.matchesKey)(data, "x")) {
82
85
  if (this.isStoppable()) {
83
86
  if (this.stopArmed) {
84
87
  this.stopArmed = false;
@@ -112,11 +115,11 @@ export class ConversationViewer {
112
115
  this.scrollOffset = Math.min(maxScroll, this.scrollOffset + viewportHeight);
113
116
  this.autoScroll = this.scrollOffset >= maxScroll;
114
117
  }
115
- else if (matchesKey(data, "home")) {
118
+ else if ((0, pi_tui_1.matchesKey)(data, "home")) {
116
119
  this.scrollOffset = 0;
117
120
  this.autoScroll = false;
118
121
  }
119
- else if (matchesKey(data, "end")) {
122
+ else if ((0, pi_tui_1.matchesKey)(data, "end")) {
120
123
  this.scrollOffset = maxScroll;
121
124
  this.autoScroll = true;
122
125
  }
@@ -129,17 +132,17 @@ export class ConversationViewer {
129
132
  this.lastInnerW = innerW;
130
133
  const lines = [];
131
134
  const pad = (s, len) => {
132
- const vis = visibleWidth(s);
135
+ const vis = (0, pi_tui_1.visibleWidth)(s);
133
136
  return s + " ".repeat(Math.max(0, len - vis));
134
137
  };
135
- const row = (content) => th.fg("border", "│") + " " + truncateToWidth(pad(content, innerW), innerW, "...", true) + " " + th.fg("border", "│");
138
+ const row = (content) => th.fg("border", "│") + " " + (0, pi_tui_1.truncateToWidth)(pad(content, innerW), innerW, "...", true) + " " + th.fg("border", "│");
136
139
  const hrTop = th.fg("border", `╭${"─".repeat(width - 2)}╮`);
137
140
  const hrBot = th.fg("border", `╰${"─".repeat(width - 2)}╯`);
138
141
  const hrMid = row(th.fg("dim", "─".repeat(innerW)));
139
142
  // Header
140
143
  lines.push(hrTop);
141
- const name = getDisplayName(this.record.type);
142
- const modeLabel = getPromptModeLabel(this.record.type);
144
+ const name = (0, agent_widget_js_1.getDisplayName)(this.record.type);
145
+ const modeLabel = (0, agent_widget_js_1.getPromptModeLabel)(this.record.type);
143
146
  const modeTag = modeLabel ? ` ${th.fg("dim", `(${modeLabel})`)}` : "";
144
147
  const statusIcon = this.record.status === "running"
145
148
  ? th.fg("accent", "●")
@@ -148,17 +151,17 @@ export class ConversationViewer {
148
151
  : this.record.status === "error"
149
152
  ? th.fg("error", "✗")
150
153
  : th.fg("dim", "○");
151
- const duration = formatDuration(this.record.startedAt, this.record.completedAt);
154
+ const duration = (0, agent_widget_js_1.formatDuration)(this.record.startedAt, this.record.completedAt);
152
155
  const headerParts = [duration];
153
156
  const toolUses = this.activity?.toolUses ?? this.record.toolUses;
154
157
  if (toolUses > 0)
155
158
  headerParts.unshift(`${toolUses} tool${toolUses === 1 ? "" : "s"}`);
156
- const tokens = getLifetimeTotal(this.activity?.lifetimeUsage);
159
+ const tokens = (0, usage_js_1.getLifetimeTotal)(this.activity?.lifetimeUsage);
157
160
  if (tokens > 0) {
158
- const percent = getSessionContextPercent(this.activity?.session);
159
- headerParts.push(formatSessionTokens(tokens, percent, th, this.record.compactionCount));
161
+ const percent = (0, usage_js_1.getSessionContextPercent)(this.activity?.session);
162
+ headerParts.push((0, agent_widget_js_1.formatSessionTokens)(tokens, percent, th, this.record.compactionCount));
160
163
  }
161
- lines.push(row(`${statusIcon} ${th.bold(name)}${modeTag} ${th.fg("muted", this.record.description)} ${th.fg("dim", "·")} ${fgPreservingNestedStyles(th, "dim", headerParts.join(" · "))}`));
164
+ lines.push(row(`${statusIcon} ${th.bold(name)}${modeTag} ${th.fg("muted", this.record.description)} ${th.fg("dim", "·")} ${(0, agent_widget_js_1.fgPreservingNestedStyles)(th, "dim", headerParts.join(" · "))}`));
162
165
  const invocationLine = this.invocationLine();
163
166
  if (invocationLine)
164
167
  lines.push(row(invocationLine));
@@ -182,7 +185,7 @@ export class ConversationViewer {
182
185
  lines.push(row(this.composer.render(innerW)[0] ?? ""));
183
186
  const composeHint = th.fg("dim", "Enter send · Esc cancel");
184
187
  const composeLeft = th.fg("accent", "✎ steer");
185
- const composeGap = Math.max(1, innerW - visibleWidth(composeLeft) - visibleWidth(composeHint));
188
+ const composeGap = Math.max(1, innerW - (0, pi_tui_1.visibleWidth)(composeLeft) - (0, pi_tui_1.visibleWidth)(composeHint));
186
189
  lines.push(row(composeLeft + " ".repeat(composeGap) + composeHint));
187
190
  }
188
191
  else {
@@ -204,10 +207,10 @@ export class ConversationViewer {
204
207
  : `${Math.round(((visibleStart + viewportHeight) / contentLines.length) * 100)}%`;
205
208
  const count = th.fg("dim", `${contentLines.length} lines · ${scrollPct}`);
206
209
  const withCount = [count, ...actions].join(sep);
207
- const footerLeft = visibleWidth(withCount) + visibleWidth(footerRight) + 1 <= innerW
210
+ const footerLeft = (0, pi_tui_1.visibleWidth)(withCount) + (0, pi_tui_1.visibleWidth)(footerRight) + 1 <= innerW
208
211
  ? withCount
209
212
  : actions.join(sep);
210
- const footerGap = Math.max(1, innerW - visibleWidth(footerLeft) - visibleWidth(footerRight));
213
+ const footerGap = Math.max(1, innerW - (0, pi_tui_1.visibleWidth)(footerLeft) - (0, pi_tui_1.visibleWidth)(footerRight));
211
214
  lines.push(row(footerLeft + " ".repeat(footerGap) + footerRight));
212
215
  }
213
216
  lines.push(hrBot);
@@ -223,7 +226,7 @@ export class ConversationViewer {
223
226
  }
224
227
  /** Open the inline steering composer and route subsequent input to it. */
225
228
  openComposer() {
226
- const input = new Input();
229
+ const input = new pi_tui_1.Input();
227
230
  input.focused = true;
228
231
  input.onSubmit = (value) => {
229
232
  const message = value.trim();
@@ -251,7 +254,7 @@ export class ConversationViewer {
251
254
  viewportHeight() {
252
255
  // Cap mirrors the overlay's maxHeight — otherwise the viewer would render
253
256
  // more lines than the overlay shows and clip the footer.
254
- const maxRows = Math.floor((this.tui.terminal.rows * VIEWPORT_HEIGHT_PCT) / 100);
257
+ const maxRows = Math.floor((this.tui.terminal.rows * exports.VIEWPORT_HEIGHT_PCT) / 100);
255
258
  return Math.max(MIN_VIEWPORT, maxRows - this.chromeLines());
256
259
  }
257
260
  chromeLines() {
@@ -259,7 +262,7 @@ export class ConversationViewer {
259
262
  return CHROME_LINES_BASE + (this.invocationLine() ? 1 : 0) + (this.composer ? 1 : 0);
260
263
  }
261
264
  invocationLine() {
262
- const { modelName, tags } = buildInvocationTags(this.record.invocation);
265
+ const { modelName, tags } = (0, agent_widget_js_1.buildInvocationTags)(this.record.invocation);
263
266
  const parts = modelName ? [modelName, ...tags] : tags;
264
267
  if (parts.length === 0)
265
268
  return undefined;
@@ -280,13 +283,13 @@ export class ConversationViewer {
280
283
  if (msg.role === "user") {
281
284
  const text = typeof msg.content === "string"
282
285
  ? msg.content
283
- : extractText(msg.content);
286
+ : (0, context_js_1.extractText)(msg.content);
284
287
  if (!text.trim())
285
288
  continue;
286
289
  if (needsSeparator)
287
290
  lines.push(th.fg("dim", "───"));
288
291
  lines.push(th.fg("accent", "[User]"));
289
- for (const line of wrapTextWithAnsi(text.trim(), width)) {
292
+ for (const line of (0, pi_tui_1.wrapTextWithAnsi)(text.trim(), width)) {
290
293
  lines.push(line);
291
294
  }
292
295
  }
@@ -304,23 +307,23 @@ export class ConversationViewer {
304
307
  lines.push(th.fg("dim", "───"));
305
308
  lines.push(th.bold("[Assistant]"));
306
309
  if (textParts.length > 0) {
307
- for (const line of wrapTextWithAnsi(textParts.join("\n").trim(), width)) {
310
+ for (const line of (0, pi_tui_1.wrapTextWithAnsi)(textParts.join("\n").trim(), width)) {
308
311
  lines.push(line);
309
312
  }
310
313
  }
311
314
  for (const name of toolCalls) {
312
- lines.push(truncateToWidth(th.fg("muted", ` [Tool: ${name}]`), width));
315
+ lines.push((0, pi_tui_1.truncateToWidth)(th.fg("muted", ` [Tool: ${name}]`), width));
313
316
  }
314
317
  }
315
318
  else if (msg.role === "toolResult") {
316
- const text = extractText(msg.content);
319
+ const text = (0, context_js_1.extractText)(msg.content);
317
320
  const truncated = text.length > 500 ? text.slice(0, 500) + "... (truncated)" : text;
318
321
  if (!truncated.trim())
319
322
  continue;
320
323
  if (needsSeparator)
321
324
  lines.push(th.fg("dim", "───"));
322
325
  lines.push(th.fg("dim", "[Result]"));
323
- for (const line of wrapTextWithAnsi(truncated.trim(), width)) {
326
+ for (const line of (0, pi_tui_1.wrapTextWithAnsi)(truncated.trim(), width)) {
324
327
  lines.push(th.fg("dim", line));
325
328
  }
326
329
  }
@@ -328,12 +331,12 @@ export class ConversationViewer {
328
331
  const bash = msg;
329
332
  if (needsSeparator)
330
333
  lines.push(th.fg("dim", "───"));
331
- lines.push(truncateToWidth(th.fg("muted", ` $ ${bash.command}`), width));
334
+ lines.push((0, pi_tui_1.truncateToWidth)(th.fg("muted", ` $ ${bash.command}`), width));
332
335
  if (bash.output?.trim()) {
333
336
  const out = bash.output.length > 500
334
337
  ? bash.output.slice(0, 500) + "... (truncated)"
335
338
  : bash.output;
336
- for (const line of wrapTextWithAnsi(out.trim(), width)) {
339
+ for (const line of (0, pi_tui_1.wrapTextWithAnsi)(out.trim(), width)) {
337
340
  lines.push(th.fg("dim", line));
338
341
  }
339
342
  }
@@ -345,10 +348,11 @@ export class ConversationViewer {
345
348
  }
346
349
  // Streaming indicator for running agents
347
350
  if (this.record.status === "running" && this.activity) {
348
- const act = describeActivity(this.activity.activeTools, this.activity.responseText);
351
+ const act = (0, agent_widget_js_1.describeActivity)(this.activity.activeTools, this.activity.responseText);
349
352
  lines.push("");
350
- lines.push(truncateToWidth(th.fg("accent", "▍ ") + th.fg("dim", act), width));
353
+ lines.push((0, pi_tui_1.truncateToWidth)(th.fg("accent", "▍ ") + th.fg("dim", act), width));
351
354
  }
352
- return lines.map(l => truncateToWidth(l, width));
355
+ return lines.map(l => (0, pi_tui_1.truncateToWidth)(l, width));
353
356
  }
354
357
  }
358
+ exports.ConversationViewer = ConversationViewer;