@esso0428/pi-subagents 0.15.4 → 0.15.6
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 +4 -17
- package/README.md +5 -5
- package/dist/agent-history-list.d.ts +18 -0
- package/dist/agent-history-list.js +68 -0
- package/dist/agent-history.d.ts +1 -1
- package/dist/agent-history.js +31 -34
- package/dist/agent-manager.d.ts +22 -8
- package/dist/agent-manager.js +100 -93
- package/dist/agent-runner.js +67 -86
- package/dist/agent-types.js +27 -45
- package/dist/context.js +2 -6
- package/dist/cross-extension-rpc.js +5 -9
- package/dist/custom-agents.js +15 -18
- package/dist/default-agents.js +1 -4
- package/dist/enabled-models.js +11 -16
- package/dist/env.js +1 -4
- package/dist/group-join.js +1 -5
- package/dist/index.js +273 -284
- package/dist/invocation-config.js +2 -6
- package/dist/memory.js +24 -34
- package/dist/model-resolver.js +1 -4
- package/dist/nico-overrides.js +14 -20
- package/dist/output-file.js +16 -22
- package/dist/prompts.js +1 -4
- package/dist/schedule-store.js +16 -21
- package/dist/schedule.js +8 -12
- package/dist/settings.js +15 -23
- package/dist/skill-loader.js +20 -23
- package/dist/status-note.js +1 -4
- package/dist/types.d.ts +0 -1
- package/dist/types.js +1 -4
- package/dist/ui/agent-widget.d.ts +1 -5
- package/dist/ui/agent-widget.js +25 -49
- package/dist/ui/conversation-blocks.d.ts +18 -0
- package/dist/ui/conversation-blocks.js +214 -0
- package/dist/ui/conversation-nvim.d.ts +7 -0
- package/dist/ui/conversation-nvim.js +116 -0
- package/dist/ui/conversation-search.d.ts +38 -0
- package/dist/ui/conversation-search.js +123 -0
- package/dist/ui/conversation-viewer.d.ts +55 -19
- package/dist/ui/conversation-viewer.js +398 -215
- package/dist/ui/fleet-list.d.ts +23 -11
- package/dist/ui/fleet-list.js +85 -78
- package/dist/ui/schedule-menu.js +1 -4
- package/dist/ui/viewer-keys.js +7 -10
- package/dist/usage.js +4 -10
- package/dist/worktree.js +26 -31
- package/package.json +1 -1
- package/src/agent-history-list.ts +91 -0
- package/src/agent-history.ts +12 -17
- package/src/agent-manager.ts +108 -81
- package/src/agent-runner.ts +5 -13
- package/src/index.ts +84 -85
- package/src/output-file.ts +1 -1
- package/src/types.ts +0 -1
- package/src/ui/agent-widget.ts +1 -8
- package/src/ui/conversation-blocks.ts +270 -0
- package/src/ui/conversation-nvim.ts +127 -0
- package/src/ui/conversation-search.ts +161 -0
- package/src/ui/conversation-viewer.ts +376 -205
- package/src/ui/fleet-list.ts +73 -52
- package/test/agent-history-list.test.ts +121 -0
- package/test/agent-manager-history.test.ts +59 -58
- package/test/agent-menu-history-navigation.test.ts +86 -0
- package/test/ui/conversation-blocks.test.ts +72 -0
- package/test/ui/conversation-search.test.ts +55 -0
- package/test/ui/conversation-viewer.test.ts +132 -0
- package/test/ui/fleet-list.test.ts +131 -0
- package/dist/ui/markdown-result.d.ts +0 -3
- package/dist/ui/markdown-result.js +0 -53
- package/src/ui/markdown-result.ts +0 -56
- package/test/agent-history.test.ts +0 -63
- package/test/ui/markdown-result.test.ts +0 -45
package/dist/settings.js
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
// Persistence for pi-subagents operational settings.
|
|
3
2
|
// - Global: ~/.pi/agent/subagents.json (via getAgentDir()) — manual defaults, never written here
|
|
4
3
|
// - Project: <cwd>/.pi/subagents.json — written by /agents → Settings; overrides global on load
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
exports.applySettings = applySettings;
|
|
9
|
-
exports.persistToastFor = persistToastFor;
|
|
10
|
-
exports.applyAndEmitLoaded = applyAndEmitLoaded;
|
|
11
|
-
exports.saveAndEmitChanged = saveAndEmitChanged;
|
|
12
|
-
const node_fs_1 = require("node:fs");
|
|
13
|
-
const node_path_1 = require("node:path");
|
|
14
|
-
const pi_coding_agent_1 = require("@earendil-works/pi-coding-agent");
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
15
7
|
const VALID_JOIN_MODES = new Set(["async", "group", "smart"]);
|
|
16
8
|
const VALID_TOOL_DESCRIPTION_MODES = new Set(["full", "compact", "custom"]);
|
|
17
9
|
const VALID_WIDGET_MODES = new Set(["all", "background", "off"]);
|
|
@@ -69,10 +61,10 @@ function sanitize(raw) {
|
|
|
69
61
|
return out;
|
|
70
62
|
}
|
|
71
63
|
function globalPath() {
|
|
72
|
-
return
|
|
64
|
+
return join(getAgentDir(), "subagents.json");
|
|
73
65
|
}
|
|
74
66
|
function projectPath(cwd) {
|
|
75
|
-
return
|
|
67
|
+
return join(cwd, ".pi", "subagents.json");
|
|
76
68
|
}
|
|
77
69
|
/**
|
|
78
70
|
* Read a settings file. Missing file is silent (returns `{}`). A file that
|
|
@@ -80,10 +72,10 @@ function projectPath(cwd) {
|
|
|
80
72
|
* silently reverted to defaults — and still returns `{}` so startup proceeds.
|
|
81
73
|
*/
|
|
82
74
|
function readSettingsFile(path) {
|
|
83
|
-
if (!
|
|
75
|
+
if (!existsSync(path))
|
|
84
76
|
return {};
|
|
85
77
|
try {
|
|
86
|
-
return sanitize(JSON.parse(
|
|
78
|
+
return sanitize(JSON.parse(readFileSync(path, "utf-8")));
|
|
87
79
|
}
|
|
88
80
|
catch (err) {
|
|
89
81
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -92,7 +84,7 @@ function readSettingsFile(path) {
|
|
|
92
84
|
}
|
|
93
85
|
}
|
|
94
86
|
/** Load merged settings: global provides defaults, project overrides. */
|
|
95
|
-
function loadSettings(cwd = process.cwd()) {
|
|
87
|
+
export function loadSettings(cwd = process.cwd()) {
|
|
96
88
|
return { ...readSettingsFile(globalPath()), ...readSettingsFile(projectPath(cwd)) };
|
|
97
89
|
}
|
|
98
90
|
/**
|
|
@@ -100,11 +92,11 @@ function loadSettings(cwd = process.cwd()) {
|
|
|
100
92
|
* Returns `true` on success, `false` if the write (or mkdir) failed so the
|
|
101
93
|
* caller can surface a warning — persistence isn't fatal but isn't silent.
|
|
102
94
|
*/
|
|
103
|
-
function saveSettings(s, cwd = process.cwd()) {
|
|
95
|
+
export function saveSettings(s, cwd = process.cwd()) {
|
|
104
96
|
const path = projectPath(cwd);
|
|
105
97
|
try {
|
|
106
|
-
|
|
107
|
-
|
|
98
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
99
|
+
writeFileSync(path, JSON.stringify(s, null, 2), "utf-8");
|
|
108
100
|
return true;
|
|
109
101
|
}
|
|
110
102
|
catch {
|
|
@@ -112,7 +104,7 @@ function saveSettings(s, cwd = process.cwd()) {
|
|
|
112
104
|
}
|
|
113
105
|
}
|
|
114
106
|
/** Apply persisted settings to the in-memory state via caller-supplied setters. */
|
|
115
|
-
function applySettings(s, appliers) {
|
|
107
|
+
export function applySettings(s, appliers) {
|
|
116
108
|
if (typeof s.maxConcurrent === "number")
|
|
117
109
|
appliers.setMaxConcurrent(s.maxConcurrent);
|
|
118
110
|
if (typeof s.defaultMaxTurns === "number")
|
|
@@ -141,7 +133,7 @@ function applySettings(s, appliers) {
|
|
|
141
133
|
* routes the success/failure of `saveSettings` into the right message + level
|
|
142
134
|
* so the UI layer (index.ts) stays a thin wire between input and notification.
|
|
143
135
|
*/
|
|
144
|
-
function persistToastFor(successMsg, persisted) {
|
|
136
|
+
export function persistToastFor(successMsg, persisted) {
|
|
145
137
|
return persisted
|
|
146
138
|
? { message: successMsg, level: "info" }
|
|
147
139
|
: { message: `${successMsg} (session only; failed to persist)`, level: "warning" };
|
|
@@ -151,7 +143,7 @@ function persistToastFor(successMsg, persisted) {
|
|
|
151
143
|
* `subagents:settings_loaded` lifecycle event. Returns the loaded settings so
|
|
152
144
|
* callers can log/inspect. Extension init wires this once.
|
|
153
145
|
*/
|
|
154
|
-
function applyAndEmitLoaded(appliers, emit, cwd = process.cwd()) {
|
|
146
|
+
export function applyAndEmitLoaded(appliers, emit, cwd = process.cwd()) {
|
|
155
147
|
const settings = loadSettings(cwd);
|
|
156
148
|
applySettings(settings, appliers);
|
|
157
149
|
emit("subagents:settings_loaded", { settings });
|
|
@@ -163,7 +155,7 @@ function applyAndEmitLoaded(appliers, emit, cwd = process.cwd()) {
|
|
|
163
155
|
* return the toast the UI should display. Event payload carries the `persisted`
|
|
164
156
|
* flag so listeners can react to write failures.
|
|
165
157
|
*/
|
|
166
|
-
function saveAndEmitChanged(snapshot, successMsg, emit, cwd = process.cwd()) {
|
|
158
|
+
export function saveAndEmitChanged(snapshot, successMsg, emit, cwd = process.cwd()) {
|
|
167
159
|
const persisted = saveSettings(snapshot, cwd);
|
|
168
160
|
emit("subagents:settings_changed", { settings: snapshot, persisted });
|
|
169
161
|
return persistToastFor(successMsg, persisted);
|
package/dist/skill-loader.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* skill-loader.ts — Preload named skills.
|
|
4
3
|
*
|
|
@@ -18,26 +17,24 @@
|
|
|
18
17
|
*
|
|
19
18
|
* Symlinks are rejected for security (deviation from Pi, which follows them).
|
|
20
19
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const memory_js_1 = require("./memory.js");
|
|
28
|
-
function preloadSkills(skillNames, cwd) {
|
|
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) {
|
|
29
26
|
return skillNames.map((name) => ({ name, content: loadSkillContent(name, cwd) }));
|
|
30
27
|
}
|
|
31
28
|
function loadSkillContent(name, cwd) {
|
|
32
|
-
if (
|
|
29
|
+
if (isUnsafeName(name)) {
|
|
33
30
|
return `(Skill "${name}" skipped: name contains path traversal characters)`;
|
|
34
31
|
}
|
|
35
32
|
const roots = [
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
41
38
|
];
|
|
42
39
|
for (const root of roots) {
|
|
43
40
|
const content = findInRoot(root, name);
|
|
@@ -47,16 +44,16 @@ function loadSkillContent(name, cwd) {
|
|
|
47
44
|
return `(Skill "${name}" not found in .pi/skills/, .agents/skills/, or global skill locations)`;
|
|
48
45
|
}
|
|
49
46
|
function findInRoot(root, name) {
|
|
50
|
-
if (
|
|
47
|
+
if (isSymlink(root))
|
|
51
48
|
return undefined; // reject symlinked roots entirely
|
|
52
|
-
const flat =
|
|
49
|
+
const flat = safeReadFile(join(root, `${name}.md`))?.trim();
|
|
53
50
|
if (flat !== undefined)
|
|
54
51
|
return flat;
|
|
55
52
|
return findSkillDirectory(root, name);
|
|
56
53
|
}
|
|
57
54
|
/** BFS under `root` for a directory named `name` containing `SKILL.md`. Pi-conforming filters. */
|
|
58
55
|
function findSkillDirectory(root, name) {
|
|
59
|
-
if (!
|
|
56
|
+
if (!existsSync(root))
|
|
60
57
|
return undefined;
|
|
61
58
|
const queue = [root];
|
|
62
59
|
while (queue.length > 0) {
|
|
@@ -65,7 +62,7 @@ function findSkillDirectory(root, name) {
|
|
|
65
62
|
continue;
|
|
66
63
|
let entries;
|
|
67
64
|
try {
|
|
68
|
-
entries =
|
|
65
|
+
entries = readdirSync(current, { withFileTypes: true });
|
|
69
66
|
}
|
|
70
67
|
catch {
|
|
71
68
|
continue;
|
|
@@ -78,12 +75,12 @@ function findSkillDirectory(root, name) {
|
|
|
78
75
|
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
79
76
|
continue;
|
|
80
77
|
// Symlinked dirs already filtered by entry.isDirectory() — Dirent uses lstat semantics.
|
|
81
|
-
const path =
|
|
82
|
-
const skillMd =
|
|
83
|
-
const isSkillDir =
|
|
78
|
+
const path = join(current, entry.name);
|
|
79
|
+
const skillMd = join(path, "SKILL.md");
|
|
80
|
+
const isSkillDir = existsSync(skillMd);
|
|
84
81
|
if (isSkillDir) {
|
|
85
82
|
if (entry.name === name) {
|
|
86
|
-
const content =
|
|
83
|
+
const content = safeReadFile(skillMd)?.trim();
|
|
87
84
|
if (content !== undefined)
|
|
88
85
|
return content;
|
|
89
86
|
}
|
package/dist/status-note.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* status-note.ts — Parenthetical status note appended to agent result text.
|
|
4
3
|
*/
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getStatusNote = getStatusNote;
|
|
7
4
|
/**
|
|
8
5
|
* Explicit parenthetical note for a non-normal terminal outcome, so the parent
|
|
9
6
|
* agent can't mistake partial output for a completed result. Empty string for a
|
|
@@ -13,7 +10,7 @@ exports.getStatusNote = getStatusNote;
|
|
|
13
10
|
* turn limit was hit) — the parent should treat human intervention differently
|
|
14
11
|
* from a budget cutoff.
|
|
15
12
|
*/
|
|
16
|
-
function getStatusNote(status) {
|
|
13
|
+
export function getStatusNote(status) {
|
|
17
14
|
switch (status) {
|
|
18
15
|
case "stopped":
|
|
19
16
|
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,8 +1,5 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* types.ts — Type definitions for the subagent system.
|
|
4
3
|
*/
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.DEFAULT_AGENT_NAMES = void 0;
|
|
7
4
|
/** Names of the three embedded default agents. */
|
|
8
|
-
|
|
5
|
+
export const DEFAULT_AGENT_NAMES = ["general-purpose", "Explore", "Plan"];
|
|
@@ -102,8 +102,6 @@ export declare class AgentWidget {
|
|
|
102
102
|
* extension supplies one defaulting to `"background"`.
|
|
103
103
|
*/
|
|
104
104
|
private mode;
|
|
105
|
-
/** Current project cwd used to validate reload-restored transcripts. */
|
|
106
|
-
private getCwd;
|
|
107
105
|
private uiCtx;
|
|
108
106
|
private widgetFrame;
|
|
109
107
|
private widgetInterval;
|
|
@@ -123,9 +121,7 @@ export declare class AgentWidget {
|
|
|
123
121
|
* `WidgetMode`. Defaults to `"all"` when a caller supplies no policy; the
|
|
124
122
|
* extension supplies one defaulting to `"background"`.
|
|
125
123
|
*/
|
|
126
|
-
mode?: () => WidgetMode
|
|
127
|
-
/** Current project cwd used to validate reload-restored transcripts. */
|
|
128
|
-
getCwd?: () => string | undefined);
|
|
124
|
+
mode?: () => WidgetMode);
|
|
129
125
|
/**
|
|
130
126
|
* Agents eligible for the widget, per the current `WidgetMode`:
|
|
131
127
|
* - `off`: none (the widget's existing empty-state path hides it entirely).
|
package/dist/ui/agent-widget.js
CHANGED
|
@@ -1,33 +1,19 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* agent-widget.ts — Persistent widget showing running/completed agents above the editor.
|
|
4
3
|
*
|
|
5
4
|
* Displays a tree of agents with animated spinners, live stats, and activity descriptions.
|
|
6
5
|
* Uses the callback form of setWidget for themed rendering.
|
|
7
6
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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_history_js_1 = require("../agent-history.js");
|
|
22
|
-
const agent_types_js_1 = require("../agent-types.js");
|
|
23
|
-
const usage_js_1 = require("../usage.js");
|
|
7
|
+
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
8
|
+
import { getConfig } from "../agent-types.js";
|
|
9
|
+
import { getLifetimeTotal, getSessionContextPercent } from "../usage.js";
|
|
24
10
|
// ---- Constants ----
|
|
25
11
|
/** Maximum number of rendered lines before overflow collapse kicks in. */
|
|
26
12
|
const MAX_WIDGET_LINES = 12;
|
|
27
13
|
/** Braille spinner frames for animated running indicator. */
|
|
28
|
-
|
|
14
|
+
export const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
29
15
|
/** Statuses that indicate an error/non-success outcome (used for linger behavior and icon rendering). */
|
|
30
|
-
|
|
16
|
+
export const ERROR_STATUSES = new Set(["error", "aborted", "steered", "stopped"]);
|
|
31
17
|
/** Tool name → human-readable action for activity descriptions. */
|
|
32
18
|
const TOOL_DISPLAY = {
|
|
33
19
|
read: "reading",
|
|
@@ -40,13 +26,13 @@ const TOOL_DISPLAY = {
|
|
|
40
26
|
};
|
|
41
27
|
// ---- Formatting helpers ----
|
|
42
28
|
/** Apply foreground styling while restoring it after nested foreground/full ANSI resets. */
|
|
43
|
-
function fgPreservingNestedStyles(theme, color, text) {
|
|
29
|
+
export function fgPreservingNestedStyles(theme, color, text) {
|
|
44
30
|
const styledEmpty = theme.fg(color, "");
|
|
45
31
|
const styleStart = styledEmpty.replace(/\u001b\[(?:0|39)m/g, "");
|
|
46
32
|
return theme.fg(color, text.replace(/\u001b\[(?:0|39)m/g, reset => `${reset}${styleStart}`));
|
|
47
33
|
}
|
|
48
34
|
/** Format a token count compactly: "33.8k token", "1.2M token". */
|
|
49
|
-
function formatTokens(count) {
|
|
35
|
+
export function formatTokens(count) {
|
|
50
36
|
if (count >= 1_000_000)
|
|
51
37
|
return `${(count / 1_000_000).toFixed(1)}M token`;
|
|
52
38
|
if (count >= 1_000)
|
|
@@ -63,7 +49,7 @@ function formatTokens(count) {
|
|
|
63
49
|
* "12.3k token (⇊2)" — compactions only (e.g. right after compact)
|
|
64
50
|
* "12.3k token (45% · ⇊2)" — both
|
|
65
51
|
*/
|
|
66
|
-
function formatSessionTokens(tokens, percent, theme, compactions = 0) {
|
|
52
|
+
export function formatSessionTokens(tokens, percent, theme, compactions = 0) {
|
|
67
53
|
const tokenStr = formatTokens(tokens);
|
|
68
54
|
const annot = [];
|
|
69
55
|
if (percent !== null) {
|
|
@@ -78,30 +64,30 @@ function formatSessionTokens(tokens, percent, theme, compactions = 0) {
|
|
|
78
64
|
return `${tokenStr} (${annot.join(" · ")})`;
|
|
79
65
|
}
|
|
80
66
|
/** Format turn count with optional max limit: "↻5≤30" or "↻5". */
|
|
81
|
-
function formatTurns(turnCount, maxTurns) {
|
|
67
|
+
export function formatTurns(turnCount, maxTurns) {
|
|
82
68
|
return maxTurns != null ? `↻${turnCount}≤${maxTurns}` : `↻${turnCount}`;
|
|
83
69
|
}
|
|
84
70
|
/** Format milliseconds as human-readable duration. */
|
|
85
|
-
function formatMs(ms) {
|
|
71
|
+
export function formatMs(ms) {
|
|
86
72
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
87
73
|
}
|
|
88
74
|
/** Format duration from start/completed timestamps. */
|
|
89
|
-
function formatDuration(startedAt, completedAt) {
|
|
75
|
+
export function formatDuration(startedAt, completedAt) {
|
|
90
76
|
if (completedAt)
|
|
91
77
|
return formatMs(completedAt - startedAt);
|
|
92
78
|
return `${formatMs(Date.now() - startedAt)} (running)`;
|
|
93
79
|
}
|
|
94
80
|
/** Get display name for any agent type (built-in or custom). */
|
|
95
|
-
function getDisplayName(type) {
|
|
96
|
-
return
|
|
81
|
+
export function getDisplayName(type) {
|
|
82
|
+
return getConfig(type).displayName;
|
|
97
83
|
}
|
|
98
84
|
/** Short label for prompt mode: "twin" for append, nothing for replace (the default). */
|
|
99
|
-
function getPromptModeLabel(type) {
|
|
100
|
-
const config =
|
|
85
|
+
export function getPromptModeLabel(type) {
|
|
86
|
+
const config = getConfig(type);
|
|
101
87
|
return config.promptMode === "append" ? "twin" : undefined;
|
|
102
88
|
}
|
|
103
89
|
/** Mode label is not included — callers add it where they want it. */
|
|
104
|
-
function buildInvocationTags(invocation) {
|
|
90
|
+
export function buildInvocationTags(invocation) {
|
|
105
91
|
const tags = [];
|
|
106
92
|
if (!invocation)
|
|
107
93
|
return { tags };
|
|
@@ -127,7 +113,7 @@ function truncateLine(text, len = 60) {
|
|
|
127
113
|
return line.slice(0, len) + "…";
|
|
128
114
|
}
|
|
129
115
|
/** Build a human-readable activity string from currently-running tools or response text. */
|
|
130
|
-
function describeActivity(activeTools, responseText) {
|
|
116
|
+
export function describeActivity(activeTools, responseText) {
|
|
131
117
|
if (activeTools.size > 0) {
|
|
132
118
|
const groups = new Map();
|
|
133
119
|
for (const toolName of activeTools.values()) {
|
|
@@ -152,11 +138,10 @@ function describeActivity(activeTools, responseText) {
|
|
|
152
138
|
return "thinking…";
|
|
153
139
|
}
|
|
154
140
|
// ---- Widget manager ----
|
|
155
|
-
class AgentWidget {
|
|
141
|
+
export class AgentWidget {
|
|
156
142
|
manager;
|
|
157
143
|
agentActivity;
|
|
158
144
|
mode;
|
|
159
|
-
getCwd;
|
|
160
145
|
uiCtx;
|
|
161
146
|
widgetFrame = 0;
|
|
162
147
|
widgetInterval;
|
|
@@ -176,13 +161,10 @@ class AgentWidget {
|
|
|
176
161
|
* `WidgetMode`. Defaults to `"all"` when a caller supplies no policy; the
|
|
177
162
|
* extension supplies one defaulting to `"background"`.
|
|
178
163
|
*/
|
|
179
|
-
mode = () => "all"
|
|
180
|
-
/** Current project cwd used to validate reload-restored transcripts. */
|
|
181
|
-
getCwd = () => undefined) {
|
|
164
|
+
mode = () => "all") {
|
|
182
165
|
this.manager = manager;
|
|
183
166
|
this.agentActivity = agentActivity;
|
|
184
167
|
this.mode = mode;
|
|
185
|
-
this.getCwd = getCwd;
|
|
186
168
|
}
|
|
187
169
|
/**
|
|
188
170
|
* Agents eligible for the widget, per the current `WidgetMode`:
|
|
@@ -196,12 +178,7 @@ class AgentWidget {
|
|
|
196
178
|
* - `all`: every agent.
|
|
197
179
|
*/
|
|
198
180
|
widgetAgents() {
|
|
199
|
-
const all = this.manager.listAgents()
|
|
200
|
-
if (record.status === "running" || record.status === "queued")
|
|
201
|
-
return true;
|
|
202
|
-
const cwd = this.getCwd();
|
|
203
|
-
return cwd !== undefined && (0, agent_history_js_1.hasAgentHistory)(cwd, record.transcriptPath);
|
|
204
|
-
});
|
|
181
|
+
const all = this.manager.listAgents();
|
|
205
182
|
switch (this.mode()) {
|
|
206
183
|
case "off": return [];
|
|
207
184
|
case "background": return all.filter(a => a.isBackground !== false);
|
|
@@ -240,7 +217,7 @@ class AgentWidget {
|
|
|
240
217
|
/** Check if a finished agent should still be shown in the widget. */
|
|
241
218
|
shouldShowFinished(agentId, status) {
|
|
242
219
|
const age = this.finishedTurnAge.get(agentId) ?? 0;
|
|
243
|
-
const maxAge =
|
|
220
|
+
const maxAge = ERROR_STATUSES.has(status) ? AgentWidget.ERROR_LINGER_TURNS : 1;
|
|
244
221
|
return age < maxAge;
|
|
245
222
|
}
|
|
246
223
|
/** Record an agent as finished (call when agent completes). */
|
|
@@ -304,10 +281,10 @@ class AgentWidget {
|
|
|
304
281
|
if (!hasActive && !hasFinished)
|
|
305
282
|
return [];
|
|
306
283
|
const w = tui.terminal.columns;
|
|
307
|
-
const truncate = (line) =>
|
|
284
|
+
const truncate = (line) => truncateToWidth(line, w);
|
|
308
285
|
const headingColor = hasActive ? "accent" : "dim";
|
|
309
286
|
const headingIcon = hasActive ? "●" : "○";
|
|
310
|
-
const frame =
|
|
287
|
+
const frame = SPINNER[this.widgetFrame % SPINNER.length];
|
|
311
288
|
// Build sections separately for overflow-aware assembly.
|
|
312
289
|
// Each running agent = 2 lines (header + activity), finished = 1 line, queued = 1 line.
|
|
313
290
|
const finishedLines = [];
|
|
@@ -322,8 +299,8 @@ class AgentWidget {
|
|
|
322
299
|
const elapsed = formatMs(Date.now() - a.startedAt);
|
|
323
300
|
const bg = this.agentActivity.get(a.id);
|
|
324
301
|
const toolUses = bg?.toolUses ?? a.toolUses;
|
|
325
|
-
const tokens =
|
|
326
|
-
const contextPercent =
|
|
302
|
+
const tokens = getLifetimeTotal(bg?.lifetimeUsage);
|
|
303
|
+
const contextPercent = getSessionContextPercent(bg?.session);
|
|
327
304
|
const tokenText = tokens > 0 ? formatSessionTokens(tokens, contextPercent, theme, a.compactionCount) : "";
|
|
328
305
|
const parts = [];
|
|
329
306
|
if (bg)
|
|
@@ -505,4 +482,3 @@ class AgentWidget {
|
|
|
505
482
|
this.lastStatusText = undefined;
|
|
506
483
|
}
|
|
507
484
|
}
|
|
508
|
-
exports.AgentWidget = AgentWidget;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AgentSession } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
export type ConversationBlockKind = "text" | "tool" | "meta";
|
|
3
|
+
export type ConversationBlockRole = "user" | "assistant" | "tool" | "custom" | "meta";
|
|
4
|
+
export interface ConversationBlock {
|
|
5
|
+
id: string;
|
|
6
|
+
kind: ConversationBlockKind;
|
|
7
|
+
role: ConversationBlockRole;
|
|
8
|
+
header: string;
|
|
9
|
+
markdown: string;
|
|
10
|
+
copyText: string;
|
|
11
|
+
fullText: string;
|
|
12
|
+
toolLine?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ConversationFormatOptions {
|
|
15
|
+
maxEntryChars?: number;
|
|
16
|
+
maxToolResultChars?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function formatConversationMessages(messages: AgentSession["messages"], options?: ConversationFormatOptions): ConversationBlock[];
|