@esso0428/pi-subagents 0.15.5 → 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 -22
- 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 -286
- 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 -89
- 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/ui/fleet-list.d.ts
CHANGED
|
@@ -4,14 +4,17 @@
|
|
|
4
4
|
* Shows `main` + each running/queued subagent as a navigable list. Pressing ↓ (or
|
|
5
5
|
* ←) at an empty prompt activates the list; ↑/↓ move the selection (filled ● marker),
|
|
6
6
|
* Enter opens the selected agent's live conversation overlay, Esc returns to the prompt.
|
|
7
|
-
* A viewer stays open when its agent finishes
|
|
7
|
+
* A viewer stays open when its agent finishes so the final output remains readable.
|
|
8
8
|
*
|
|
9
9
|
* Mechanics (see plan): the list is a `belowEditor` widget (render-only), and ALL key
|
|
10
10
|
* handling goes through `onTerminalInput` — which fires before the focused editor and
|
|
11
11
|
* can `consume` keys — gated on `getEditorText() === ""` so normal typing is untouched.
|
|
12
12
|
*/
|
|
13
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
13
14
|
import type { AgentManager } from "../agent-manager.js";
|
|
14
15
|
import { type AgentActivity, type Theme } from "./agent-widget.js";
|
|
16
|
+
/** Maximum total rows contributed by FleetView, including hint and indicators. */
|
|
17
|
+
export declare const FLEET_MAX_RENDER_ROWS = 6;
|
|
15
18
|
/** Minimal UI surface the FleetView needs from `ctx.ui` (structural subset). */
|
|
16
19
|
export type FleetUICtx = {
|
|
17
20
|
setWidget(key: string, content: undefined | ((tui: any, theme: Theme) => {
|
|
@@ -41,14 +44,27 @@ export type FleetUICtx = {
|
|
|
41
44
|
export declare function formatFleetElapsed(ms: number): string;
|
|
42
45
|
/** `↓ 13.1k tokens` — down-arrow prefix, compact magnitude, plural "tokens". */
|
|
43
46
|
export declare function formatFleetTokens(count: number): string;
|
|
47
|
+
/**
|
|
48
|
+
* Calculate a bounded agent slice for the complete logical roster.
|
|
49
|
+
* `selectedRosterIndex` includes main at index 0; `end` is exclusive.
|
|
50
|
+
*/
|
|
51
|
+
export declare function calculateFleetAgentWindow(agentCount: number, selectedRosterIndex: number): {
|
|
52
|
+
start: number;
|
|
53
|
+
end: number;
|
|
54
|
+
hiddenAbove: number;
|
|
55
|
+
hiddenBelow: number;
|
|
56
|
+
};
|
|
44
57
|
export declare class FleetList {
|
|
45
58
|
private manager;
|
|
46
59
|
private agentActivity;
|
|
47
60
|
private getCwd;
|
|
61
|
+
private pi?;
|
|
62
|
+
private getCtx;
|
|
48
63
|
private ui;
|
|
49
64
|
private tui;
|
|
50
65
|
private inputUnsub;
|
|
51
66
|
private widgetRegistered;
|
|
67
|
+
private lastRosterKey;
|
|
52
68
|
private timer;
|
|
53
69
|
private enabled;
|
|
54
70
|
/** Whether arrow keys currently navigate the list (vs. flow to the editor). */
|
|
@@ -58,7 +74,7 @@ export declare class FleetList {
|
|
|
58
74
|
/** Set while a conversation overlay is open; calling it closes the overlay. */
|
|
59
75
|
private viewerClose;
|
|
60
76
|
private viewingAgentId;
|
|
61
|
-
constructor(manager: AgentManager, agentActivity: Map<string, AgentActivity>, getCwd?: () => string | undefined);
|
|
77
|
+
constructor(manager: AgentManager, agentActivity: Map<string, AgentActivity>, getCwd?: () => string | undefined, pi?: ExtensionAPI | undefined, getCtx?: () => ExtensionContext | undefined);
|
|
62
78
|
setEnabled(enabled: boolean): void;
|
|
63
79
|
/** Capture the UI context and (re)register the global input handler. */
|
|
64
80
|
setUICtx(ui: FleetUICtx): void;
|
|
@@ -66,21 +82,17 @@ export declare class FleetList {
|
|
|
66
82
|
ensureTimer(): void;
|
|
67
83
|
/**
|
|
68
84
|
* Called when an agent finishes. The viewer (if open on it) stays open so the
|
|
69
|
-
* final output remains readable
|
|
85
|
+
* final output remains readable; refresh the active-only roster.
|
|
70
86
|
*/
|
|
71
87
|
onAgentFinished(_id: string): void;
|
|
72
88
|
dispose(): void;
|
|
73
89
|
/** Re-register/refresh the below-editor widget; clears it when no agents remain. */
|
|
74
90
|
update(): void;
|
|
75
91
|
/**
|
|
76
|
-
*
|
|
77
|
-
* started sooner sit at the top.
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* Included: running/queued, plus the agent currently being viewed, plus
|
|
81
|
-
* recently-finished ones (they linger briefly before dropping out).
|
|
82
|
-
* Pending agents with no session yet are hidden until they start.
|
|
83
|
-
* (`listAgents()` is newest-first, so we re-sort.)
|
|
92
|
+
* Active agents shown in the list, ordered earliest-launched first so the
|
|
93
|
+
* ones you started sooner sit at the top. Pending agents with no session yet
|
|
94
|
+
* are hidden until they start. (`listAgents()` is newest-first, so we
|
|
95
|
+
* re-sort.)
|
|
84
96
|
*/
|
|
85
97
|
private agentRecords;
|
|
86
98
|
private roster;
|
package/dist/ui/fleet-list.js
CHANGED
|
@@ -1,39 +1,34 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* fleet-list.ts — Claude Code-style "FleetView" list rendered below the editor.
|
|
4
3
|
*
|
|
5
4
|
* Shows `main` + each running/queued subagent as a navigable list. Pressing ↓ (or
|
|
6
5
|
* ←) at an empty prompt activates the list; ↑/↓ move the selection (filled ● marker),
|
|
7
6
|
* Enter opens the selected agent's live conversation overlay, Esc returns to the prompt.
|
|
8
|
-
* A viewer stays open when its agent finishes
|
|
7
|
+
* A viewer stays open when its agent finishes so the final output remains readable.
|
|
9
8
|
*
|
|
10
9
|
* Mechanics (see plan): the list is a `belowEditor` widget (render-only), and ALL key
|
|
11
10
|
* handling goes through `onTerminalInput` — which fires before the focused editor and
|
|
12
11
|
* can `consume` keys — gated on `getEditorText() === ""` so normal typing is untouched.
|
|
13
12
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const agent_history_js_1 = require("../agent-history.js");
|
|
20
|
-
const usage_js_1 = require("../usage.js");
|
|
21
|
-
const agent_widget_js_1 = require("./agent-widget.js");
|
|
22
|
-
const conversation_viewer_js_1 = require("./conversation-viewer.js");
|
|
13
|
+
import { Editor, isKeyRelease, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
14
|
+
import { canOpenActiveAgent } from "../agent-history-list.js";
|
|
15
|
+
import { getLifetimeTotal } from "../usage.js";
|
|
16
|
+
import { getDisplayName } from "./agent-widget.js";
|
|
17
|
+
import { ConversationViewer, VIEWPORT_HEIGHT_PCT } from "./conversation-viewer.js";
|
|
23
18
|
/** Widget key for the below-editor fleet list. */
|
|
24
19
|
const FLEET_KEY = "fleet";
|
|
25
|
-
/**
|
|
26
|
-
const
|
|
20
|
+
/** Maximum total rows contributed by FleetView, including hint and indicators. */
|
|
21
|
+
export const FLEET_MAX_RENDER_ROWS = 6;
|
|
22
|
+
const FLEET_BASE_ROWS = 2; // hint + main
|
|
23
|
+
const FLEET_BODY_ROWS = FLEET_MAX_RENDER_ROWS - FLEET_BASE_ROWS;
|
|
27
24
|
/** Re-render cadence so elapsed/token stats tick while agents run. */
|
|
28
25
|
const TICK_MS = 200;
|
|
29
|
-
/** How long a finished agent lingers in the list before it drops out. */
|
|
30
|
-
const FINISHED_LINGER_MS = 4000;
|
|
31
26
|
/** `11s` — integer seconds, no decimal/suffix (matches Claude Code, unlike formatMs). */
|
|
32
|
-
function formatFleetElapsed(ms) {
|
|
27
|
+
export function formatFleetElapsed(ms) {
|
|
33
28
|
return `${Math.max(0, Math.round(ms / 1000))}s`;
|
|
34
29
|
}
|
|
35
30
|
/** `↓ 13.1k tokens` — down-arrow prefix, compact magnitude, plural "tokens". */
|
|
36
|
-
function formatFleetTokens(count) {
|
|
31
|
+
export function formatFleetTokens(count) {
|
|
37
32
|
let compact;
|
|
38
33
|
if (count >= 1_000_000)
|
|
39
34
|
compact = `${(count / 1_000_000).toFixed(1)}M`;
|
|
@@ -43,26 +38,45 @@ function formatFleetTokens(count) {
|
|
|
43
38
|
compact = `${count}`;
|
|
44
39
|
return `↓ ${compact} tokens`;
|
|
45
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Calculate a bounded agent slice for the complete logical roster.
|
|
43
|
+
* `selectedRosterIndex` includes main at index 0; `end` is exclusive.
|
|
44
|
+
*/
|
|
45
|
+
export function calculateFleetAgentWindow(agentCount, selectedRosterIndex) {
|
|
46
|
+
const count = Math.max(0, Math.floor(agentCount));
|
|
47
|
+
if (count === 0)
|
|
48
|
+
return { start: 0, end: 0, hiddenAbove: 0, hiddenBelow: 0 };
|
|
49
|
+
const selectedAgent = Math.min(count - 1, Math.max(0, Math.floor(selectedRosterIndex) - 1));
|
|
50
|
+
const reserveAbove = selectedAgent > 0 ? 1 : 0;
|
|
51
|
+
const reserveBelow = selectedAgent < count - 1 ? 1 : 0;
|
|
52
|
+
const visibleCount = Math.max(1, FLEET_BODY_ROWS - reserveAbove - reserveBelow);
|
|
53
|
+
const start = Math.min(Math.max(0, selectedAgent - visibleCount + 1), Math.max(0, count - visibleCount));
|
|
54
|
+
const end = Math.min(count, start + visibleCount);
|
|
55
|
+
return { start, end, hiddenAbove: start, hiddenBelow: count - end };
|
|
56
|
+
}
|
|
46
57
|
/**
|
|
47
58
|
* Place `right` flush to `width`, truncating `left` first so the stats survive.
|
|
48
59
|
* The final clamp guarantees the line never exceeds `width` (which would wrap and
|
|
49
60
|
* desync pi's line-diff → flicker) even on a terminal too narrow for the stats.
|
|
50
61
|
*/
|
|
51
62
|
function rightAlign(left, right, width) {
|
|
52
|
-
const rightW =
|
|
63
|
+
const rightW = visibleWidth(right);
|
|
53
64
|
const maxLeft = Math.max(0, width - rightW - 1);
|
|
54
|
-
const leftClamped =
|
|
55
|
-
const gap = Math.max(1, width -
|
|
56
|
-
return
|
|
65
|
+
const leftClamped = truncateToWidth(left, maxLeft);
|
|
66
|
+
const gap = Math.max(1, width - visibleWidth(leftClamped) - rightW);
|
|
67
|
+
return truncateToWidth(leftClamped + " ".repeat(gap) + right, width);
|
|
57
68
|
}
|
|
58
|
-
class FleetList {
|
|
69
|
+
export class FleetList {
|
|
59
70
|
manager;
|
|
60
71
|
agentActivity;
|
|
61
72
|
getCwd;
|
|
73
|
+
pi;
|
|
74
|
+
getCtx;
|
|
62
75
|
ui;
|
|
63
76
|
tui;
|
|
64
77
|
inputUnsub;
|
|
65
78
|
widgetRegistered = false;
|
|
79
|
+
lastRosterKey;
|
|
66
80
|
timer;
|
|
67
81
|
enabled = true;
|
|
68
82
|
/** Whether arrow keys currently navigate the list (vs. flow to the editor). */
|
|
@@ -72,10 +86,12 @@ class FleetList {
|
|
|
72
86
|
/** Set while a conversation overlay is open; calling it closes the overlay. */
|
|
73
87
|
viewerClose;
|
|
74
88
|
viewingAgentId;
|
|
75
|
-
constructor(manager, agentActivity, getCwd = () => undefined) {
|
|
89
|
+
constructor(manager, agentActivity, getCwd = () => undefined, pi, getCtx = () => undefined) {
|
|
76
90
|
this.manager = manager;
|
|
77
91
|
this.agentActivity = agentActivity;
|
|
78
92
|
this.getCwd = getCwd;
|
|
93
|
+
this.pi = pi;
|
|
94
|
+
this.getCtx = getCtx;
|
|
79
95
|
}
|
|
80
96
|
// ---- Lifecycle ----
|
|
81
97
|
setEnabled(enabled) {
|
|
@@ -93,6 +109,7 @@ class FleetList {
|
|
|
93
109
|
this.inputUnsub?.();
|
|
94
110
|
this.ui = ui;
|
|
95
111
|
this.widgetRegistered = false;
|
|
112
|
+
this.lastRosterKey = undefined;
|
|
96
113
|
this.tui = undefined;
|
|
97
114
|
this.inputUnsub = ui.onTerminalInput(data => this.handleKey(data));
|
|
98
115
|
}
|
|
@@ -103,7 +120,7 @@ class FleetList {
|
|
|
103
120
|
}
|
|
104
121
|
/**
|
|
105
122
|
* Called when an agent finishes. The viewer (if open on it) stays open so the
|
|
106
|
-
* final output remains readable
|
|
123
|
+
* final output remains readable; refresh the active-only roster.
|
|
107
124
|
*/
|
|
108
125
|
onAgentFinished(_id) {
|
|
109
126
|
this.update();
|
|
@@ -123,6 +140,7 @@ class FleetList {
|
|
|
123
140
|
if (this.ui && this.widgetRegistered)
|
|
124
141
|
this.ui.setWidget(FLEET_KEY, undefined);
|
|
125
142
|
this.widgetRegistered = false;
|
|
143
|
+
this.lastRosterKey = undefined;
|
|
126
144
|
this.tui = undefined;
|
|
127
145
|
this.active = false;
|
|
128
146
|
// Null last so a `viewerClose()` microtask above can't re-register the widget.
|
|
@@ -132,7 +150,11 @@ class FleetList {
|
|
|
132
150
|
update() {
|
|
133
151
|
if (!this.ui)
|
|
134
152
|
return;
|
|
135
|
-
const
|
|
153
|
+
const records = this.agentRecords();
|
|
154
|
+
const hasAgents = this.enabled && records.length > 0;
|
|
155
|
+
const rosterKey = records.map(record => record.id).join("\0");
|
|
156
|
+
const rosterChanged = rosterKey !== this.lastRosterKey;
|
|
157
|
+
this.lastRosterKey = rosterKey;
|
|
136
158
|
if (!hasAgents) {
|
|
137
159
|
if (this.widgetRegistered) {
|
|
138
160
|
this.ui.setWidget(FLEET_KEY, undefined);
|
|
@@ -145,6 +167,7 @@ class FleetList {
|
|
|
145
167
|
}
|
|
146
168
|
this.active = false;
|
|
147
169
|
this.selectedIndex = 0;
|
|
170
|
+
this.lastRosterKey = undefined;
|
|
148
171
|
return;
|
|
149
172
|
}
|
|
150
173
|
this.clampSelection();
|
|
@@ -160,31 +183,19 @@ class FleetList {
|
|
|
160
183
|
this.widgetRegistered = true;
|
|
161
184
|
}
|
|
162
185
|
else {
|
|
163
|
-
this.tui?.requestRender();
|
|
186
|
+
this.tui?.requestRender(rosterChanged);
|
|
164
187
|
}
|
|
165
188
|
}
|
|
166
189
|
// ---- Roster ----
|
|
167
190
|
/**
|
|
168
|
-
*
|
|
169
|
-
* started sooner sit at the top.
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
* Included: running/queued, plus the agent currently being viewed, plus
|
|
173
|
-
* recently-finished ones (they linger briefly before dropping out).
|
|
174
|
-
* Pending agents with no session yet are hidden until they start.
|
|
175
|
-
* (`listAgents()` is newest-first, so we re-sort.)
|
|
191
|
+
* Active agents shown in the list, ordered earliest-launched first so the
|
|
192
|
+
* ones you started sooner sit at the top. Pending agents with no session yet
|
|
193
|
+
* are hidden until they start. (`listAgents()` is newest-first, so we
|
|
194
|
+
* re-sort.)
|
|
176
195
|
*/
|
|
177
196
|
agentRecords() {
|
|
178
|
-
const now = Date.now();
|
|
179
197
|
return this.manager.listAgents()
|
|
180
|
-
.filter(
|
|
181
|
-
const live = a.session && (a.status === "running" || a.status === "queued"
|
|
182
|
-
|| a.id === this.viewingAgentId
|
|
183
|
-
|| (a.completedAt != null && now - a.completedAt < FINISHED_LINGER_MS));
|
|
184
|
-
const cwd = this.getCwd();
|
|
185
|
-
const history = cwd !== undefined && (0, agent_history_js_1.hasAgentHistory)(cwd, a.transcriptPath);
|
|
186
|
-
return !!live || history;
|
|
187
|
-
})
|
|
198
|
+
.filter(record => canOpenActiveAgent(record))
|
|
188
199
|
.sort((a, b) => a.startedAt - b.startedAt);
|
|
189
200
|
}
|
|
190
201
|
roster() {
|
|
@@ -205,7 +216,7 @@ class FleetList {
|
|
|
205
216
|
// Input listeners receive BOTH key-press and key-release (the kitty protocol
|
|
206
217
|
// emits both, and matchesKey matches either) — act on press only, or every
|
|
207
218
|
// tap would move/fire twice. Repeats still pass through for held-key nav.
|
|
208
|
-
if (
|
|
219
|
+
if (isKeyRelease(data))
|
|
209
220
|
return undefined;
|
|
210
221
|
// While an overlay is open, let it own all input.
|
|
211
222
|
if (this.viewerClose)
|
|
@@ -221,7 +232,7 @@ class FleetList {
|
|
|
221
232
|
}
|
|
222
233
|
if (!this.active) {
|
|
223
234
|
// Activate: ↓ or ← at an empty prompt moves focus into the list.
|
|
224
|
-
const isActivator =
|
|
235
|
+
const isActivator = matchesKey(data, "down") || matchesKey(data, "left");
|
|
225
236
|
if (isActivator && this.agentRecords().length > 0 && this.ui.getEditorText() === "") {
|
|
226
237
|
this.active = true;
|
|
227
238
|
this.selectedIndex = 0;
|
|
@@ -231,13 +242,13 @@ class FleetList {
|
|
|
231
242
|
return undefined;
|
|
232
243
|
}
|
|
233
244
|
// Active — arrows navigate, Enter opens, Esc / Up-past-top exits.
|
|
234
|
-
if (
|
|
245
|
+
if (matchesKey(data, "down")) {
|
|
235
246
|
const max = this.roster().length - 1;
|
|
236
247
|
this.selectedIndex = Math.min(max, this.selectedIndex + 1);
|
|
237
248
|
this.update();
|
|
238
249
|
return { consume: true };
|
|
239
250
|
}
|
|
240
|
-
if (
|
|
251
|
+
if (matchesKey(data, "up")) {
|
|
241
252
|
if (this.selectedIndex === 0) {
|
|
242
253
|
this.deactivate();
|
|
243
254
|
return { consume: true };
|
|
@@ -246,11 +257,11 @@ class FleetList {
|
|
|
246
257
|
this.update();
|
|
247
258
|
return { consume: true };
|
|
248
259
|
}
|
|
249
|
-
if (
|
|
260
|
+
if (matchesKey(data, "escape")) {
|
|
250
261
|
this.deactivate();
|
|
251
262
|
return { consume: true };
|
|
252
263
|
}
|
|
253
|
-
if (
|
|
264
|
+
if (matchesKey(data, Key.enter)) {
|
|
254
265
|
this.openSelected();
|
|
255
266
|
return { consume: true };
|
|
256
267
|
}
|
|
@@ -268,7 +279,7 @@ class FleetList {
|
|
|
268
279
|
*/
|
|
269
280
|
editorHasFocus() {
|
|
270
281
|
const focused = this.tui?.focusedComponent;
|
|
271
|
-
return focused == null || focused instanceof
|
|
282
|
+
return focused == null || focused instanceof Editor;
|
|
272
283
|
}
|
|
273
284
|
deactivate() {
|
|
274
285
|
this.active = false;
|
|
@@ -285,28 +296,28 @@ class FleetList {
|
|
|
285
296
|
const record = entry.record;
|
|
286
297
|
if (!this.ui)
|
|
287
298
|
return;
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
299
|
+
if (!canOpenActiveAgent(record) || !record.session) {
|
|
300
|
+
this.ui.notify(`Agent is ${record.status} — no live session available.`, "info");
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const session = record.session;
|
|
304
|
+
const ctx = this.getCtx();
|
|
305
|
+
const pi = this.pi;
|
|
306
|
+
if (!ctx || !pi) {
|
|
307
|
+
this.ui.notify("Agent conversation is unavailable — no active session.", "info");
|
|
296
308
|
return;
|
|
297
309
|
}
|
|
298
310
|
const activity = this.agentActivity.get(record.id);
|
|
299
|
-
const isLive = record.session !== undefined;
|
|
300
311
|
this.viewingAgentId = record.id;
|
|
301
312
|
void this.ui.custom((tui, theme, keybindings, done) => {
|
|
302
313
|
this.viewerClose = () => done(undefined);
|
|
303
|
-
return new
|
|
314
|
+
return new ConversationViewer(tui, session, record, activity, theme, done, () => {
|
|
304
315
|
if (this.manager.abort(record.id))
|
|
305
316
|
this.ui?.notify(`Stopped "${record.description}".`, "info");
|
|
306
|
-
}
|
|
317
|
+
}, keybindings, (message) => this.manager.steer(record.id, message), { pi, ctx, readOnly: false });
|
|
307
318
|
}, {
|
|
308
319
|
overlay: true,
|
|
309
|
-
overlayOptions: { anchor: "center", width: "90%", maxHeight: `${
|
|
320
|
+
overlayOptions: { anchor: "center", width: "90%", maxHeight: `${VIEWPORT_HEIGHT_PCT}%` },
|
|
310
321
|
}).then(() => this.clearViewer(), () => this.clearViewer());
|
|
311
322
|
}
|
|
312
323
|
/** Reset overlay state and return to the list (on close, auto-close, or error). */
|
|
@@ -336,32 +347,28 @@ class FleetList {
|
|
|
336
347
|
? "↑↓ select · enter view · esc back"
|
|
337
348
|
: "esc to interrupt · ← for agents · ↓ to manage";
|
|
338
349
|
const lines = [];
|
|
339
|
-
lines.push(
|
|
340
|
-
lines.push(
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const hiddenBelow = agents.length - (start + visible);
|
|
347
|
-
if (start > 0)
|
|
348
|
-
lines.push(rightAlign("", theme.fg("dim", `↑ ${start} more`), width));
|
|
349
|
-
for (let a = start; a < start + visible; a++) {
|
|
350
|
+
lines.push(truncateToWidth(" " + theme.fg("dim", hint), width));
|
|
351
|
+
lines.push(truncateToWidth(` ${this.bullet(0, sel, theme)} main`, width));
|
|
352
|
+
const window = calculateFleetAgentWindow(agents.length, sel);
|
|
353
|
+
if (window.hiddenAbove > 0) {
|
|
354
|
+
lines.push(rightAlign("", theme.fg("dim", `↑ ${window.hiddenAbove} more`), width));
|
|
355
|
+
}
|
|
356
|
+
for (let a = window.start; a < window.end; a++) {
|
|
350
357
|
lines.push(this.renderAgentRow(a + 1, sel, agents[a].record, width, theme));
|
|
351
358
|
}
|
|
352
|
-
if (hiddenBelow > 0)
|
|
353
|
-
lines.push(rightAlign("", theme.fg("dim", `↓ ${hiddenBelow} more`), width));
|
|
359
|
+
if (window.hiddenBelow > 0) {
|
|
360
|
+
lines.push(rightAlign("", theme.fg("dim", `↓ ${window.hiddenBelow} more`), width));
|
|
361
|
+
}
|
|
354
362
|
return lines;
|
|
355
363
|
}
|
|
356
364
|
bullet(rosterIndex, sel, theme) {
|
|
357
365
|
return rosterIndex === sel ? theme.fg("accent", "●") : theme.fg("dim", "○");
|
|
358
366
|
}
|
|
359
367
|
renderAgentRow(rosterIndex, sel, record, width, theme) {
|
|
360
|
-
const left = ` ${this.bullet(rosterIndex, sel, theme)} ${theme.fg("muted",
|
|
361
|
-
const tokens =
|
|
368
|
+
const left = ` ${this.bullet(rosterIndex, sel, theme)} ${theme.fg("muted", getDisplayName(record.type))} ${record.description}`;
|
|
369
|
+
const tokens = getLifetimeTotal(this.agentActivity.get(record.id)?.lifetimeUsage ?? record.lifetimeUsage);
|
|
362
370
|
const elapsedMs = (record.completedAt ?? Date.now()) - record.startedAt; // freezes once finished
|
|
363
371
|
const right = theme.fg("dim", `${formatFleetElapsed(elapsedMs)} · ${formatFleetTokens(tokens)}`);
|
|
364
372
|
return rightAlign(left, right, width);
|
|
365
373
|
}
|
|
366
374
|
}
|
|
367
|
-
exports.FleetList = FleetList;
|
package/dist/ui/schedule-menu.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* schedule-menu.ts — `/agents → Scheduled jobs` submenu.
|
|
4
3
|
*
|
|
@@ -8,8 +7,6 @@
|
|
|
8
7
|
* "I scheduled something dumb, get rid of it"). Add management surfaces here
|
|
9
8
|
* if real demand emerges.
|
|
10
9
|
*/
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.showSchedulesMenu = showSchedulesMenu;
|
|
13
10
|
/** Format an ISO timestamp as relative time ("in 4h", "2d ago", "—"). */
|
|
14
11
|
function relTime(iso, now = Date.now()) {
|
|
15
12
|
if (!iso)
|
|
@@ -72,7 +69,7 @@ function formatDetails(j, scheduler) {
|
|
|
72
69
|
* List scheduled jobs; selecting one opens a cancel-confirm with details.
|
|
73
70
|
* Returns when the user backs out or after a cancellation.
|
|
74
71
|
*/
|
|
75
|
-
async function showSchedulesMenu(ctx, scheduler) {
|
|
72
|
+
export async function showSchedulesMenu(ctx, scheduler) {
|
|
76
73
|
if (!scheduler.isActive()) {
|
|
77
74
|
ctx.ui.notify("Scheduler is not active in this session.", "warning");
|
|
78
75
|
return;
|
package/dist/ui/viewer-keys.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* viewer-keys.ts — Scroll key matchers for the conversation viewer.
|
|
4
3
|
*
|
|
@@ -6,15 +5,13 @@
|
|
|
6
5
|
* manager, falling back to the previous hardcoded keys otherwise. The viewer's
|
|
7
6
|
* k/j and shift+arrow aliases always work alongside whatever is bound.
|
|
8
7
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
function createViewerKeys(keybindings) {
|
|
13
|
-
const matches = (data, id, fallback) => keybindings ? keybindings.matches(data, id) : (0, pi_tui_1.matchesKey)(data, fallback);
|
|
8
|
+
import { matchesKey } from "@earendil-works/pi-tui";
|
|
9
|
+
export function createViewerKeys(keybindings) {
|
|
10
|
+
const matches = (data, id, fallback) => keybindings ? keybindings.matches(data, id) : matchesKey(data, fallback);
|
|
14
11
|
return {
|
|
15
|
-
scrollUp: (data) => matches(data, "tui.select.up", "up") ||
|
|
16
|
-
scrollDown: (data) => matches(data, "tui.select.down", "down") ||
|
|
17
|
-
pageUp: (data) => matches(data, "tui.select.pageUp", "pageUp") ||
|
|
18
|
-
pageDown: (data) => matches(data, "tui.select.pageDown", "pageDown") ||
|
|
12
|
+
scrollUp: (data) => matches(data, "tui.select.up", "up") || matchesKey(data, "k"),
|
|
13
|
+
scrollDown: (data) => matches(data, "tui.select.down", "down") || matchesKey(data, "j"),
|
|
14
|
+
pageUp: (data) => matches(data, "tui.select.pageUp", "pageUp") || matchesKey(data, "shift+up"),
|
|
15
|
+
pageDown: (data) => matches(data, "tui.select.pageDown", "pageDown") || matchesKey(data, "shift+down"),
|
|
19
16
|
};
|
|
20
17
|
}
|
package/dist/usage.js
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/** usage.ts — Token usage: shapes, accumulator operators, session-stats readers. */
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.getLifetimeTotal = getLifetimeTotal;
|
|
5
|
-
exports.addUsage = addUsage;
|
|
6
|
-
exports.getSessionTokens = getSessionTokens;
|
|
7
|
-
exports.getSessionContextPercent = getSessionContextPercent;
|
|
8
2
|
/** Sum of lifetime usage components, or 0 if undefined. */
|
|
9
|
-
function getLifetimeTotal(u) {
|
|
3
|
+
export function getLifetimeTotal(u) {
|
|
10
4
|
return u ? u.input + u.output + u.cacheWrite : 0;
|
|
11
5
|
}
|
|
12
6
|
/** Add a usage delta into a target accumulator (mutates target). */
|
|
13
|
-
function addUsage(into, delta) {
|
|
7
|
+
export function addUsage(into, delta) {
|
|
14
8
|
into.input += delta.input;
|
|
15
9
|
into.output += delta.output;
|
|
16
10
|
into.cacheWrite += delta.cacheWrite;
|
|
@@ -28,7 +22,7 @@ function addUsage(into, delta) {
|
|
|
28
22
|
* and so counts the cumulative cached prefix N times across N turns
|
|
29
23
|
* (issue #38).
|
|
30
24
|
*/
|
|
31
|
-
function getSessionTokens(session) {
|
|
25
|
+
export function getSessionTokens(session) {
|
|
32
26
|
if (!session)
|
|
33
27
|
return 0;
|
|
34
28
|
try {
|
|
@@ -43,7 +37,7 @@ function getSessionTokens(session) {
|
|
|
43
37
|
* Context-window utilization (0–100), or null when unavailable
|
|
44
38
|
* (no model contextWindow, or post-compaction before the next response).
|
|
45
39
|
*/
|
|
46
|
-
function getSessionContextPercent(session) {
|
|
40
|
+
export function getSessionContextPercent(session) {
|
|
47
41
|
if (!session)
|
|
48
42
|
return null;
|
|
49
43
|
try {
|