@tintinweb/pi-subagents 0.9.0 → 0.10.0

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.
@@ -25,6 +25,8 @@ export class ConversationViewer implements Component {
25
25
  private unsubscribe: (() => void) | undefined;
26
26
  private lastInnerW = 0;
27
27
  private closed = false;
28
+ /** Two-press confirm guard for the stop key, so a stray key can't kill the agent. */
29
+ private stopArmed = false;
28
30
 
29
31
  constructor(
30
32
  private tui: TUI,
@@ -33,6 +35,8 @@ export class ConversationViewer implements Component {
33
35
  private activity: AgentActivity | undefined,
34
36
  private theme: Theme,
35
37
  private done: (result: undefined) => void,
38
+ /** Abort the agent shown here. Omitted → no stop affordance (e.g. read-only history). */
39
+ private onStop?: () => void,
36
40
  ) {
37
41
  this.unsubscribe = session.subscribe(() => {
38
42
  if (this.closed) return;
@@ -47,6 +51,22 @@ export class ConversationViewer implements Component {
47
51
  return;
48
52
  }
49
53
 
54
+ // Stop/abort the agent (only while it can still be stopped). Two-press:
55
+ // first "x" arms, second confirms — any other key disarms.
56
+ if (matchesKey(data, "x")) {
57
+ if (this.isStoppable()) {
58
+ if (this.stopArmed) {
59
+ this.stopArmed = false;
60
+ this.onStop?.();
61
+ } else {
62
+ this.stopArmed = true;
63
+ }
64
+ this.tui.requestRender();
65
+ }
66
+ return;
67
+ }
68
+ if (this.stopArmed) this.stopArmed = false;
69
+
50
70
  const totalLines = this.buildContentLines(this.lastInnerW).length;
51
71
  const viewportHeight = this.viewportHeight();
52
72
  const maxScroll = Math.max(0, totalLines - viewportHeight);
@@ -141,7 +161,13 @@ export class ConversationViewer implements Component {
141
161
  ? "100%"
142
162
  : `${Math.round(((visibleStart + viewportHeight) / contentLines.length) * 100)}%`;
143
163
  const footerLeft = th.fg("dim", `${contentLines.length} lines · ${scrollPct}`);
144
- const footerRight = th.fg("dim", "↑↓ scroll · PgUp/PgDn or Shift+↑↓ · Esc close");
164
+ const scrollHint = th.fg("dim", "↑↓ scroll · PgUp/PgDn or Shift+↑↓ · Esc close");
165
+ // Stop hint goes first in the right group so it survives right-edge
166
+ // truncation on narrow terminals (the scroll hint is the expendable part).
167
+ const footerRight = this.isStoppable()
168
+ ? (this.stopArmed ? th.fg("error", "x again to STOP") : th.fg("dim", "x stop")) +
169
+ th.fg("dim", " · ") + scrollHint
170
+ : scrollHint;
145
171
  const footerGap = Math.max(1, innerW - visibleWidth(footerLeft) - visibleWidth(footerRight));
146
172
  lines.push(row(footerLeft + " ".repeat(footerGap) + footerRight));
147
173
  lines.push(hrBot);
@@ -149,6 +175,11 @@ export class ConversationViewer implements Component {
149
175
  return lines;
150
176
  }
151
177
 
178
+ /** Stoppable only when a stop handler exists and the agent is still active. */
179
+ private isStoppable(): boolean {
180
+ return !!this.onStop && (this.record.status === "running" || this.record.status === "queued");
181
+ }
182
+
152
183
  invalidate(): void { /* no cached state to clear */ }
153
184
 
154
185
  dispose(): void {