@esso0428/pi-subagents 0.17.1 → 0.17.3
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 +19 -0
- package/README.md +5 -4
- package/dist/agent-history.d.ts +6 -0
- package/dist/agent-history.d.ts.map +1 -1
- package/dist/agent-history.js +38 -0
- package/dist/agent-history.js.map +1 -1
- package/dist/agent-manager.d.ts +23 -6
- package/dist/agent-manager.d.ts.map +1 -1
- package/dist/agent-manager.js +165 -27
- package/dist/agent-manager.js.map +1 -1
- package/dist/agent-recovery.d.ts +37 -0
- package/dist/agent-recovery.d.ts.map +1 -0
- package/dist/agent-recovery.js +171 -0
- package/dist/agent-recovery.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +88 -25
- package/dist/index.js.map +1 -1
- package/dist/ui/agent-widget.d.ts +4 -0
- package/dist/ui/agent-widget.d.ts.map +1 -1
- package/dist/ui/agent-widget.js +100 -23
- package/dist/ui/agent-widget.js.map +1 -1
- package/dist/ui/conversation-timeline.d.ts.map +1 -1
- package/dist/ui/conversation-timeline.js +1 -1
- package/dist/ui/conversation-timeline.js.map +1 -1
- package/dist/ui/conversation-viewer.d.ts +6 -0
- package/dist/ui/conversation-viewer.d.ts.map +1 -1
- package/dist/ui/conversation-viewer.js +79 -7
- package/dist/ui/conversation-viewer.js.map +1 -1
- package/package.json +1 -1
- package/src/agent-history.ts +38 -0
- package/src/agent-manager.ts +170 -28
- package/src/agent-recovery.ts +185 -0
- package/src/index.ts +103 -23
- package/src/ui/agent-widget.ts +102 -23
- package/src/ui/conversation-timeline.ts +4 -1
- package/src/ui/conversation-viewer.ts +97 -7
|
@@ -29,6 +29,44 @@ const MATCH_BG = "\x1b[48;5;238m";
|
|
|
29
29
|
const MATCH_BG_CURRENT = "\x1b[48;5;220m\x1b[30m";
|
|
30
30
|
const BG_RESET = "\x1b[49m\x1b[39m";
|
|
31
31
|
|
|
32
|
+
function renderScrollbarCell(
|
|
33
|
+
theme: Theme,
|
|
34
|
+
totalLines: number,
|
|
35
|
+
viewportHeight: number,
|
|
36
|
+
scrollOffset: number,
|
|
37
|
+
lineIndex: number,
|
|
38
|
+
active: boolean,
|
|
39
|
+
): string {
|
|
40
|
+
const total = Math.max(0, Math.floor(totalLines));
|
|
41
|
+
const viewport = Math.max(1, Math.floor(viewportHeight));
|
|
42
|
+
if (total <= viewport) return theme.fg("scrollbarTrack", " ");
|
|
43
|
+
const maxOffset = Math.max(0, total - viewport);
|
|
44
|
+
const offset = Math.max(0, Math.min(maxOffset, Math.floor(scrollOffset)));
|
|
45
|
+
const thumbHeight = Math.max(1, Math.round((viewport * viewport) / total));
|
|
46
|
+
const thumbStart = Math.round(((viewport - thumbHeight) * offset) / maxOffset);
|
|
47
|
+
const inThumb = lineIndex >= thumbStart && lineIndex < thumbStart + thumbHeight;
|
|
48
|
+
return theme.fg(
|
|
49
|
+
inThumb ? "scrollbarThumb" : "scrollbarTrack",
|
|
50
|
+
inThumb ? (active ? "█" : "┃") : "│",
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function renderScrollMoreRule(
|
|
55
|
+
theme: Theme,
|
|
56
|
+
width: number,
|
|
57
|
+
direction: "top" | "bottom",
|
|
58
|
+
hiddenLines: number,
|
|
59
|
+
): string {
|
|
60
|
+
const safeWidth = Math.max(1, Math.floor(width));
|
|
61
|
+
if (hiddenLines <= 0) return theme.fg("border", `├${"─".repeat(safeWidth)}┤`);
|
|
62
|
+
const label = `${direction === "top" ? "↑" : "↓"} ${hiddenLines} more`;
|
|
63
|
+
const labelWidth = visibleWidth(label);
|
|
64
|
+
if (labelWidth + 2 > safeWidth) return theme.fg("border", `├${"─".repeat(safeWidth)}┤`);
|
|
65
|
+
const left = Math.floor((safeWidth - labelWidth) / 2);
|
|
66
|
+
const right = Math.max(0, safeWidth - left - labelWidth);
|
|
67
|
+
return `${theme.fg("border", `├${"─".repeat(left)}`)}${theme.fg("dim", label)}${theme.fg("border", `${"─".repeat(right)}┤`)}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
32
70
|
/** The live fields needed by the viewer; historical viewers use a static source. */
|
|
33
71
|
export type ConversationSource = Pick<AgentSession, "messages" | "subscribe">;
|
|
34
72
|
|
|
@@ -48,6 +86,7 @@ class FullToolPreview implements Component {
|
|
|
48
86
|
private totalLines = 1;
|
|
49
87
|
private pendingTop = false;
|
|
50
88
|
private hoveredClose = false;
|
|
89
|
+
private scrollbarActive = false;
|
|
51
90
|
|
|
52
91
|
constructor(
|
|
53
92
|
private readonly tui: TUI,
|
|
@@ -99,10 +138,12 @@ class FullToolPreview implements Component {
|
|
|
99
138
|
render(width: number): string[] {
|
|
100
139
|
const safeWidth = Math.max(12, Math.floor(Number.isFinite(width) ? width : 12));
|
|
101
140
|
const inner = Math.max(1, safeWidth - 2);
|
|
141
|
+
// Reserve the rightmost inner cell for the same track/thumb rail used by
|
|
142
|
+
// pi-tui's native ScrollView layout.
|
|
102
143
|
const bodyWidth = Math.max(1, inner - 1);
|
|
103
144
|
const terminalRows = Math.max(1, this.tui.terminal.rows);
|
|
104
145
|
const viewport = Math.max(1, Math.min(30, Math.floor(terminalRows * 0.8), terminalRows - 6));
|
|
105
|
-
const wrapped = this.body.render(bodyWidth);
|
|
146
|
+
const wrapped = this.body.render(Math.max(1, bodyWidth - 1));
|
|
106
147
|
this.totalLines = wrapped.length;
|
|
107
148
|
this.pageSize = viewport;
|
|
108
149
|
this.scrollOffset = Math.min(this.scrollOffset, Math.max(0, this.totalLines - this.pageSize));
|
|
@@ -112,15 +153,28 @@ class FullToolPreview implements Component {
|
|
|
112
153
|
const clipped = truncateToWidth(text, rowWidth, "…", true);
|
|
113
154
|
return clipped + " ".repeat(Math.max(0, rowWidth - visibleWidth(clipped)));
|
|
114
155
|
};
|
|
156
|
+
const hiddenAbove = this.scrollOffset;
|
|
157
|
+
const hiddenBelow = Math.max(0, this.totalLines - this.scrollOffset - this.pageSize);
|
|
115
158
|
const close = this.theme.fg(this.hoveredClose ? "text" : "dim", "[esc]");
|
|
116
159
|
const header = this.theme.bold(this.theme.fg("accent", this.title));
|
|
117
160
|
const status = `${this.scrollOffset + 1}-${Math.min(this.totalLines, this.scrollOffset + this.pageSize)} / ${this.totalLines} lines · j/k gg/G ↑↓ PgUp/PgDn · [esc] close`;
|
|
118
161
|
return [
|
|
119
162
|
border(`╭${"─".repeat(inner)}╮`),
|
|
120
163
|
`${border("│")}${pad(` ${header}`, inner - visibleWidth(close))}${close}${border("│")}`,
|
|
121
|
-
|
|
122
|
-
...Array.from({ length: viewport }, (_, index) =>
|
|
123
|
-
|
|
164
|
+
renderScrollMoreRule(this.theme, inner, "top", hiddenAbove),
|
|
165
|
+
...Array.from({ length: viewport }, (_, index) => {
|
|
166
|
+
const content = pad(` ${visible[index] ?? ""}`, bodyWidth);
|
|
167
|
+
const rail = renderScrollbarCell(
|
|
168
|
+
this.theme,
|
|
169
|
+
this.totalLines,
|
|
170
|
+
this.pageSize,
|
|
171
|
+
this.scrollOffset,
|
|
172
|
+
index,
|
|
173
|
+
this.scrollbarActive,
|
|
174
|
+
);
|
|
175
|
+
return `${border("│")}${content}${rail}${border("│")}`;
|
|
176
|
+
}),
|
|
177
|
+
renderScrollMoreRule(this.theme, inner, "bottom", hiddenBelow),
|
|
124
178
|
`${border("│")}${pad(this.theme.fg("dim", ` ${status}`))}${border("│")}`,
|
|
125
179
|
border(`╰${"─".repeat(inner)}╯`),
|
|
126
180
|
];
|
|
@@ -134,6 +188,7 @@ class FullToolPreview implements Component {
|
|
|
134
188
|
const next = Math.max(0, Math.min(Math.max(0, this.totalLines - this.pageSize), offset));
|
|
135
189
|
if (next === this.scrollOffset) return;
|
|
136
190
|
this.scrollOffset = next;
|
|
191
|
+
this.scrollbarActive = true;
|
|
137
192
|
this.tui.requestRender();
|
|
138
193
|
}
|
|
139
194
|
}
|
|
@@ -171,6 +226,10 @@ export class ConversationViewer implements Component {
|
|
|
171
226
|
private searchQuery = "";
|
|
172
227
|
private searchMatches: ConversationMatch[] = [];
|
|
173
228
|
private currentMatchIdx = -1;
|
|
229
|
+
/** Follow-up drafts, newest first, matching pi editor history semantics. */
|
|
230
|
+
private steerHistory: string[] = [];
|
|
231
|
+
private steerHistoryIndex = -1;
|
|
232
|
+
private steerHistoryDraft = "";
|
|
174
233
|
private timeline: ConversationTimeline;
|
|
175
234
|
|
|
176
235
|
constructor(
|
|
@@ -202,7 +261,16 @@ export class ConversationViewer implements Component {
|
|
|
202
261
|
|
|
203
262
|
handleInput(data: string): void {
|
|
204
263
|
if (this.composer) {
|
|
205
|
-
|
|
264
|
+
if (matchesKey(data, Key.alt("up")) || data === "a-up" || data === "alt+up") {
|
|
265
|
+
this.recallSteerDraft();
|
|
266
|
+
} else {
|
|
267
|
+
const before = this.composer.getValue();
|
|
268
|
+
this.composer.handleInput(data);
|
|
269
|
+
if (this.composer && before !== this.composer.getValue()) {
|
|
270
|
+
this.steerHistoryIndex = -1;
|
|
271
|
+
this.steerHistoryDraft = "";
|
|
272
|
+
}
|
|
273
|
+
}
|
|
206
274
|
this.tui.requestRender();
|
|
207
275
|
return;
|
|
208
276
|
}
|
|
@@ -442,7 +510,7 @@ export class ConversationViewer implements Component {
|
|
|
442
510
|
lines.push(hrMid);
|
|
443
511
|
if (this.composer) {
|
|
444
512
|
lines.push(row(this.composer.render(innerW)[0] ?? ""));
|
|
445
|
-
const hint = th.fg("dim", "Enter send · Esc cancel");
|
|
513
|
+
const hint = th.fg("dim", "Enter send · Esc cancel · Alt+Up recall");
|
|
446
514
|
const label = th.fg("accent", "✎ steer");
|
|
447
515
|
lines.push(row(label + " ".repeat(Math.max(1, innerW - visibleWidth(label) - visibleWidth(hint))) + hint));
|
|
448
516
|
} else if (this.searchMode) {
|
|
@@ -865,17 +933,39 @@ export class ConversationViewer implements Component {
|
|
|
865
933
|
private openComposer(): void {
|
|
866
934
|
const input = new Input();
|
|
867
935
|
input.focused = true;
|
|
936
|
+
this.steerHistoryIndex = -1;
|
|
937
|
+
this.steerHistoryDraft = "";
|
|
868
938
|
input.onSubmit = (value: string) => {
|
|
869
939
|
const message = value.trim();
|
|
870
940
|
this.composer = undefined;
|
|
871
|
-
|
|
941
|
+
this.steerHistoryIndex = -1;
|
|
942
|
+
this.steerHistoryDraft = "";
|
|
943
|
+
if (message) {
|
|
944
|
+
if (this.steerHistory[0] !== message) this.steerHistory.unshift(message);
|
|
945
|
+
if (this.steerHistory.length > 100) this.steerHistory.pop();
|
|
946
|
+
this.onSteer?.(message);
|
|
947
|
+
}
|
|
872
948
|
this.tui.requestRender();
|
|
873
949
|
};
|
|
874
950
|
input.onEscape = () => {
|
|
875
951
|
this.composer = undefined;
|
|
952
|
+
this.steerHistoryIndex = -1;
|
|
953
|
+
this.steerHistoryDraft = "";
|
|
876
954
|
this.tui.requestRender();
|
|
877
955
|
};
|
|
878
956
|
this.composer = input;
|
|
879
957
|
this.tui.requestRender();
|
|
880
958
|
}
|
|
959
|
+
|
|
960
|
+
/** Recall the newest prior steer, preserving the draft for future editing. */
|
|
961
|
+
private recallSteerDraft(): void {
|
|
962
|
+
if (!this.composer || this.steerHistory.length === 0) return;
|
|
963
|
+
if (this.steerHistoryIndex < 0) {
|
|
964
|
+
this.steerHistoryDraft = this.composer.getValue();
|
|
965
|
+
this.steerHistoryIndex = 0;
|
|
966
|
+
} else if (this.steerHistoryIndex < this.steerHistory.length - 1) {
|
|
967
|
+
this.steerHistoryIndex++;
|
|
968
|
+
}
|
|
969
|
+
this.composer.setValue(this.steerHistory[this.steerHistoryIndex] ?? this.steerHistoryDraft);
|
|
970
|
+
}
|
|
881
971
|
}
|