@oh-my-pi/pi-coding-agent 17.2.12 → 17.2.13

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 (134) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/dist/{CHANGELOG-k9ghy5sn.md → CHANGELOG-d8xh7keh.md} +57 -0
  3. package/dist/cli.js +3069 -3047
  4. package/dist/types/advisor/delta-split.d.ts +24 -0
  5. package/dist/types/advisor/runtime.d.ts +2 -2
  6. package/dist/types/async/job-manager.d.ts +8 -1
  7. package/dist/types/cli/update-cli.d.ts +53 -1
  8. package/dist/types/config/keybindings.d.ts +10 -0
  9. package/dist/types/config/model-resolver.d.ts +15 -2
  10. package/dist/types/config/settings-schema.d.ts +4 -0
  11. package/dist/types/discovery/agents-md.d.ts +10 -1
  12. package/dist/types/eval/runner-cache.d.ts +12 -0
  13. package/dist/types/extensibility/extensions/runner.d.ts +12 -3
  14. package/dist/types/extensibility/extensions/types.d.ts +15 -4
  15. package/dist/types/extensibility/plugins/marketplace/manager.d.ts +4 -1
  16. package/dist/types/lib/xai-http.d.ts +0 -1
  17. package/dist/types/mcp/tool-bridge.d.ts +8 -5
  18. package/dist/types/modes/components/agent-hub-renderer.d.ts +6 -1
  19. package/dist/types/modes/components/status-line/types.d.ts +4 -0
  20. package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -4
  21. package/dist/types/modes/interactive-mode.d.ts +17 -8
  22. package/dist/types/modes/types.d.ts +7 -10
  23. package/dist/types/modes/utils/hotkeys-markdown.d.ts +1 -1
  24. package/dist/types/session/agent-session-types.d.ts +2 -0
  25. package/dist/types/session/agent-session.d.ts +22 -3
  26. package/dist/types/session/messages.d.ts +20 -0
  27. package/dist/types/session/retry-fallback-chains.d.ts +13 -0
  28. package/dist/types/session/session-advisors.d.ts +1 -1
  29. package/dist/types/session/session-history-format.d.ts +10 -0
  30. package/dist/types/session/session-maintenance.d.ts +1 -1
  31. package/dist/types/session/session-tools.d.ts +24 -5
  32. package/dist/types/session/turn-recovery.d.ts +34 -5
  33. package/dist/types/slash-commands/types.d.ts +5 -1
  34. package/dist/types/task/executor.d.ts +1 -1
  35. package/dist/types/tools/approval.d.ts +7 -0
  36. package/dist/types/tools/todo.d.ts +14 -15
  37. package/dist/types/tools/write.d.ts +2 -2
  38. package/dist/types/utils/local-date.d.ts +2 -0
  39. package/dist/types/vibe/runtime.d.ts +1 -1
  40. package/dist/types/web/parallel.d.ts +1 -0
  41. package/dist/types/web/search/providers/brave.d.ts +8 -3
  42. package/dist/types/web/search/providers/codex.d.ts +6 -0
  43. package/dist/types/web/search/providers/firecrawl.d.ts +3 -2
  44. package/dist/types/web/search/providers/jina.d.ts +3 -3
  45. package/dist/types/web/search/providers/parallel.d.ts +1 -0
  46. package/dist/types/web/search/providers/perplexity.d.ts +4 -0
  47. package/dist/types/web/search/providers/tinyfish.d.ts +2 -0
  48. package/package.json +13 -13
  49. package/src/advisor/delta-split.ts +98 -0
  50. package/src/advisor/runtime.ts +321 -69
  51. package/src/async/job-manager.ts +14 -3
  52. package/src/cli/plugin-cli.ts +30 -2
  53. package/src/cli/update-cli.ts +259 -24
  54. package/src/config/keybindings.ts +52 -9
  55. package/src/config/model-resolver.ts +19 -3
  56. package/src/config/settings-schema.ts +5 -0
  57. package/src/cursor.ts +10 -5
  58. package/src/discovery/agents-md.ts +61 -23
  59. package/src/eval/jl/kernel.ts +2 -20
  60. package/src/eval/py/kernel.ts +2 -20
  61. package/src/eval/rb/kernel.ts +2 -20
  62. package/src/eval/runner-cache.ts +41 -0
  63. package/src/exec/non-interactive-env.ts +14 -3
  64. package/src/extensibility/extensions/loader.ts +5 -2
  65. package/src/extensibility/extensions/runner.ts +184 -66
  66. package/src/extensibility/extensions/types.ts +26 -2
  67. package/src/extensibility/extensions/wrapper.ts +13 -7
  68. package/src/extensibility/plugins/marketplace/manager.ts +6 -2
  69. package/src/hindsight/client.ts +1 -1
  70. package/src/lib/xai-http.ts +0 -4
  71. package/src/lsp/client.ts +2 -0
  72. package/src/lsp/servers.ts +1 -1
  73. package/src/mcp/tool-bridge.ts +15 -6
  74. package/src/modes/components/agent-hub-renderer.ts +9 -3
  75. package/src/modes/components/agent-hub.ts +2 -1
  76. package/src/modes/components/status-line/component.ts +58 -6
  77. package/src/modes/components/status-line/segments.ts +12 -1
  78. package/src/modes/components/status-line/types.ts +1 -0
  79. package/src/modes/components/user-message.ts +20 -5
  80. package/src/modes/controllers/event-controller.ts +48 -0
  81. package/src/modes/controllers/extension-ui-controller.ts +14 -7
  82. package/src/modes/controllers/input-controller.ts +25 -6
  83. package/src/modes/interactive-mode.ts +315 -129
  84. package/src/modes/rpc/rpc-frame.ts +13 -5
  85. package/src/modes/theme/tui-adapters.ts +4 -5
  86. package/src/modes/types.ts +13 -7
  87. package/src/modes/utils/hotkeys-markdown.ts +10 -6
  88. package/src/prompts/system/system-prompt.md +1 -1
  89. package/src/registry/persisted-agents.ts +43 -8
  90. package/src/sdk.ts +139 -8
  91. package/src/session/agent-session-types.ts +2 -0
  92. package/src/session/agent-session.ts +66 -10
  93. package/src/session/messages.ts +98 -28
  94. package/src/session/retry-fallback-chains.ts +14 -0
  95. package/src/session/session-advisors.ts +32 -15
  96. package/src/session/session-history-format.ts +15 -1
  97. package/src/session/session-maintenance.ts +8 -8
  98. package/src/session/session-manager.ts +6 -2
  99. package/src/session/session-tools.ts +321 -184
  100. package/src/session/turn-recovery.ts +225 -47
  101. package/src/slash-commands/builtin-modes.ts +41 -12
  102. package/src/slash-commands/types.ts +5 -1
  103. package/src/task/executor.ts +92 -45
  104. package/src/task/structured-subagent.ts +5 -5
  105. package/src/tools/approval.ts +44 -10
  106. package/src/tools/fetch.ts +21 -2
  107. package/src/tools/image-gen.ts +6 -8
  108. package/src/tools/todo.ts +70 -26
  109. package/src/tools/tts.ts +3 -2
  110. package/src/tools/write.ts +7 -3
  111. package/src/utils/local-date.ts +13 -0
  112. package/src/utils/tools-manager.ts +2 -2
  113. package/src/vibe/runtime.ts +22 -14
  114. package/src/web/kagi.ts +91 -34
  115. package/src/web/parallel.ts +11 -2
  116. package/src/web/scrapers/crates-io.ts +2 -2
  117. package/src/web/scrapers/discogs.ts +2 -2
  118. package/src/web/scrapers/docs-rs.ts +2 -2
  119. package/src/web/scrapers/github.ts +2 -2
  120. package/src/web/scrapers/musicbrainz.ts +1 -2
  121. package/src/web/scrapers/pubmed.ts +2 -2
  122. package/src/web/scrapers/sec-edgar.ts +2 -2
  123. package/src/web/search/providers/brave.ts +121 -46
  124. package/src/web/search/providers/codex.ts +88 -12
  125. package/src/web/search/providers/exa.ts +45 -10
  126. package/src/web/search/providers/firecrawl.ts +53 -11
  127. package/src/web/search/providers/gemini.ts +139 -27
  128. package/src/web/search/providers/jina.ts +48 -25
  129. package/src/web/search/providers/parallel.ts +23 -9
  130. package/src/web/search/providers/perplexity.ts +24 -7
  131. package/src/web/search/providers/searxng.ts +77 -1
  132. package/src/web/search/providers/tavily.ts +23 -22
  133. package/src/web/search/providers/tinyfish.ts +44 -10
  134. package/src/web/search/providers/xai.ts +85 -14
@@ -37,6 +37,19 @@ export interface ActiveRetryFallbackState {
37
37
  originalThinkingLevel: ConfiguredThinkingLevel | undefined;
38
38
  lastAppliedFallbackThinkingLevel: ConfiguredThinkingLevel | undefined;
39
39
  pinned: boolean;
40
+ /**
41
+ * Set once a turn on the fallback target settles successfully. Until then the
42
+ * switch is only a routing decision — nothing has been produced by the new
43
+ * model, so no observer may report the run as having used it.
44
+ */
45
+ served?: boolean;
46
+ }
47
+ /** Model a session's produced work is attributed to. */
48
+ export interface ServingModel {
49
+ /** Full selector including routing and thinking level. */
50
+ selector: string;
51
+ /** Whether fallback routing, rather than the configured primary, owns it. */
52
+ isFallback: boolean;
40
53
  }
41
54
  /** Calculates capped exponential retry delay with downward jitter. */
42
55
  export declare function calculateRetryBackoffDelayMs(baseDelayMs: number, attempt: number): number;
@@ -180,7 +180,7 @@ export declare class SessionAdvisors {
180
180
  */
181
181
  refreshProviderIdentity(): void;
182
182
  /** Re-primes advisor transcript views after an in-conversation history rewrite. */
183
- resetAllRuntimes(): void;
183
+ resetAllRuntimes(reason?: string): void;
184
184
  /** Whether live runtimes still match the resolved advisor configuration. */
185
185
  runtimeMatchesCurrentConfig(): boolean;
186
186
  /** Whether concern/blocker delivery is inside the post-interrupt immunity window. */
@@ -34,6 +34,16 @@ export interface HistoryFormatOptions {
34
34
  */
35
35
  toolResultIndex?: ReadonlyMap<string, ToolResultMessage>;
36
36
  consumedToolCallIds?: Set<string>;
37
+ /**
38
+ * Chunked rendering state: a mutable holder for the watched-role label
39
+ * (`**user**:` / `**agent**:`) that ended the previous chunk. Lets a caller
40
+ * formatting one logical transcript across several calls (advisor
41
+ * multi-message split) keep consecutive same-role collapsing byte-identical
42
+ * to the single-block render: pass one object across all chunk calls.
43
+ */
44
+ watchedRoleState?: {
45
+ lastLabel: string | undefined;
46
+ };
37
47
  }
38
48
  export declare function formatExecutionSourcePreview(source: string): string;
39
49
  /** Pick the most informative scalar argument of a tool call. */
@@ -85,7 +85,7 @@ export interface SessionMaintenanceHost {
85
85
  resetCodexProviderAfterCompaction(compaction: CodexCompactionContext): void;
86
86
  resetPlanReference(): void;
87
87
  syncTodoPhasesFromBranch(): void;
88
- resetAdvisorRuntimes(): void;
88
+ resetAdvisorRuntimes(reason?: string): void;
89
89
  rebaseAfterCompaction(): void;
90
90
  recordAnchoredHistoryRewrite(tokensRemoved: number): void;
91
91
  getContextBreakdown(options?: {
@@ -47,6 +47,8 @@ interface SessionToolsOptions {
47
47
  createInspectImageTool?: () => Promise<AgentTool | null>;
48
48
  builtInToolNames?: Iterable<string>;
49
49
  presentationPinnedToolNames?: ReadonlySet<string>;
50
+ /** MCP tool names whose current registry entries came from the manager snapshot. */
51
+ mcpManagerToolNames?: Iterable<string>;
50
52
  ensureWriteRegistered?: () => Promise<boolean>;
51
53
  rebuildSystemPrompt?: (toolNames: string[], tools: Map<string, AgentTool>) => Promise<{
52
54
  systemPrompt: string[];
@@ -138,6 +140,20 @@ export declare class SessionTools {
138
140
  getToolByName(name: string): AgentTool | undefined;
139
141
  /** Whether a registry entry came from a built-in factory. */
140
142
  hasBuiltInTool(name: string): boolean;
143
+ /** Updates source provenance when a live registry entry is replaced or restored. */
144
+ setToolBuiltIn(name: string, builtIn: boolean): void;
145
+ /** Whether the live registry entry is owned by the RPC host. */
146
+ hasRpcHostTool(name: string): boolean;
147
+ /** Whether the current MCP entry came from the manager snapshot. */
148
+ hasMCPManagerTool(name: string): boolean;
149
+ /** Restores manager ownership after a lifecycle registration rollback. */
150
+ setMCPManagerTool(name: string, managerOwned: boolean): void;
151
+ /** Current extension-owned MCP entry retained across manager refreshes. */
152
+ getExtensionMCPTool(name: string): AgentTool | undefined;
153
+ /** Updates extension ownership when a lifecycle registration commits or rolls back. */
154
+ setExtensionMCPTool(name: string, tool: AgentTool | undefined): void;
155
+ /** Serializes every registry and presentation mutation for this session. */
156
+ runToolRegistryMutation<T>(mutation: () => Promise<T>, signal?: AbortSignal): Promise<T>;
141
157
  /** Names of every registered tool. */
142
158
  getAllToolNames(): string[];
143
159
  /**
@@ -162,7 +178,7 @@ export declare class SessionTools {
162
178
  /** Enabled MCP tools in their current presentation partition. */
163
179
  getSelectedMCPToolNames(): string[];
164
180
  /** Applies an enabled tool set and reconciles its `xd://` partition. */
165
- applyActiveToolsByName(toolNames: string[]): Promise<void>;
181
+ applyActiveToolsByName(toolNames: string[], forcePromptRefresh?: boolean, signal?: AbortSignal): Promise<void>;
166
182
  /**
167
183
  * Forget the announced-mount baseline for a replaced transcript. Called when
168
184
  * session history is swapped wholesale (`/new`, `switchSession`, `branch`): the
@@ -192,11 +208,14 @@ export declare class SessionTools {
192
208
  * `xd://` remain mount-eligible, even when the live mount set has drifted.
193
209
  *
194
210
  * Names outside `mountedToolNames` are pinned top-level for this application;
195
- * names in the mounted subset remain eligible for xdev mounting. Delegates the
196
- * actual apply through {@link applyActiveToolsByName} and restores the prior runtime
197
- * selection if that apply throws.
211
+ * names in the mounted subset remain eligible for xdev mounting. Set
212
+ * `forcePromptRefresh` when an enabled tool's schema or prompt-visible metadata
213
+ * changed without changing its name or presentation.
214
+ *
215
+ * Delegates the actual apply through {@link applyActiveToolsByName} and restores
216
+ * the prior runtime selection if that apply throws.
198
217
  */
199
- setActiveToolPresentation(toolNames: string[], mountedToolNames: string[]): Promise<void>;
218
+ setActiveToolPresentation(toolNames: string[], mountedToolNames: string[], forcePromptRefresh?: boolean, signal?: AbortSignal): Promise<void>;
200
219
  /** Replaces memory-backend tools while preserving unrelated selections. */
201
220
  replaceMemoryTools(tools: AgentTool[]): Promise<void>;
202
221
  /**
@@ -5,7 +5,7 @@ import type { Settings } from "../config/settings.js";
5
5
  import { type ConfiguredThinkingLevel } from "../thinking.js";
6
6
  import type { AgentSessionEvent } from "./agent-session-events.js";
7
7
  import type { InitialRetryFallbackState, UsageFallbackConfirmer } from "./agent-session-types.js";
8
- import { type RetryFallbackSelector } from "./retry-fallback-chains.js";
8
+ import { type RetryFallbackSelector, type ServingModel } from "./retry-fallback-chains.js";
9
9
  import type { SessionManager } from "./session-manager.js";
10
10
  /** Result shape shared with automatic maintenance recovery. */
11
11
  export interface RecoveryCompactionResult {
@@ -77,13 +77,42 @@ export declare class TurnRecovery {
77
77
  get attempt(): number;
78
78
  /** Promise settled when the active retry saga finishes. */
79
79
  get retryPromise(): Promise<void> | undefined;
80
- /** Resolved selector while fallback routing owns the current model. */
81
- get retryFallbackModel(): string | undefined;
80
+ /**
81
+ * Model this session's produced work is attributed to.
82
+ *
83
+ * A model switch is a routing decision, not evidence the target can produce
84
+ * anything: a candidate that errors on its first request produced none of the
85
+ * turns already in this session. So attribution only ever names a model that
86
+ * has settled a turn here, and a switch — into a fallback, back to a restored
87
+ * primary, or anywhere else — moves it only once the new model answers.
88
+ *
89
+ * Before anything has served there is no earlier work to miscredit, so the
90
+ * configured model is both the only available answer and a safe one.
91
+ */
92
+ get servingModel(): ServingModel | undefined;
93
+ /**
94
+ * Carries attribution onto a new session id that continues this conversation.
95
+ *
96
+ * The session-id anchor assumes a new id means an unrelated transcript, which
97
+ * holds for `/new` and for resuming something else. A fork breaks that
98
+ * assumption on purpose: it clones the transcript and keeps running the same
99
+ * recovery state under a fresh id. Dropping attribution there would bootstrap
100
+ * an unproven fallback as the primary and re-credit it with the work the
101
+ * previous model did — the very bug the anchor exists to prevent.
102
+ *
103
+ * Only state belonging to `previousSessionId` moves, so an id left behind by
104
+ * an earlier switch stays expired.
105
+ */
106
+ reanchorServedAttribution(previousSessionId: string): void;
82
107
  /** Resets per-prompt recovery counters and terminal-stop acceptance. */
83
108
  resetForNewPrompt(): void;
84
109
  /** Sets whether one terminal empty stop is accepted for the current prompt. */
85
110
  setAcceptTerminalEmptyStop(accept: boolean): void;
86
- /** Closes a successful retry saga and annotates recovered persisted errors. */
111
+ /**
112
+ * Records which model produced this turn, marks an active fallback as having
113
+ * served, then closes a successful retry saga and annotates recovered
114
+ * persisted errors.
115
+ */
87
116
  onAssistantSettledSuccessfully(message: AssistantMessage): Promise<void>;
88
117
  /** Closes a failed retry saga when no compaction continuation took ownership. */
89
118
  onErrorSettledWithoutRetry(message: AssistantMessage, compaction: RecoveryCompactionResult): Promise<void>;
@@ -170,7 +199,7 @@ export declare class TurnRecovery {
170
199
  classifyResolvedInterruptedToolTurn(message: AssistantMessage): "reasonless-abort" | "stream-stall" | undefined;
171
200
  /** Checks whether a provider error represents a classifier refusal. */
172
201
  isClassifierRefusal(message: AssistantMessage): boolean;
173
- /** Clears fallback ownership after an explicit model change. */
202
+ /** Clears fallback ownership after an explicit model change or a restore. */
174
203
  clearActiveRetryFallback(): void;
175
204
  /** Checks whether a fallback selector remains in cooldown. */
176
205
  isRetryFallbackSelectorSuppressed(selector: RetryFallbackSelector): boolean;
@@ -1,5 +1,5 @@
1
1
  import type { Settings } from "../config/settings.js";
2
- import type { InteractiveModeContext } from "../modes/types.js";
2
+ import type { InteractiveModeContext, SubmittedUserInput } from "../modes/types.js";
3
3
  import type { AgentSession } from "../session/agent-session.js";
4
4
  import type { SessionManager } from "../session/session-manager.js";
5
5
  /** Declarative subcommand definition for commands like /mcp. */
@@ -81,6 +81,10 @@ export interface SlashCommandRuntime {
81
81
  */
82
82
  export interface TuiSlashCommandRuntime {
83
83
  ctx: InteractiveModeContext;
84
+ /** Post-extension-hook attachments belonging to the submitted slash draft. */
85
+ input?: Pick<SubmittedUserInput, "images" | "imageLinks">;
86
+ /** The editor snapshot was cleared before asynchronous input hooks ran. */
87
+ draftDetached?: boolean;
84
88
  }
85
89
  /** Unified slash-command spec consumed by both TUI and ACP dispatchers. */
86
90
  export interface SlashCommandSpec extends BuiltinSlashCommand {
@@ -253,7 +253,7 @@ export declare function finalizeSubprocessOutput(args: FinalizeSubprocessOutputA
253
253
  */
254
254
  export declare function createMCPProxyTools(mcpManager: MCPManager): CustomTool[];
255
255
  export declare function createSubagentSettings(baseSettings: Settings, overrides?: Partial<Record<SettingPath, unknown>>, inheritedServiceTier?: ServiceTierByFamily | null): Settings;
256
- export type AbortReason = "signal" | "terminate" | "timeout" | "budget";
256
+ export type AbortReason = "signal" | "shutdown" | "terminate" | "timeout" | "budget";
257
257
  /** Inputs for {@link attachIrcWakeTurnMonitor}. */
258
258
  export interface IrcWakeTurnMonitorOptions {
259
259
  /** Registry id of the kept-alive subagent whose autonomous IRC wake turns are monitored. */
@@ -17,6 +17,8 @@ export interface ResolvedApproval {
17
17
  reason?: string;
18
18
  override: boolean;
19
19
  source?: "tool" | "user" | "mode";
20
+ /** User-policy key that produced `source: "user"` (defaults to the tool name). */
21
+ policyKey?: string;
20
22
  }
21
23
  /**
22
24
  * Evaluate a tool's own approval declaration against `args` and return the
@@ -31,6 +33,11 @@ export declare function resolveToolTier(tool: ApprovalSubject, args: unknown): T
31
33
  *
32
34
  * Resolution order:
33
35
  * 1. Tool `approval(args)` decision, defaulting to tier "exec" when omitted.
36
+ * A decision may carry a `policyKey` — `tools.approval.<policyKey>` is then
37
+ * the user override consulted instead of `tools.approval.<tool.name>`, with
38
+ * the invoking tool's own policy as the fallback when the user set none for
39
+ * the keyed sub-tool (e.g. an `xd://` device dispatch without a device
40
+ * policy still honors `tools.approval.write`).
34
41
  * 2. User per-tool override, if set and valid.
35
42
  * 3. Active mode tier comparison.
36
43
  *
@@ -74,6 +74,12 @@ export declare function getLatestTodoPhasesFromEntries(entries: SessionEntry[]):
74
74
  * the contained side.
75
75
  */
76
76
  export declare function todoMatchesAnyDescription(content: string, descriptions: readonly string[]): boolean;
77
+ /** Whether a todo is settled: completed or deliberately abandoned. Shared so
78
+ * the collapsed viewport, the HUD progress counters, and the HUD's closed-todo
79
+ * auto-clear can never disagree about what "done" hides. */
80
+ export declare function isClosedTodo<T extends {
81
+ status: TodoStatus;
82
+ }>(task: T): boolean;
77
83
  /** Result of {@link selectCollapsedTodos}: the rows to render plus an optional
78
84
  * summary line (empty string ⇒ no summary row). */
79
85
  export interface CollapsedTodoSelection<T> {
@@ -83,22 +89,15 @@ export interface CollapsedTodoSelection<T> {
83
89
  /**
84
90
  * Walking-viewport selection for a phase's collapsed todo preview (#5873).
85
91
  *
86
- * Policy, applied to `tasks` in todo order:
87
- * 1. While the phase has open work, completed/abandoned tasks are omitted. A
88
- * phase with no open tasks left falls back to its closed tasks so the sticky
89
- * HUD's closed-todo persistence still has something to render.
90
- * 2. Every active task (in-progress, or pending matched to a live subagent) is
91
- * placed at the head in stable todo order never dropped for lying outside
92
- * an ordinary window.
93
- * 3. Remaining rows up to `cap` are filled with the pending tasks that follow
94
- * the first active one, in todo order (falling back to leading pending tasks
95
- * when no active task exists), so a freshly-promoted task leads the preview.
96
- * 4. When active tasks alone exceed `cap`, only the first `cap` active tasks are
97
- * shown and the summary counts the hidden *active* todos, never replacing
98
- * them with unrelated pending rows.
92
+ * Applied to `tasks` in todo order: the open tasks run through
93
+ * {@link selectWithinCap}, led by the last {@link COLLAPSED_CLOSED_CONTEXT}
94
+ * closed tasks in todo order so a checked row remains visible even when callers
95
+ * complete work out of sequence. The lead is additive — it never costs an open
96
+ * row and a phase with no open work left falls back to its closed tasks so the
97
+ * sticky HUD's closed-todo persistence still has something to render.
99
98
  *
100
- * The summary otherwise counts the remaining tasks in the display base. Returns
101
- * the whole base with an empty summary when it already fits.
99
+ * `summary` counts the open tasks that did not fit; the closed lead is context,
100
+ * not part of the budget.
102
101
  */
103
102
  export declare function selectCollapsedTodos<T extends {
104
103
  status: TodoStatus;
@@ -1,4 +1,4 @@
1
- import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback, ToolTier } from "@oh-my-pi/pi-agent-core";
1
+ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback, ToolApprovalDecision } from "@oh-my-pi/pi-agent-core";
2
2
  import { type Component } from "@oh-my-pi/pi-tui";
3
3
  import type { RenderResultOptions } from "../extensibility/custom-tools/types.js";
4
4
  import { type FileDiagnosticsResult } from "../lsp/index.js";
@@ -36,7 +36,7 @@ export declare class WriteTool implements AgentTool<typeof writeSchema, WriteToo
36
36
  #private;
37
37
  private readonly session;
38
38
  readonly name = "write";
39
- readonly approval: (args: unknown) => ToolTier;
39
+ readonly approval: (args: unknown) => ToolApprovalDecision;
40
40
  readonly formatApprovalDetails: (args: unknown) => string[];
41
41
  readonly label = "Write";
42
42
  readonly description: string;
@@ -1,2 +1,4 @@
1
1
  /** formatLocalCalendarDate formats a Date as YYYY-MM-DD in the host local timezone. */
2
2
  export declare function formatLocalCalendarDate(date?: Date): string;
3
+ /** Format a local date and minute with a compact numeric UTC offset. */
4
+ export declare function formatLocalDateTimeWithOffset(date: Date): string;
@@ -7,7 +7,7 @@ export type VibeCli = "fast" | "good";
7
7
  * CLI flavor → bundled agent type. This IS the model-tier mapping: `sonic`
8
8
  * carries `model: "@smol"` (the configured fast/low-latency role) and `task`
9
9
  * carries `model: "@task"` (inherits the session's strong model).
10
- * Resolution goes through {@link resolveAgentModelPatterns} exactly like a
10
+ * Resolution goes through {@link resolveAgentModelSelection} exactly like a
11
11
  * `task` spawn, so `task.agentModelOverrides` and model-role settings apply.
12
12
  */
13
13
  export declare const VIBE_CLI_AGENT: Record<VibeCli, string>;
@@ -58,6 +58,7 @@ export declare class ParallelApiError extends Error {
58
58
  constructor(message: string, statusCode?: number);
59
59
  }
60
60
  export declare function parseParallelErrorResponse(statusCode: number, responseText: string): ParallelApiError;
61
+ export declare function parseParallelJsonResponse(response: Response, operation: "search" | "extract"): Promise<unknown>;
61
62
  export declare function parseParallelSearchPayload(payload: unknown, options?: {
62
63
  parseMetadata?: boolean;
63
64
  }): ParallelSearchResult;
@@ -14,18 +14,23 @@ export interface BraveSearchParams {
14
14
  num_results?: number;
15
15
  recency?: "day" | "week" | "month" | "year";
16
16
  parsedQuery?: StructuredQuery;
17
+ /** Two-letter market code, or `ALL`. */
18
+ country?: string;
19
+ /** Brave search language code, such as `en` or `zh-hans`. */
20
+ search_lang?: string;
21
+ safesearch?: "off" | "moderate" | "strict";
22
+ authStorage: AuthStorage;
23
+ sessionId?: string;
17
24
  signal?: AbortSignal;
18
25
  timeoutMs?: number;
19
26
  fetch?: FetchImpl;
20
27
  }
21
- /** Find BRAVE_API_KEY from environment or .env files. */
22
- export declare function findApiKey(): string | null;
23
28
  /** Execute Brave web search. */
24
29
  export declare function searchBrave(params: BraveSearchParams): Promise<SearchResponse>;
25
30
  /** Search provider for Brave web search. */
26
31
  export declare class BraveProvider extends SearchProvider {
27
32
  readonly id = "brave";
28
33
  readonly label = "Brave";
29
- isAvailable(_authStorage: AuthStorage): boolean;
34
+ isAvailable(authStorage: AuthStorage): boolean;
30
35
  search(params: SearchParams): Promise<SearchResponse>;
31
36
  }
@@ -1,3 +1,9 @@
1
+ /**
2
+ * OpenAI Codex Web Search Provider
3
+ *
4
+ * Uses the configured Codex Responses transport for proxy/API-key setups and
5
+ * the official ChatGPT backend for OAuth logins.
6
+ */
1
7
  import { type AuthStorage, type FetchImpl } from "@oh-my-pi/pi-ai";
2
8
  import type { SearchResponse } from "../../../web/search/types.js";
3
9
  import type { SearchParams } from "./base.js";
@@ -27,8 +27,9 @@ export declare class FirecrawlProvider extends SearchProvider {
27
27
  readonly id = "firecrawl";
28
28
  readonly label = "Firecrawl";
29
29
  /**
30
- * Auto-chain admission: requires a credential so an unconfigured Firecrawl
31
- * doesn't displace other providers that the user has set up with API keys.
30
+ * Auto-chain admission requires either a credential or an explicitly
31
+ * configured self-hosted endpoint. Hosted keyless mode remains explicit-only
32
+ * so it does not displace providers the user configured.
32
33
  */
33
34
  isAvailable(authStorage: AuthStorage): boolean;
34
35
  /**
@@ -13,6 +13,8 @@ type SearchParamsWithFetch = SearchParams & {
13
13
  };
14
14
  export interface JinaSearchParams {
15
15
  query: string;
16
+ authStorage: AuthStorage;
17
+ sessionId?: string;
16
18
  num_results?: number;
17
19
  /** Single bare host for Jina's `X-Site` in-site search header. */
18
20
  site?: string;
@@ -20,15 +22,13 @@ export interface JinaSearchParams {
20
22
  timeoutMs?: number;
21
23
  fetch?: FetchImpl;
22
24
  }
23
- /** Find JINA_API_KEY from environment or .env files. */
24
- export declare function findApiKey(): string | null;
25
25
  /** Execute Jina web search. */
26
26
  export declare function searchJina(params: JinaSearchParams): Promise<SearchResponse>;
27
27
  /** Search provider for Jina Reader. */
28
28
  export declare class JinaProvider extends SearchProvider {
29
29
  readonly id = "jina";
30
30
  readonly label = "Jina";
31
- isAvailable(_authStorage: AuthStorage): boolean;
31
+ isAvailable(authStorage: AuthStorage): boolean;
32
32
  search(params: SearchParamsWithFetch): Promise<SearchResponse>;
33
33
  }
34
34
  export {};
@@ -6,6 +6,7 @@ import { SearchProvider } from "./base.js";
6
6
  export declare function searchParallel(params: {
7
7
  query: string;
8
8
  num_results?: number;
9
+ recency?: SearchParams["recency"];
9
10
  signal?: AbortSignal;
10
11
  timeoutMs?: number;
11
12
  fetch?: FetchImpl;
@@ -19,7 +19,11 @@ export interface PerplexitySearchParams {
19
19
  system_prompt?: string;
20
20
  /** Pre-parsed view of `query` from the search pipeline; parsed locally when absent. */
21
21
  parsedQuery?: StructuredQuery;
22
+ /** Direct API model. Defaults to `PI_PERPLEXITY_API_MODEL`, then `sonar-pro`. */
23
+ api_model?: string;
22
24
  search_recency_filter?: "hour" | "day" | "week" | "month" | "year";
25
+ /** Consumer subscription model preference. Defaults to `PI_PERPLEXITY_MODEL`, then Sonar (`experimental`). */
26
+ subscription_model?: string;
23
27
  num_results?: number;
24
28
  /** Maximum output tokens. Defaults to 8192. */
25
29
  max_tokens?: number;
@@ -13,6 +13,8 @@ export interface TinyFishSearchParams {
13
13
  num_results?: number;
14
14
  recency?: SearchParams["recency"];
15
15
  page?: number;
16
+ include_domains?: string[];
17
+ exclude_domains?: string[];
16
18
  signal?: AbortSignal;
17
19
  timeoutMs?: number;
18
20
  fetch?: FetchImpl;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "17.2.12",
4
+ "version": "17.2.13",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -50,18 +50,18 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@babel/parser": "^7.29.7",
53
- "@oh-my-pi/hashline": "17.2.12",
54
- "@oh-my-pi/omp-stats": "17.2.12",
55
- "@oh-my-pi/omptype": "17.2.12",
56
- "@oh-my-pi/pi-agent-core": "17.2.12",
57
- "@oh-my-pi/pi-ai": "17.2.12",
58
- "@oh-my-pi/pi-catalog": "17.2.12",
59
- "@oh-my-pi/pi-mnemopi": "17.2.12",
60
- "@oh-my-pi/pi-natives": "17.2.12",
61
- "@oh-my-pi/pi-tui": "17.2.12",
62
- "@oh-my-pi/pi-utils": "17.2.12",
63
- "@oh-my-pi/pi-wire": "17.2.12",
64
- "@oh-my-pi/snapcompact": "17.2.12",
53
+ "@oh-my-pi/hashline": "17.2.13",
54
+ "@oh-my-pi/omp-stats": "17.2.13",
55
+ "@oh-my-pi/omptype": "17.2.13",
56
+ "@oh-my-pi/pi-agent-core": "17.2.13",
57
+ "@oh-my-pi/pi-ai": "17.2.13",
58
+ "@oh-my-pi/pi-catalog": "17.2.13",
59
+ "@oh-my-pi/pi-mnemopi": "17.2.13",
60
+ "@oh-my-pi/pi-natives": "17.2.13",
61
+ "@oh-my-pi/pi-tui": "17.2.13",
62
+ "@oh-my-pi/pi-utils": "17.2.13",
63
+ "@oh-my-pi/pi-wire": "17.2.13",
64
+ "@oh-my-pi/snapcompact": "17.2.13",
65
65
  "@opentelemetry/api": "^1.9.1",
66
66
  "@opentelemetry/api-logs": "^0.220.0",
67
67
  "@opentelemetry/context-async-hooks": "^2.9.0",
@@ -0,0 +1,98 @@
1
+ // Candidate 4 (multi-message split) pure renderer, extracted for direct unit
2
+ // testing. Renders an advisor delta as MULTIPLE user messages — one per source
3
+ // message — instead of one ever-growing user message, so the provider prompt
4
+ // cache can incrementally hit each appended message. Provider caches are
5
+ // prefix-based: a single user message whose text keeps growing invalidates the
6
+ // whole message on every turn, pinning cache_read at the instructions/tools
7
+ // boundary (observed 14491 in production, 11066 in tests). Splitting into
8
+ // per-source user messages grows cache_read with the session (verified
9
+ // experimentally: 11066 → 11091 → 11112).
10
+ //
11
+ // Each source message is rendered INDEPENDENTLY via formatSessionHistoryMarkdown
12
+ // in chunked mode (shared toolResultIndex + consumedToolCallIds + watchedRoleState
13
+ // over the WHOLE delta), so toolCall/toolResult pairings resolve across chunk
14
+ // boundaries and consecutive same-role collapsing is byte-identical to the old
15
+ // single-block render. Concatenating the chunk texts reproduces the old advisor
16
+ // context exactly (equivalence-tested).
17
+ //
18
+ // The heading stays on the FIRST chunk; the WIP marker stays on the LAST chunk
19
+ // (candidate 3) so a wip/final flip never changes the stable prefix.
20
+ import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
21
+ import type { TextContent, ToolResultMessage } from "@oh-my-pi/pi-ai";
22
+ import { formatSessionHistoryMarkdown } from "../session/session-history-format";
23
+
24
+ /**
25
+ * Obfuscation surface the split renderer needs: a single text redaction pass.
26
+ * Narrowed from the full SecretObfuscator class to the method actually
27
+ * consumed, so tests can satisfy the contract with a typed helper instead of
28
+ * an `as any` escape. A SecretObfuscator instance is structurally assignable.
29
+ */
30
+ export interface AdvisorObfuscator {
31
+ obfuscate(text: string, sharedRegexSecretValues?: ReadonlySet<string>): string;
32
+ }
33
+
34
+ /** Render options shared by the advisor single-block and multi-message paths. */
35
+ export const ADVISOR_RENDER_OPTIONS = {
36
+ includeToolIntent: true,
37
+ watchedRoles: true,
38
+ expandPrimaryContext: true,
39
+ expandEditDiffs: true,
40
+ } as const;
41
+
42
+ export interface RenderAdvisorDeltaChunksOptions {
43
+ wip: boolean;
44
+ includeThinking: boolean;
45
+ obfuscator?: AdvisorObfuscator;
46
+ advisorRegexSecretValues: ReadonlySet<string>;
47
+ }
48
+
49
+ export function renderAdvisorDeltaChunks(
50
+ delta: AgentMessage[],
51
+ opts: RenderAdvisorDeltaChunksOptions,
52
+ ): AgentMessage[] | null {
53
+ if (delta.length === 0) return null;
54
+
55
+ const resultsByCallId = new Map<string, ToolResultMessage>();
56
+ for (const msg of delta) {
57
+ if (msg.role === "toolResult") resultsByCallId.set(msg.toolCallId, msg);
58
+ }
59
+ const consumed = new Set<string>();
60
+ const watchedRoleState = { lastLabel: undefined as string | undefined };
61
+
62
+ const renderChunk = (chunk: AgentMessage[]): string =>
63
+ formatSessionHistoryMarkdown(chunk, {
64
+ ...ADVISOR_RENDER_OPTIONS,
65
+ includeThinking: opts.includeThinking,
66
+ toolResultIndex: resultsByCallId,
67
+ consumedToolCallIds: consumed,
68
+ watchedRoleState,
69
+ });
70
+
71
+ const heading = "### Session update";
72
+ // Concrete local chunk type: content blocks are minted here, so the WIP
73
+ // marker append below is a plain field access (no double-cast).
74
+ const chunks: { role: "user"; content: TextContent[]; timestamp: number }[] = [];
75
+ for (let i = 0; i < delta.length; i++) {
76
+ const text = renderChunk([delta[i]]);
77
+ if (!text.trim()) continue;
78
+ chunks.push({ role: "user", content: [{ type: "text", text }], timestamp: Date.now() });
79
+ }
80
+ if (chunks.length === 0) return null;
81
+ if (opts.obfuscator) {
82
+ const fullText = chunks.map(chunk => chunk.content[0].text).join("\n");
83
+ const individuallyObfuscated = chunks.map(chunk =>
84
+ opts.obfuscator!.obfuscate(chunk.content[0].text, opts.advisorRegexSecretValues),
85
+ );
86
+ if (opts.obfuscator.obfuscate(fullText, opts.advisorRegexSecretValues) !== individuallyObfuscated.join("\n")) {
87
+ return null;
88
+ }
89
+ for (let i = 0; i < chunks.length; i++) chunks[i].content[0].text = individuallyObfuscated[i];
90
+ }
91
+ chunks[0].content[0].text = `${heading}\n\n${chunks[0].content[0].text}`;
92
+ if (chunks.length === 0) return null;
93
+ if (opts.wip) {
94
+ const last = chunks[chunks.length - 1];
95
+ last.content[0].text += `\n\n---\n\n[in progress — more steps follow]`;
96
+ }
97
+ return chunks as AgentMessage[];
98
+ }