@oh-my-pi/pi-coding-agent 17.0.1 → 17.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/CHANGELOG.md +162 -40
  2. package/dist/cli.js +4559 -4543
  3. package/dist/types/advisor/config.d.ts +14 -6
  4. package/dist/types/advisor/runtime.d.ts +20 -11
  5. package/dist/types/autolearn/controller.d.ts +1 -0
  6. package/dist/types/config/model-discovery.d.ts +17 -0
  7. package/dist/types/config/model-resolver.d.ts +6 -2
  8. package/dist/types/config/settings-schema.d.ts +39 -10
  9. package/dist/types/config/settings.d.ts +50 -0
  10. package/dist/types/cursor.d.ts +11 -0
  11. package/dist/types/discovery/helpers.d.ts +5 -2
  12. package/dist/types/eval/agent-bridge.d.ts +7 -19
  13. package/dist/types/exec/bash-executor.d.ts +2 -0
  14. package/dist/types/extensibility/extensions/managed-timers.d.ts +15 -0
  15. package/dist/types/extensibility/extensions/runner.d.ts +7 -0
  16. package/dist/types/extensibility/extensions/types.d.ts +18 -0
  17. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +55 -1
  18. package/dist/types/extensibility/shared-events.d.ts +6 -0
  19. package/dist/types/extensibility/utils.d.ts +2 -2
  20. package/dist/types/lsp/client.d.ts +2 -0
  21. package/dist/types/lsp/types.d.ts +3 -0
  22. package/dist/types/mcp/transports/stdio.d.ts +42 -1
  23. package/dist/types/modes/components/advisor-config.d.ts +8 -1
  24. package/dist/types/modes/components/agent-hub.d.ts +15 -0
  25. package/dist/types/modes/components/hook-editor.d.ts +7 -0
  26. package/dist/types/modes/components/model-hub.d.ts +5 -2
  27. package/dist/types/modes/components/session-selector.d.ts +2 -0
  28. package/dist/types/modes/controllers/command-controller.d.ts +8 -0
  29. package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
  30. package/dist/types/modes/print-mode.d.ts +1 -1
  31. package/dist/types/modes/warp-events.d.ts +24 -0
  32. package/dist/types/modes/warp-events.test.d.ts +1 -0
  33. package/dist/types/plan-mode/model-transition.d.ts +47 -0
  34. package/dist/types/plan-mode/model-transition.test.d.ts +1 -0
  35. package/dist/types/registry/agent-lifecycle.d.ts +26 -1
  36. package/dist/types/registry/persisted-agents.d.ts +3 -0
  37. package/dist/types/sdk.d.ts +27 -5
  38. package/dist/types/session/agent-session.d.ts +34 -10
  39. package/dist/types/session/session-entries.d.ts +6 -1
  40. package/dist/types/session/session-history-format.d.ts +10 -0
  41. package/dist/types/session/session-manager.d.ts +19 -0
  42. package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +9 -0
  43. package/dist/types/task/executor.d.ts +26 -1
  44. package/dist/types/task/index.d.ts +1 -1
  45. package/dist/types/task/label.d.ts +1 -1
  46. package/dist/types/task/parallel.d.ts +14 -0
  47. package/dist/types/task/structured-subagent.d.ts +111 -0
  48. package/dist/types/task/types.d.ts +51 -0
  49. package/dist/types/telemetry-export.d.ts +34 -9
  50. package/dist/types/tools/approval.d.ts +8 -0
  51. package/dist/types/tools/bash.d.ts +2 -0
  52. package/dist/types/tools/essential-tools.d.ts +29 -0
  53. package/dist/types/tools/fetch.d.ts +4 -5
  54. package/dist/types/tools/hub/messaging.d.ts +5 -1
  55. package/dist/types/tools/image-gen.d.ts +2 -1
  56. package/dist/types/tools/index.d.ts +17 -1
  57. package/dist/types/tools/todo.d.ts +31 -0
  58. package/dist/types/tools/xdev.d.ts +11 -2
  59. package/dist/types/tui/tree-list.d.ts +7 -0
  60. package/dist/types/utils/markit.d.ts +11 -0
  61. package/dist/types/utils/title-generator.d.ts +2 -1
  62. package/dist/types/web/search/providers/kimi.d.ts +4 -1
  63. package/dist/types/web/search/types.d.ts +1 -1
  64. package/package.json +21 -16
  65. package/src/advisor/__tests__/advisor.test.ts +1304 -42
  66. package/src/advisor/__tests__/config.test.ts +58 -2
  67. package/src/advisor/config.ts +76 -24
  68. package/src/advisor/runtime.ts +445 -92
  69. package/src/autolearn/controller.ts +23 -28
  70. package/src/cli/file-processor.ts +1 -2
  71. package/src/cli.ts +5 -1
  72. package/src/config/model-discovery.ts +81 -21
  73. package/src/config/model-registry.ts +25 -6
  74. package/src/config/model-resolver.ts +14 -7
  75. package/src/config/settings-schema.ts +46 -9
  76. package/src/config/settings.ts +405 -25
  77. package/src/cursor.ts +20 -3
  78. package/src/debug/report-bundle.ts +40 -4
  79. package/src/discovery/helpers.ts +28 -5
  80. package/src/eval/__tests__/agent-bridge.test.ts +133 -47
  81. package/src/eval/__tests__/prelude-agent.test.ts +29 -0
  82. package/src/eval/agent-bridge.ts +104 -477
  83. package/src/eval/jl/prelude.jl +7 -6
  84. package/src/eval/js/shared/prelude.txt +5 -4
  85. package/src/eval/py/prelude.py +11 -39
  86. package/src/eval/rb/prelude.rb +5 -6
  87. package/src/exec/bash-executor.ts +14 -5
  88. package/src/extensibility/custom-tools/loader.ts +3 -3
  89. package/src/extensibility/custom-tools/wrapper.ts +2 -1
  90. package/src/extensibility/extensions/loader.ts +3 -3
  91. package/src/extensibility/extensions/managed-timers.ts +83 -0
  92. package/src/extensibility/extensions/runner.ts +26 -0
  93. package/src/extensibility/extensions/types.ts +18 -0
  94. package/src/extensibility/extensions/wrapper.ts +2 -1
  95. package/src/extensibility/hooks/loader.ts +3 -3
  96. package/src/extensibility/legacy-pi-coding-agent-shim.ts +149 -8
  97. package/src/extensibility/plugins/legacy-pi-compat.ts +225 -22
  98. package/src/extensibility/plugins/manager.ts +2 -2
  99. package/src/extensibility/shared-events.ts +6 -0
  100. package/src/extensibility/utils.ts +91 -25
  101. package/src/irc/bus.ts +22 -3
  102. package/src/launch/broker.ts +3 -2
  103. package/src/launch/client.ts +2 -2
  104. package/src/launch/presence.ts +2 -2
  105. package/src/lsp/client.ts +58 -1
  106. package/src/lsp/index.ts +62 -6
  107. package/src/lsp/types.ts +3 -0
  108. package/src/main.ts +11 -8
  109. package/src/mcp/manager.ts +9 -3
  110. package/src/mcp/oauth-flow.ts +20 -0
  111. package/src/mcp/transports/stdio.test.ts +269 -1
  112. package/src/mcp/transports/stdio.ts +255 -24
  113. package/src/modes/components/advisor-config.ts +65 -3
  114. package/src/modes/components/agent-hub.ts +1 -72
  115. package/src/modes/components/ask-dialog.ts +1 -1
  116. package/src/modes/components/bash-execution.ts +7 -3
  117. package/src/modes/components/eval-execution.ts +3 -1
  118. package/src/modes/components/hook-editor.ts +18 -3
  119. package/src/modes/components/model-hub.ts +138 -42
  120. package/src/modes/components/session-selector.ts +4 -0
  121. package/src/modes/components/status-line/component.test.ts +1 -0
  122. package/src/modes/components/status-line/segments.ts +21 -6
  123. package/src/modes/controllers/command-controller.ts +167 -47
  124. package/src/modes/controllers/event-controller.ts +5 -0
  125. package/src/modes/controllers/extension-ui-controller.ts +4 -22
  126. package/src/modes/controllers/input-controller.ts +12 -12
  127. package/src/modes/controllers/selector-controller.ts +191 -31
  128. package/src/modes/interactive-mode.ts +169 -62
  129. package/src/modes/print-mode.ts +3 -3
  130. package/src/modes/rpc/host-tools.ts +2 -1
  131. package/src/modes/rpc/rpc-mode.ts +19 -4
  132. package/src/modes/warp-events.test.ts +794 -0
  133. package/src/modes/warp-events.ts +232 -0
  134. package/src/plan-mode/model-transition.test.ts +60 -0
  135. package/src/plan-mode/model-transition.ts +51 -0
  136. package/src/prompts/system/system-prompt.md +1 -1
  137. package/src/prompts/tools/eval.md +5 -2
  138. package/src/prompts/tools/read.md +1 -1
  139. package/src/prompts/tools/task.md +12 -8
  140. package/src/prompts/tools/write.md +1 -1
  141. package/src/registry/agent-lifecycle.ts +133 -18
  142. package/src/registry/persisted-agents.ts +74 -0
  143. package/src/sdk.ts +343 -122
  144. package/src/session/agent-session.ts +1359 -368
  145. package/src/session/session-entries.ts +6 -1
  146. package/src/session/session-history-format.ts +20 -5
  147. package/src/session/session-manager.ts +57 -0
  148. package/src/session/streaming-output.ts +41 -1
  149. package/src/slash-commands/builtin-registry.ts +7 -0
  150. package/src/slash-commands/helpers/active-oauth-account.ts +16 -0
  151. package/src/system-prompt.ts +7 -2
  152. package/src/task/executor.ts +100 -22
  153. package/src/task/index.ts +258 -429
  154. package/src/task/label.ts +2 -0
  155. package/src/task/parallel.ts +43 -0
  156. package/src/task/persisted-revive.ts +19 -7
  157. package/src/task/structured-subagent.ts +642 -0
  158. package/src/task/types.ts +58 -0
  159. package/src/telemetry-export.ts +453 -97
  160. package/src/tools/__tests__/eval-description.test.ts +1 -1
  161. package/src/tools/approval.ts +11 -0
  162. package/src/tools/bash.ts +71 -38
  163. package/src/tools/essential-tools.ts +45 -0
  164. package/src/tools/fetch.ts +28 -105
  165. package/src/tools/gh.ts +169 -2
  166. package/src/tools/hub/messaging.ts +16 -3
  167. package/src/tools/image-gen.ts +69 -7
  168. package/src/tools/index.ts +50 -15
  169. package/src/tools/path-utils.ts +1 -0
  170. package/src/tools/read.ts +62 -29
  171. package/src/tools/todo.ts +126 -13
  172. package/src/tools/write.ts +22 -4
  173. package/src/tools/xdev.ts +14 -3
  174. package/src/tui/tree-list.ts +39 -0
  175. package/src/utils/markit.ts +12 -0
  176. package/src/utils/title-generator.ts +15 -4
  177. package/src/utils/zip.ts +16 -1
  178. package/src/web/search/providers/kimi.ts +18 -12
  179. package/src/web/search/types.ts +6 -1
@@ -37,6 +37,18 @@ export interface StdioSpawnCommand {
37
37
  * grandchildren keep stdout routed through our pipe (#3544).
38
38
  */
39
39
  detached: boolean;
40
+ /**
41
+ * Pass argv to `Bun.spawn` verbatim (Windows only), suppressing the
42
+ * default libuv backslash-quoting.
43
+ *
44
+ * Set when `cmd` already holds a `cmd.exe /d /e:ON /v:OFF /c "<line>"`
45
+ * command line escaped for `cmd.exe`'s parser (see `buildCmdExeArgv`).
46
+ * libuv's quoting targets `CommandLineToArgvW`, not `cmd.exe`, so letting
47
+ * it re-quote a batch launch would corrupt arguments and re-open the
48
+ * `%VAR%` / quote-injection holes the escaping closes (BatBadBut,
49
+ * CVE-2024-24576).
50
+ */
51
+ windowsVerbatimArguments?: boolean;
40
52
  }
41
53
  /** Inputs used to resolve platform-specific stdio spawn behavior. */
42
54
  export interface ResolveStdioSpawnOptions {
@@ -55,7 +67,7 @@ export interface ResolveStdioSpawnOptions {
55
67
  * only appends `.exe` for extensionless names — `.cmd`/`.bat` are never
56
68
  * tried, so `npx` (which exists only as `npx.cmd` on Windows) crashes the
57
69
  * subprocess immediately. When the resolver can't pin the command down,
58
- * route through `cmd.exe /d /s /c` so Windows's own PATHEXT lookup runs.
70
+ * route through `cmd.exe` so Windows's own PATHEXT lookup runs.
59
71
  */
60
72
  export declare function resolveStdioSpawnCommand(config: MCPStdioServerConfig, options: ResolveStdioSpawnOptions): Promise<StdioSpawnCommand>;
61
73
  /** Minimal write surface of `Subprocess.stdin` we need for framed sends. */
@@ -85,6 +97,35 @@ interface FrameSink {
85
97
  * the dead transport is detected by the read loop / request timeout instead.
86
98
  */
87
99
  export declare function writeFrame(stdin: FrameSink, frame: string): boolean;
100
+ /**
101
+ * The subset of `Subprocess` that termination needs. Decoupled from the
102
+ * `Subprocess<In, Out, Err>` stdio generics — `#process`'s pipes are
103
+ * irrelevant to signaling — so tests can exercise it against a plain
104
+ * `Bun.spawn(cmd, { stdio: "ignore" })` child without fighting the generics.
105
+ */
106
+ interface KillableSubprocess {
107
+ readonly pid: number;
108
+ readonly exited: Promise<number>;
109
+ kill(signal?: number | NodeJS.Signals): void;
110
+ }
111
+ /**
112
+ * Terminate an MCP stdio subprocess: SIGTERM (process-group when `detached`
113
+ * on POSIX, direct child otherwise), wait up to `TERM_GRACE_MS` for a
114
+ * cooperative exit, then escalate to SIGKILL — waiting up to `KILL_GRACE_MS`
115
+ * more only when the leader itself hadn't already exited. A detached
116
+ * leader's cooperative exit does not prove the whole process group is gone
117
+ * (a grandchild can outlive it and ignore SIGTERM), so detached transports
118
+ * always fire the group SIGKILL sweep, even after a clean SIGTERM exit.
119
+ * Every step is a no-op-safe signal against an already-exited target, so
120
+ * repeat calls (idempotent `close()`) never throw.
121
+ *
122
+ * Exported so tests can exercise group-signal escalation with an explicit
123
+ * `detached`/`platform` pair: `StdioTransport.connect()` derives `detached`
124
+ * from `resolveStdioSpawnCommand()`, which is tied to the host's real
125
+ * `process.platform`, so a POSIX detached session cannot be reproduced
126
+ * end-to-end through `connect()` on a non-Linux dev/CI host.
127
+ */
128
+ export declare function terminateStdioProcess(proc: KillableSubprocess, detached: boolean, platform?: NodeJS.Platform): Promise<void>;
88
129
  /**
89
130
  * Stdio transport for MCP servers.
90
131
  * Spawns a subprocess and communicates via stdin/stdout.
@@ -16,11 +16,13 @@
16
16
  * `save` callback.
17
17
  */
18
18
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
19
- import type { Model } from "@oh-my-pi/pi-ai";
19
+ import type { Model, UsageReport } from "@oh-my-pi/pi-ai";
20
20
  import { type Component, type TUI } from "@oh-my-pi/pi-tui";
21
21
  import { type AdvisorConfigScope, type WatchdogConfigDoc } from "../../advisor/index.js";
22
22
  import type { ModelRegistry } from "../../config/model-registry.js";
23
23
  import type { Settings } from "../../config/settings.js";
24
+ import type { PerAdvisorStat } from "../../session/agent-session.js";
25
+ import type { OAuthAccountIdentity } from "../../session/auth-storage.js";
24
26
  /** Host callbacks: all disk + live-runtime effects flow through these. */
25
27
  export interface AdvisorConfigCallbacks {
26
28
  /** Load a scope's `WATCHDOG.yml` into an editable doc (empty when absent). */
@@ -32,6 +34,11 @@ export interface AdvisorConfigCallbacks {
32
34
  requestRender: () => void;
33
35
  /** Surface a transient status/warning line to the user. */
34
36
  notify: (message: string) => void;
37
+ /** Live advisor usage stats; lets the preview show tokens/cost per advisor. */
38
+ getAdvisorStats?: () => PerAdvisorStat[];
39
+ getUsageReports?: () => Promise<UsageReport[] | null>;
40
+ /** Resolve the active OAuth identity for quota filtering (per-advisor account stickiness). */
41
+ resolveActiveAccount?: (provider: string, sessionId?: string) => OAuthAccountIdentity | undefined;
35
42
  }
36
43
  export interface AdvisorConfigDeps {
37
44
  modelRegistry: ModelRegistry;
@@ -1,3 +1,18 @@
1
+ /**
2
+ * Agent Hub overlay component.
3
+ *
4
+ * One overlay, two views:
5
+ * - Table view: every registered agent except Main (Main IS the ambient
6
+ * chat), live from the global AgentRegistry — status, unread irc count,
7
+ * current/last task, last activity. Select with j/k, Enter opens a chat,
8
+ * `r` revives a parked agent, `x` aborts + releases one.
9
+ * - Chat view: per-agent transcript (incremental session-file tail, absorbed
10
+ * from the old session observer overlay) plus an input line. Submitting
11
+ * revives a parked agent, then prompts/steers it; the message lands in the
12
+ * agent's persisted history via the normal prompt path.
13
+ *
14
+ * Replaces the old SessionObserverOverlayComponent (ctrl+s observer).
15
+ */
1
16
  import { type AgentTool } from "@oh-my-pi/pi-agent-core";
2
17
  import { Container, type TUI } from "@oh-my-pi/pi-tui";
3
18
  import type { KeyId } from "../../config/keybindings.js";
@@ -11,6 +11,13 @@ import { Container, type Focusable, type TUI } from "@oh-my-pi/pi-tui";
11
11
  export interface HookEditorOptions {
12
12
  /** When true, use prompt-style keybindings with the legacy ask prompt chrome. */
13
13
  promptStyle?: boolean;
14
+ /**
15
+ * Max rows the inner Editor may occupy. When omitted, the editor is
16
+ * bounded to the current terminal height minus the component's chrome
17
+ * (≈10 rows) so long content scrolls instead of pushing the submit
18
+ * hint out of view.
19
+ */
20
+ maxHeight?: number;
14
21
  }
15
22
  /** Interactive multiline dialog used by hooks and the ask tool's Other response. */
16
23
  export declare class HookEditorComponent extends Container implements Focusable {
@@ -8,11 +8,12 @@ export interface ScopedModelItem {
8
8
  model: Model;
9
9
  thinkingLevel?: string;
10
10
  }
11
+ export type ModelRoleSelectionScope = "global" | "project";
11
12
  export interface ModelHubCallbacks {
12
13
  /** Persist a role assignment. */
13
- onAssign: (model: Model, role: string, thinkingLevel: ConfiguredThinkingLevel | undefined, selector: string) => void;
14
+ onAssign: (model: Model, role: string, thinkingLevel: ConfiguredThinkingLevel | undefined, selector: string, scope?: ModelRoleSelectionScope) => void;
14
15
  /** Clear a configured role back to auto-selection. */
15
- onUnassign: (role: string) => void;
16
+ onUnassign: (role: string, scope?: ModelRoleSelectionScope) => void;
16
17
  /** Persist a `retry.fallbackChains` entry — keyed by a role, `provider/model-id`, or `provider/*`; an empty chain clears the key. */
17
18
  onFallbackChainChange?: (role: string, chain: string[]) => void;
18
19
  /** Locked provider activation: forward to the /login flow. */
@@ -37,6 +38,8 @@ export declare class ModelHubComponent implements Component {
37
38
  /** Cancel pending provider refresh timers and the spinner. Host calls this on overlay close. */
38
39
  dispose(): void;
39
40
  invalidate(): void;
41
+ /** Re-sync after an asynchronous callback finishes mutating settings. */
42
+ refreshAfterExternalMutation(): void;
40
43
  handleInput(data: string): void;
41
44
  render(width: number): string[];
42
45
  }
@@ -85,6 +85,8 @@ export declare class SessionSelectorComponent extends Container {
85
85
  setOnRequestRender(callback: () => void): void;
86
86
  /** Ignore input after selection while the host resumes the session. */
87
87
  lockInput(): void;
88
+ /** Re-enable input after a failed resume so the user can pick again. */
89
+ unlockInput(): void;
88
90
  /**
89
91
  * Dispose the session list explicitly: while the delete-confirmation dialog
90
92
  * is mounted the list is detached from the child tree, so Container's
@@ -52,4 +52,12 @@ export declare class CommandController {
52
52
  handleHandoffCommand(customInstructions?: string): Promise<void>;
53
53
  }
54
54
  export declare function renderProviderSection(details: ProviderDetails, uiTheme: Pick<typeof theme, "fg">): string;
55
+ /**
56
+ * Compact one-line quota summary for a single advisor's provider.
57
+ * Returns `null` when the provider has no usage data.
58
+ * When `activeAccount` is provided, only limits matching that credential
59
+ * are shown (mirrors `renderUsageReports`'s account-stickiness filtering).
60
+ * Example output: `Quota: 7d window · 67% used · resets in 3.2d`
61
+ */
62
+ export declare function formatCompactQuota(provider: string, reports: UsageReport[], nowMs: number, activeAccount?: OAuthAccountIdentity): string | null;
55
63
  export declare function renderUsageReports(reports: UsageReport[], uiTheme: typeof theme, nowMs: number, availableWidth: number, resolveActiveAccount?: (provider: string) => OAuthAccountIdentity | undefined): string;
@@ -50,7 +50,9 @@ export declare class SelectorController {
50
50
  showCopySelector(): void;
51
51
  showTreeSelector(): void;
52
52
  showSessionSelector(): Promise<void>;
53
- handleResumeSession(sessionPath: string): Promise<void>;
53
+ handleResumeSession(sessionPath: string, options?: {
54
+ settingsFlushed?: boolean;
55
+ }): Promise<boolean>;
54
56
  handleSessionDeleteCommand(): Promise<void>;
55
57
  showOAuthSelector(mode: "login" | "logout", providerId?: string): Promise<void>;
56
58
  showResetUsageSelector(): Promise<void>;
@@ -1,5 +1,5 @@
1
1
  import type { ImageContent } from "@oh-my-pi/pi-ai";
2
- import type { AgentSession, AgentSessionEvent } from "../session/agent-session.js";
2
+ import { type AgentSession, type AgentSessionEvent } from "../session/agent-session.js";
3
3
  /**
4
4
  * Options for print mode.
5
5
  */
@@ -0,0 +1,24 @@
1
+ import type { ExtensionFactory } from "../extensibility/extensions/types.js";
2
+ /** True when Warp has negotiated the structured CLI-agent OSC protocol. */
3
+ export declare function isWarpCliAgentProtocolActive(): boolean;
4
+ export type WarpEventValue = string | number | boolean | null | readonly WarpEventValue[] | {
5
+ readonly [key: string]: WarpEventValue | undefined;
6
+ };
7
+ /** Fields added to the Warp CLI-agent event envelope by the event bridge. */
8
+ export type WarpEvent = Readonly<Record<string, WarpEventValue | undefined>>;
9
+ export interface WarpEventEmitterOptions {
10
+ sessionId: string;
11
+ getCwd?: () => string;
12
+ }
13
+ export interface WarpEventEmitter {
14
+ emit(event: WarpEvent): void;
15
+ }
16
+ /**
17
+ * Creates the Warp event transport for a top-level interactive TUI session.
18
+ * The caller MUST enforce that install-site invariant; the sole production
19
+ * caller is gated by `isInteractive`, so ACP, RPC, print, headless, and
20
+ * subagent sessions never construct an emitter.
21
+ */
22
+ export declare function createWarpEventEmitter(options: WarpEventEmitterOptions): WarpEventEmitter | undefined;
23
+ /** Internal event bridge installed only by the top-level interactive TUI runner. */
24
+ export declare function createWarpEventBridgeExtension(): ExtensionFactory;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Plan-mode model transition policy.
3
+ *
4
+ * Plan mode drives the active session model from the `plan` role: it switches
5
+ * to the plan model on entry and restores the pre-plan model on exit. The same
6
+ * policy also runs when the `plan` role is reassigned mid-planning, so a
7
+ * correction to the wrong plan model takes effect at the next turn boundary
8
+ * (issue #5657).
9
+ *
10
+ * This module holds only the pure decision — what transition a given
11
+ * (current model, resolved role, streaming) triple implies — so the branching
12
+ * is testable without a live session or TUI. The interactive mode performs the
13
+ * resulting side effect.
14
+ */
15
+ import type { Model } from "@oh-my-pi/pi-ai";
16
+ import type { ResolvedModelRoleValue } from "../config/model-resolver.js";
17
+ import type { ConfiguredThinkingLevel } from "../thinking.js";
18
+ /** The action implied by resolving the `plan` role against the active model. */
19
+ export type PlanModelTransition =
20
+ /** Already on the plan model with no thinking-level change to apply. */
21
+ {
22
+ kind: "none";
23
+ }
24
+ /** Same model; only the plan role's explicit thinking level differs. */
25
+ | {
26
+ kind: "thinking";
27
+ thinkingLevel: ConfiguredThinkingLevel;
28
+ }
29
+ /**
30
+ * Switch to `model`. `deferred` is set when the session is mid-stream: the
31
+ * switch must wait for the current turn to end (a live `setModelTemporary`
32
+ * resets the provider session), so the caller queues it instead.
33
+ */
34
+ | {
35
+ kind: "apply";
36
+ model: Model;
37
+ thinkingLevel: ConfiguredThinkingLevel | undefined;
38
+ deferred: boolean;
39
+ };
40
+ /**
41
+ * Decide how to reconcile the active model with the resolved `plan` role.
42
+ *
43
+ * @param currentModel - The session's active model, or `undefined` before one is set.
44
+ * @param resolved - The `plan` role resolved against the available models.
45
+ * @param isStreaming - Whether the session is mid-turn (forces a deferred switch).
46
+ */
47
+ export declare function resolvePlanModelTransition(currentModel: Model | undefined, resolved: ResolvedModelRoleValue, isStreaming: boolean): PlanModelTransition;
@@ -0,0 +1 @@
1
+ export {};
@@ -8,6 +8,12 @@
8
8
  * sessionFile), and revives it on demand through
9
9
  * {@link AgentLifecycleManager.ensureLive}. Only this manager flips
10
10
  * `parked` ↔ `idle`.
11
+ *
12
+ * Park/dispose is gated against concurrent ensureLive/hub-send:
13
+ * - A disposing session is never handed out.
14
+ * - ensureLive during an in-flight park either cancels the park (session still
15
+ * live) or waits for detach+park and then revives.
16
+ * - Concurrent ensureLive/park operations coalesce per id.
11
17
  */
12
18
  import type { AgentSession } from "../session/agent-session.js";
13
19
  import { type AgentRef, AgentRegistry } from "./agent-registry.js";
@@ -46,17 +52,36 @@ export declare class AgentLifecycleManager {
46
52
  adopt(id: string, opts: AdoptOptions): void;
47
53
  /** True if the id is adopted (parked or live). */
48
54
  has(id: string): boolean;
49
- /** True while {@link park} is disposing this agent's session (lets dispose hooks distinguish park from teardown). */
55
+ /**
56
+ * True when this manager owns `registry` — i.e. its adopt/park/revive state
57
+ * describes that registry's refs. Lets a caller holding a specific registry
58
+ * (e.g. a custom-registry {@link IrcBus} that fell back to the global
59
+ * manager) skip lifecycle gating that would consult unrelated park state.
60
+ */
61
+ manages(registry: AgentRegistry): boolean;
62
+ /**
63
+ * True while {@link park} is disposing this agent's session (lets dispose
64
+ * hooks distinguish park from teardown). False once the park is cancelled
65
+ * by ensureLive or after detach+dispose completes.
66
+ */
50
67
  isParking(id: string): boolean;
51
68
  /**
52
69
  * Dispose the live session, detach it from the registry, and mark the
53
70
  * agent `parked`. No-op unless the id is adopted and live.
71
+ *
72
+ * The session is detached (and status flipped to `parked`) *before*
73
+ * `session.dispose()` so concurrent {@link ensureLive}/hub-send never
74
+ * observe or inject into a disposing session. A concurrent ensureLive that
75
+ * arrives before detach cancels the park and keeps the live session.
54
76
  */
55
77
  park(id: string): Promise<void>;
56
78
  /**
57
79
  * Return the live session, reviving from the sessionFile if parked.
58
80
  * Throws a plain Error if the id is unknown or parked without a reviver.
59
81
  * Concurrent calls share one in-flight revive.
82
+ *
83
+ * Never returns a session that is mid-dispose: an in-flight park is either
84
+ * cancelled (session still live) or awaited to completion before revive.
60
85
  */
61
86
  ensureLive(id: string): Promise<AgentSession>;
62
87
  /** Hard removal: dispose if live, unregister from registry, drop timers. */
@@ -0,0 +1,3 @@
1
+ import { type AgentRegistry } from "./agent-registry.js";
2
+ /** Register persisted subagent and advisor transcripts as parked registry refs. */
3
+ export declare function registerPersistedSubagents(registry: AgentRegistry, sessionFile: string | null | undefined): Promise<void>;
@@ -1,5 +1,5 @@
1
- import { type AgentTelemetryConfig, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
- import type { Model } from "@oh-my-pi/pi-ai";
1
+ import { Agent, type AgentOptions, type AgentTelemetryConfig, type AgentTool, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
+ import type { Model, SimpleStreamOptions } from "@oh-my-pi/pi-ai";
3
3
  import type { Dialect } from "@oh-my-pi/pi-ai/dialect";
4
4
  import { type Rule } from "./capability/rule.js";
5
5
  import { ModelRegistry } from "./config/model-registry.js";
@@ -21,6 +21,7 @@ import { AgentSession, type PlanYolo, type Prewalk } from "./session/agent-sessi
21
21
  import type { AuthStorage } from "./session/auth-storage.js";
22
22
  import { SessionManager } from "./session/session-manager.js";
23
23
  import { type BuildSystemPromptResult } from "./system-prompt.js";
24
+ import type { StructuredSubagentSchemaMode } from "./task/types.js";
24
25
  import { type ConfiguredThinkingLevel } from "./thinking.js";
25
26
  import { BashTool, BUILTIN_TOOLS, createTools, EditTool, EvalTool, GlobTool, GrepTool, HIDDEN_TOOLS, type LspStartupServerInfo, ReadTool, type Tool, type ToolSession, WebSearchTool, WriteTool } from "./tools/index.js";
26
27
  import { EventBus } from "./utils/event-bus.js";
@@ -141,18 +142,28 @@ export interface CreateAgentSessionOptions {
141
142
  promptTemplates?: PromptTemplate[];
142
143
  /** File-based slash commands. Default: discovered from commands/ directories */
143
144
  slashCommands?: FileSlashCommand[];
144
- /** Enable MCP server discovery from .mcp.json files. Default: true */
145
+ /**
146
+ * Enable MCP capabilities. `false` skips MCP discovery and ignores
147
+ * `mcpManager`, preventing process-global or inherited MCP access. Default:
148
+ * true.
149
+ */
145
150
  enableMCP?: boolean;
146
- /** Existing MCP manager to reuse (skips discovery, propagates to toolSession). */
151
+ /** Existing MCP manager to reuse when MCP is enabled (skips discovery, propagates to toolSession). */
147
152
  mcpManager?: MCPManager;
148
153
  /** Enable LSP integration (tool, formatting, diagnostics, warmup). Default: true */
149
154
  enableLsp?: boolean;
155
+ /** Whether this invocation may expose IRC. `false` removes it even for subagents. */
156
+ enableIrc?: boolean;
150
157
  /** Skip subprocess-kernel availability checks and prelude warmup */
151
158
  skipPythonPreflight?: boolean;
152
159
  /** Tool names explicitly requested (enables disabled-by-default tools) */
153
160
  toolNames?: string[];
154
- /** Output schema for structured completion (subagents) */
161
+ /** Limit the session to explicitly supplied tool names, without discovered extras. */
162
+ restrictToolNames?: boolean;
163
+ /** Output schema for structured completion (subagents). */
155
164
  outputSchema?: unknown;
165
+ /** Enforcement policy for {@link outputSchema}; defaults to legacy permissive behavior. */
166
+ outputSchemaMode?: StructuredSubagentSchemaMode;
156
167
  /** Whether to include the yield tool by default */
157
168
  requireYieldTool?: boolean;
158
169
  /** Task recursion depth (for subagent sessions). Default: 0 */
@@ -346,6 +357,17 @@ export interface BuildSystemPromptOptions {
346
357
  * as separate entries so providers can cache prompt prefixes without concatenating blocks.
347
358
  */
348
359
  export declare function buildSystemPrompt(options?: BuildSystemPromptOptions): Promise<BuildSystemPromptResult>;
360
+ /** Dependencies used to construct an isolated auto-learn capture agent. */
361
+ export interface AutoLearnCaptureRunnerOptions {
362
+ sourceAgent: Agent;
363
+ captureTools: AgentTool[];
364
+ createAgent: (options: AgentOptions) => Agent;
365
+ onPayload?: SimpleStreamOptions["onPayload"];
366
+ onResponse?: SimpleStreamOptions["onResponse"];
367
+ createSessionId?: () => string;
368
+ }
369
+ /** Build a private capture runner over a detached message snapshot and provider session. */
370
+ export declare function createAutoLearnCaptureRunner(options: AutoLearnCaptureRunnerOptions): (content: string, signal?: AbortSignal) => Promise<void>;
349
371
  /**
350
372
  * Create an AgentSession with the specified options.
351
373
  *
@@ -18,7 +18,7 @@ import { type CompactionResult, type ShakeConfig } from "@oh-my-pi/pi-agent-core
18
18
  import type { AssistantMessage, Context, ImageContent, Message, MessageAttribution, Model, ProviderSessionState, ResetCreditAccountStatus, ResetCreditRedeemOutcome, ResetCreditTarget, ServiceTier, ServiceTierByFamily, ServiceTierFamily, SimpleStreamOptions, TextContent, ToolChoice, UsageReport } from "@oh-my-pi/pi-ai";
19
19
  import { Effort } from "@oh-my-pi/pi-ai";
20
20
  import { postmortem } from "@oh-my-pi/pi-utils";
21
- import { type AdvisorConfig } from "../advisor/index.js";
21
+ import { type AdvisorConfig, type AdvisorRuntimeStatus } from "../advisor/index.js";
22
22
  import { type AsyncJob, type AsyncJobDeliveryState, AsyncJobManager } from "../async/index.js";
23
23
  import type { Rule } from "../capability/rule.js";
24
24
  import type { ModelRegistry } from "../config/model-registry.js";
@@ -224,6 +224,8 @@ export interface AgentSessionConfig {
224
224
  builtInToolNames?: Iterable<string>;
225
225
  /** Update tool-session predicates that render guidance from the live active tool set. */
226
226
  setActiveToolNames?: (names: Iterable<string>) => void;
227
+ /** Register the write transport lazily when runtime xdev mounts first need it. */
228
+ ensureWriteRegistered?: () => Promise<boolean>;
227
229
  /** Current session pre-LLM message transform pipeline */
228
230
  transformContext?: (messages: AgentMessage[], signal?: AbortSignal) => AgentMessage[] | Promise<AgentMessage[]>;
229
231
  /**
@@ -275,7 +277,8 @@ export interface AgentSessionConfig {
275
277
  xdevRegistry?: XdevRegistry;
276
278
  /** Discoverable tools mounted under `xd://` in the initial enabled set (startup partition in `sdk.ts`). */
277
279
  initialMountedXdevToolNames?: string[];
278
- requestedToolNames?: ReadonlySet<string>;
280
+ /** Explicit/effective names pinned top-level during runtime repartitioning. */
281
+ presentationPinnedToolNames?: ReadonlySet<string>;
279
282
  /**
280
283
  * Optional accessor for live MCP server instructions. Read by the session's
281
284
  * `rebuildSystemPrompt`-skip optimization to detect server-side instruction
@@ -491,15 +494,20 @@ export interface AdvisorStats {
491
494
  /** Per-advisor breakdown; one entry per active advisor (single-advisor sessions have one). */
492
495
  advisors: PerAdvisorStat[];
493
496
  }
494
- /** One advisor's slice of {@link AdvisorStats}, surfaced for the multi-advisor status panel. */
497
+ /** One advisor's slice of {@link AdvisorStats}. Active advisors carry full
498
+ * token/cost data; disabled/no-model/quota-exhausted advisors appear with
499
+ * just `name` + `status` so the status line can render a dot for every
500
+ * configured advisor. */
495
501
  export interface PerAdvisorStat {
496
502
  name: string;
497
- model: Model;
503
+ status: AdvisorRuntimeStatus;
504
+ model?: Model;
498
505
  contextWindow: number;
499
506
  contextTokens: number;
500
507
  tokens: AdvisorStats["tokens"];
501
508
  cost: number;
502
509
  messages: AdvisorStats["messages"];
510
+ sessionId?: string;
503
511
  }
504
512
  export interface FreshSessionResult {
505
513
  previousSessionId: string;
@@ -612,6 +620,8 @@ export declare class AgentSession {
612
620
  */
613
621
  subscribe(listener: AgentSessionEventListener): () => void;
614
622
  subscribeCommandMetadataChanged(listener: CommandMetadataChangedListener): () => void;
623
+ /** Run one abortable auto-learn capture outside the primary agent loop. */
624
+ runAutolearnCapture(capture: (signal: AbortSignal) => Promise<void>): Promise<void>;
615
625
  /** True once dispose() has begun; deferred background work (e.g. the deferred
616
626
  * MCP discovery task in sdk.ts) must not touch the session past this point. */
617
627
  get isDisposed(): boolean;
@@ -915,6 +925,12 @@ export declare class AgentSession {
915
925
  get skillWarnings(): readonly SkillWarning[];
916
926
  getTodoPhases(): TodoPhase[];
917
927
  setTodoPhases(phases: TodoPhase[]): void;
928
+ /**
929
+ * Generate an automatic session title tied to this session's lifecycle.
930
+ * Input and replan callers share the signal so disposal cancels provider and
931
+ * local-worker requests instead of leaving background inference alive.
932
+ */
933
+ generateTitle(firstMessage: string): Promise<string | null>;
918
934
  /** Currently-applied {@link TITLE_SYSTEM.md} override, or undefined when the
919
935
  * bundled prompt is in effect. Consumed by {@link InteractiveMode} so the
920
936
  * first-input title path and the replan refresh share one source. */
@@ -1167,8 +1183,7 @@ export declare class AgentSession {
1167
1183
  */
1168
1184
  retry(): Promise<boolean>;
1169
1185
  /**
1170
- * Execute a bash command.
1171
- * Adds result to agent context and session.
1186
+ * Execute a bash command and retain the session/branch that owned its start.
1172
1187
  * @param command The bash command to execute
1173
1188
  * @param onChunk Optional streaming callback for output
1174
1189
  * @param options.excludeFromContext If true, command output won't be sent to LLM (!! prefix)
@@ -1178,10 +1193,7 @@ export declare class AgentSession {
1178
1193
  excludeFromContext?: boolean;
1179
1194
  useUserShell?: boolean;
1180
1195
  }): Promise<BashResult>;
1181
- /**
1182
- * Record a bash execution result in session history.
1183
- * Used by executeBash and by extensions that handle bash execution themselves.
1184
- */
1196
+ /** Record a bash result supplied outside executeBash in the current ownership scope. */
1185
1197
  recordBashResult(command: string, result: BashResult, options?: {
1186
1198
  excludeFromContext?: boolean;
1187
1199
  }): void;
@@ -1471,6 +1483,18 @@ export declare class AgentSession {
1471
1483
  * (`streamFn`, `promptCacheKey`, `providerSessionState`, ...).
1472
1484
  */
1473
1485
  getAdvisorAgent(): Agent | undefined;
1486
+ /**
1487
+ * Lightweight advisor status for the status line: returns just the configured
1488
+ * flag and per-advisor name/status without computing token/cost breakdowns.
1489
+ * Avoids re-tokenizing the advisor transcript on every render frame.
1490
+ */
1491
+ getAdvisorStatusOverview(): {
1492
+ configured: boolean;
1493
+ advisors: {
1494
+ name: string;
1495
+ status: AdvisorRuntimeStatus;
1496
+ }[];
1497
+ };
1474
1498
  /**
1475
1499
  * Return structured advisor stats for the status command and TUI panel.
1476
1500
  */
@@ -1,5 +1,6 @@
1
1
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
2
2
  import type { ImageContent, MessageAttribution, ServiceTierByFamily, TextContent } from "@oh-my-pi/pi-ai";
3
+ import type { StructuredSubagentSchemaMode } from "../task/types.js";
3
4
  export declare const CURRENT_SESSION_VERSION = 3;
4
5
  export declare const SESSION_TITLE_SLOT_BYTES = 256;
5
6
  export declare const SESSION_TITLE_SLOT_ENTRY_TYPE = "title";
@@ -142,8 +143,12 @@ export interface SessionInitEntry extends SessionEntryBase {
142
143
  task: string;
143
144
  /** Tools available to the agent */
144
145
  tools: string[];
145
- /** Output schema if structured output was requested */
146
+ /** Output schema if structured output was requested. */
146
147
  outputSchema?: unknown;
148
+ /** Enforcement policy recorded with the output schema for faithful revival. */
149
+ outputSchemaMode?: StructuredSubagentSchemaMode;
150
+ /** Whether revival must retain only the explicitly persisted tool names. */
151
+ restrictToolNames?: boolean;
147
152
  /** Spawn allowlist the subagent ran with ("" = none, "*" = any, else CSV); absent on pre-spawns files. */
148
153
  spawns?: string;
149
154
  /** The agent's `readSummarize` setting (`false` = read summarization disabled); absent uses the session default. */
@@ -1,3 +1,4 @@
1
+ import type { ToolResultMessage } from "@oh-my-pi/pi-ai";
1
2
  export interface HistoryFormatOptions {
2
3
  /** Optional H1 prepended to the transcript. */
3
4
  title?: string;
@@ -24,6 +25,15 @@ export interface HistoryFormatOptions {
24
25
  * this so it sees what changed without re-reading the file.
25
26
  */
26
27
  expandEditDiffs?: boolean;
28
+ /**
29
+ * Chunked rendering support: a caller formatting one logical transcript in
30
+ * several calls (the advisor's chunked delta render) passes a result index
31
+ * built over the WHOLE delta plus one shared consumed-id set, so a toolCall
32
+ * finds its toolResult across chunk boundaries and the result is never
33
+ * re-rendered as an orphan in a later chunk.
34
+ */
35
+ toolResultIndex?: ReadonlyMap<string, ToolResultMessage>;
36
+ consumedToolCallIds?: Set<string>;
27
37
  }
28
38
  /**
29
39
  * Hidden custom messages that inject the primary agent's operative *constraints*
@@ -1,4 +1,5 @@
1
1
  import type { ImageContent, Message, MessageAttribution, ServiceTierByFamily, TextContent } from "@oh-my-pi/pi-ai";
2
+ import type { StructuredSubagentSchemaMode } from "../task/types.js";
2
3
  import { ArtifactManager } from "./artifacts.js";
3
4
  import { type BlobPutOptions, type BlobPutResult } from "./blob-store.js";
4
5
  import { type BashExecutionMessage, type CustomMessage, type FileMentionMessage, type HookMessage, type PythonExecutionMessage } from "./messages.js";
@@ -48,6 +49,15 @@ export declare class SessionManager {
48
49
  /** Synchronous variant of {@link putBlob} for rebuild-only render paths. */
49
50
  putBlobSync(data: Buffer, options?: BlobPutOptions): BlobPutResult;
50
51
  captureState(): SessionManagerStateSnapshot;
52
+ /**
53
+ * Create an independent manager for the current logical session and branch.
54
+ * The clone shares the storage backend but owns its entry index and writer, so
55
+ * callers can finish session-owned work after this manager switches elsewhere.
56
+ * Set `persist` false when the original session is intentionally being dropped.
57
+ */
58
+ cloneCurrentSession(options?: {
59
+ persist?: boolean;
60
+ }): SessionManager;
51
61
  restoreState(snapshot: SessionManagerStateSnapshot): void;
52
62
  /** Switch to a different session file (resume / branch). */
53
63
  setSessionFile(sessionFile: string): Promise<void>;
@@ -140,6 +150,11 @@ export declare class SessionManager {
140
150
  * top-level entries via appendCompaction()/branchWithSummary().
141
151
  */
142
152
  appendMessage(message: Message | CustomMessage | HookMessage | BashExecutionMessage | PythonExecutionMessage | FileMentionMessage): string;
153
+ /**
154
+ * Append to a non-active branch without changing the current leaf.
155
+ * Used by work that retains ownership of a branch across tree navigation.
156
+ */
157
+ appendMessageToBranch(message: Message | CustomMessage | HookMessage | BashExecutionMessage | PythonExecutionMessage | FileMentionMessage, parentId: string | null): string;
143
158
  /** Append a thinking level change as child of current leaf, then advance leaf. Returns entry id. */
144
159
  appendThinkingLevelChange(thinkingLevel?: string, configured?: string): string;
145
160
  appendServiceTierChange(serviceTier: ServiceTierByFamily | null): string;
@@ -155,6 +170,8 @@ export declare class SessionManager {
155
170
  task: string;
156
171
  tools: string[];
157
172
  outputSchema?: unknown;
173
+ outputSchemaMode?: StructuredSubagentSchemaMode;
174
+ restrictToolNames?: boolean;
158
175
  spawns?: string;
159
176
  readSummarize?: boolean;
160
177
  }): string;
@@ -279,6 +296,8 @@ export declare class SessionManager {
279
296
  task: string;
280
297
  tools: string[];
281
298
  outputSchema?: unknown;
299
+ outputSchemaMode?: StructuredSubagentSchemaMode;
300
+ restrictToolNames?: boolean;
282
301
  spawns?: string;
283
302
  readSummarize?: boolean;
284
303
  } | null;
@@ -1,5 +1,14 @@
1
1
  import type { UsageLimit, UsageReport } from "@oh-my-pi/pi-ai";
2
2
  import type { OAuthAccountIdentity } from "../../session/auth-storage.js";
3
+ /**
4
+ * Session marker label for an active OAuth identity: the base identifier
5
+ * (email → accountId → projectId) suffixed with the organization when present
6
+ * and distinct. Same-email Anthropic multi-org accounts share the base, so the
7
+ * org suffix is the only field that tells the session's quota pool apart —
8
+ * mirrors the account-list rows (`formatUsageReportAccount`) and login success.
9
+ * Returns `undefined` when no identifier is recoverable.
10
+ */
11
+ export declare function formatActiveAccountLabel(identity: OAuthAccountIdentity | undefined): string | undefined;
3
12
  /**
4
13
  * True when a single usage-limit column belongs to the given OAuth identity.
5
14
  *