@oh-my-pi/pi-coding-agent 16.4.4 → 16.4.5

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 (89) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/cli.js +3563 -3540
  3. package/dist/types/async/job-manager.d.ts +8 -0
  4. package/dist/types/config/settings-schema.d.ts +18 -8
  5. package/dist/types/config/settings.d.ts +3 -2
  6. package/dist/types/discovery/helpers.d.ts +2 -2
  7. package/dist/types/extensibility/extensions/types.d.ts +36 -0
  8. package/dist/types/irc/bus.d.ts +4 -0
  9. package/dist/types/modes/components/__tests__/pause-screen.test.d.ts +1 -0
  10. package/dist/types/modes/components/ask-dialog.d.ts +27 -0
  11. package/dist/types/modes/components/index.d.ts +2 -1
  12. package/dist/types/modes/components/model-browser.d.ts +97 -0
  13. package/dist/types/modes/components/model-hub.d.ts +51 -0
  14. package/dist/types/modes/components/pause-screen.d.ts +43 -0
  15. package/dist/types/modes/components/session-selector.d.ts +13 -0
  16. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  17. package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -1
  18. package/dist/types/modes/interactive-mode.d.ts +1 -0
  19. package/dist/types/modes/shared.d.ts +1 -1
  20. package/dist/types/modes/types.d.ts +2 -0
  21. package/dist/types/session/agent-session.d.ts +1 -1
  22. package/dist/types/session/session-context.d.ts +9 -0
  23. package/dist/types/task/executor.d.ts +26 -13
  24. package/dist/types/task/index.d.ts +12 -11
  25. package/dist/types/task/label.d.ts +4 -0
  26. package/dist/types/task/repair-args.d.ts +8 -8
  27. package/dist/types/task/types.d.ts +31 -56
  28. package/dist/types/tools/ask.d.ts +12 -0
  29. package/dist/types/tools/conflict-detect.d.ts +17 -1
  30. package/dist/types/tools/job.d.ts +16 -0
  31. package/package.json +12 -12
  32. package/src/async/job-manager.ts +9 -0
  33. package/src/commit/agentic/tools/analyze-file.ts +2 -3
  34. package/src/config/settings-schema.ts +17 -6
  35. package/src/config/settings.ts +13 -4
  36. package/src/discovery/helpers.ts +3 -4
  37. package/src/extensibility/custom-tools/loader.ts +70 -37
  38. package/src/extensibility/extensions/types.ts +46 -0
  39. package/src/irc/bus.ts +61 -20
  40. package/src/modes/components/__tests__/pause-screen.test.ts +143 -0
  41. package/src/modes/components/advisor-config.ts +30 -22
  42. package/src/modes/components/ask-dialog.ts +888 -0
  43. package/src/modes/components/index.ts +2 -1
  44. package/src/modes/components/model-browser.ts +688 -0
  45. package/src/modes/components/model-hub.ts +1655 -0
  46. package/src/modes/components/pause-screen.ts +208 -0
  47. package/src/modes/components/session-selector.ts +299 -42
  48. package/src/modes/components/tool-execution.ts +2 -0
  49. package/src/modes/controllers/event-controller.ts +8 -2
  50. package/src/modes/controllers/extension-ui-controller.ts +252 -5
  51. package/src/modes/controllers/selector-controller.ts +143 -87
  52. package/src/modes/controllers/tan-command-controller.ts +1 -1
  53. package/src/modes/interactive-mode.ts +3 -0
  54. package/src/modes/shared.ts +1 -1
  55. package/src/modes/theme/theme.ts +3 -3
  56. package/src/modes/types.ts +2 -0
  57. package/src/modes/utils/ui-helpers.ts +31 -4
  58. package/src/prompts/agents/scout.md +0 -1
  59. package/src/prompts/agents/task.md +1 -1
  60. package/src/prompts/system/subagent-system-prompt.md +1 -5
  61. package/src/prompts/system/subagent-yield-reminder.md +10 -0
  62. package/src/prompts/system/task-label.md +23 -0
  63. package/src/prompts/tools/job.md +1 -1
  64. package/src/prompts/tools/task-summary.md +3 -0
  65. package/src/prompts/tools/task.md +17 -18
  66. package/src/session/agent-session.ts +2 -1
  67. package/src/session/session-context.test.ts +73 -0
  68. package/src/session/session-context.ts +43 -26
  69. package/src/slash-commands/builtin-registry.ts +9 -0
  70. package/src/task/agents.ts +2 -0
  71. package/src/task/executor.ts +159 -46
  72. package/src/task/index.ts +377 -239
  73. package/src/task/label.ts +38 -0
  74. package/src/task/render.ts +74 -22
  75. package/src/task/repair-args.ts +20 -31
  76. package/src/task/spawn-policy.test.ts +4 -4
  77. package/src/task/types.ts +46 -66
  78. package/src/tools/ask.ts +233 -40
  79. package/src/tools/conflict-detect.ts +102 -5
  80. package/src/tools/index.ts +1 -0
  81. package/src/tools/irc.ts +20 -11
  82. package/src/tools/job.ts +158 -18
  83. package/src/tools/write.ts +70 -6
  84. package/src/vibe/runtime.ts +1 -1
  85. package/src/web/search/providers/browser-headers.ts +30 -13
  86. package/dist/types/modes/components/model-selector.d.ts +0 -37
  87. package/dist/types/tools/bash-command-fixup.d.ts +0 -3
  88. package/src/modes/components/model-selector.ts +0 -1291
  89. 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;
@@ -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 {};
@@ -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,97 @@
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 ConfiguredThinkingLevel } from "../../thinking.js";
5
+ /** One selectable model row. `selector` is the canonical `provider/id` key. */
6
+ export interface ModelBrowserItem {
7
+ provider: string;
8
+ id: string;
9
+ model: Model;
10
+ selector: string;
11
+ }
12
+ /** Resolved role assignment as displayed by the browser and the hub. */
13
+ export interface RoleAssignment {
14
+ model: Model;
15
+ thinkingLevel: ConfiguredThinkingLevel;
16
+ /** True when the role has no configured value and fell back to auto-selection. */
17
+ autoSelected: boolean;
18
+ }
19
+ /** Map of role id to its resolved assignment (absent roles are unresolved). */
20
+ export type RoleAssignments = Record<string, RoleAssignment | undefined>;
21
+ /** Wrap raw models into browser items. */
22
+ export declare function buildBrowserItems(models: ReadonlyArray<Model>): ModelBrowserItem[];
23
+ /** Options for {@link sortModelItems}. */
24
+ export interface SortModelItemsOptions {
25
+ roles?: RoleAssignments;
26
+ mruOrder?: ReadonlyArray<string>;
27
+ /**
28
+ * When a search query is narrowing the list, role assignments should NOT
29
+ * promote a weakly-matching default model above a perfect text match —
30
+ * defer to MRU/version instead so user affinity drives the order.
31
+ */
32
+ skipRoleRank?: boolean;
33
+ }
34
+ /**
35
+ * Order models for display: role-assigned first, then most-recently-used,
36
+ * then per provider by priority, version, and recency.
37
+ */
38
+ export declare function sortModelItems(items: ModelBrowserItem[], options?: SortModelItemsOptions): void;
39
+ /** Compact glyph for a configured thinking level; empty for `inherit` (nothing to show). */
40
+ export declare function thinkingLevelGlyph(level: ConfiguredThinkingLevel): string;
41
+ /**
42
+ * A slim role chip: `●default ◉` — solid dot for configured assignments,
43
+ * hollow for auto-selected fallbacks, thinking glyph attached when set.
44
+ */
45
+ export declare function formatRoleChip(role: string, assignment: RoleAssignment, settings: Settings): string;
46
+ /** Behavior switches for {@link ModelBrowser}. */
47
+ export interface ModelBrowserOptions {
48
+ /** Render the dim `provider/` prefix before model ids. Default true. */
49
+ showProvider?: boolean;
50
+ /** Session token count used to disable models whose context window is exceeded. */
51
+ currentContextTokens?: number;
52
+ /** When true, rows over the current context are unselectable (session-switch mode). */
53
+ disableOverContext?: boolean;
54
+ /** Host-provided empty-state text (e.g. provider discovery status). */
55
+ emptyText?: () => string | undefined;
56
+ initialQuery?: string;
57
+ }
58
+ /**
59
+ * The reusable browser component. Renders a fixed-height block
60
+ * (`maxVisible + LIST_ROW_START + DETAIL_ROWS` rows) so host mouse geometry
61
+ * stays stable across renders.
62
+ */
63
+ export declare class ModelBrowser implements Component {
64
+ #private;
65
+ /** Enter or click-on-selected. */
66
+ onActivate?: (item: ModelBrowserItem) => void;
67
+ onSelectionChange?: (item: ModelBrowserItem | undefined) => void;
68
+ onQueryChange?: (query: string) => void;
69
+ /** Cancel key with an empty query (a non-empty query is cleared first). */
70
+ onCancel?: () => void;
71
+ constructor(settings: Settings, options?: ModelBrowserOptions);
72
+ /** Replace the scope's base items; the live query re-applies and selection is pinned by selector. */
73
+ setItems(items: ModelBrowserItem[]): void;
74
+ setRoles(roles: RoleAssignments): void;
75
+ setMruOrder(order: ReadonlyArray<string>): void;
76
+ setMaxVisible(rows: number): void;
77
+ setShowProvider(show: boolean): void;
78
+ /** Total rendered height for the current `maxVisible` (host layout budgeting). */
79
+ get renderedRows(): number;
80
+ get query(): string;
81
+ setQuery(query: string): void;
82
+ getSelected(): ModelBrowserItem | undefined;
83
+ get visibleCount(): number;
84
+ /** Move selection to `selector`; false when it is not in the current view. */
85
+ selectSelector(selector: string): boolean;
86
+ moveSelection(delta: number): void;
87
+ handleInput(data: string): void;
88
+ /** Cancel-key ladder: clear a non-empty query first, then bubble to the host. */
89
+ handleCancel(): void;
90
+ /**
91
+ * Route a mouse event. `line` is relative to the browser's first rendered
92
+ * row (the search row).
93
+ */
94
+ routeMouse(event: SgrMouseEvent, line: number): void;
95
+ render(width: number): string[];
96
+ invalidate(): void;
97
+ }
@@ -0,0 +1,51 @@
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
+ export type ModelHubAction = "modelRole" | "retryFallback";
9
+ /** A `--models` scope entry (mirrors the session's scoped model list). */
10
+ export interface ScopedModelItem {
11
+ model: Model;
12
+ thinkingLevel?: string;
13
+ }
14
+ export interface ModelHubCallbacks {
15
+ /** Persist a role assignment (or a retry-fallback registration). */
16
+ onAssign: (model: Model, role: string, thinkingLevel: ConfiguredThinkingLevel | undefined, selector: string, action: ModelHubAction) => void;
17
+ /** Clear a configured role back to auto-selection. */
18
+ onUnassign: (role: string) => void;
19
+ /** Pick-mode activation: session-only switch or embedded pick. */
20
+ onPick?: (model: Model, selector: string) => void;
21
+ /** Locked provider activation: forward to the /login flow. */
22
+ onLoginRequest?: (providerId: string) => void;
23
+ /** Persist a new quick-switch cycle order (the ctrl+p role cycle). */
24
+ onCycleOrderChange?: (order: string[]) => void;
25
+ onCancel: () => void;
26
+ }
27
+ export interface ModelHubOptions {
28
+ mode?: ModelHubMode;
29
+ /** Session token count; in pick mode, models with smaller context windows are disabled. */
30
+ currentContextTokens?: number;
31
+ /** Preselect this provider's sidebar entry (e.g. when reopening after /login). */
32
+ initialProviderId?: string;
33
+ /** Status-row hint shown in pick mode. */
34
+ pickerHint?: string;
35
+ initialQuery?: string;
36
+ }
37
+ /** Test hook: forget which providers were auto-refreshed this process. */
38
+ export declare function resetProviderAutoRefreshGuard(): void;
39
+ /**
40
+ * The fullscreen model hub component. Hosted via `ui.showOverlay(..., { fullscreen: true })`;
41
+ * the host must call {@link ModelHubComponent.dispose} when the overlay closes.
42
+ */
43
+ export declare class ModelHubComponent implements Component {
44
+ #private;
45
+ constructor(tui: TUI, settings: Settings, registry: ModelRegistry, scopedModels: ReadonlyArray<ScopedModelItem>, callbacks: ModelHubCallbacks, options?: ModelHubOptions);
46
+ /** Cancel pending provider refresh timers and the spinner. Host calls this on overlay close. */
47
+ dispose(): void;
48
+ invalidate(): void;
49
+ handleInput(data: string): void;
50
+ render(width: number): string[];
51
+ }
@@ -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
  */
@@ -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>;
@@ -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. */
@@ -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. */
@@ -40,5 +40,14 @@ export interface BuildSessionContextOptions {
40
40
  transcript?: boolean;
41
41
  /** In transcript mode, elide entries replaced by the latest compaction. */
42
42
  collapseCompactedHistory?: boolean;
43
+ /**
44
+ * Transcript mode only: keep `toolCall` blocks that have no matching
45
+ * `toolResult` on the path instead of stripping them. Pass this when the
46
+ * session is mid-turn (a tool is still executing, its result not yet
47
+ * persisted) so the rebuilt transcript renders the in-flight call as
48
+ * pending; without it a focus/unfocus or overlay-close rebuild silently
49
+ * hides the call the agent is still waiting on.
50
+ */
51
+ keepDanglingToolCalls?: boolean;
43
52
  }
44
53
  export declare function buildSessionContext(entries: SessionEntry[], leafId?: string | null, byId?: Map<string, SessionEntry>, options?: BuildSessionContextOptions): SessionContext;
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Runs each subagent on the main thread and forwards AgentEvents for progress tracking.
5
5
  */
6
- import type { AgentTelemetryConfig, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
6
+ import type { AgentTelemetryConfig } from "@oh-my-pi/pi-agent-core";
7
7
  import type { ServiceTierByFamily } from "@oh-my-pi/pi-ai";
8
8
  import type { Rule } from "../capability/rule.js";
9
9
  import { ModelRegistry } from "../config/model-registry.js";
@@ -20,23 +20,27 @@ import type { MnemopiSessionState } from "../mnemopi/state.js";
20
20
  import type { AgentSession } from "../session/agent-session.js";
21
21
  import type { ArtifactManager } from "../session/artifacts.js";
22
22
  import type { AuthStorage } from "../session/auth-storage.js";
23
+ import type { ConfiguredThinkingLevel } from "../thinking.js";
23
24
  import type { ContextFileEntry } from "../tools/index.js";
24
25
  import type { EventBus } from "../utils/event-bus.js";
25
26
  import type { WorkspaceTree } from "../workspace-tree.js";
26
27
  import { type AgentDefinition, type AgentProgress, type ReviewFinding, type SingleResult, type YieldItem } from "./types.js";
27
28
  export type { YieldItem } from "./types.js";
28
29
  /**
29
- * Soft per-agent request budgets (assistant requests per run). When a subagent
30
- * crosses its budget it can receive an optional steering notice asking it to
31
- * wrap up; at 1.5x the budget the run is aborted gracefully so partial output is
32
- * salvaged. The `default` key applies to agents without an explicit entry and
33
- * can be overridden via the `task.softRequestBudget` setting (0 disables the
34
- * guard). The notice is off by default and controlled separately by
35
- * `task.softRequestBudgetNotice`.
30
+ * Soft per-agent request budgets (assistant requests per run). Crossing the
31
+ * budget injects a wrap-up steering notice (`task.softRequestBudgetNotice`,
32
+ * on by default). At 1.5x the budget the free-running turn is stopped and the
33
+ * agent is driven to one forced final `yield` so partial findings come back
34
+ * as a real report; only if it still refuses to yield within
35
+ * {@link BUDGET_STOP_GRACE_REQUESTS} more requests is the run hard-aborted.
36
+ * The `default` key applies to agents without an explicit entry and can be
37
+ * overridden via the `task.softRequestBudget` setting (0 disables the guard).
36
38
  */
37
39
  export declare const SOFT_REQUEST_BUDGET: Record<string, number>;
38
- /** Optional steering notice injected when a subagent crosses its soft request budget. */
39
- export declare function buildBudgetNotice(requests: number): string;
40
+ /** Extra requests allowed after a budget stop for the forced yield to land before the run is hard-aborted. */
41
+ export declare const BUDGET_STOP_GRACE_REQUESTS = 5;
42
+ /** Steering notice injected when a subagent crosses its soft request budget. */
43
+ export declare function buildBudgetNotice(requests: number, budget: number): string;
40
44
  /** Options for subagent execution */
41
45
  export interface ExecutorOptions {
42
46
  cwd: string;
@@ -55,9 +59,8 @@ export interface ExecutorOptions {
55
59
  path: string;
56
60
  content: string;
57
61
  };
62
+ /** Pre-set UI label (e.g. eval bridge label). When absent, a tiny-model label is generated from the assignment. */
58
63
  description?: string;
59
- /** Specialist role/expertise for this spawn; drives the system-prompt preamble, display name, and telemetry identity. */
60
- role?: string;
61
64
  index: number;
62
65
  id: string;
63
66
  parentToolCallId?: string;
@@ -74,7 +77,7 @@ export interface ExecutorOptions {
74
77
  * if the resolved subagent model has no working credentials. See #985.
75
78
  */
76
79
  parentActiveModelPattern?: string;
77
- thinkingLevel?: ThinkingLevel;
80
+ thinkingLevel?: ConfiguredThinkingLevel;
78
81
  outputSchema?: unknown;
79
82
  /**
80
83
  * Caller supplied a schema that supersedes the agent's native output prompt.
@@ -199,10 +202,20 @@ export declare function finalizeSubprocessOutput(args: FinalizeSubprocessOutputA
199
202
  */
200
203
  export declare function createMCPProxyTools(mcpManager: MCPManager): CustomTool[];
201
204
  export declare function createSubagentSettings(baseSettings: Settings, overrides?: Partial<Record<SettingPath, unknown>>, inheritedServiceTier?: ServiceTierByFamily | null): Settings;
205
+ export type AbortReason = "signal" | "terminate" | "timeout" | "budget";
206
+ /**
207
+ * Settle a subagent's registry lifecycle after a run: terminal teardown for
208
+ * hard aborts, unregister for one-shot helpers, park for isolated runs, and
209
+ * idle + lifecycle adoption for kept-alive agents. A soft-budget abort on a
210
+ * kept-alive, revivable agent is treated as a self-inflicted stop rather than
211
+ * a kill — the agent stays interrogable and resumable (irc wake / revival).
212
+ */
202
213
  export declare function finalizeSubagentLifecycle(args: {
203
214
  id: string;
204
215
  session: AgentSession;
205
216
  aborted: boolean;
217
+ /** Which watchdog (if any) requested the abort; decides revivability. */
218
+ abortKind?: AbortReason;
206
219
  keepAlive: boolean;
207
220
  isolated: boolean;
208
221
  agentIdleTtlMs: number;