@oh-my-pi/pi-coding-agent 16.0.11 → 16.1.1

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 (71) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/cli.js +3166 -3202
  3. package/dist/types/config/settings-schema.d.ts +40 -39
  4. package/dist/types/lsp/types.d.ts +5 -3
  5. package/dist/types/modes/components/__tests__/skill-message.test.d.ts +1 -0
  6. package/dist/types/modes/components/assistant-message.d.ts +8 -0
  7. package/dist/types/modes/components/cache-invalidation-marker.d.ts +39 -0
  8. package/dist/types/modes/components/compaction-summary-message.d.ts +14 -1
  9. package/dist/types/modes/components/index.d.ts +0 -1
  10. package/dist/types/modes/components/message-frame.d.ts +6 -4
  11. package/dist/types/modes/interactive-mode.d.ts +2 -1
  12. package/dist/types/modes/theme/theme.d.ts +7 -1
  13. package/dist/types/modes/types.d.ts +7 -1
  14. package/dist/types/sdk.d.ts +1 -1
  15. package/dist/types/session/agent-session.d.ts +20 -1
  16. package/dist/types/session/session-context.d.ts +7 -0
  17. package/dist/types/session/session-dump-format.d.ts +1 -0
  18. package/dist/types/session/tool-choice-queue.d.ts +14 -0
  19. package/dist/types/system-prompt.d.ts +3 -3
  20. package/dist/types/tools/index.d.ts +4 -0
  21. package/dist/types/tools/resolve.d.ts +15 -5
  22. package/package.json +12 -12
  23. package/src/config/settings-schema.ts +48 -39
  24. package/src/config/settings.ts +40 -0
  25. package/src/debug/log-viewer.ts +4 -4
  26. package/src/debug/raw-sse.ts +4 -4
  27. package/src/edit/renderer.ts +2 -2
  28. package/src/internal-urls/docs-index.generated.txt +1 -1
  29. package/src/lsp/client.ts +9 -9
  30. package/src/lsp/render.ts +7 -7
  31. package/src/lsp/types.ts +6 -3
  32. package/src/modes/components/__tests__/skill-message.test.ts +92 -0
  33. package/src/modes/components/agent-dashboard.ts +1 -1
  34. package/src/modes/components/assistant-message.ts +21 -0
  35. package/src/modes/components/cache-invalidation-marker.ts +94 -0
  36. package/src/modes/components/chat-transcript-builder.ts +16 -2
  37. package/src/modes/components/compaction-summary-message.ts +29 -1
  38. package/src/modes/components/custom-message.ts +4 -1
  39. package/src/modes/components/dynamic-border.ts +1 -1
  40. package/src/modes/components/extensions/extension-dashboard.ts +1 -1
  41. package/src/modes/components/extensions/inspector-panel.ts +5 -5
  42. package/src/modes/components/hook-selector.ts +2 -2
  43. package/src/modes/components/index.ts +0 -1
  44. package/src/modes/components/message-frame.ts +10 -6
  45. package/src/modes/components/model-selector.ts +2 -2
  46. package/src/modes/components/overlay-box.ts +10 -9
  47. package/src/modes/components/settings-defs.ts +7 -0
  48. package/src/modes/components/skill-message.ts +39 -19
  49. package/src/modes/components/tiny-title-download-progress.ts +1 -1
  50. package/src/modes/components/welcome.ts +1 -1
  51. package/src/modes/controllers/event-controller.ts +14 -0
  52. package/src/modes/controllers/selector-controller.ts +7 -0
  53. package/src/modes/interactive-mode.ts +9 -1
  54. package/src/modes/theme/theme.ts +14 -0
  55. package/src/modes/types.ts +7 -1
  56. package/src/modes/utils/ui-helpers.ts +20 -2
  57. package/src/prompts/steering/user-interjection.md +3 -4
  58. package/src/sdk.ts +8 -6
  59. package/src/session/agent-session.ts +96 -23
  60. package/src/session/messages.ts +7 -9
  61. package/src/session/session-context.ts +54 -7
  62. package/src/session/session-dump-format.ts +3 -1
  63. package/src/session/snapcompact-inline.ts +2 -2
  64. package/src/session/tool-choice-queue.ts +59 -0
  65. package/src/system-prompt.ts +10 -9
  66. package/src/tools/bash-interactive.ts +4 -4
  67. package/src/tools/index.ts +4 -0
  68. package/src/tools/resolve.ts +66 -41
  69. package/src/tui/output-block.ts +9 -9
  70. package/dist/types/modes/components/branch-summary-message.d.ts +0 -13
  71. package/src/modes/components/branch-summary-message.ts +0 -46
@@ -138,44 +138,32 @@ export declare const SETTINGS_SCHEMA: {
138
138
  readonly description: "Automatically resume the most recent session in the current directory";
139
139
  };
140
140
  };
141
- readonly "power.preventIdleSleep": {
142
- readonly type: "boolean";
143
- readonly default: true;
144
- readonly ui: {
145
- readonly tab: "interaction";
146
- readonly group: "Power (macOS)";
147
- readonly label: "Prevent Idle Sleep";
148
- readonly description: "Keep the system awake while a session is open (caffeinate -i)";
149
- };
150
- };
151
- readonly "power.preventSystemSleep": {
152
- readonly type: "boolean";
153
- readonly default: false;
154
- readonly ui: {
155
- readonly tab: "interaction";
156
- readonly group: "Power (macOS)";
157
- readonly label: "Prevent System Sleep on AC";
158
- readonly description: "Block all system sleep while on AC power (caffeinate -s)";
159
- };
160
- };
161
- readonly "power.declareUserActive": {
162
- readonly type: "boolean";
163
- readonly default: false;
164
- readonly ui: {
165
- readonly tab: "interaction";
166
- readonly group: "Power (macOS)";
167
- readonly label: "Declare User Active";
168
- readonly description: "Keep the display lit and treat the user as active (caffeinate -u)";
169
- };
170
- };
171
- readonly "power.preventDisplaySleep": {
172
- readonly type: "boolean";
173
- readonly default: false;
141
+ readonly "power.sleepPrevention": {
142
+ readonly type: "enum";
143
+ readonly values: readonly ["off", "idle", "display", "system"];
144
+ readonly default: "idle";
174
145
  readonly ui: {
175
146
  readonly tab: "interaction";
176
147
  readonly group: "Power (macOS)";
177
- readonly label: "Prevent Display Sleep";
178
- readonly description: "Keep the display from idle-sleeping while a session is open (caffeinate -d)";
148
+ readonly label: "Sleep Prevention";
149
+ readonly description: "Prevent macOS sleep during active sessions. Each level is cumulative it adds the flags of all lower levels.";
150
+ readonly options: readonly [{
151
+ readonly value: "off";
152
+ readonly label: "Off";
153
+ readonly description: "Do not prevent any sleep";
154
+ }, {
155
+ readonly value: "idle";
156
+ readonly label: "Prevent Idle Sleep";
157
+ readonly description: "Keep the system awake while a session is open (caffeinate -i)";
158
+ }, {
159
+ readonly value: "display";
160
+ readonly label: "Prevent Display Sleep";
161
+ readonly description: "Also keep the display from idle-sleeping (caffeinate -i -d)";
162
+ }, {
163
+ readonly value: "system";
164
+ readonly label: "Prevent System Sleep";
165
+ readonly description: "Also block all system sleep on AC and declare the user active (caffeinate -i -d -s -u)";
166
+ }];
179
167
  };
180
168
  };
181
169
  readonly "advisor.enabled": {
@@ -196,6 +184,7 @@ export declare const SETTINGS_SCHEMA: {
196
184
  readonly group: "Advisor";
197
185
  readonly label: "Advisor for Subagents";
198
186
  readonly description: "Also enable the advisor on spawned task/eval subagents.";
187
+ readonly condition: "advisorEnabled";
199
188
  };
200
189
  };
201
190
  readonly "advisor.syncBacklog": {
@@ -207,6 +196,7 @@ export declare const SETTINGS_SCHEMA: {
207
196
  readonly group: "Advisor";
208
197
  readonly label: "Advisor Sync Backlog";
209
198
  readonly description: "Pause the main agent for up to 30 seconds if the advisor falls behind by this many turns. Off disables catch-up delays.";
199
+ readonly condition: "advisorEnabled";
210
200
  };
211
201
  };
212
202
  readonly "advisor.immuneTurns": {
@@ -238,6 +228,7 @@ export declare const SETTINGS_SCHEMA: {
238
228
  readonly value: "5";
239
229
  readonly label: "5 turns";
240
230
  }];
231
+ readonly condition: "advisorEnabled";
241
232
  };
242
233
  };
243
234
  readonly shellPath: {
@@ -819,6 +810,16 @@ export declare const SETTINGS_SCHEMA: {
819
810
  readonly description: "Show per-turn token usage on assistant messages";
820
811
  };
821
812
  };
813
+ readonly "display.cacheMissMarker": {
814
+ readonly type: "boolean";
815
+ readonly default: false;
816
+ readonly ui: {
817
+ readonly tab: "appearance";
818
+ readonly group: "Display";
819
+ readonly label: "Cache Miss Marker";
820
+ readonly description: "Show a divider above an assistant turn whose request lost (missed) the prompt cache";
821
+ };
822
+ };
822
823
  readonly showHardwareCursor: {
823
824
  readonly type: "boolean";
824
825
  readonly default: true;
@@ -871,14 +872,14 @@ export declare const SETTINGS_SCHEMA: {
871
872
  readonly description: "Apply loop guard to assistant prose messages in addition to thinking logs";
872
873
  };
873
874
  };
874
- readonly repeatToolDescriptions: {
875
+ readonly inlineToolDescriptors: {
875
876
  readonly type: "boolean";
876
- readonly default: false;
877
+ readonly default: true;
877
878
  readonly ui: {
878
879
  readonly tab: "model";
879
880
  readonly group: "Prompt";
880
- readonly label: "Repeat Tool Descriptions";
881
- readonly description: "Render full tool descriptions in the system prompt instead of a tool name list";
881
+ readonly label: "Inline Tool Descriptors";
882
+ readonly description: "Render full tool descriptors in the system prompt and strip top-level/nested descriptions from provider tool schemas so descriptor text is sent once";
882
883
  };
883
884
  };
884
885
  readonly includeModelInPrompt: {
@@ -259,7 +259,7 @@ export interface LspClient {
259
259
  diagnostics: Map<string, PublishedDiagnostics>;
260
260
  diagnosticsVersion: number;
261
261
  openFiles: Map<string, OpenFile>;
262
- pendingRequests: Map<number, PendingRequest>;
262
+ pendingRequests: Map<number | string, PendingRequest>;
263
263
  messageBuffer: Uint8Array;
264
264
  isReading: boolean;
265
265
  /** Lifecycle state: "connecting" until initialize completes, then "ready"; "error" on init failure or reader death. */
@@ -275,15 +275,17 @@ export interface LspClient {
275
275
  /** Call to signal that project loading has completed */
276
276
  resolveProjectLoaded: () => void;
277
277
  }
278
+ /** JSON-RPC request/response identifier accepted by LSP peers. */
279
+ export type LspJsonRpcId = number | string;
278
280
  export interface LspJsonRpcRequest {
279
281
  jsonrpc: "2.0";
280
- id: number;
282
+ id: LspJsonRpcId;
281
283
  method: string;
282
284
  params: unknown;
283
285
  }
284
286
  export interface LspJsonRpcResponse {
285
287
  jsonrpc: "2.0";
286
- id?: number;
288
+ id?: LspJsonRpcId;
287
289
  result?: unknown;
288
290
  error?: {
289
291
  code: number;
@@ -1,6 +1,7 @@
1
1
  import type { AssistantMessage, ImageContent } from "@oh-my-pi/pi-ai";
2
2
  import { Container, type ImageBudget } from "@oh-my-pi/pi-tui";
3
3
  import type { AssistantThinkingRenderer } from "../../extensibility/extensions/types";
4
+ import { type CacheInvalidation } from "./cache-invalidation-marker";
4
5
  /**
5
6
  * Component that renders a complete assistant message
6
7
  */
@@ -11,6 +12,13 @@ export declare class AssistantMessageComponent extends Container {
11
12
  private readonly thinkingRenderers;
12
13
  private readonly imageBudget?;
13
14
  constructor(message?: AssistantMessage, hideThinkingBlock?: boolean, onImageUpdate?: (() => void) | undefined, thinkingRenderers?: readonly AssistantThinkingRenderer[], imageBudget?: ImageBudget | undefined);
15
+ /**
16
+ * Show or clear the slim cache-invalidation divider above this turn. Set at
17
+ * `message_end` (live) or during rebuild, once the turn's usage is known and
18
+ * compared against the previous turn's cache footprint. Bumps the transcript
19
+ * block version so the change repaints even after content finalized.
20
+ */
21
+ setCacheInvalidation(info: CacheInvalidation | undefined): void;
14
22
  invalidate(): void;
15
23
  setHideThinkingBlock(hide: boolean): void;
16
24
  dispose(): void;
@@ -0,0 +1,39 @@
1
+ import type { Usage } from "@oh-my-pi/pi-ai";
2
+ import type { Component } from "@oh-my-pi/pi-tui";
3
+ /** A prompt-cache invalidation detected from a turn's usage. */
4
+ export interface CacheInvalidation {
5
+ /** Prompt tokens the cold turn had to (re)process instead of reading from cache. */
6
+ reprocessedTokens: number;
7
+ }
8
+ /**
9
+ * Decide whether `current` turn lost the prompt cache that `prev` established.
10
+ *
11
+ * The provider reports a warm prefix as `cacheRead`; a model/thinking/tool/
12
+ * system-prompt change (or a history rewrite) breaks the prefix, so the next
13
+ * request reads nothing from cache and re-pays for the whole prompt. We detect
14
+ * that as: the previous turn cached a meaningful prefix, yet this turn's
15
+ * `cacheRead` collapsed to zero while it still reprocessed a non-trivial prompt.
16
+ * Returns `undefined` (no marker) for the first turn, tiny contexts, turns
17
+ * that reused any cache, and — crucially — turns on providers with *implicit*
18
+ * best-effort caching. Only an explicit, prefix-controlled cache (Anthropic /
19
+ * Bedrock `cache_control`) re-creates the prefix on a cold turn (`cacheWrite >
20
+ * 0`); implicit caches (Google / OpenAI / Fireworks) report `cacheWrite: 0` and
21
+ * drop `cacheRead` to zero intermittently as routine propagation noise that
22
+ * self-heals the next turn, so flagging it would be a false positive.
23
+ */
24
+ export declare function detectCacheInvalidation(prev: Usage | undefined, current: Usage): CacheInvalidation | undefined;
25
+ /**
26
+ * Slim left-aligned divider rendered above an assistant turn whose request lost
27
+ * the prompt cache. Mirrors the compaction divider's banner styling but spans
28
+ * only a short rule plus label (not the full width) and carries no expandable
29
+ * detail:
30
+ *
31
+ * ────────── ⊘ cache miss · 50.9k tokens
32
+ */
33
+ export declare class CacheInvalidationMarkerComponent implements Component {
34
+ #private;
35
+ private readonly info;
36
+ constructor(info: CacheInvalidation);
37
+ invalidate(): void;
38
+ render(width: number): readonly string[];
39
+ }
@@ -1,5 +1,5 @@
1
1
  import { type Component } from "@oh-my-pi/pi-tui";
2
- import type { CompactionSummaryMessage, CustomMessage } from "../../session/messages";
2
+ import type { BranchSummaryMessage, CompactionSummaryMessage, CustomMessage } from "../../session/messages";
3
3
  /**
4
4
  * Compaction point in the transcript, rendered as a slim horizontal divider:
5
5
  *
@@ -31,3 +31,16 @@ export declare class HandoffSummaryMessageComponent implements Component {
31
31
  render(width: number): readonly string[];
32
32
  }
33
33
  export declare function createHandoffSummaryMessageComponent(message: CustomMessage<unknown>, expanded: boolean): HandoffSummaryMessageComponent | undefined;
34
+ /**
35
+ * A branch summary collapses a side branch back into the main line. Render it
36
+ * with the same slim divider as `/compact` and handoff rather than a `[branch]`
37
+ * box, so every history-collapse point reads as one consistent banner.
38
+ */
39
+ export declare class BranchSummaryMessageComponent implements Component {
40
+ #private;
41
+ private readonly message;
42
+ constructor(message: BranchSummaryMessage);
43
+ setExpanded(expanded: boolean): void;
44
+ invalidate(): void;
45
+ render(width: number): readonly string[];
46
+ }
@@ -1,7 +1,6 @@
1
1
  export * from "./assistant-message";
2
2
  export * from "./bash-execution";
3
3
  export * from "./bordered-loader";
4
- export * from "./branch-summary-message";
5
4
  export * from "./compaction-summary-message";
6
5
  export * from "./countdown-timer";
7
6
  export * from "./custom-editor";
@@ -29,14 +29,16 @@ export interface RebuildFrameOptions<M extends FramedMessage> {
29
29
  message: M;
30
30
  box: Box;
31
31
  expanded: boolean;
32
+ /** Icon glyph shown before the customType in the default header (e.g. a hook/extension icon). */
33
+ icon?: string;
32
34
  /** Collapse the markdown body to this many lines when `expanded` is false. Omit to never collapse. */
33
35
  collapseAfterLines?: number;
34
36
  customRenderer?: FramedRenderer<M>;
35
37
  }
36
38
  /**
37
- * Attempt the custom renderer; on failure or undefined return, populate
38
- * `box` with the default `[customType]` label + markdown body and return
39
- * undefined. When the custom renderer succeeds, return its Component so the
40
- * caller can mount it and skip the default box.
39
+ * Attempt the custom renderer; on failure or undefined return, populate `box`
40
+ * with the default outlined card — an `icon customType` header + markdown body
41
+ * and return undefined. When the custom renderer succeeds, return its Component
42
+ * so the caller can mount it and skip the default box.
41
43
  */
42
44
  export declare function renderFramedMessage<M extends FramedMessage>(opts: RebuildFrameOptions<M>): Component | undefined;
@@ -1,6 +1,6 @@
1
1
  import { type Agent, type AgentMessage } from "@oh-my-pi/pi-agent-core";
2
2
  import type { CompactionOutcome } from "@oh-my-pi/pi-agent-core/compaction";
3
- import type { AssistantMessage, ImageContent, Message, UsageReport } from "@oh-my-pi/pi-ai";
3
+ import type { AssistantMessage, ImageContent, Message, Usage, UsageReport } from "@oh-my-pi/pi-ai";
4
4
  import type { Component, EditorTheme } from "@oh-my-pi/pi-tui";
5
5
  import { Container, Loader, Spacer, Text, TUI } from "@oh-my-pi/pi-tui";
6
6
  import type { CollabGuestLink } from "../collab/guest";
@@ -119,6 +119,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
119
119
  isPythonMode: boolean;
120
120
  streamingComponent: AssistantMessageComponent | undefined;
121
121
  streamingMessage: AssistantMessage | undefined;
122
+ lastAssistantUsage: Usage | undefined;
122
123
  loadingAnimation: Loader | undefined;
123
124
  autoCompactionLoader: Loader | undefined;
124
125
  retryLoader: Loader | undefined;
@@ -6,7 +6,7 @@ export type SymbolPreset = "unicode" | "nerd" | "ascii";
6
6
  /**
7
7
  * All available symbol keys organized by category.
8
8
  */
9
- export type SymbolKey = "status.success" | "status.error" | "status.warning" | "status.info" | "status.pending" | "status.disabled" | "status.enabled" | "status.running" | "status.shadowed" | "status.aborted" | "status.done" | "nav.cursor" | "nav.selected" | "nav.expand" | "nav.collapse" | "nav.back" | "tree.branch" | "tree.last" | "tree.vertical" | "tree.horizontal" | "tree.hook" | "boxRound.topLeft" | "boxRound.topRight" | "boxRound.bottomLeft" | "boxRound.bottomRight" | "boxRound.horizontal" | "boxRound.vertical" | "boxSharp.topLeft" | "boxSharp.topRight" | "boxSharp.bottomLeft" | "boxSharp.bottomRight" | "boxSharp.horizontal" | "boxSharp.vertical" | "boxSharp.cross" | "boxSharp.teeDown" | "boxSharp.teeUp" | "boxSharp.teeRight" | "boxSharp.teeLeft" | "sep.powerline" | "sep.powerlineThin" | "sep.powerlineLeft" | "sep.powerlineRight" | "sep.powerlineThinLeft" | "sep.powerlineThinRight" | "sep.block" | "sep.space" | "sep.asciiLeft" | "sep.asciiRight" | "sep.dot" | "sep.slash" | "sep.pipe" | "icon.model" | "icon.plan" | "icon.goal" | "icon.pause" | "icon.loop" | "icon.folder" | "icon.search" | "icon.scratchFolder" | "icon.file" | "icon.git" | "icon.branch" | "icon.pr" | "icon.tokens" | "icon.context" | "icon.cost" | "icon.time" | "icon.pi" | "icon.ghost" | "icon.agents" | "icon.job" | "icon.cache" | "icon.input" | "icon.output" | "icon.host" | "icon.session" | "icon.package" | "icon.warning" | "icon.rewind" | "icon.auto" | "icon.fast" | "icon.extensionSkill" | "icon.extensionTool" | "icon.extensionSlashCommand" | "icon.extensionMcp" | "icon.extensionRule" | "icon.extensionHook" | "icon.extensionPrompt" | "icon.extensionContextFile" | "icon.extensionInstruction" | "icon.mic" | "icon.camera" | "thinking.minimal" | "thinking.low" | "thinking.medium" | "thinking.high" | "thinking.xhigh" | "thinking.autoPending" | "checkbox.checked" | "checkbox.unchecked" | "radio.selected" | "radio.unselected" | "format.bullet" | "format.dash" | "format.bracketLeft" | "format.bracketRight" | "md.quoteBorder" | "md.hrChar" | "md.bullet" | "md.colorSwatch" | "lang.default" | "lang.typescript" | "lang.javascript" | "lang.python" | "lang.rust" | "lang.go" | "lang.java" | "lang.c" | "lang.cpp" | "lang.csharp" | "lang.ruby" | "lang.php" | "lang.swift" | "lang.kotlin" | "lang.shell" | "lang.html" | "lang.css" | "lang.json" | "lang.yaml" | "lang.markdown" | "lang.sql" | "lang.docker" | "lang.lua" | "lang.text" | "lang.env" | "lang.toml" | "lang.xml" | "lang.ini" | "lang.conf" | "lang.log" | "lang.csv" | "lang.tsv" | "lang.image" | "lang.pdf" | "lang.archive" | "lang.binary" | "tab.appearance" | "tab.model" | "tab.interaction" | "tab.context" | "tab.files" | "tab.shell" | "tab.tools" | "tab.memory" | "tab.tasks" | "tab.providers" | "tool.write" | "tool.edit" | "tool.bash" | "tool.ssh" | "tool.lsp" | "tool.gh" | "tool.webSearch" | "tool.exa" | "tool.browser" | "tool.eval" | "tool.debug" | "tool.mcp" | "tool.job" | "tool.task" | "tool.todo" | "tool.memory" | "tool.ask" | "tool.resolve" | "tool.review" | "tool.inspectImage" | "tool.goal" | "tool.irc";
9
+ export type SymbolKey = "status.success" | "status.error" | "status.warning" | "status.info" | "status.pending" | "status.disabled" | "status.enabled" | "status.running" | "status.shadowed" | "status.aborted" | "status.done" | "nav.cursor" | "nav.selected" | "nav.expand" | "nav.collapse" | "nav.back" | "tree.branch" | "tree.last" | "tree.vertical" | "tree.horizontal" | "tree.hook" | "boxRound.topLeft" | "boxRound.topRight" | "boxRound.bottomLeft" | "boxRound.bottomRight" | "boxRound.horizontal" | "boxRound.vertical" | "boxSharp.topLeft" | "boxSharp.topRight" | "boxSharp.bottomLeft" | "boxSharp.bottomRight" | "boxSharp.horizontal" | "boxSharp.vertical" | "boxSharp.cross" | "boxSharp.teeDown" | "boxSharp.teeUp" | "boxSharp.teeRight" | "boxSharp.teeLeft" | "sep.powerline" | "sep.powerlineThin" | "sep.powerlineLeft" | "sep.powerlineRight" | "sep.powerlineThinLeft" | "sep.powerlineThinRight" | "sep.block" | "sep.space" | "sep.asciiLeft" | "sep.asciiRight" | "sep.dot" | "sep.slash" | "sep.pipe" | "icon.model" | "icon.plan" | "icon.goal" | "icon.pause" | "icon.loop" | "icon.folder" | "icon.search" | "icon.scratchFolder" | "icon.file" | "icon.git" | "icon.branch" | "icon.pr" | "icon.tokens" | "icon.context" | "icon.cost" | "icon.time" | "icon.pi" | "icon.ghost" | "icon.agents" | "icon.job" | "icon.cache" | "icon.cacheMiss" | "icon.input" | "icon.output" | "icon.host" | "icon.session" | "icon.package" | "icon.warning" | "icon.rewind" | "icon.auto" | "icon.fast" | "icon.extensionSkill" | "icon.extensionTool" | "icon.extensionSlashCommand" | "icon.extensionMcp" | "icon.extensionRule" | "icon.extensionHook" | "icon.extensionPrompt" | "icon.extensionContextFile" | "icon.extensionInstruction" | "icon.mic" | "icon.camera" | "thinking.minimal" | "thinking.low" | "thinking.medium" | "thinking.high" | "thinking.xhigh" | "thinking.autoPending" | "checkbox.checked" | "checkbox.unchecked" | "radio.selected" | "radio.unselected" | "format.bullet" | "format.dash" | "format.bracketLeft" | "format.bracketRight" | "md.quoteBorder" | "md.hrChar" | "md.bullet" | "md.colorSwatch" | "lang.default" | "lang.typescript" | "lang.javascript" | "lang.python" | "lang.rust" | "lang.go" | "lang.java" | "lang.c" | "lang.cpp" | "lang.csharp" | "lang.ruby" | "lang.php" | "lang.swift" | "lang.kotlin" | "lang.shell" | "lang.html" | "lang.css" | "lang.json" | "lang.yaml" | "lang.markdown" | "lang.sql" | "lang.docker" | "lang.lua" | "lang.text" | "lang.env" | "lang.toml" | "lang.xml" | "lang.ini" | "lang.conf" | "lang.log" | "lang.csv" | "lang.tsv" | "lang.image" | "lang.pdf" | "lang.archive" | "lang.binary" | "tab.appearance" | "tab.model" | "tab.interaction" | "tab.context" | "tab.files" | "tab.shell" | "tab.tools" | "tab.memory" | "tab.tasks" | "tab.providers" | "tool.write" | "tool.edit" | "tool.bash" | "tool.ssh" | "tool.lsp" | "tool.gh" | "tool.webSearch" | "tool.exa" | "tool.browser" | "tool.eval" | "tool.debug" | "tool.mcp" | "tool.job" | "tool.task" | "tool.todo" | "tool.memory" | "tool.ask" | "tool.resolve" | "tool.review" | "tool.inspectImage" | "tool.goal" | "tool.irc";
10
10
  export type SpinnerType = "status" | "activity";
11
11
  export type ThemeColor = "accent" | "border" | "borderAccent" | "borderMuted" | "success" | "error" | "warning" | "muted" | "dim" | "text" | "thinkingText" | "userMessageText" | "customMessageText" | "customMessageLabel" | "toolTitle" | "toolOutput" | "mdHeading" | "mdLink" | "mdLinkUrl" | "mdCode" | "mdCodeBlock" | "mdCodeBlockBorder" | "mdQuote" | "mdQuoteBorder" | "mdHr" | "mdListBullet" | "toolDiffAdded" | "toolDiffRemoved" | "toolDiffContext" | "syntaxComment" | "syntaxKeyword" | "syntaxFunction" | "syntaxVariable" | "syntaxString" | "syntaxNumber" | "syntaxType" | "syntaxOperator" | "syntaxPunctuation" | "thinkingOff" | "thinkingMinimal" | "thinkingLow" | "thinkingMedium" | "thinkingHigh" | "thinkingXhigh" | "bashMode" | "pythonMode" | "statusLineSep" | "statusLineModel" | "statusLinePath" | "statusLineGitClean" | "statusLineGitDirty" | "statusLineContext" | "statusLineSpend" | "statusLineStaged" | "statusLineDirty" | "statusLineUntracked" | "statusLineOutput" | "statusLineCost" | "statusLineSubagents";
12
12
  /** Check if a string is a valid ThemeColor value */
@@ -123,6 +123,11 @@ export declare class Theme {
123
123
  bottomRight: string;
124
124
  horizontal: string;
125
125
  vertical: string;
126
+ cross: string;
127
+ teeDown: string;
128
+ teeUp: string;
129
+ teeRight: string;
130
+ teeLeft: string;
126
131
  };
127
132
  get boxSharp(): {
128
133
  topLeft: string;
@@ -173,6 +178,7 @@ export declare class Theme {
173
178
  agents: string;
174
179
  job: string;
175
180
  cache: string;
181
+ cacheMiss: string;
176
182
  input: string;
177
183
  output: string;
178
184
  host: string;
@@ -1,6 +1,6 @@
1
1
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
2
2
  import type { CompactionOutcome } from "@oh-my-pi/pi-agent-core/compaction";
3
- import type { AssistantMessage, ImageContent, Message, UsageReport } from "@oh-my-pi/pi-ai";
3
+ import type { AssistantMessage, ImageContent, Message, Usage, UsageReport } from "@oh-my-pi/pi-ai";
4
4
  import type { Component, Container, EditorTheme, Loader, Spacer, Text, TUI } from "@oh-my-pi/pi-tui";
5
5
  import type { CollabGuestLink } from "../collab/guest";
6
6
  import type { CollabHost } from "../collab/host";
@@ -140,6 +140,12 @@ export interface InteractiveModeContext {
140
140
  isPythonMode: boolean;
141
141
  streamingComponent: AssistantMessageComponent | undefined;
142
142
  streamingMessage: AssistantMessage | undefined;
143
+ /**
144
+ * Usage of the most recently rendered assistant turn, used to detect a
145
+ * prompt-cache invalidation on the next turn (cache footprint collapse).
146
+ * Reseeded by `renderSessionContext` on every rebuild/session switch.
147
+ */
148
+ lastAssistantUsage: Usage | undefined;
143
149
  loadingAnimation: Loader | undefined;
144
150
  autoCompactionLoader: Loader | undefined;
145
151
  retryLoader: Loader | undefined;
@@ -288,7 +288,7 @@ export interface BuildSystemPromptOptions {
288
288
  }>;
289
289
  cwd?: string;
290
290
  appendPrompt?: string;
291
- repeatToolDescriptions?: boolean;
291
+ inlineToolDescriptors?: boolean;
292
292
  }
293
293
  /**
294
294
  * Build the default provider-facing system prompt blocks.
@@ -13,7 +13,7 @@
13
13
  * Modes use this class and add their own I/O layer on top.
14
14
  */
15
15
  import type { InMemorySnapshotStore } from "@oh-my-pi/hashline";
16
- import { Agent, type AgentEvent, type AgentMessage, type AgentState, type AgentTool, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
16
+ import { Agent, type AgentEvent, type AgentMessage, type AgentState, type AgentTool, ThinkingLevel, type ToolChoiceDirective } from "@oh-my-pi/pi-agent-core";
17
17
  import { type CompactionResult, type ShakeConfig } from "@oh-my-pi/pi-agent-core/compaction";
18
18
  import type { AssistantMessage, ImageContent, Message, MessageAttribution, Model, ProviderSessionState, ResetCreditAccountStatus, ResetCreditRedeemOutcome, ResetCreditTarget, ServiceTier, SimpleStreamOptions, TextContent, ToolChoice, UsageReport } from "@oh-my-pi/pi-ai";
19
19
  import { Effort } from "@oh-my-pi/pi-ai";
@@ -237,6 +237,12 @@ export interface AgentSessionConfig {
237
237
  advisorReadOnlyTools?: AgentTool[];
238
238
  /** Preloaded watchdog prompt content for the advisor. */
239
239
  advisorWatchdogPrompt?: string;
240
+ /**
241
+ * Strip tool descriptions from provider-bound tool specs on side requests
242
+ * (handoff). Must match the session-start value used to build the system
243
+ * prompt so inline descriptors are not also sent through provider schemas.
244
+ */
245
+ pruneToolDescriptions?: boolean;
240
246
  /**
241
247
  * Disconnect this session's OWNED MCP manager on dispose. Provided only when
242
248
  * the session created the manager (top-level sessions); subagents reuse a
@@ -380,6 +386,19 @@ export declare class AgentSession {
380
386
  getAgentId(): string | undefined;
381
387
  /** Advance the tool-choice queue and return the next directive for the upcoming LLM call. */
382
388
  nextToolChoice(): ToolChoice | undefined;
389
+ /**
390
+ * The per-turn tool-choice directive for the agent loop's `getToolChoice`. Priority:
391
+ * 1. a HARD forced choice from the queue (genuine forces: user-force, eager-todo, …) —
392
+ * consuming, unchanged from `nextToolChoice`;
393
+ * 2. else, when a non-forcing preview is pending, a {@link SoftToolRequirement} — a
394
+ * PEEK (advances/pops nothing), so the agent-loop injects the reminder once per head
395
+ * and escalates to a forced `resolve` only if the model declines. A compliant turn
396
+ * pays ZERO tool_choice change (no prompt-cache messages-cache invalidation);
397
+ * 3. else undefined.
398
+ */
399
+ nextToolChoiceDirective(): ToolChoiceDirective | undefined;
400
+ /** Peek the head non-forcing pending preview invoker, for the `resolve` tool's dispatch. */
401
+ peekPendingInvoker(): ((input: unknown) => Promise<unknown> | unknown) | undefined;
383
402
  /**
384
403
  * Force the next model call to target a specific active tool, then terminate
385
404
  * the agent loop. Pushes a two-step sequence [forced, "none"] so the model
@@ -17,6 +17,13 @@ export interface SessionContext {
17
17
  mode: string;
18
18
  /** Mode-specific data from the last mode_change entry */
19
19
  modeData?: Record<string, unknown>;
20
+ /**
21
+ * Array parallel to messages, indicating which assistant turns should
22
+ * have their prompt-cache misses suppressed/explained (because a model,
23
+ * compaction, or plan-mode transition directly preceded them).
24
+ * Only populated in transcript mode.
25
+ */
26
+ cacheMissExplainedAt?: boolean[];
20
27
  }
21
28
  /** Lists session model strings to try when restoring, in fallback order. */
22
29
  export declare function getRestorableSessionModels(models: Readonly<Record<string, string>>, lastModelChangeRole: string | undefined): string[];
@@ -21,6 +21,7 @@ export interface FormatSessionDumpTextOptions {
21
21
  model?: Model | null;
22
22
  thinkingLevel?: ThinkingLevel | string | null;
23
23
  tools?: readonly SessionDumpToolInfo[];
24
+ inlineToolDescriptors?: boolean;
24
25
  }
25
26
  /**
26
27
  * Format messages and session metadata as markdown/plain text (same as
@@ -67,6 +67,20 @@ export declare class ToolChoiceQueue {
67
67
  get hasInFlight(): boolean;
68
68
  /** Return the in-flight directive's onInvoked handler and mark it when called. */
69
69
  peekInFlightInvoker(): ((input: unknown) => Promise<unknown> | unknown) | undefined;
70
+ /** Register (or replace by exact id) a non-forcing pending preview invoker. */
71
+ registerPendingInvoker(id: string, sourceToolName: string, onInvoked: (input: unknown) => Promise<unknown> | unknown): void;
72
+ /** Drop the pending invoker with this id (e.g. after it resolves). */
73
+ removePendingInvoker(id: string): void;
74
+ /** True when at least one non-forcing pending preview is registered. */
75
+ get hasPendingInvoker(): boolean;
76
+ /** The head (most-recently registered) pending invoker's handler, for resolve dispatch. */
77
+ peekPendingInvoker(): ((input: unknown) => Promise<unknown> | unknown) | undefined;
78
+ /** The head pending preview's stable id + source tool, for building the agent-level
79
+ * SoftToolRequirement (the id drives reminder re-injection when the head changes). */
80
+ peekPendingHead(): {
81
+ id: string;
82
+ sourceToolName: string;
83
+ } | undefined;
70
84
  /** Remove all directives with the given label. Rejects in-flight if it matches. */
71
85
  removeByLabel(label: string): void;
72
86
  /** Empty the queue and reject any in-flight yield. */
@@ -54,11 +54,11 @@ export interface BuildSystemPromptOptions {
54
54
  toolNames?: string[];
55
55
  /** Text to append to system prompt. */
56
56
  appendSystemPrompt?: string;
57
- /** Repeat full tool descriptions in system prompt. Default: false */
58
- repeatToolDescriptions?: boolean;
57
+ /** Inline full tool descriptors in the system prompt. Default: true */
58
+ inlineToolDescriptors?: boolean;
59
59
  /**
60
60
  * Whether provider-native tool calling is active (no owned/in-band syntax).
61
- * When true and `repeatToolDescriptions` is false, the inventory renders as a
61
+ * When true and `inlineToolDescriptors` is false, the inventory renders as a
62
62
  * compact tool-name list; otherwise it renders full `# Tool:` sections. Default: true
63
63
  */
64
64
  nativeTools?: boolean;
@@ -272,6 +272,10 @@ export interface ToolSession {
272
272
  }): void;
273
273
  /** Peek the currently in-flight tool-choice queue directive's invocation handler. Used by the `resolve` tool to dispatch to the pending action. */
274
274
  peekQueueInvoker?(): ((input: unknown) => Promise<unknown> | unknown) | undefined;
275
+ /** Peek the most-recently registered non-forcing pending preview invoker. The `resolve`
276
+ * tool dispatches to it so a staged preview resolves WITHOUT forcing tool_choice — the
277
+ * agent-loop's SoftToolRequirement lifecycle owns reminder injection and escalation. */
278
+ peekPendingInvoker?(): ((input: unknown) => Promise<unknown> | unknown) | undefined;
275
279
  /** Peek the long-lived "standing" resolve handler registered by a mode (e.g. plan mode).
276
280
  * Consulted by the `resolve` tool as a fallback when no queue invoker is in flight,
277
281
  * letting modes accept `resolve` invocations without forcing the tool choice every turn. */
@@ -1,4 +1,4 @@
1
- import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
1
+ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback, CustomMessage } 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";
4
4
  import type { Theme } from "../modes/theme/theme";
@@ -18,10 +18,13 @@ export interface ResolveToolDetails {
18
18
  sourceResultDetails?: unknown;
19
19
  }
20
20
  /**
21
- * Queue a resolve-protocol handler on the tool-choice queue. Forces the next
22
- * LLM call to invoke the hidden `resolve` tool, wraps the caller's apply/reject
23
- * callbacks into an onInvoked closure that matches the resolve schema, and
24
- * steers a preview reminder so the model understands why.
21
+ * Register a non-forcing resolve-protocol handler for a staged preview. Wraps the
22
+ * caller's apply/reject into an onInvoked closure (matching the resolve schema) and
23
+ * stores it on the tool-choice queue's pending-invoker registry under a UNIQUE id.
24
+ * The `resolve` tool dispatches to it; the agent-loop's SoftToolRequirement
25
+ * lifecycle injects the preview reminder and escalates to a forced `resolve` only
26
+ * if the model declines — so a compliant turn pays ZERO tool_choice change (no
27
+ * prompt-cache messages-cache invalidation).
25
28
  *
26
29
  * This is the canonical entry point for any tool that wants preview/apply
27
30
  * semantics. No session-level abstraction is needed: callers pass their
@@ -33,6 +36,13 @@ export declare function queueResolveHandler(session: ToolSession, options: {
33
36
  apply(reason: string, extra?: Record<string, unknown>): Promise<AgentToolResult<unknown>>;
34
37
  reject?(reason: string, extra?: Record<string, unknown>): Promise<AgentToolResult<unknown> | undefined>;
35
38
  }): void;
39
+ /**
40
+ * The canonical preview reminder. The resolve mechanism owns the wording; the
41
+ * agent-loop delivers it via the session's `SoftToolRequirement.reminder` (injected
42
+ * once per pending-preview head) instead of a host-side steer, so it lands as a
43
+ * stable mid-history append and never churns the cached prefix.
44
+ */
45
+ export declare function buildResolveReminderMessage(sourceToolName: string): CustomMessage;
36
46
  /**
37
47
  * Shared invocation runner used by both queued (in-flight) handlers and
38
48
  * standing handlers (e.g. plan-mode approval). Discriminates on action,
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": "16.0.11",
4
+ "version": "16.1.1",
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",
@@ -48,17 +48,17 @@
48
48
  "@agentclientprotocol/sdk": "0.25.0",
49
49
  "@babel/parser": "^7.29.7",
50
50
  "@mozilla/readability": "^0.6.0",
51
- "@oh-my-pi/hashline": "16.0.11",
52
- "@oh-my-pi/omp-stats": "16.0.11",
53
- "@oh-my-pi/pi-agent-core": "16.0.11",
54
- "@oh-my-pi/pi-ai": "16.0.11",
55
- "@oh-my-pi/pi-catalog": "16.0.11",
56
- "@oh-my-pi/pi-mnemopi": "16.0.11",
57
- "@oh-my-pi/pi-natives": "16.0.11",
58
- "@oh-my-pi/pi-tui": "16.0.11",
59
- "@oh-my-pi/pi-utils": "16.0.11",
60
- "@oh-my-pi/pi-wire": "16.0.11",
61
- "@oh-my-pi/snapcompact": "16.0.11",
51
+ "@oh-my-pi/hashline": "16.1.1",
52
+ "@oh-my-pi/omp-stats": "16.1.1",
53
+ "@oh-my-pi/pi-agent-core": "16.1.1",
54
+ "@oh-my-pi/pi-ai": "16.1.1",
55
+ "@oh-my-pi/pi-catalog": "16.1.1",
56
+ "@oh-my-pi/pi-mnemopi": "16.1.1",
57
+ "@oh-my-pi/pi-natives": "16.1.1",
58
+ "@oh-my-pi/pi-tui": "16.1.1",
59
+ "@oh-my-pi/pi-utils": "16.1.1",
60
+ "@oh-my-pi/pi-wire": "16.1.1",
61
+ "@oh-my-pi/snapcompact": "16.1.1",
62
62
  "@opentelemetry/api": "^1.9.1",
63
63
  "@opentelemetry/context-async-hooks": "^2.7.1",
64
64
  "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",