pi-ui-extend 1.0.23 → 1.0.28

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.
Files changed (44) hide show
  1. package/README.md +5 -1
  2. package/dist/app/app.d.ts +1 -0
  3. package/dist/app/app.js +12 -0
  4. package/dist/app/constants.d.ts +1 -1
  5. package/dist/app/constants.js +1 -1
  6. package/dist/app/icons.d.ts +1 -0
  7. package/dist/app/icons.js +2 -0
  8. package/dist/app/input/input-controller.d.ts +2 -0
  9. package/dist/app/input/input-controller.js +8 -5
  10. package/dist/app/rendering/render-controller.js +2 -0
  11. package/dist/app/rendering/status-line-renderer.d.ts +4 -1
  12. package/dist/app/rendering/status-line-renderer.js +12 -0
  13. package/dist/app/runtime.d.ts +1 -8
  14. package/dist/app/runtime.js +0 -39
  15. package/dist/app/screen/mouse-controller.d.ts +4 -1
  16. package/dist/app/screen/mouse-controller.js +9 -0
  17. package/dist/app/session/agent-pause-controller.d.ts +30 -0
  18. package/dist/app/session/agent-pause-controller.js +178 -0
  19. package/dist/app/session/session-event-controller.d.ts +1 -0
  20. package/dist/app/session/session-event-controller.js +22 -15
  21. package/dist/app/session/session-lifecycle-controller.d.ts +1 -0
  22. package/dist/app/session/session-lifecycle-controller.js +1 -0
  23. package/dist/app/types.d.ts +10 -0
  24. package/dist/config.js +1 -1
  25. package/dist/default-pix-config.js +2 -2
  26. package/external/pi-tools-suite/README.md +1 -1
  27. package/external/pi-tools-suite/docs/browser-qa-subagent.md +34 -2
  28. package/external/pi-tools-suite/package.json +3 -3
  29. package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +24 -18
  30. package/external/pi-tools-suite/src/async-subagents/core/config.ts +14 -3
  31. package/external/pi-tools-suite/src/async-subagents/core/process.ts +19 -0
  32. package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +97 -17
  33. package/external/pi-tools-suite/src/async-subagents/core/stop.ts +4 -2
  34. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/SKILL.md +108 -13
  35. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/qa-design.md +80 -0
  36. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/qa-flow.example.jsonc +54 -1
  37. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/scripts/browser-qa-runner.mjs +952 -76
  38. package/external/pi-tools-suite/src/coding-discipline/index.ts +2 -94
  39. package/external/pi-tools-suite/src/dcp/index.ts +21 -0
  40. package/external/pi-tools-suite/src/dcp/pruner-compression-blocks.ts +92 -0
  41. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +21 -16
  42. package/external/pi-tools-suite/src/todo/index.ts +86 -46
  43. package/external/pi-tools-suite/src/todo/todo.ts +1 -1
  44. package/package.json +4 -4
package/README.md CHANGED
@@ -371,12 +371,16 @@ Both support the published schema:
371
371
  "apply_patch": { "defaultExpanded": true }
372
372
  }
373
373
  },
374
- "promptEnhancer": { "modelRef": "zai/glm-5-turbo" },
374
+ "promptEnhancer": { "modelRef": "openai-codex/gpt-5.6-luna" },
375
375
  "autocomplete": {
376
376
  "modelRef": "zai/glm-5-turbo",
377
377
  "debounceMs": 350,
378
378
  "timeoutMs": 3000
379
379
  },
380
+ "sessionTitle": {
381
+ "modelRef": "openai-codex/gpt-5.6-luna",
382
+ "fallbackModels": ["zai/glm-5-turbo"]
383
+ },
380
384
  "dictation": { "language": "en" },
381
385
  "ignoreContextFiles": false,
382
386
  "maxProjectSessions": 0
package/dist/app/app.d.ts CHANGED
@@ -36,6 +36,7 @@ export declare class PiUiExtendApp {
36
36
  private readonly popupMenus;
37
37
  private readonly voiceController;
38
38
  private readonly sessionEvents;
39
+ private readonly agentPauseController;
39
40
  private readonly shellController;
40
41
  private readonly queuedMessages;
41
42
  private readonly workspaceActions;
package/dist/app/app.js CHANGED
@@ -31,6 +31,7 @@ import { AppSessionLifecycleController } from "./session/session-lifecycle-contr
31
31
  import { AppShellController } from "./commands/shell-controller.js";
32
32
  import { runInteractiveShellCommand } from "./commands/shell-command.js";
33
33
  import { AppSessionEventController } from "./session/session-event-controller.js";
34
+ import { AgentPauseController } from "./session/agent-pause-controller.js";
34
35
  import { AppStatusController } from "./screen/status-controller.js";
35
36
  import { StatusLineRenderer } from "./rendering/status-line-renderer.js";
36
37
  import { AppModelUsageController } from "./model/model-usage-controller.js";
@@ -91,6 +92,7 @@ export class PiUiExtendApp {
91
92
  popupMenus;
92
93
  voiceController;
93
94
  sessionEvents;
95
+ agentPauseController;
94
96
  shellController;
95
97
  queuedMessages;
96
98
  workspaceActions;
@@ -167,6 +169,11 @@ export class PiUiExtendApp {
167
169
  runtimeSession: () => this.runtime?.session,
168
170
  render: () => this.scheduleRender(),
169
171
  });
172
+ this.agentPauseController = new AgentPauseController({
173
+ showToast: (message, kind) => this.showToast(message, kind),
174
+ render: () => this.scheduleRender(),
175
+ isCurrentSession: (session) => this.runtime?.session === session,
176
+ });
170
177
  this.modelUsageController = new AppModelUsageController({
171
178
  runtimeSession: () => this.runtime?.session,
172
179
  render: () => this.render(),
@@ -308,6 +315,8 @@ export class PiUiExtendApp {
308
315
  promptEnhancerStatusWidgetEnabled: () => this.promptEnhancer.statusWidgetEnabled(),
309
316
  terminalBellSoundStatusWidgetText: () => this.terminalBellSoundController.statusWidgetText(),
310
317
  terminalBellSoundStatusWidgetEnabled: () => this.terminalBellSoundController.isEnabled(),
318
+ agentPauseStatusWidgetText: () => this.agentPauseController.statusWidgetText(this.runtime?.session),
319
+ agentPauseStatusWidgetActive: () => this.agentPauseController.statusWidgetActive(this.runtime?.session),
311
320
  voiceStatusWidgetText: () => this.voiceController.statusWidgetText(),
312
321
  voiceStatusWidgetActive: () => this.voiceController.statusWidgetActive(),
313
322
  conversationQuickScrollDirections: () => this.conversationQuickScrollDirections(),
@@ -633,6 +642,7 @@ export class PiUiExtendApp {
633
642
  this.render();
634
643
  },
635
644
  toggleTerminalBellSound: () => this.toggleTerminalBellSound(),
645
+ toggleAgentPause: () => this.agentPauseController.toggle(this.runtime?.session),
636
646
  handleExtensionInputMouse: (event) => this.extensionUiController.handleCustomUiMouse(event),
637
647
  render: () => this.render(),
638
648
  }, this.popupMenus, this.popupActions, this.scrollController, this.commandController);
@@ -783,6 +793,7 @@ export class PiUiExtendApp {
783
793
  createExtensionCommandContextActions: (runtime) => this.extensionActions.createCommandContextActions(runtime),
784
794
  handleExtensionError: (error) => this.extensionActions.handleExtensionError(error),
785
795
  handleSessionEvent: (event) => this.handleSessionEvent(event),
796
+ bindAgentPause: (session) => this.agentPauseController.bind(session),
786
797
  addEntry: (entry) => this.addEntry(entry),
787
798
  setStatus: (status) => this.setStatus(status),
788
799
  showToast: (message, kind) => this.showToast(message, kind),
@@ -809,6 +820,7 @@ export class PiUiExtendApp {
809
820
  this.mouseController.statusTerminalBellSoundTarget = undefined;
810
821
  this.mouseController.statusSessionTarget = undefined;
811
822
  this.mouseController.statusPromptEnhancerTarget = undefined;
823
+ this.mouseController.statusAgentPauseTarget = undefined;
812
824
  this.mouseController.statusVoiceMicTarget = undefined;
813
825
  this.mouseController.statusVoiceLanguageTarget = undefined;
814
826
  this.mouseController.tabLineTargets.length = 0;
@@ -1,7 +1,7 @@
1
1
  import type { ResolvedToolRule } from "../config.js";
2
2
  export declare const THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
3
3
  export declare const THINKING_MENU_MAX_ROWS: number;
4
- export declare const PI_FAVORITE_MODEL_REFS: readonly ["amazon-bedrock/us.anthropic.claude-opus-4-6-v1", "anthropic/claude-opus-4-8", "openai/gpt-5.4", "azure-openai-responses/gpt-5.4", "openai-codex/gpt-5.6-sol", "deepseek/deepseek-v4-pro", "google/gemini-3.1-pro-preview", "google-vertex/gemini-3.1-pro-preview", "github-copilot/gpt-5.4", "openrouter/moonshotai/kimi-k2.6", "vercel-ai-gateway/zai/glm-5.2", "xai/grok-4.5", "groq/openai/gpt-oss-120b", "cerebras/zai-glm-4.7", "zai/glm-5.3", "mistral/devstral-medium-latest", "minimax/MiniMax-M2.7", "minimax-cn/MiniMax-M2.7", "moonshotai/kimi-k2.6", "moonshotai-cn/kimi-k2.6", "huggingface/moonshotai/Kimi-K2.6", "fireworks/accounts/fireworks/models/kimi-k2p6", "together/moonshotai/Kimi-K2.6", "opencode/kimi-k2.6", "opencode-go/kimi-k2.6", "kimi-coding/kimi-for-coding", "cloudflare-workers-ai/@cf/moonshotai/kimi-k2.6", "cloudflare-ai-gateway/workers-ai/@cf/moonshotai/kimi-k2.6", "xiaomi/mimo-v2.5-pro", "xiaomi-token-plan-cn/mimo-v2.5-pro", "xiaomi-token-plan-ams/mimo-v2.5-pro", "xiaomi-token-plan-sgp/mimo-v2.5-pro"];
4
+ export declare const PI_FAVORITE_MODEL_REFS: readonly ["amazon-bedrock/us.anthropic.claude-opus-4-6-v1", "anthropic/claude-opus-4-8", "openai/gpt-5.4", "azure-openai-responses/gpt-5.4", "openai-codex/gpt-5.6-sol", "deepseek/deepseek-v4-pro", "google/gemini-3.1-pro-preview", "google-vertex/gemini-3.1-pro-preview", "github-copilot/gpt-5.4", "openrouter/moonshotai/kimi-k2.6", "vercel-ai-gateway/zai/glm-5.2", "xai/grok-4.5", "groq/openai/gpt-oss-120b", "cerebras/gpt-oss-120b", "zai/glm-5.3", "mistral/devstral-medium-latest", "minimax/MiniMax-M2.7", "minimax-cn/MiniMax-M2.7", "moonshotai/kimi-k2.6", "moonshotai-cn/kimi-k2.6", "huggingface/moonshotai/Kimi-K2.6", "fireworks/accounts/fireworks/models/kimi-k2p6", "together/moonshotai/Kimi-K2.6", "opencode/kimi-k2.6", "opencode-go/kimi-k2.6", "kimi-coding/kimi-for-coding", "cloudflare-workers-ai/@cf/moonshotai/kimi-k2.6", "cloudflare-ai-gateway/workers-ai/@cf/moonshotai/kimi-k2.6", "xiaomi/mimo-v2.5-pro", "xiaomi-token-plan-cn/mimo-v2.5-pro", "xiaomi-token-plan-ams/mimo-v2.5-pro", "xiaomi-token-plan-sgp/mimo-v2.5-pro"];
5
5
  export declare const SLASH_COMMAND_MENU_MAX_ROWS = 6;
6
6
  export declare const RESUME_MENU_MAX_ROWS = 20;
7
7
  export declare const RESUME_MENU_INITIAL_SESSION_ROWS = 30;
@@ -15,7 +15,7 @@ export const PI_FAVORITE_MODEL_REFS = [
15
15
  "vercel-ai-gateway/zai/glm-5.2",
16
16
  "xai/grok-4.5",
17
17
  "groq/openai/gpt-oss-120b",
18
- "cerebras/zai-glm-4.7",
18
+ "cerebras/gpt-oss-120b",
19
19
  "zai/glm-5.3",
20
20
  "mistral/devstral-medium-latest",
21
21
  "minimax/MiniMax-M2.7",
@@ -17,6 +17,7 @@ declare const NERD_FONT_ICONS: {
17
17
  readonly microphone: "󰍬";
18
18
  readonly plus: "󰐕";
19
19
  readonly pause: "󰏤";
20
+ readonly play: "󰐊";
20
21
  readonly record: "󰑊";
21
22
  readonly refresh: "󰑐";
22
23
  readonly volumeHigh: "󰕾";
package/dist/app/icons.js CHANGED
@@ -28,6 +28,7 @@ const NERD_FONT_ICONS = {
28
28
  microphone: "\u{f036c}",
29
29
  plus: "\u{f0415}",
30
30
  pause: "\u{f03e4}",
31
+ play: "\u{f040a}",
31
32
  record: "\u{f044a}",
32
33
  refresh: "\u{f0450}",
33
34
  volumeHigh: "\u{f057e}",
@@ -61,6 +62,7 @@ const FALLBACK_ICONS = {
61
62
  microphone: "m",
62
63
  plus: "+",
63
64
  pause: "⏸",
65
+ play: "▶",
64
66
  record: "●",
65
67
  refresh: "↻",
66
68
  volumeHigh: "♪",
@@ -9,6 +9,7 @@ export type InputControllerHost = {
9
9
  handleExtensionTerminalInput(data: string): ExtensionTerminalInputResult;
10
10
  extensionInputUsesEditor?(): boolean;
11
11
  isShiftPressed?(): boolean;
12
+ isCommandPressed?(): boolean;
12
13
  getInput(): string;
13
14
  getDirectPopupMenu(): DirectPopupMenu | undefined;
14
15
  resetRequestHistoryNavigation(): void;
@@ -68,5 +69,6 @@ export declare class AppInputController {
68
69
  private deleteCurrentInputLine;
69
70
  private handleChar;
70
71
  private isShiftPressed;
72
+ private isCommandPressed;
71
73
  }
72
74
  export {};
@@ -179,7 +179,7 @@ export class AppInputController {
179
179
  break;
180
180
  // Native undo/redo shortcuts must retain their individual key behavior.
181
181
  const lower = char.toLowerCase();
182
- if ((lower === "z" || lower === "y") && isNativeCommandPressed())
182
+ if ((lower === "z" || lower === "y") && this.isCommandPressed())
183
183
  break;
184
184
  length += 1;
185
185
  }
@@ -417,7 +417,7 @@ export class AppInputController {
417
417
  return "consumed";
418
418
  }
419
419
  handleArrowUp() {
420
- if (isNativeCommandPressed()) {
420
+ if (this.isCommandPressed()) {
421
421
  this.host.scrollByPage(-1);
422
422
  return;
423
423
  }
@@ -438,7 +438,7 @@ export class AppInputController {
438
438
  this.host.scrollByLines(-1);
439
439
  }
440
440
  handleArrowDown() {
441
- if (isNativeCommandPressed()) {
441
+ if (this.isCommandPressed()) {
442
442
  this.host.scrollByPage(1);
443
443
  return;
444
444
  }
@@ -517,7 +517,7 @@ export class AppInputController {
517
517
  this.redoInput();
518
518
  return true;
519
519
  }
520
- if (!isNativeCommandPressed())
520
+ if (!this.isCommandPressed())
521
521
  return false;
522
522
  const lower = char.toLowerCase();
523
523
  if (lower === "z") {
@@ -595,7 +595,7 @@ export class AppInputController {
595
595
  this.host.handleEnter();
596
596
  return;
597
597
  }
598
- if (char === "\u0015" || ((char === "\u007f" || char === "\b") && isNativeCommandPressed())) {
598
+ if (char === "\u0015" || ((char === "\u007f" || char === "\b") && this.isCommandPressed())) {
599
599
  this.deleteCurrentInputLine();
600
600
  return;
601
601
  }
@@ -625,6 +625,9 @@ export class AppInputController {
625
625
  isShiftPressed() {
626
626
  return this.host.isShiftPressed?.() ?? isNativeShiftPressed();
627
627
  }
628
+ isCommandPressed() {
629
+ return this.host.isCommandPressed?.() ?? isNativeCommandPressed();
630
+ }
628
631
  }
629
632
  function normalizeBracketedPastePayload(payload) {
630
633
  return payload.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
@@ -73,6 +73,7 @@ export class AppRenderController {
73
73
  this.deps.mouseController.statusTerminalBellSoundTarget = undefined;
74
74
  this.deps.mouseController.statusSessionTarget = undefined;
75
75
  this.deps.mouseController.statusPromptEnhancerTarget = undefined;
76
+ this.deps.mouseController.statusAgentPauseTarget = undefined;
76
77
  this.deps.mouseController.statusVoiceMicTarget = undefined;
77
78
  this.deps.mouseController.statusVoiceLanguageTarget = undefined;
78
79
  this.deps.mouseController.tabLineTargets.length = 0;
@@ -284,6 +285,7 @@ export class AppRenderController {
284
285
  this.deps.mouseController.statusTerminalBellSoundTarget = this.deps.statusLineRenderer.terminalBellSoundTarget?.(statusLayout, statusRow);
285
286
  this.deps.mouseController.statusSessionTarget = this.deps.statusLineRenderer.sessionTarget(statusLayout.text, statusRow, statusLayout.sessionLabel, statusLayout.workspaceLabel);
286
287
  this.deps.mouseController.statusPromptEnhancerTarget = this.deps.statusLineRenderer.promptEnhancerTarget(statusLayout, statusRow);
288
+ this.deps.mouseController.statusAgentPauseTarget = this.deps.statusLineRenderer.agentPauseTarget?.(statusLayout, statusRow);
287
289
  this.deps.mouseController.statusVoiceMicTarget = this.deps.statusLineRenderer.voiceMicTarget(statusLayout, statusRow);
288
290
  this.deps.mouseController.statusVoiceLanguageTarget = this.deps.statusLineRenderer.voiceLanguageTarget(statusLayout, statusRow);
289
291
  this.deps.mouseController.renderedRowTexts.set(statusRow, statusLayout.text);
@@ -1,6 +1,6 @@
1
1
  import type { AgentSession } from "@earendil-works/pi-coding-agent";
2
2
  import type { Theme } from "../../theme.js";
3
- import type { SessionActivity, StatusCompactToolsTarget, StatusContextTarget, StatusDraftQueueTarget, StatusInternalClipboardTarget, StatusLineLayout, StatusModelTarget, StatusModelUsageTarget, StatusPromptEnhancerTarget, StatusQuickScrollTarget, StatusSessionTarget, StatusTerminalBellSoundTarget, StatusThinkingExpandTarget, StatusThinkingTarget, StatusUserJumpTarget, StatusVoiceLanguageTarget, StatusVoiceMicTarget } from "../types.js";
3
+ import type { SessionActivity, StatusAgentPauseTarget, StatusCompactToolsTarget, StatusContextTarget, StatusDraftQueueTarget, StatusInternalClipboardTarget, StatusLineLayout, StatusModelTarget, StatusModelUsageTarget, StatusPromptEnhancerTarget, StatusQuickScrollTarget, StatusSessionTarget, StatusTerminalBellSoundTarget, StatusThinkingExpandTarget, StatusThinkingTarget, StatusUserJumpTarget, StatusVoiceLanguageTarget, StatusVoiceMicTarget } from "../types.js";
4
4
  import type { ScreenStyler } from "../screen/screen-styler.js";
5
5
  import { type ModelColorsConfig } from "../../config.js";
6
6
  export type StatusLineRendererHost = {
@@ -24,6 +24,8 @@ export type StatusLineRendererHost = {
24
24
  promptEnhancerStatusWidgetEnabled(): boolean;
25
25
  terminalBellSoundStatusWidgetText(): string;
26
26
  terminalBellSoundStatusWidgetEnabled(): boolean;
27
+ agentPauseStatusWidgetText?(): string;
28
+ agentPauseStatusWidgetActive?(): boolean;
27
29
  voiceStatusWidgetText(): string;
28
30
  voiceStatusWidgetActive(): boolean;
29
31
  conversationQuickScrollDirections?(): {
@@ -49,6 +51,7 @@ export declare class StatusLineRenderer {
49
51
  contextTarget(statusText: string, row: number, layout: StatusLineLayout): StatusContextTarget | undefined;
50
52
  modelUsageTarget(statusText: string, row: number, layout: StatusLineLayout): StatusModelUsageTarget | undefined;
51
53
  promptEnhancerTarget(layout: StatusLineLayout, row: number): StatusPromptEnhancerTarget | undefined;
54
+ agentPauseTarget(layout: StatusLineLayout, row: number): StatusAgentPauseTarget | undefined;
52
55
  voiceMicTarget(layout: StatusLineLayout, row: number): StatusVoiceMicTarget | undefined;
53
56
  voiceLanguageTarget(layout: StatusLineLayout, row: number): StatusVoiceLanguageTarget | undefined;
54
57
  userJumpTarget(layout: StatusLineLayout, row: number): StatusUserJumpTarget | undefined;
@@ -91,6 +91,10 @@ export class StatusLineRenderer {
91
91
  appendWidget(quickScrollDirections.down ? this.iconButtonText(APP_ICONS.down) : "", (column, text) => {
92
92
  layout.quickScrollDownWidget = this.widgetLayout(column, text);
93
93
  });
94
+ const agentPauseWidgetText = this.host.agentPauseStatusWidgetText?.() ?? "";
95
+ appendWidget(agentPauseWidgetText ? this.iconButtonText(agentPauseWidgetText) : "", (column, text) => {
96
+ layout.agentPauseWidget = this.widgetLayout(column, text);
97
+ });
94
98
  const voiceWidgetText = this.host.voiceStatusWidgetText();
95
99
  appendWidget(this.voiceBorderWidgetText(voiceWidgetText), (column, text) => {
96
100
  layout.voiceWidget = this.voiceWidgetLayout(column, voiceWidgetText, text);
@@ -156,6 +160,7 @@ export class StatusLineRenderer {
156
160
  pushWidgetSegment(layout.terminalBellSoundWidget, this.host.terminalBellSoundStatusWidgetEnabled() ? colors.info : colors.muted);
157
161
  pushWidgetSegment(layout.thinkingExpandWidget, this.host.allThinkingExpandedActive?.() ? colors.info : colors.muted);
158
162
  pushWidgetSegment(layout.compactToolsWidget, this.host.superCompactToolsActive?.() ? colors.info : colors.muted);
163
+ pushWidgetSegment(layout.agentPauseWidget, this.host.agentPauseStatusWidgetActive?.() ? colors.info : colors.muted);
159
164
  const voiceWidget = layout.voiceWidget;
160
165
  if (voiceWidget) {
161
166
  segments.push({
@@ -229,6 +234,12 @@ export class StatusLineRenderer {
229
234
  return undefined;
230
235
  return { row, startColumn: widget.startColumn, endColumn: widget.endColumn };
231
236
  }
237
+ agentPauseTarget(layout, row) {
238
+ const widget = layout.agentPauseWidget;
239
+ if (!widget)
240
+ return undefined;
241
+ return { row, startColumn: widget.startColumn, endColumn: widget.endColumn };
242
+ }
232
243
  voiceMicTarget(layout, row) {
233
244
  const voiceWidget = layout.voiceWidget;
234
245
  if (!voiceWidget)
@@ -359,6 +370,7 @@ export class StatusLineRenderer {
359
370
  { widget: layout.terminalBellSoundWidget, foreground: this.host.terminalBellSoundStatusWidgetEnabled() ? colors.info : colors.muted },
360
371
  { widget: layout.thinkingExpandWidget, foreground: this.host.allThinkingExpandedActive?.() ? colors.info : colors.muted },
361
372
  { widget: layout.compactToolsWidget, foreground: this.host.superCompactToolsActive?.() ? colors.info : colors.muted },
373
+ { widget: layout.agentPauseWidget, foreground: this.host.agentPauseStatusWidgetActive?.() ? colors.info : colors.muted },
362
374
  ].filter((entry) => Boolean(entry.widget));
363
375
  for (const { widget, foreground } of widgets) {
364
376
  segments.push({
@@ -46,13 +46,6 @@ type RuntimeSessionManagerModelState = Pick<SessionManager, "getEntries" | "getB
46
46
  export declare function resolvePixRuntimeModelRef(options: Pick<AppOptions, "modelRef">, sessionManager: RuntimeSessionManagerModelState, config?: PixConfig): string | undefined;
47
47
  export declare function resolvePixRuntimeInitialThinkingLevel(options: Pick<AppOptions, "modelRef">, sessionManager: RuntimeSessionManagerModelState, config: PixConfig): ThinkingLevel | undefined;
48
48
  export declare function resolveSessionModelRefFromTail(entries: readonly SessionEntry[]): string | undefined;
49
- /**
50
- * pi-ai 0.84.2 predates GLM-5.3's final thinking contract. The released model
51
- * is always-thinking and accepts exactly low/high/max via reasoning_effort.
52
- * Patch the mutable catalog model before AgentSession clamps the selected level.
53
- */
54
- export declare function patchGlm53ThinkingMetadata(model: unknown): boolean;
55
- export declare function patchPixModelRuntimeCompatibility(modelRuntime: Pick<AgentSessionServices["modelRuntime"], "getModels">): number;
56
- export declare function refreshPixModelRuntimeForStartup(modelRuntime: Pick<AgentSessionServices["modelRuntime"], "refresh" | "getModels">): Promise<void>;
49
+ export declare function refreshPixModelRuntimeForStartup(modelRuntime: Pick<AgentSessionServices["modelRuntime"], "refresh">): Promise<void>;
57
50
  export declare function createPixRuntime(options: AppOptions, runtimeOptions?: CreatePixRuntimeOptions): Promise<AgentSessionRuntime>;
58
51
  export {};
@@ -235,49 +235,10 @@ export function resolveSessionModelRefFromTail(entries) {
235
235
  return undefined;
236
236
  return thinkingLevel ? `${modelRef}:${thinkingLevel}` : modelRef;
237
237
  }
238
- const GLM_53_THINKING_LEVEL_MAP = {
239
- off: null,
240
- minimal: null,
241
- low: "low",
242
- medium: null,
243
- high: "high",
244
- xhigh: null,
245
- max: "max",
246
- };
247
- /**
248
- * pi-ai 0.84.2 predates GLM-5.3's final thinking contract. The released model
249
- * is always-thinking and accepts exactly low/high/max via reasoning_effort.
250
- * Patch the mutable catalog model before AgentSession clamps the selected level.
251
- */
252
- export function patchGlm53ThinkingMetadata(model) {
253
- if (!model || typeof model !== "object" || Array.isArray(model))
254
- return false;
255
- const candidate = model;
256
- if (candidate.id !== "glm-5.3")
257
- return false;
258
- if (!candidate.compat || typeof candidate.compat !== "object" || Array.isArray(candidate.compat))
259
- return false;
260
- const compat = candidate.compat;
261
- if (compat.thinkingFormat !== "zai")
262
- return false;
263
- candidate.reasoning = true;
264
- candidate.thinkingLevelMap = { ...GLM_53_THINKING_LEVEL_MAP };
265
- candidate.compat = { ...compat, supportsReasoningEffort: true };
266
- return true;
267
- }
268
- export function patchPixModelRuntimeCompatibility(modelRuntime) {
269
- let patched = 0;
270
- for (const model of modelRuntime.getModels()) {
271
- if (patchGlm53ThinkingMetadata(model))
272
- patched += 1;
273
- }
274
- return patched;
275
- }
276
238
  export async function refreshPixModelRuntimeForStartup(modelRuntime) {
277
239
  // Startup only needs the locally configured model catalog. Remote catalog
278
240
  // refreshes belong to explicit model-management flows and must not block boot.
279
241
  await modelRuntime.refresh({ allowNetwork: false });
280
- patchPixModelRuntimeCompatibility(modelRuntime);
281
242
  }
282
243
  export async function createPixRuntime(options, runtimeOptions = {}) {
283
244
  const agentDir = getAgentDir();
@@ -6,7 +6,7 @@ import type { ToastEntry, ToastVariant } from "../../ui.js";
6
6
  import type { AppPopupActionController } from "../popup/popup-action-controller.js";
7
7
  import type { AppPopupMenuController } from "../popup/popup-menu-controller.js";
8
8
  import type { AppScrollController } from "./scroll-controller.js";
9
- import type { Entry, ImageClickTarget, MouseEvent, MouseSelection, StatusContextTarget, StatusCompactToolsTarget, StatusDraftQueueTarget, StatusInternalClipboardTarget, StatusModelTarget, StatusModelUsageTarget, StatusPromptEnhancerTarget, StatusQuickScrollTarget, StatusSessionTarget, StatusTerminalBellSoundTarget, TabLineMouseTarget, StatusThinkingExpandTarget, StatusThinkingTarget, StatusUserJumpTarget, StatusVoiceLanguageTarget, StatusVoiceMicTarget } from "../types.js";
9
+ import type { Entry, ImageClickTarget, MouseEvent, MouseSelection, StatusAgentPauseTarget, StatusContextTarget, StatusCompactToolsTarget, StatusDraftQueueTarget, StatusInternalClipboardTarget, StatusModelTarget, StatusModelUsageTarget, StatusPromptEnhancerTarget, StatusQuickScrollTarget, StatusSessionTarget, StatusTerminalBellSoundTarget, TabLineMouseTarget, StatusThinkingExpandTarget, StatusThinkingTarget, StatusUserJumpTarget, StatusVoiceLanguageTarget, StatusVoiceMicTarget } from "../types.js";
10
10
  import { type RenderedLink } from "./file-links.js";
11
11
  import type { AgentSession } from "@earendil-works/pi-coding-agent";
12
12
  type ClickFlash = {
@@ -63,6 +63,7 @@ export type AppMouseControllerHost = {
63
63
  toggleAllThinkingExpanded?(): void;
64
64
  toggleSuperCompactTools?(): void;
65
65
  toggleTerminalBellSound?(): void;
66
+ toggleAgentPause?(): void | Promise<void>;
66
67
  copyTextToClipboard?(text: string): void | Promise<void>;
67
68
  handleExtensionInputMouse(event: MouseEvent & {
68
69
  localRow: number;
@@ -114,6 +115,7 @@ export declare class AppMouseController {
114
115
  statusTerminalBellSoundTarget: StatusTerminalBellSoundTarget | undefined;
115
116
  statusSessionTarget: StatusSessionTarget | undefined;
116
117
  statusPromptEnhancerTarget: StatusPromptEnhancerTarget | undefined;
118
+ statusAgentPauseTarget: StatusAgentPauseTarget | undefined;
117
119
  statusVoiceMicTarget: StatusVoiceMicTarget | undefined;
118
120
  statusVoiceLanguageTarget: StatusVoiceLanguageTarget | undefined;
119
121
  readonly tabLineTargets: TabLineMouseTarget[];
@@ -172,6 +174,7 @@ export declare class AppMouseController {
172
174
  private handleStatusTerminalBellSoundClick;
173
175
  private handleStatusSessionClick;
174
176
  private handleStatusPromptEnhancerClick;
177
+ private handleStatusAgentPauseClick;
175
178
  private handleStatusVoiceMicClick;
176
179
  private handleStatusVoiceLanguageClick;
177
180
  private handleStatusQuickScrollClick;
@@ -35,6 +35,7 @@ export class AppMouseController {
35
35
  statusTerminalBellSoundTarget;
36
36
  statusSessionTarget;
37
37
  statusPromptEnhancerTarget;
38
+ statusAgentPauseTarget;
38
39
  statusVoiceMicTarget;
39
40
  statusVoiceLanguageTarget;
40
41
  tabLineTargets = [];
@@ -333,6 +334,7 @@ export class AppMouseController {
333
334
  this.statusTerminalBellSoundTarget,
334
335
  this.statusSessionTarget,
335
336
  this.statusPromptEnhancerTarget,
337
+ this.statusAgentPauseTarget,
336
338
  this.statusVoiceMicTarget,
337
339
  this.statusVoiceLanguageTarget,
338
340
  ].find((candidate) => !!candidate
@@ -510,6 +512,7 @@ export class AppMouseController {
510
512
  || this.handleStatusCompactToolsClick(event)
511
513
  || this.handleStatusTerminalBellSoundClick(event)
512
514
  || this.handleStatusPromptEnhancerClick(event)
515
+ || this.handleStatusAgentPauseClick(event)
513
516
  || this.handleStatusVoiceMicClick(event)
514
517
  || this.handleStatusVoiceLanguageClick(event);
515
518
  }
@@ -570,6 +573,12 @@ export class AppMouseController {
570
573
  void this.host.enhancePrompt();
571
574
  return true;
572
575
  }
576
+ handleStatusAgentPauseClick(event) {
577
+ if (!this.statusTargetContains(this.statusAgentPauseTarget, event))
578
+ return false;
579
+ void this.host.toggleAgentPause?.();
580
+ return true;
581
+ }
573
582
  handleStatusVoiceMicClick(event) {
574
583
  if (!this.statusTargetContains(this.statusVoiceMicTarget, event))
575
584
  return false;
@@ -0,0 +1,30 @@
1
+ import type { AgentSession } from "@earendil-works/pi-coding-agent";
2
+ export type AgentPauseState = "idle" | "pause-requested" | "paused" | "resuming";
3
+ export type AgentPauseControllerHost = {
4
+ showToast(message: string, kind: "success" | "error" | "warning" | "info"): void;
5
+ render(): void;
6
+ isCurrentSession(session: AgentSession): boolean;
7
+ };
8
+ /**
9
+ * Implements a turn-boundary pause without interrupting an active tool batch.
10
+ *
11
+ * The pinned SDK exposes Agent.shouldStopAfterTurn and Agent.continue(), but not a
12
+ * session-level continuation method. The small private adapter below mirrors the
13
+ * bookkeeping in AgentSession._runAgentPrompt so resumed runs still get retries,
14
+ * compaction, queue draining, isStreaming, and agent_settled behavior.
15
+ */
16
+ export declare class AgentPauseController {
17
+ private readonly host;
18
+ private readonly records;
19
+ constructor(host: AgentPauseControllerHost);
20
+ bind(session: AgentSession): void;
21
+ state(session: AgentSession | undefined): AgentPauseState;
22
+ statusWidgetText(session: AgentSession | undefined): string;
23
+ statusWidgetActive(session: AgentSession | undefined): boolean;
24
+ toggle(session: AgentSession | undefined): Promise<void>;
25
+ private resume;
26
+ private setState;
27
+ private finishSettlement;
28
+ private canContinue;
29
+ private sessionInternals;
30
+ }
@@ -0,0 +1,178 @@
1
+ import { APP_ICONS } from "../icons.js";
2
+ /**
3
+ * Implements a turn-boundary pause without interrupting an active tool batch.
4
+ *
5
+ * The pinned SDK exposes Agent.shouldStopAfterTurn and Agent.continue(), but not a
6
+ * session-level continuation method. The small private adapter below mirrors the
7
+ * bookkeeping in AgentSession._runAgentPrompt so resumed runs still get retries,
8
+ * compaction, queue draining, isStreaming, and agent_settled behavior.
9
+ */
10
+ export class AgentPauseController {
11
+ host;
12
+ records = new WeakMap();
13
+ constructor(host) {
14
+ this.host = host;
15
+ }
16
+ bind(session) {
17
+ if (this.records.has(session))
18
+ return;
19
+ const internals = this.sessionInternals(session);
20
+ const record = {
21
+ state: "idle",
22
+ pauseBoundaryReached: false,
23
+ originalHandlePostAgentRun: internals._handlePostAgentRun.bind(session),
24
+ };
25
+ this.records.set(session, record);
26
+ session.subscribe((event) => {
27
+ if (event.type === "agent_start" && record.state === "paused") {
28
+ // A new prompt supersedes a paused continuation.
29
+ this.setState(record, "idle");
30
+ }
31
+ });
32
+ const originalEmitAgentSettled = internals._emitAgentSettled.bind(session);
33
+ internals._emitAgentSettled = async () => {
34
+ try {
35
+ await originalEmitAgentSettled();
36
+ }
37
+ finally {
38
+ this.finishSettlement(record);
39
+ }
40
+ };
41
+ const originalShouldStopAfterTurn = session.agent.shouldStopAfterTurn;
42
+ const shouldStopAfterTurn = async (context, signal) => {
43
+ if (await originalShouldStopAfterTurn?.(context, signal)) {
44
+ if (record.state === "pause-requested") {
45
+ record.pauseBoundaryReached = false;
46
+ this.setState(record, "idle");
47
+ }
48
+ return true;
49
+ }
50
+ if (record.state !== "pause-requested")
51
+ return false;
52
+ record.pauseBoundaryReached = true;
53
+ return true;
54
+ };
55
+ session.agent.shouldStopAfterTurn = shouldStopAfterTurn;
56
+ internals._handlePostAgentRun = async () => {
57
+ if (record.state === "paused")
58
+ return false;
59
+ const pauseBoundaryReached = record.pauseBoundaryReached;
60
+ record.pauseBoundaryReached = false;
61
+ const shouldContinue = await record.originalHandlePostAgentRun();
62
+ if (pauseBoundaryReached || record.state === "pause-requested") {
63
+ if (shouldContinue || this.canContinue(session)) {
64
+ record.pauseSettled = new Promise((resolve) => {
65
+ record.resolvePauseSettled = resolve;
66
+ });
67
+ this.setState(record, "paused");
68
+ this.host.showToast("Agent paused after the current turn", "success");
69
+ return false;
70
+ }
71
+ this.setState(record, "idle");
72
+ return false;
73
+ }
74
+ return shouldContinue;
75
+ };
76
+ }
77
+ state(session) {
78
+ if (!session)
79
+ return "idle";
80
+ return this.records.get(session)?.state ?? "idle";
81
+ }
82
+ statusWidgetText(session) {
83
+ return this.state(session) === "paused" ? APP_ICONS.play : APP_ICONS.pause;
84
+ }
85
+ statusWidgetActive(session) {
86
+ if (!session)
87
+ return false;
88
+ return this.state(session) !== "idle" || session.isStreaming || session.agent.state.isStreaming;
89
+ }
90
+ async toggle(session) {
91
+ if (!session) {
92
+ this.host.showToast("No active agent session", "info");
93
+ return;
94
+ }
95
+ this.bind(session);
96
+ const record = this.records.get(session);
97
+ if (record.state === "paused") {
98
+ await this.resume(session, record);
99
+ return;
100
+ }
101
+ if (record.state === "pause-requested" || record.state === "resuming")
102
+ return;
103
+ if (!session.isStreaming && !session.agent.state.isStreaming) {
104
+ this.host.showToast("Agent is not running", "info");
105
+ return;
106
+ }
107
+ this.setState(record, "pause-requested");
108
+ this.host.showToast("Pause requested; waiting for the current turn to finish", "info");
109
+ }
110
+ async resume(session, record) {
111
+ this.setState(record, "resuming");
112
+ this.host.showToast("Continuing agent", "info");
113
+ try {
114
+ // isIdle becomes true before async agent_settled extension handlers finish,
115
+ // so wait for the event itself rather than relying on waitForIdle().
116
+ await record.pauseSettled;
117
+ if (!this.host.isCurrentSession(session)) {
118
+ throw new Error("agent session changed before it could continue");
119
+ }
120
+ const internals = this.sessionInternals(session);
121
+ internals._isAgentRunActive = true;
122
+ try {
123
+ // Continue exactly as AgentSession._runAgentPrompt would.
124
+ await session.agent.continue();
125
+ while (this.host.isCurrentSession(session)) {
126
+ const shouldContinue = await record.originalHandlePostAgentRun();
127
+ if (!shouldContinue || !this.host.isCurrentSession(session))
128
+ break;
129
+ await session.agent.continue();
130
+ }
131
+ }
132
+ finally {
133
+ internals._systemPromptOverride = undefined;
134
+ internals._flushPendingBashMessages();
135
+ await internals._emitAgentSettled();
136
+ }
137
+ }
138
+ catch (error) {
139
+ this.setState(record, "paused");
140
+ this.host.showToast(`Could not continue agent: ${errorMessage(error)}`, "error");
141
+ return;
142
+ }
143
+ this.setState(record, "idle");
144
+ }
145
+ setState(record, state) {
146
+ if (record.state === state)
147
+ return;
148
+ record.state = state;
149
+ this.host.render();
150
+ }
151
+ finishSettlement(record) {
152
+ record.resolvePauseSettled?.();
153
+ delete record.resolvePauseSettled;
154
+ if (record.state === "pause-requested") {
155
+ record.pauseBoundaryReached = false;
156
+ this.setState(record, "idle");
157
+ }
158
+ }
159
+ canContinue(session) {
160
+ const messages = session.agent.state.messages;
161
+ const lastMessage = messages[messages.length - 1];
162
+ return Boolean(lastMessage && lastMessage.role !== "assistant") || session.agent.hasQueuedMessages();
163
+ }
164
+ sessionInternals(session) {
165
+ const internals = session;
166
+ if (typeof internals._handlePostAgentRun !== "function"
167
+ || typeof internals._flushPendingBashMessages !== "function"
168
+ || typeof internals._emitAgentSettled !== "function"
169
+ || typeof internals._isAgentRunActive !== "boolean"
170
+ || !("_systemPromptOverride" in internals)) {
171
+ throw new Error("Agent pause is incompatible with this pi SDK version");
172
+ }
173
+ return internals;
174
+ }
175
+ }
176
+ function errorMessage(error) {
177
+ return error instanceof Error ? error.message : String(error);
178
+ }
@@ -144,6 +144,7 @@ export declare class AppSessionEventController {
144
144
  touchEntry(entry: Entry): void;
145
145
  addEntry(entry: Entry): void;
146
146
  prependEntries(entries: readonly Entry[]): void;
147
+ private prependLoadedHistoryEntries;
147
148
  private shiftHistoryWindow;
148
149
  private setHistoryWindowStart;
149
150
  private historyWindowSize;