@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.
- package/CHANGELOG.md +10 -0
- package/README.md +2 -2
- package/dist/agent-manager.d.ts +8 -0
- package/dist/agent-manager.js +87 -19
- package/dist/agent-runner.js +85 -66
- package/dist/agent-types.js +45 -27
- package/dist/context.js +6 -2
- package/dist/cross-extension-rpc.js +9 -5
- package/dist/custom-agents.js +18 -15
- package/dist/default-agents.js +4 -1
- package/dist/enabled-models.js +16 -11
- package/dist/env.js +4 -1
- package/dist/group-join.js +5 -1
- package/dist/index.js +280 -221
- package/dist/invocation-config.js +6 -2
- package/dist/memory.js +34 -24
- package/dist/model-resolver.js +4 -1
- package/dist/nico-overrides.js +20 -14
- package/dist/output-file.js +21 -15
- package/dist/prompts.js +4 -1
- package/dist/schedule-store.js +21 -16
- package/dist/schedule.js +12 -8
- package/dist/settings.js +23 -15
- package/dist/skill-loader.js +23 -20
- package/dist/status-note.js +4 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.js +4 -1
- package/dist/ui/agent-widget.js +37 -23
- package/dist/ui/conversation-viewer.js +43 -39
- package/dist/ui/fleet-list.js +30 -24
- package/dist/ui/markdown-result.d.ts +3 -0
- package/dist/ui/markdown-result.js +53 -0
- package/dist/ui/schedule-menu.js +4 -1
- package/dist/ui/viewer-keys.js +10 -7
- package/dist/usage.js +10 -4
- package/dist/worktree.js +31 -26
- package/package.json +1 -1
- package/src/agent-manager.ts +72 -0
- package/src/agent-runner.ts +11 -3
- package/src/index.ts +39 -16
- package/src/types.ts +1 -0
- package/src/ui/markdown-result.ts +56 -0
- package/test/agent-manager-history.test.ts +84 -0
- package/test/ui/markdown-result.test.ts +45 -0
package/dist/skill-loader.js
CHANGED
|
@@ -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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
}
|
package/dist/status-note.js
CHANGED
|
@@ -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
|
-
|
|
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
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
|
-
|
|
8
|
+
exports.DEFAULT_AGENT_NAMES = ["general-purpose", "Explore", "Plan"];
|
package/dist/ui/agent-widget.js
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
27
|
+
exports.SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
15
28
|
/** Statuses that indicate an error/non-success outcome (used for linger behavior and icon rendering). */
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
80
|
+
function formatTurns(turnCount, maxTurns) {
|
|
68
81
|
return maxTurns != null ? `↻${turnCount}≤${maxTurns}` : `↻${turnCount}`;
|
|
69
82
|
}
|
|
70
83
|
/** Format milliseconds as human-readable duration. */
|
|
71
|
-
|
|
84
|
+
function formatMs(ms) {
|
|
72
85
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
73
86
|
}
|
|
74
87
|
/** Format duration from start/completed timestamps. */
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
17
|
-
|
|
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;
|