@oh-my-pi/pi-coding-agent 16.4.4 → 16.4.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.
Files changed (109) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/dist/cli.js +3799 -3729
  3. package/dist/types/async/job-manager.d.ts +8 -0
  4. package/dist/types/cli/bench-cli.d.ts +1 -7
  5. package/dist/types/cli/usage-cli.d.ts +1 -0
  6. package/dist/types/commands/usage.d.ts +7 -0
  7. package/dist/types/config/settings-schema.d.ts +19 -9
  8. package/dist/types/config/settings.d.ts +3 -2
  9. package/dist/types/discovery/helpers.d.ts +2 -2
  10. package/dist/types/extensibility/extensions/types.d.ts +36 -0
  11. package/dist/types/irc/bus.d.ts +4 -0
  12. package/dist/types/modes/components/__tests__/pause-screen.test.d.ts +1 -0
  13. package/dist/types/modes/components/ask-dialog.d.ts +27 -0
  14. package/dist/types/modes/components/custom-editor.d.ts +3 -8
  15. package/dist/types/modes/components/index.d.ts +2 -1
  16. package/dist/types/modes/components/model-browser.d.ts +100 -0
  17. package/dist/types/modes/components/model-hub.d.ts +52 -0
  18. package/dist/types/modes/components/pause-screen.d.ts +43 -0
  19. package/dist/types/modes/components/session-selector.d.ts +13 -0
  20. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  21. package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -1
  22. package/dist/types/modes/controllers/input-controller.d.ts +2 -0
  23. package/dist/types/modes/interactive-mode.d.ts +3 -0
  24. package/dist/types/modes/queue-input.d.ts +8 -0
  25. package/dist/types/modes/shared.d.ts +1 -1
  26. package/dist/types/modes/types.d.ts +4 -0
  27. package/dist/types/session/agent-session.d.ts +1 -1
  28. package/dist/types/session/agent-storage.d.ts +57 -0
  29. package/dist/types/session/session-context.d.ts +9 -0
  30. package/dist/types/task/executor.d.ts +26 -13
  31. package/dist/types/task/index.d.ts +12 -11
  32. package/dist/types/task/label.d.ts +4 -0
  33. package/dist/types/task/repair-args.d.ts +8 -8
  34. package/dist/types/task/types.d.ts +31 -56
  35. package/dist/types/tools/ask.d.ts +12 -0
  36. package/dist/types/tools/conflict-detect.d.ts +17 -1
  37. package/dist/types/tools/job.d.ts +16 -0
  38. package/package.json +12 -12
  39. package/scripts/build-binary.ts +0 -1
  40. package/scripts/compile-binary.ts +4 -3
  41. package/src/async/job-manager.ts +9 -0
  42. package/src/cli/bench-cli.ts +7 -26
  43. package/src/cli/usage-cli.ts +11 -0
  44. package/src/commands/usage.ts +13 -2
  45. package/src/commit/agentic/tools/analyze-file.ts +2 -3
  46. package/src/config/settings-schema.ts +18 -7
  47. package/src/config/settings.ts +13 -4
  48. package/src/discovery/helpers.ts +3 -4
  49. package/src/extensibility/custom-tools/loader.ts +70 -37
  50. package/src/extensibility/extensions/types.ts +46 -0
  51. package/src/irc/bus.ts +61 -20
  52. package/src/modes/components/__tests__/pause-screen.test.ts +143 -0
  53. package/src/modes/components/advisor-config.ts +32 -22
  54. package/src/modes/components/ask-dialog.ts +888 -0
  55. package/src/modes/components/custom-editor.test.ts +58 -1
  56. package/src/modes/components/custom-editor.ts +42 -11
  57. package/src/modes/components/index.ts +2 -1
  58. package/src/modes/components/model-browser.ts +769 -0
  59. package/src/modes/components/model-hub.ts +2002 -0
  60. package/src/modes/components/pause-screen.ts +208 -0
  61. package/src/modes/components/session-selector.ts +299 -42
  62. package/src/modes/components/tool-execution.ts +2 -0
  63. package/src/modes/components/usage-row.ts +5 -6
  64. package/src/modes/controllers/event-controller.ts +8 -2
  65. package/src/modes/controllers/extension-ui-controller.ts +252 -5
  66. package/src/modes/controllers/input-controller.ts +140 -6
  67. package/src/modes/controllers/selector-controller.ts +160 -97
  68. package/src/modes/controllers/tan-command-controller.ts +1 -1
  69. package/src/modes/controllers/todo-command-controller.ts +1 -2
  70. package/src/modes/interactive-mode.ts +8 -0
  71. package/src/modes/queue-input.ts +132 -0
  72. package/src/modes/shared.ts +1 -1
  73. package/src/modes/theme/theme.ts +3 -3
  74. package/src/modes/types.ts +4 -0
  75. package/src/modes/utils/ui-helpers.ts +50 -24
  76. package/src/prompts/agents/scout.md +0 -1
  77. package/src/prompts/agents/task.md +1 -1
  78. package/src/prompts/system/subagent-system-prompt.md +1 -5
  79. package/src/prompts/system/subagent-yield-reminder.md +10 -0
  80. package/src/prompts/system/task-label.md +23 -0
  81. package/src/prompts/tools/job.md +1 -1
  82. package/src/prompts/tools/task-summary.md +3 -0
  83. package/src/prompts/tools/task.md +17 -18
  84. package/src/session/agent-session.ts +186 -49
  85. package/src/session/agent-storage.ts +330 -3
  86. package/src/session/history-storage.ts +1 -34
  87. package/src/session/session-context.test.ts +73 -0
  88. package/src/session/session-context.ts +43 -26
  89. package/src/slash-commands/builtin-registry.ts +18 -0
  90. package/src/task/agents.ts +2 -0
  91. package/src/task/executor.ts +159 -46
  92. package/src/task/index.ts +377 -239
  93. package/src/task/label.ts +38 -0
  94. package/src/task/render.ts +74 -22
  95. package/src/task/repair-args.ts +20 -31
  96. package/src/task/spawn-policy.test.ts +4 -4
  97. package/src/task/types.ts +46 -66
  98. package/src/tools/ask.ts +233 -40
  99. package/src/tools/conflict-detect.ts +102 -5
  100. package/src/tools/index.ts +1 -0
  101. package/src/tools/irc.ts +20 -11
  102. package/src/tools/job.ts +158 -18
  103. package/src/tools/write.ts +70 -6
  104. package/src/vibe/runtime.ts +1 -1
  105. package/src/web/search/providers/browser-headers.ts +30 -13
  106. package/dist/types/modes/components/model-selector.d.ts +0 -37
  107. package/dist/types/tools/bash-command-fixup.d.ts +0 -3
  108. package/src/modes/components/model-selector.ts +0 -1291
  109. package/src/tools/bash-command-fixup.ts +0 -4
@@ -15,6 +15,12 @@ export interface AsyncJob {
15
15
  * supply an id (e.g. legacy tests, SDK consumers without an agent context).
16
16
  */
17
17
  ownerId?: string;
18
+ /**
19
+ * Registry id of the subagent this job runs (task/tan/vibe jobs). Lets
20
+ * job-view code link a job row to its AgentRegistry ref even when the job
21
+ * id differs from the agent id (vibe turn jobs, tan clones).
22
+ */
23
+ agentId?: string;
18
24
  /**
19
25
  * Job is registered but parked behind a caller-managed gate (e.g. a task
20
26
  * batch semaphore). Queued jobs do not count toward the running-job limit
@@ -37,6 +43,8 @@ export interface AsyncJobRegisterOptions {
37
43
  id?: string;
38
44
  /** Registry id of the agent that owns this job; used to scope cancelAll. */
39
45
  ownerId?: string;
46
+ /** Registry id of the subagent this job runs; see {@link AsyncJob.agentId}. */
47
+ agentId?: string;
40
48
  onProgress?: (text: string, details?: Record<string, unknown>) => void | Promise<void>;
41
49
  /** Register the job in queued state; see {@link AsyncJob.queued}. */
42
50
  queued?: boolean;
@@ -30,7 +30,7 @@ export interface BenchRunSuccess {
30
30
  ttftMs: number;
31
31
  durationMs: number;
32
32
  outputTokens: number;
33
- /** Generation throughput measured over the post-first-token window. */
33
+ /** Output tokens/sec over the total request duration. */
34
34
  tokensPerSecond: number;
35
35
  }
36
36
  export interface BenchRunFailure {
@@ -74,12 +74,6 @@ export interface BenchDependencies {
74
74
  now?: () => number;
75
75
  stdoutIsTTY?: boolean;
76
76
  }
77
- /**
78
- * Tokens/s over the generation window (duration minus TTFT) so queue/prefill
79
- * latency does not dilute throughput. Falls back to total duration when the
80
- * response arrived as a single chunk (TTFT ~ duration).
81
- */
82
- export declare function computeTokensPerSecond(outputTokens: number, durationMs: number, ttftMs: number, deltaChunkCount: number): number;
83
77
  export declare function formatBenchTable(summary: BenchSummary): string;
84
78
  export declare function runBenchCommand(command: BenchCommandArgs, deps?: BenchDependencies): Promise<BenchSummary>;
85
79
  export {};
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { type UsageHistoryEntry, type UsageReport } from "@oh-my-pi/pi-ai";
11
11
  export interface UsageCommandArgs {
12
+ action?: string;
12
13
  json?: boolean;
13
14
  provider?: string;
14
15
  redact?: boolean;
@@ -4,6 +4,13 @@
4
4
  import { Command } from "@oh-my-pi/pi-utils/cli";
5
5
  export default class Usage extends Command {
6
6
  static description: string;
7
+ static args: {
8
+ action: import("@oh-my-pi/pi-utils/cli").ArgDescriptor & {
9
+ description: string;
10
+ required: false;
11
+ options: string[];
12
+ };
13
+ };
7
14
  static flags: {
8
15
  json: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"boolean"> & {
9
16
  char: string;
@@ -1384,7 +1384,7 @@ export declare const SETTINGS_SCHEMA: {
1384
1384
  readonly tab: "model";
1385
1385
  readonly group: "Retry & Fallback";
1386
1386
  readonly label: "Retry Fallback Chains";
1387
- readonly description: 'JSON object mapping model roles to ordered fallback model selectors, e.g. {"default":["openai/gpt-4o-mini"]}.';
1387
+ readonly description: 'JSON object mapping model roles, model selectors ("provider/model-id"), or provider wildcards ("provider/*") to ordered fallback selectors, e.g. {"default":["openai/gpt-4o-mini"],"google-antigravity/*":["google/*","google-vertex/*"]}. Model-oriented keys apply whenever that model/provider is active, regardless of role; a "provider/*" entry keeps the failing model\'s id and swaps the provider.';
1388
1388
  };
1389
1389
  };
1390
1390
  readonly "retry.fallbackRevertPolicy": {
@@ -3749,6 +3749,16 @@ export declare const SETTINGS_SCHEMA: {
3749
3749
  readonly description: "Enable the web_search tool for live web results";
3750
3750
  };
3751
3751
  };
3752
+ readonly "ask.enabled": {
3753
+ readonly type: "boolean";
3754
+ readonly default: true;
3755
+ readonly ui: {
3756
+ readonly tab: "tools";
3757
+ readonly group: "Available Tools";
3758
+ readonly label: "Ask";
3759
+ readonly description: "Enable the ask tool for interactive user questions";
3760
+ };
3761
+ };
3752
3762
  readonly "browser.enabled": {
3753
3763
  readonly type: "boolean";
3754
3764
  readonly default: true;
@@ -4291,36 +4301,36 @@ export declare const SETTINGS_SCHEMA: {
4291
4301
  };
4292
4302
  readonly "task.softRequestBudget": {
4293
4303
  readonly type: "number";
4294
- readonly default: 90;
4304
+ readonly default: 200;
4295
4305
  readonly ui: {
4296
4306
  readonly tab: "tasks";
4297
4307
  readonly group: "Subagents";
4298
4308
  readonly label: "Soft Subagent Request Budget";
4299
- readonly description: "Soft per-subagent request budget (assistant requests per run). Crossing it can inject a steering notice when task.softRequestBudgetNotice is enabled; at 1.5x the budget the run is aborted gracefully, salvaging partial output. 0 disables the guard. Bundled scout/sonic agents use a lower built-in budget.";
4309
+ readonly description: "Soft per-subagent request budget (assistant requests per run). Crossing it injects a wrap-up steering notice (see task.softRequestBudgetNotice); at 1.5x the budget the run is force-stopped and the agent must yield its partial findings. 0 disables the guard. Bundled scout/sonic agents use a lower built-in budget.";
4300
4310
  readonly options: readonly [{
4301
4311
  readonly value: "0";
4302
4312
  readonly label: "Disabled";
4303
- }, {
4304
- readonly value: "40";
4305
- readonly label: "40 requests";
4306
4313
  }, {
4307
4314
  readonly value: "90";
4308
4315
  readonly label: "90 requests";
4309
- readonly description: "Default";
4310
4316
  }, {
4311
4317
  readonly value: "150";
4312
4318
  readonly label: "150 requests";
4319
+ }, {
4320
+ readonly value: "200";
4321
+ readonly label: "200 requests";
4322
+ readonly description: "Default";
4313
4323
  }];
4314
4324
  };
4315
4325
  };
4316
4326
  readonly "task.softRequestBudgetNotice": {
4317
4327
  readonly type: "boolean";
4318
- readonly default: false;
4328
+ readonly default: true;
4319
4329
  readonly ui: {
4320
4330
  readonly tab: "tasks";
4321
4331
  readonly group: "Subagents";
4322
4332
  readonly label: "Soft Request Budget Notice";
4323
- readonly description: "Inject one steering notice when a subagent crosses its soft request budget. Off by default; enabling it asks the child to wrap up before the 1.5x graceful abort guard.";
4333
+ readonly description: "Inject one steering notice when a subagent crosses its soft request budget, asking it to wrap up before the 1.5x forced-yield stop.";
4324
4334
  };
4325
4335
  };
4326
4336
  readonly "task.disabledAgents": {
@@ -129,9 +129,10 @@ export declare class Settings {
129
129
  */
130
130
  getBashInterceptorRules(): BashInterceptorRule[];
131
131
  /**
132
- * Set a model role (helper for modelRoles record).
132
+ * Set a model role (helper for modelRoles record). Passing `undefined`
133
+ * clears the role from the persisted record and any runtime override.
133
134
  */
134
- setModelRole(role: ModelRole | string, modelId: string): void;
135
+ setModelRole(role: ModelRole | string, modelId: string | undefined): void;
135
136
  /**
136
137
  * Get a model role (helper for modelRoles record).
137
138
  */
@@ -1,8 +1,8 @@
1
- import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
1
  import type { ExtensionModule } from "../capability/extension-module.js";
3
2
  import { type Rule } from "../capability/rule.js";
4
3
  import type { Skill } from "../capability/skill.js";
5
4
  import type { LoadContext, LoadResult, SourceMeta } from "../capability/types.js";
5
+ import { type ConfiguredThinkingLevel } from "../thinking.js";
6
6
  /**
7
7
  * Standard paths for each config source.
8
8
  */
@@ -107,7 +107,7 @@ export interface ParsedAgentFields {
107
107
  spawns?: string[] | "*";
108
108
  model?: string[];
109
109
  output?: unknown;
110
- thinkingLevel?: ThinkingLevel;
110
+ thinkingLevel?: ConfiguredThinkingLevel;
111
111
  autoloadSkills?: string[];
112
112
  readSummarize?: boolean;
113
113
  blocking?: boolean;
@@ -42,6 +42,40 @@ export interface ExtensionUISelectOption {
42
42
  description?: string;
43
43
  }
44
44
  export type ExtensionUISelectItem = string | ExtensionUISelectOption;
45
+ export interface ExtensionAskDialogOption {
46
+ label: string;
47
+ description?: string;
48
+ preview?: string;
49
+ }
50
+ export interface ExtensionAskDialogQuestion {
51
+ id: string;
52
+ question: string;
53
+ header?: string;
54
+ options: ExtensionAskDialogOption[];
55
+ multi?: boolean;
56
+ recommended?: number;
57
+ }
58
+ export interface ExtensionAskDialogResultItem {
59
+ id: string;
60
+ question: string;
61
+ options: string[];
62
+ multi: boolean;
63
+ selectedOptions: string[];
64
+ customInput?: string;
65
+ note?: string;
66
+ timedOut?: boolean;
67
+ }
68
+ export interface ExtensionAskDialogSubmitResult {
69
+ kind: "submit";
70
+ results: ExtensionAskDialogResultItem[];
71
+ }
72
+ /** Chat-redirect result: the user chose "Chat about this" instead of
73
+ * answering. Distinct from `undefined` (cancel) so AskTool can hand off to
74
+ * the chat loop rather than aborting. */
75
+ export interface ExtensionAskDialogChatResult {
76
+ kind: "chat";
77
+ }
78
+ export type ExtensionAskDialogResult = ExtensionAskDialogSubmitResult | ExtensionAskDialogChatResult;
45
79
  export declare function getExtensionUISelectOptionLabel(option: ExtensionUISelectItem): string;
46
80
  /**
47
81
  * UI dialog options for extensions.
@@ -108,6 +142,8 @@ export interface ExtensionUIContext {
108
142
  confirm(title: string, message: string, dialogOptions?: ExtensionUIDialogOptions): Promise<boolean>;
109
143
  /** Show a text input dialog. */
110
144
  input(title: string, placeholder?: string, dialogOptions?: ExtensionUIDialogOptions): Promise<string | undefined>;
145
+ /** Show the rich ask dialog when the interactive TUI surface is available. */
146
+ askDialog?(questions: ExtensionAskDialogQuestion[], dialogOptions?: ExtensionUIDialogOptions): Promise<ExtensionAskDialogResult | undefined>;
111
147
  /** Show a notification to the user. */
112
148
  notify(message: string, type?: "info" | "warning" | "error"): void;
113
149
  /** Listen to raw terminal input (interactive mode only). Returns an unsubscribe function. */
@@ -77,6 +77,10 @@ export declare class IrcBus {
77
77
  from?: string;
78
78
  }, timeoutMs: number, signal?: AbortSignal, options?: {
79
79
  drainPending?: boolean;
80
+ liveness?: {
81
+ registry: AgentRegistry;
82
+ senderId: string;
83
+ };
80
84
  }): Promise<IrcMessage | null>;
81
85
  /** Drain (or peek) pending messages for `agentId`. */
82
86
  inbox(agentId: string, opts?: {
@@ -0,0 +1,27 @@
1
+ import { type Component, type TUI } from "@oh-my-pi/pi-tui";
2
+ import type { ExtensionAskDialogQuestion, ExtensionAskDialogSubmitResult } from "../../extensibility/extensions/index.js";
3
+ /** Bound a prompt editor title to a fixed row/width budget so long or
4
+ * multi-line questions stay usable inside the small prompt overlay. */
5
+ export declare function boundPromptTitle(prefix: string, question: string): string;
6
+ interface AskDialogCallbacks {
7
+ onSubmit(result: ExtensionAskDialogSubmitResult): void;
8
+ onCancel(): void;
9
+ onPrompt(title: string, prefill?: string): Promise<string | undefined>;
10
+ }
11
+ interface AskDialogOptions {
12
+ timeout?: number;
13
+ onTimeout?: () => void;
14
+ tui?: TUI;
15
+ }
16
+ export declare class AskDialogComponent implements Component {
17
+ #private;
18
+ private readonly questions;
19
+ private readonly callbacks;
20
+ private readonly options;
21
+ constructor(questions: ExtensionAskDialogQuestion[], callbacks: AskDialogCallbacks, options?: AskDialogOptions);
22
+ invalidate(): void;
23
+ dispose(): void;
24
+ handleInput(keyData: string): void;
25
+ render(width: number): readonly string[];
26
+ }
27
+ export {};
@@ -86,14 +86,9 @@ export declare class CustomEditor extends Editor {
86
86
  static readonly SHIMMER_FRAME_MS = 70;
87
87
  /** Time for the gradient to sweep one full cycle across each keyword. */
88
88
  static readonly SHIMMER_PERIOD_MS = 1800;
89
- /** Gradient-highlight the "ultrathink" / "orchestrate" / "workflowz" keywords as the user types
90
- * them, skipping any occurrence inside code spans, fenced blocks, or XML sections. Also make
91
- * pasted image placeholders visually distinct and hyperlink them once their blob file exists.
92
- * When the editor is focused, the buffer contains a magic keyword, and `magicKeywords.enabled`
93
- * is on, the gradient shifts every frame to produce a Claude-Code-style shimmer; each render
94
- * schedules the next frame, so losing focus, deleting the keyword, or flipping the setting
95
- * stops the animation on its own. The static glow itself runs even when shimmering is gated
96
- * off, matching existing behavior for the editor and sent bubbles. */
89
+ /** Decorate magic keywords, attachments, and the queue-composer header/list markers.
90
+ * Queue shorthand reserves its first logical line as a dim `Queueing` label; sequential
91
+ * item markers use the accent color so separate follow-ups remain visible while composing. */
97
92
  decorateText: (text: string) => string;
98
93
  /** Optional test/host override for the magic-keyword shimmer gate. When
99
94
  * defined, takes precedence over the global `magicKeywords.enabled` setting,
@@ -16,7 +16,8 @@ export * from "./hook-selector.js";
16
16
  export * from "./keybinding-hints.js";
17
17
  export * from "./login-dialog.js";
18
18
  export * from "./logout-account-selector.js";
19
- export * from "./model-selector.js";
19
+ export * from "./model-browser.js";
20
+ export * from "./model-hub.js";
20
21
  export * from "./oauth-selector.js";
21
22
  export * from "./queue-mode-selector.js";
22
23
  export * from "./read-tool-group.js";
@@ -0,0 +1,100 @@
1
+ import type { Model } from "@oh-my-pi/pi-ai";
2
+ import { type Component, type SgrMouseEvent } from "@oh-my-pi/pi-tui";
3
+ import type { Settings } from "../../config/settings.js";
4
+ import type { ModelPerfStats } from "../../session/agent-storage.js";
5
+ import { type ConfiguredThinkingLevel } from "../../thinking.js";
6
+ /** One selectable model row. `selector` is the canonical `provider/id` key. */
7
+ export interface ModelBrowserItem {
8
+ provider: string;
9
+ id: string;
10
+ model: Model;
11
+ selector: string;
12
+ }
13
+ /** Resolved role assignment as displayed by the browser and the hub. */
14
+ export interface RoleAssignment {
15
+ model: Model;
16
+ thinkingLevel: ConfiguredThinkingLevel;
17
+ /** True when the role has no configured value and fell back to auto-selection. */
18
+ autoSelected: boolean;
19
+ }
20
+ /** Map of role id to its resolved assignment (absent roles are unresolved). */
21
+ export type RoleAssignments = Record<string, RoleAssignment | undefined>;
22
+ /** Wrap raw models into browser items. */
23
+ export declare function buildBrowserItems(models: ReadonlyArray<Model>): ModelBrowserItem[];
24
+ /** Options for {@link sortModelItems}. */
25
+ export interface SortModelItemsOptions {
26
+ roles?: RoleAssignments;
27
+ mruOrder?: ReadonlyArray<string>;
28
+ /**
29
+ * When a search query is narrowing the list, role assignments should NOT
30
+ * promote a weakly-matching default model above a perfect text match —
31
+ * defer to MRU/version instead so user affinity drives the order.
32
+ */
33
+ skipRoleRank?: boolean;
34
+ }
35
+ /**
36
+ * Order models for display: role-assigned first, then most-recently-used,
37
+ * then per provider by priority, version, and recency.
38
+ */
39
+ export declare function sortModelItems(items: ModelBrowserItem[], options?: SortModelItemsOptions): void;
40
+ /** Compact glyph for a configured thinking level; empty for `inherit` (nothing to show). */
41
+ export declare function thinkingLevelGlyph(level: ConfiguredThinkingLevel): string;
42
+ /**
43
+ * A slim role chip: `●default ◉` — solid dot for configured assignments,
44
+ * hollow for auto-selected fallbacks, thinking glyph attached when set.
45
+ */
46
+ export declare function formatRoleChip(role: string, assignment: RoleAssignment, settings: Settings): string;
47
+ /** Behavior switches for {@link ModelBrowser}. */
48
+ export interface ModelBrowserOptions {
49
+ /** Render the dim `provider/` prefix before model ids. Default true. */
50
+ showProvider?: boolean;
51
+ /** Session token count used to disable models whose context window is exceeded. */
52
+ currentContextTokens?: number;
53
+ /** When true, rows over the current context are unselectable (session-switch mode). */
54
+ disableOverContext?: boolean;
55
+ /** Host-provided empty-state text (e.g. provider discovery status). */
56
+ emptyText?: () => string | undefined;
57
+ initialQuery?: string;
58
+ }
59
+ /**
60
+ * The reusable browser component. Renders a fixed-height block
61
+ * (`maxVisible + LIST_ROW_START + DETAIL_ROWS` rows) so host mouse geometry
62
+ * stays stable across renders.
63
+ */
64
+ export declare class ModelBrowser implements Component {
65
+ #private;
66
+ /** Enter or click-on-selected. */
67
+ onActivate?: (item: ModelBrowserItem) => void;
68
+ onSelectionChange?: (item: ModelBrowserItem | undefined) => void;
69
+ onQueryChange?: (query: string) => void;
70
+ /** Cancel key with an empty query (a non-empty query is cleared first). */
71
+ onCancel?: () => void;
72
+ constructor(settings: Settings, options?: ModelBrowserOptions);
73
+ /** Replace the scope's base items; the live query re-applies and selection is pinned by selector. */
74
+ setItems(items: ModelBrowserItem[]): void;
75
+ setRoles(roles: RoleAssignments): void;
76
+ setMruOrder(order: ReadonlyArray<string>): void;
77
+ /** Measured TPS/TTFT averages keyed by `provider/id` selector (see AgentStorage.getModelPerf). */
78
+ setPerfStats(perf: ReadonlyMap<string, ModelPerfStats>): void;
79
+ setMaxVisible(rows: number): void;
80
+ setShowProvider(show: boolean): void;
81
+ /** Total rendered height for the current `maxVisible` (host layout budgeting). */
82
+ get renderedRows(): number;
83
+ get query(): string;
84
+ setQuery(query: string): void;
85
+ getSelected(): ModelBrowserItem | undefined;
86
+ get visibleCount(): number;
87
+ /** Move selection to `selector`; false when it is not in the current view. */
88
+ selectSelector(selector: string): boolean;
89
+ moveSelection(delta: number): void;
90
+ handleInput(data: string): void;
91
+ /** Cancel-key ladder: clear a non-empty query first, then bubble to the host. */
92
+ handleCancel(): void;
93
+ /**
94
+ * Route a mouse event. `line` is relative to the browser's first rendered
95
+ * row (the search row).
96
+ */
97
+ routeMouse(event: SgrMouseEvent, line: number): void;
98
+ render(width: number): string[];
99
+ invalidate(): void;
100
+ }
@@ -0,0 +1,52 @@
1
+ import type { Model } from "@oh-my-pi/pi-ai";
2
+ import { type Component, type TUI } from "@oh-my-pi/pi-tui";
3
+ import type { ModelRegistry } from "../../config/model-registry.js";
4
+ import type { Settings } from "../../config/settings.js";
5
+ import { type ConfiguredThinkingLevel } from "../../thinking.js";
6
+ /** `roles` is the full /models hub; `pick` is a one-shot session/embedded picker. */
7
+ export type ModelHubMode = "roles" | "pick";
8
+ /** A `--models` scope entry (mirrors the session's scoped model list). */
9
+ export interface ScopedModelItem {
10
+ model: Model;
11
+ thinkingLevel?: string;
12
+ }
13
+ export interface ModelHubCallbacks {
14
+ /** Persist a role assignment. */
15
+ onAssign: (model: Model, role: string, thinkingLevel: ConfiguredThinkingLevel | undefined, selector: string) => void;
16
+ /** Clear a configured role back to auto-selection. */
17
+ onUnassign: (role: string) => void;
18
+ /** Persist a `retry.fallbackChains` entry — keyed by a role, `provider/model-id`, or `provider/*`; an empty chain clears the key. */
19
+ onFallbackChainChange?: (role: string, chain: string[]) => void;
20
+ /** Pick-mode activation: session-only switch or embedded pick. */
21
+ onPick?: (model: Model, selector: string) => void;
22
+ /** Locked provider activation: forward to the /login flow. */
23
+ onLoginRequest?: (providerId: string) => void;
24
+ /** Persist a new quick-switch cycle order (the ctrl+p role cycle). */
25
+ onCycleOrderChange?: (order: string[]) => void;
26
+ onCancel: () => void;
27
+ }
28
+ export interface ModelHubOptions {
29
+ mode?: ModelHubMode;
30
+ /** Session token count; in pick mode, models with smaller context windows are disabled. */
31
+ currentContextTokens?: number;
32
+ /** Preselect this provider's sidebar entry (e.g. when reopening after /login). */
33
+ initialProviderId?: string;
34
+ /** Status-row hint shown in pick mode. */
35
+ pickerHint?: string;
36
+ initialQuery?: string;
37
+ }
38
+ /** Test hook: forget which providers were auto-refreshed this process. */
39
+ export declare function resetProviderAutoRefreshGuard(): void;
40
+ /**
41
+ * The fullscreen model hub component. Hosted via `ui.showOverlay(..., { fullscreen: true })`;
42
+ * the host must call {@link ModelHubComponent.dispose} when the overlay closes.
43
+ */
44
+ export declare class ModelHubComponent implements Component {
45
+ #private;
46
+ constructor(tui: TUI, settings: Settings, registry: ModelRegistry, scopedModels: ReadonlyArray<ScopedModelItem>, callbacks: ModelHubCallbacks, options?: ModelHubOptions);
47
+ /** Cancel pending provider refresh timers and the spinner. Host calls this on overlay close. */
48
+ dispose(): void;
49
+ invalidate(): void;
50
+ handleInput(data: string): void;
51
+ render(width: number): string[];
52
+ }
@@ -0,0 +1,43 @@
1
+ import { type Component, type OverlayFocusOwner, type OverlayHandle, type OverlayOptions } from "@oh-my-pi/pi-tui";
2
+ /**
3
+ * Slice of `InteractiveModeContext` the pause screen drives. Narrow so tests
4
+ * can exercise the full engage → hold → release lifecycle without a real TUI.
5
+ */
6
+ export interface PauseScreenHost {
7
+ ui: {
8
+ showOverlay(component: Component, options?: OverlayOptions): OverlayHandle;
9
+ setFocus(component: Component): void;
10
+ requestRender(): void;
11
+ readonly terminal: {
12
+ readonly rows: number;
13
+ };
14
+ };
15
+ showStatus(message: string, options?: {
16
+ dim?: boolean;
17
+ }): void;
18
+ readonly sessionName?: string;
19
+ }
20
+ /**
21
+ * Paint the pause scene as exactly `height` rows, vertically centered.
22
+ * Exported for tests.
23
+ */
24
+ export declare function renderPauseScreen(width: number, height: number, elapsedMs: number, sessionName?: string): string[];
25
+ /** Fullscreen overlay component; resolves {@link run} when a resume key lands. */
26
+ export declare class PauseScreenComponent implements Component, OverlayFocusOwner {
27
+ #private;
28
+ readonly host: PauseScreenHost;
29
+ constructor(host: PauseScreenHost);
30
+ /** Start the clock; resolves once the user asks to resume. */
31
+ run(): Promise<void>;
32
+ dispose(): void;
33
+ ownsOverlayFocusTarget(component: Component): boolean;
34
+ handleInput(data: string): void;
35
+ render(width: number): readonly string[];
36
+ }
37
+ /**
38
+ * Engage the global pause gate and hold the fullscreen pause screen until the
39
+ * user resumes. No-op when the gate is already engaged. Always releases the
40
+ * gate on the way out (including teardown throws) — a leaked pause would
41
+ * freeze every agent in the process with no UI left to release it.
42
+ */
43
+ export declare function runPauseScreen(host: PauseScreenHost): Promise<void>;
@@ -9,6 +9,9 @@ export type SessionHistoryMatcher = (query: string) => string[];
9
9
  * as a literal substring, newer sessions should beat a slightly better fuzzy
10
10
  * position match. Pure fuzzy/acronym matches still sort by fuzzy score after
11
11
  * literal matches, but weak pure fuzzy tokens are dropped as noise.
12
+ *
13
+ * This is the synchronous reference implementation; {@link SessionList} runs
14
+ * the same primitives incrementally so huge listings never block a keystroke.
12
15
  */
13
16
  export declare function rankSessionSearchMatches(allSessions: SessionInfo[], query: string): SessionInfo[];
14
17
  /**
@@ -35,9 +38,13 @@ declare class SessionList implements Component {
35
38
  onExit: () => void;
36
39
  onToggleScope?: () => void;
37
40
  onDeleteRequest?: (session: SessionInfo) => void;
41
+ /** Re-render hook for async list updates (fuzzy scan chunks, history merge). */
42
+ onRequestRender?: () => void;
38
43
  constructor(sessions: SessionInfo[], showCwd?: boolean, historyMatcher?: SessionHistoryMatcher, getTerminalRows?: () => number);
39
44
  /** Replace the visible dataset, e.g. when toggling folder/all-projects scope. */
40
45
  setSessions(sessions: SessionInfo[], showCwd: boolean): void;
46
+ /** Cancel pending async search work; idempotent, called on every picker exit path. */
47
+ dispose(): void;
41
48
  removeSession(sessionPath: string): void;
42
49
  /** Resolve a list-local rendered-line index to a filtered-session index. */
43
50
  hitTestSession(line: number): number | undefined;
@@ -76,6 +83,12 @@ export declare class SessionSelectorComponent extends Container {
76
83
  #private;
77
84
  constructor(sessions: SessionInfo[], onSelect: (session: SessionInfo) => void, onCancel: () => void, onExit: () => void, options?: SessionSelectorOptions);
78
85
  setOnRequestRender(callback: () => void): void;
86
+ /**
87
+ * Dispose the session list explicitly: while the delete-confirmation dialog
88
+ * is mounted the list is detached from the child tree, so Container's
89
+ * child-walking dispose would miss its pending history-merge timer.
90
+ */
91
+ dispose(): void;
79
92
  /**
80
93
  * Concatenate the children's renders (like {@link Container}) while recording
81
94
  * the line where the session list begins, so the fullscreen picker can hit-
@@ -32,6 +32,8 @@ export interface ToolExecutionHandle extends Component {
32
32
  }, isPartial?: boolean, toolCallId?: string): void;
33
33
  setArgsComplete(toolCallId?: string): void;
34
34
  setExpanded(expanded: boolean): void;
35
+ /** Freeze the block as final history: stop spinners and let it commit to scrollback. */
36
+ seal(): void;
35
37
  }
36
38
  /** Redraw live tool blocks at the spinner's glyph-advance rate. Rendering more
37
39
  * often produced identical frames — the previous 30fps cadence emitted ~2.4
@@ -1,6 +1,6 @@
1
1
  import type { Component, TUI } from "@oh-my-pi/pi-tui";
2
2
  import { KeybindingsManager } from "../../config/keybindings.js";
3
- import type { ExtensionUIContext, ExtensionUIDialogOptions, ExtensionUISelectItem, ExtensionWidgetContent, ExtensionWidgetOptions, TerminalInputHandler } from "../../extensibility/extensions/index.js";
3
+ import type { ExtensionAskDialogQuestion, ExtensionAskDialogResult, ExtensionUIContext, ExtensionUIDialogOptions, ExtensionUISelectItem, ExtensionWidgetContent, ExtensionWidgetOptions, TerminalInputHandler } from "../../extensibility/extensions/index.js";
4
4
  import { type HookSelectorSlider } from "../../modes/components/hook-selector.js";
5
5
  import { type Theme } from "../../modes/theme/theme.js";
6
6
  import type { InteractiveModeContext, InteractiveSelectorDialogOptions } from "../../modes/types.js";
@@ -32,6 +32,7 @@ export declare class ExtensionUiController {
32
32
  showCollabAwareEditor(title: string, prefill?: string, dialogOptions?: ExtensionUIDialogOptions, editorOptions?: {
33
33
  promptStyle?: boolean;
34
34
  }): Promise<string | undefined>;
35
+ showAskDialog(questions: ExtensionAskDialogQuestion[], dialogOptions?: ExtensionUIDialogOptions): Promise<ExtensionAskDialogResult | undefined>;
35
36
  /**
36
37
  * Show a selector for hooks.
37
38
  */
@@ -33,6 +33,8 @@ export declare class InputController {
33
33
  handleCtrlZ(): void;
34
34
  handleDequeue(): void;
35
35
  handleRetry(): Promise<void>;
36
+ /** Queue `/queue` input behind an active turn, or start it immediately when idle. */
37
+ handleQueueCommand(text: string): Promise<void>;
36
38
  /** Send editor text as a follow-up message (queued behind current stream). */
37
39
  handleFollowUp(): Promise<void>;
38
40
  restoreQueuedMessagesToEditor(options?: {
@@ -166,6 +166,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
166
166
  get eventBus(): EventBus | undefined;
167
167
  get viewSession(): AgentSession;
168
168
  get focusedAgentId(): string | undefined;
169
+ get sessionName(): string | undefined;
169
170
  focusAgentSession(id: string): Promise<void>;
170
171
  focusParentSession(): Promise<void>;
171
172
  unfocusSession(): Promise<void>;
@@ -364,6 +365,8 @@ export declare class InteractiveMode implements InteractiveModeContext {
364
365
  handleCtrlZ(): void;
365
366
  handleDequeue(): void;
366
367
  handleImagePaste(): Promise<boolean>;
368
+ /** Queue slash-command input behind the active turn. */
369
+ handleQueueCommand(message: string): Promise<void>;
367
370
  handleBtwCommand(question: string): Promise<void>;
368
371
  handleTanCommand(work: string): Promise<void>;
369
372
  hasActiveBtw(): boolean;
@@ -0,0 +1,8 @@
1
+ /** Prefix matcher shared by queue-list parsing and editor highlighting. */
2
+ export declare const QUEUE_LIST_MARKER_RE: RegExp;
3
+ /** Extract the message body from the `->` / `=>` yield-queue shorthand. */
4
+ export declare function parseQueueShorthand(text: string): string | undefined;
5
+ /** Whether text currently forms a sequential queue list, including an unfinished trailing item. */
6
+ export declare function isQueuedMessageList(text: string): boolean;
7
+ /** Split a sequential numeric, Roman-numeral, or alphabetic list into queue entries. */
8
+ export declare function splitQueuedMessages(text: string): string[];
@@ -1,7 +1,7 @@
1
1
  import type { TabBarTheme } from "@oh-my-pi/pi-tui";
2
2
  /** Sanitize text for display in a single-line status. Strips ANSI/VT escape sequences, maps remaining C0/C1 control characters to spaces, collapses whitespace, trims. */
3
3
  export declare function sanitizeStatusText(text: string): string;
4
- /** Shared tab bar theme used by model-selector and settings-selector. */
4
+ /** Shared tab bar theme used by fullscreen overlays (settings, agent hub). */
5
5
  export declare function getTabBarTheme(): TabBarTheme;
6
6
  /**
7
7
  * Suffix appended to the loader's working message to remind users they can
@@ -95,6 +95,8 @@ export interface InteractiveModeContext {
95
95
  statusLine: StatusLineComponent;
96
96
  session: AgentSession;
97
97
  sessionManager: SessionManager;
98
+ /** The current session display name / title. */
99
+ readonly sessionName: string | undefined;
98
100
  /** Session the transcript/editor/status are attached to: the focused agent's, else `session`. */
99
101
  readonly viewSession: AgentSession;
100
102
  /** Id of the focused agent, undefined when the main session is attached. */
@@ -350,6 +352,8 @@ export interface InteractiveModeContext {
350
352
  handleCtrlZ(): void;
351
353
  handleDequeue(): void;
352
354
  handleImagePaste(): Promise<boolean>;
355
+ /** Queue a message for delivery only after the active agent turn would stop. */
356
+ handleQueueCommand(message: string): Promise<void>;
353
357
  handleBtwCommand(question: string): Promise<void>;
354
358
  handleTanCommand(work: string): Promise<void>;
355
359
  hasActiveBtw(): boolean;
@@ -711,7 +711,7 @@ export declare class AgentSession {
711
711
  * surface bounded. Display-only — NEVER feed the result to
712
712
  * `agent.replaceMessages` or a provider.
713
713
  */
714
- buildTranscriptSessionContext(options?: Pick<BuildSessionContextOptions, "collapseCompactedHistory">): SessionContext;
714
+ buildTranscriptSessionContext(options?: Pick<BuildSessionContextOptions, "collapseCompactedHistory" | "keepDanglingToolCalls">): SessionContext;
715
715
  /** Convert session messages using the same pre-LLM pipeline as the active session. */
716
716
  convertMessagesToLlm(messages: AgentMessage[], signal?: AbortSignal): Promise<Message[]>;
717
717
  /** Apply session-level stream hooks to a direct side request. */