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

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 (66) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/cli.js +2872 -2908
  3. package/dist/types/config/settings-schema.d.ts +14 -4
  4. package/dist/types/modes/components/__tests__/skill-message.test.d.ts +1 -0
  5. package/dist/types/modes/components/assistant-message.d.ts +8 -0
  6. package/dist/types/modes/components/cache-invalidation-marker.d.ts +34 -0
  7. package/dist/types/modes/components/compaction-summary-message.d.ts +14 -1
  8. package/dist/types/modes/components/index.d.ts +0 -1
  9. package/dist/types/modes/components/message-frame.d.ts +6 -4
  10. package/dist/types/modes/interactive-mode.d.ts +2 -1
  11. package/dist/types/modes/theme/theme.d.ts +7 -1
  12. package/dist/types/modes/types.d.ts +7 -1
  13. package/dist/types/sdk.d.ts +1 -1
  14. package/dist/types/session/agent-session.d.ts +20 -1
  15. package/dist/types/session/session-context.d.ts +7 -0
  16. package/dist/types/session/session-dump-format.d.ts +1 -0
  17. package/dist/types/session/tool-choice-queue.d.ts +14 -0
  18. package/dist/types/system-prompt.d.ts +3 -3
  19. package/dist/types/tools/index.d.ts +4 -0
  20. package/dist/types/tools/resolve.d.ts +15 -5
  21. package/package.json +12 -12
  22. package/src/config/settings-schema.ts +16 -4
  23. package/src/debug/log-viewer.ts +4 -4
  24. package/src/debug/raw-sse.ts +4 -4
  25. package/src/edit/renderer.ts +2 -2
  26. package/src/internal-urls/docs-index.generated.txt +1 -1
  27. package/src/lsp/render.ts +7 -7
  28. package/src/modes/components/__tests__/skill-message.test.ts +92 -0
  29. package/src/modes/components/agent-dashboard.ts +1 -1
  30. package/src/modes/components/assistant-message.ts +21 -0
  31. package/src/modes/components/cache-invalidation-marker.ts +84 -0
  32. package/src/modes/components/chat-transcript-builder.ts +16 -2
  33. package/src/modes/components/compaction-summary-message.ts +29 -1
  34. package/src/modes/components/custom-message.ts +4 -1
  35. package/src/modes/components/dynamic-border.ts +1 -1
  36. package/src/modes/components/extensions/extension-dashboard.ts +1 -1
  37. package/src/modes/components/extensions/inspector-panel.ts +5 -5
  38. package/src/modes/components/hook-selector.ts +2 -2
  39. package/src/modes/components/index.ts +0 -1
  40. package/src/modes/components/message-frame.ts +10 -6
  41. package/src/modes/components/model-selector.ts +2 -2
  42. package/src/modes/components/overlay-box.ts +10 -9
  43. package/src/modes/components/skill-message.ts +39 -19
  44. package/src/modes/components/tiny-title-download-progress.ts +1 -1
  45. package/src/modes/components/welcome.ts +1 -1
  46. package/src/modes/controllers/event-controller.ts +14 -0
  47. package/src/modes/controllers/selector-controller.ts +7 -0
  48. package/src/modes/interactive-mode.ts +9 -1
  49. package/src/modes/theme/theme.ts +14 -0
  50. package/src/modes/types.ts +7 -1
  51. package/src/modes/utils/ui-helpers.ts +20 -2
  52. package/src/prompts/steering/user-interjection.md +3 -4
  53. package/src/sdk.ts +8 -6
  54. package/src/session/agent-session.ts +90 -13
  55. package/src/session/messages.ts +7 -9
  56. package/src/session/session-context.ts +54 -7
  57. package/src/session/session-dump-format.ts +3 -1
  58. package/src/session/snapcompact-inline.ts +2 -2
  59. package/src/session/tool-choice-queue.ts +59 -0
  60. package/src/system-prompt.ts +10 -9
  61. package/src/tools/bash-interactive.ts +4 -4
  62. package/src/tools/index.ts +4 -0
  63. package/src/tools/resolve.ts +66 -41
  64. package/src/tui/output-block.ts +9 -9
  65. package/dist/types/modes/components/branch-summary-message.d.ts +0 -13
  66. package/src/modes/components/branch-summary-message.ts +0 -46
@@ -819,6 +819,16 @@ export declare const SETTINGS_SCHEMA: {
819
819
  readonly description: "Show per-turn token usage on assistant messages";
820
820
  };
821
821
  };
822
+ readonly "display.cacheMissMarker": {
823
+ readonly type: "boolean";
824
+ readonly default: true;
825
+ readonly ui: {
826
+ readonly tab: "appearance";
827
+ readonly group: "Display";
828
+ readonly label: "Cache Miss Marker";
829
+ readonly description: "Show a divider above an assistant turn whose request lost (missed) the prompt cache";
830
+ };
831
+ };
822
832
  readonly showHardwareCursor: {
823
833
  readonly type: "boolean";
824
834
  readonly default: true;
@@ -871,14 +881,14 @@ export declare const SETTINGS_SCHEMA: {
871
881
  readonly description: "Apply loop guard to assistant prose messages in addition to thinking logs";
872
882
  };
873
883
  };
874
- readonly repeatToolDescriptions: {
884
+ readonly inlineToolDescriptors: {
875
885
  readonly type: "boolean";
876
- readonly default: false;
886
+ readonly default: true;
877
887
  readonly ui: {
878
888
  readonly tab: "model";
879
889
  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";
890
+ readonly label: "Inline Tool Descriptors";
891
+ 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
892
  };
883
893
  };
884
894
  readonly includeModelInPrompt: {
@@ -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,34 @@
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, and turns
17
+ * that reused any cache.
18
+ */
19
+ export declare function detectCacheInvalidation(prev: Usage | undefined, current: Usage): CacheInvalidation | undefined;
20
+ /**
21
+ * Slim left-aligned divider rendered above an assistant turn whose request lost
22
+ * the prompt cache. Mirrors the compaction divider's banner styling but spans
23
+ * only a short rule plus label (not the full width) and carries no expandable
24
+ * detail:
25
+ *
26
+ * ────────── ⊘ cache miss · 50.9k tokens
27
+ */
28
+ export declare class CacheInvalidationMarkerComponent implements Component {
29
+ #private;
30
+ private readonly info;
31
+ constructor(info: CacheInvalidation);
32
+ invalidate(): void;
33
+ render(width: number): readonly string[];
34
+ }
@@ -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.0",
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.0",
52
+ "@oh-my-pi/omp-stats": "16.1.0",
53
+ "@oh-my-pi/pi-agent-core": "16.1.0",
54
+ "@oh-my-pi/pi-ai": "16.1.0",
55
+ "@oh-my-pi/pi-catalog": "16.1.0",
56
+ "@oh-my-pi/pi-mnemopi": "16.1.0",
57
+ "@oh-my-pi/pi-natives": "16.1.0",
58
+ "@oh-my-pi/pi-tui": "16.1.0",
59
+ "@oh-my-pi/pi-utils": "16.1.0",
60
+ "@oh-my-pi/pi-wire": "16.1.0",
61
+ "@oh-my-pi/snapcompact": "16.1.0",
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",
@@ -849,6 +849,17 @@ export const SETTINGS_SCHEMA = {
849
849
  },
850
850
  },
851
851
 
852
+ "display.cacheMissMarker": {
853
+ type: "boolean",
854
+ default: true,
855
+ ui: {
856
+ tab: "appearance",
857
+ group: "Display",
858
+ label: "Cache Miss Marker",
859
+ description: "Show a divider above an assistant turn whose request lost (missed) the prompt cache",
860
+ },
861
+ },
862
+
852
863
  showHardwareCursor: {
853
864
  type: "boolean",
854
865
  default: true, // will be computed based on platform if undefined
@@ -914,14 +925,15 @@ export const SETTINGS_SCHEMA = {
914
925
  },
915
926
  },
916
927
 
917
- repeatToolDescriptions: {
928
+ inlineToolDescriptors: {
918
929
  type: "boolean",
919
- default: false,
930
+ default: true,
920
931
  ui: {
921
932
  tab: "model",
922
933
  group: "Prompt",
923
- label: "Repeat Tool Descriptions",
924
- description: "Render full tool descriptions in the system prompt instead of a tool name list",
934
+ label: "Inline Tool Descriptors",
935
+ description:
936
+ "Render full tool descriptors in the system prompt and strip top-level/nested descriptions from provider tool schemas so descriptor text is sent once",
925
937
  },
926
938
  },
927
939
 
@@ -866,21 +866,21 @@ export class DebugLogViewerComponent implements Component {
866
866
  }
867
867
 
868
868
  #frameTop(innerWidth: number): string {
869
- return `${theme.boxSharp.topLeft}${theme.boxSharp.horizontal.repeat(innerWidth)}${theme.boxSharp.topRight}`;
869
+ return `${theme.boxRound.topLeft}${theme.boxRound.horizontal.repeat(innerWidth)}${theme.boxRound.topRight}`;
870
870
  }
871
871
 
872
872
  #frameSeparator(innerWidth: number): string {
873
- return `${theme.boxSharp.teeRight}${theme.boxSharp.horizontal.repeat(innerWidth)}${theme.boxSharp.teeLeft}`;
873
+ return `${theme.boxRound.teeRight}${theme.boxRound.horizontal.repeat(innerWidth)}${theme.boxRound.teeLeft}`;
874
874
  }
875
875
 
876
876
  #frameBottom(innerWidth: number): string {
877
- return `${theme.boxSharp.bottomLeft}${theme.boxSharp.horizontal.repeat(innerWidth)}${theme.boxSharp.bottomRight}`;
877
+ return `${theme.boxRound.bottomLeft}${theme.boxRound.horizontal.repeat(innerWidth)}${theme.boxRound.bottomRight}`;
878
878
  }
879
879
 
880
880
  #frameLine(content: string, innerWidth: number): string {
881
881
  const truncated = truncateToWidth(content, innerWidth);
882
882
  const remaining = Math.max(0, innerWidth - visibleWidth(truncated));
883
- return `${theme.boxSharp.vertical}${truncated}${padding(remaining)}${theme.boxSharp.vertical}`;
883
+ return `${theme.boxRound.vertical}${truncated}${padding(remaining)}${theme.boxRound.vertical}`;
884
884
  }
885
885
 
886
886
  #copySelected() {
@@ -273,20 +273,20 @@ export class RawSseViewerComponent implements Component {
273
273
  }
274
274
 
275
275
  #frameTop(innerWidth: number): string {
276
- return `${theme.boxSharp.topLeft}${theme.boxSharp.horizontal.repeat(innerWidth)}${theme.boxSharp.topRight}`;
276
+ return `${theme.boxRound.topLeft}${theme.boxRound.horizontal.repeat(innerWidth)}${theme.boxRound.topRight}`;
277
277
  }
278
278
 
279
279
  #frameSeparator(innerWidth: number): string {
280
- return `${theme.boxSharp.teeRight}${theme.boxSharp.horizontal.repeat(innerWidth)}${theme.boxSharp.teeLeft}`;
280
+ return `${theme.boxRound.teeRight}${theme.boxRound.horizontal.repeat(innerWidth)}${theme.boxRound.teeLeft}`;
281
281
  }
282
282
 
283
283
  #frameBottom(innerWidth: number): string {
284
- return `${theme.boxSharp.bottomLeft}${theme.boxSharp.horizontal.repeat(innerWidth)}${theme.boxSharp.bottomRight}`;
284
+ return `${theme.boxRound.bottomLeft}${theme.boxRound.horizontal.repeat(innerWidth)}${theme.boxRound.bottomRight}`;
285
285
  }
286
286
 
287
287
  #frameLine(content: string, innerWidth: number): string {
288
288
  const truncated = truncateToWidth(content, innerWidth);
289
289
  const remaining = Math.max(0, innerWidth - visibleWidth(truncated));
290
- return `${theme.boxSharp.vertical}${truncated}${padding(remaining)}${theme.boxSharp.vertical}`;
290
+ return `${theme.boxRound.vertical}${truncated}${padding(remaining)}${theme.boxRound.vertical}`;
291
291
  }
292
292
  }
@@ -275,8 +275,8 @@ function formatEditDescription(
275
275
  }
276
276
 
277
277
  function editHeaderLabelBudget(width: number, uiTheme: Theme): number {
278
- const leftGlyphs = `${uiTheme.boxSharp.topLeft}${uiTheme.boxSharp.horizontal.repeat(3)}`;
279
- return Math.max(0, width - visibleWidth(leftGlyphs) - visibleWidth(uiTheme.boxSharp.topRight) - 2);
278
+ const leftGlyphs = `${uiTheme.boxRound.topLeft}${uiTheme.boxRound.horizontal.repeat(3)}`;
279
+ return Math.max(0, width - visibleWidth(leftGlyphs) - visibleWidth(uiTheme.boxRound.topRight) - 2);
280
280
  }
281
281
 
282
282
  function renderEditHeader(