@oh-my-pi/pi-coding-agent 15.10.4 → 15.10.6

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 (165) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/dist/types/capability/rule-buckets.d.ts +1 -1
  3. package/dist/types/capability/rule.d.ts +6 -1
  4. package/dist/types/cli/update-cli.d.ts +11 -1
  5. package/dist/types/config/model-registry.d.ts +18 -1
  6. package/dist/types/discovery/at-imports.d.ts +15 -0
  7. package/dist/types/edit/diff.d.ts +3 -2
  8. package/dist/types/eval/__tests__/helpers-local-roots.test.d.ts +1 -0
  9. package/dist/types/eval/backend.d.ts +7 -0
  10. package/dist/types/eval/js/context-manager.d.ts +1 -0
  11. package/dist/types/eval/js/executor.d.ts +2 -0
  12. package/dist/types/eval/js/index.d.ts +1 -1
  13. package/dist/types/eval/js/shared/helpers.d.ts +6 -0
  14. package/dist/types/eval/js/shared/runtime.d.ts +5 -0
  15. package/dist/types/eval/js/worker-protocol.d.ts +6 -0
  16. package/dist/types/eval/py/executor.d.ts +7 -0
  17. package/dist/types/eval/py/index.d.ts +1 -1
  18. package/dist/types/exa/index.d.ts +1 -19
  19. package/dist/types/exa/mcp-client.d.ts +10 -3
  20. package/dist/types/exa/types.d.ts +0 -83
  21. package/dist/types/export/ttsr.d.ts +14 -0
  22. package/dist/types/extensibility/extensions/types.d.ts +8 -1
  23. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +1 -1
  24. package/dist/types/internal-urls/local-protocol.d.ts +10 -0
  25. package/dist/types/mcp/oauth-flow.d.ts +2 -2
  26. package/dist/types/modes/components/custom-editor.d.ts +3 -0
  27. package/dist/types/modes/components/{status-line.d.ts → status-line/component.d.ts} +2 -32
  28. package/dist/types/modes/components/status-line/index.d.ts +1 -0
  29. package/dist/types/modes/components/status-line/types.d.ts +31 -2
  30. package/dist/types/modes/controllers/mcp-command-controller.d.ts +8 -0
  31. package/dist/types/modes/image-references.d.ts +8 -3
  32. package/dist/types/modes/interactive-mode.d.ts +9 -1
  33. package/dist/types/modes/theme/theme.d.ts +2 -1
  34. package/dist/types/modes/types.d.ts +3 -1
  35. package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
  36. package/dist/types/session/agent-session.d.ts +0 -2
  37. package/dist/types/task/render.d.ts +1 -0
  38. package/dist/types/tools/ask.d.ts +1 -0
  39. package/dist/types/tools/browser/tab-worker.d.ts +15 -0
  40. package/dist/types/tools/index.d.ts +17 -2
  41. package/dist/types/tools/render-utils.d.ts +1 -1
  42. package/dist/types/tools/tool-timeouts.d.ts +1 -1
  43. package/dist/types/utils/block-context.d.ts +35 -0
  44. package/dist/types/utils/git.d.ts +6 -0
  45. package/dist/types/utils/image-loading.d.ts +12 -0
  46. package/package.json +29 -9
  47. package/src/capability/rule-buckets.ts +4 -2
  48. package/src/capability/rule.ts +10 -1
  49. package/src/cli/auth-broker-cli.ts +6 -7
  50. package/src/cli/auth-gateway-cli.ts +4 -3
  51. package/src/cli/list-models.ts +5 -0
  52. package/src/cli/update-cli.ts +138 -16
  53. package/src/commit/agentic/tools/split-commit.ts +8 -1
  54. package/src/config/model-provider-priority.ts +1 -0
  55. package/src/config/model-registry.ts +81 -2
  56. package/src/debug/index.ts +4 -8
  57. package/src/discovery/at-imports.ts +273 -0
  58. package/src/discovery/builtin-rules/index.ts +4 -0
  59. package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
  60. package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
  61. package/src/discovery/helpers.ts +2 -1
  62. package/src/edit/diff.ts +114 -4
  63. package/src/edit/hashline/diff.ts +1 -1
  64. package/src/edit/hashline/execute.ts +1 -1
  65. package/src/edit/modes/patch.ts +6 -2
  66. package/src/edit/modes/replace.ts +1 -1
  67. package/src/edit/renderer.ts +12 -2
  68. package/src/eval/__tests__/helpers-local-roots.test.ts +58 -0
  69. package/src/eval/backend.ts +15 -0
  70. package/src/eval/js/context-manager.ts +4 -2
  71. package/src/eval/js/executor.ts +3 -0
  72. package/src/eval/js/index.ts +7 -1
  73. package/src/eval/js/shared/helpers.ts +53 -6
  74. package/src/eval/js/shared/runtime.ts +8 -0
  75. package/src/eval/js/worker-core.ts +1 -0
  76. package/src/eval/js/worker-protocol.ts +6 -0
  77. package/src/eval/py/executor.ts +12 -0
  78. package/src/eval/py/index.ts +7 -1
  79. package/src/eval/py/prelude.py +43 -4
  80. package/src/eval/py/runner.py +1 -0
  81. package/src/exa/index.ts +1 -26
  82. package/src/exa/mcp-client.ts +10 -10
  83. package/src/exa/types.ts +0 -97
  84. package/src/export/ttsr.ts +122 -1
  85. package/src/extensibility/extensions/types.ts +8 -1
  86. package/src/extensibility/legacy-pi-ai-shim.ts +1 -1
  87. package/src/extensibility/plugins/doctor.ts +1 -1
  88. package/src/extensibility/plugins/legacy-pi-compat.ts +6 -5
  89. package/src/goals/tools/goal-tool.ts +1 -1
  90. package/src/internal-urls/docs-index.generated.ts +7 -6
  91. package/src/internal-urls/local-protocol.ts +13 -0
  92. package/src/lsp/render.ts +8 -6
  93. package/src/mcp/oauth-flow.ts +3 -3
  94. package/src/mcp/render.ts +7 -1
  95. package/src/modes/components/agent-dashboard.ts +6 -4
  96. package/src/modes/components/custom-editor.ts +12 -6
  97. package/src/modes/components/login-dialog.ts +1 -1
  98. package/src/modes/components/oauth-selector.ts +4 -4
  99. package/src/modes/components/read-tool-group.ts +10 -3
  100. package/src/modes/components/{status-line.ts → status-line/component.ts} +18 -40
  101. package/src/modes/components/status-line/index.ts +1 -0
  102. package/src/modes/components/status-line/types.ts +23 -8
  103. package/src/modes/components/tool-execution.ts +1 -1
  104. package/src/modes/components/transcript-container.ts +17 -10
  105. package/src/modes/components/user-message.ts +6 -3
  106. package/src/modes/components/welcome.ts +1 -1
  107. package/src/modes/controllers/event-controller.ts +8 -0
  108. package/src/modes/controllers/extension-ui-controller.ts +143 -127
  109. package/src/modes/controllers/input-controller.ts +60 -11
  110. package/src/modes/controllers/mcp-command-controller.ts +52 -17
  111. package/src/modes/controllers/selector-controller.ts +4 -11
  112. package/src/modes/controllers/ssh-command-controller.ts +2 -2
  113. package/src/modes/image-references.ts +13 -7
  114. package/src/modes/interactive-mode.ts +35 -3
  115. package/src/modes/rpc/rpc-mode.ts +1 -1
  116. package/src/modes/setup-wizard/scenes/sign-in.ts +3 -11
  117. package/src/modes/theme/theme.ts +95 -1
  118. package/src/modes/types.ts +3 -1
  119. package/src/modes/utils/ui-helpers.ts +14 -5
  120. package/src/prompts/tools/bash.md +1 -1
  121. package/src/prompts/tools/eval.md +4 -4
  122. package/src/sdk.ts +31 -14
  123. package/src/session/agent-session.ts +290 -196
  124. package/src/session/session-manager.ts +1 -1
  125. package/src/slash-commands/builtin-registry.ts +9 -1
  126. package/src/system-prompt.ts +15 -9
  127. package/src/task/index.ts +9 -1
  128. package/src/task/render.ts +36 -14
  129. package/src/tools/ask.ts +14 -5
  130. package/src/tools/bash-interactive.ts +1 -1
  131. package/src/tools/bash.ts +14 -2
  132. package/src/tools/browser/render.ts +5 -2
  133. package/src/tools/browser/tab-worker.ts +211 -91
  134. package/src/tools/debug.ts +5 -2
  135. package/src/tools/eval-render.ts +6 -3
  136. package/src/tools/eval.ts +1 -1
  137. package/src/tools/gh-renderer.ts +29 -15
  138. package/src/tools/index.ts +32 -4
  139. package/src/tools/inspect-image-renderer.ts +12 -5
  140. package/src/tools/job.ts +9 -6
  141. package/src/tools/memory-render.ts +19 -5
  142. package/src/tools/read.ts +165 -18
  143. package/src/tools/render-utils.ts +3 -1
  144. package/src/tools/resolve.ts +1 -1
  145. package/src/tools/review.ts +1 -1
  146. package/src/tools/ssh.ts +4 -1
  147. package/src/tools/todo.ts +8 -1
  148. package/src/tools/tool-timeouts.ts +1 -1
  149. package/src/tools/write.ts +1 -1
  150. package/src/tui/code-cell.ts +1 -1
  151. package/src/utils/block-context.ts +312 -0
  152. package/src/utils/git.ts +41 -0
  153. package/src/utils/image-loading.ts +31 -1
  154. package/src/web/search/providers/codex.ts +1 -1
  155. package/src/web/search/render.ts +14 -6
  156. package/dist/types/exa/factory.d.ts +0 -13
  157. package/dist/types/exa/render.d.ts +0 -19
  158. package/dist/types/exa/researcher.d.ts +0 -9
  159. package/dist/types/exa/search.d.ts +0 -9
  160. package/dist/types/exa/websets.d.ts +0 -9
  161. package/src/exa/factory.ts +0 -60
  162. package/src/exa/render.ts +0 -244
  163. package/src/exa/researcher.ts +0 -36
  164. package/src/exa/search.ts +0 -47
  165. package/src/exa/websets.ts +0 -248
@@ -133,6 +133,19 @@ export function resolveLocalUrlToPath(input: string | InternalUrl, options: Loca
133
133
  return resolved;
134
134
  }
135
135
 
136
+ /**
137
+ * On-disk roots the eval helpers (`read`/`write`/`append`) substitute for
138
+ * internal-URL schemes so e.g. `write("local://x.md")` lands where a later
139
+ * `read local://x.md` resolves — instead of a literal `local:/` directory under
140
+ * the cwd (a stdlib `pathlib.Path`/`path.resolve` collapses `local://` to
141
+ * `local:/`). Keyed by scheme without the `://`. Currently only `local`, but the
142
+ * shape is a map so additional file-backed schemes can be added without
143
+ * re-plumbing the worker boundary.
144
+ */
145
+ export function buildEvalUrlRoots(options: LocalProtocolOptions): Record<string, string> {
146
+ return { local: resolveLocalRoot(options) };
147
+ }
148
+
136
149
  /**
137
150
  * Protocol handler for local:// URLs.
138
151
  *
package/src/lsp/render.ts CHANGED
@@ -166,15 +166,17 @@ export function renderResult(
166
166
  } else if (result.details?.action === "diagnostics" && text === "OK") {
167
167
  label = "Diagnostics";
168
168
  state = "success";
169
- bodyLines = [`${theme.styledSymbol("status.success", "success")} ${theme.fg("dim", "OK")}`];
169
+ bodyLines = [`${theme.styledSymbol("tool.lsp", "accent")} ${theme.fg("dim", "OK")}`];
170
170
  } else {
171
171
  label = "Response";
172
172
  bodyLines = renderGeneric(text, lines, expanded, theme);
173
173
  }
174
174
 
175
175
  const actionLabel = (request?.action ?? result.details?.action ?? label.toLowerCase()).replace(/_/g, " ");
176
- const status = isPartial ? "running" : result.isError ? "error" : "success";
177
- const icon = formatStatusIcon(status, theme, spinnerFrame);
176
+ const isSuccess = !isPartial && !result.isError;
177
+ const icon = isSuccess
178
+ ? theme.styledSymbol("tool.lsp", "accent")
179
+ : formatStatusIcon(isPartial ? "running" : "error", theme, spinnerFrame);
178
180
  const header = `${icon} LSP ${actionLabel}`;
179
181
 
180
182
  return outputBlock.render(
@@ -325,7 +327,7 @@ function renderDiagnostics(
325
327
  ? theme.styledSymbol("status.error", "error")
326
328
  : warnCount > 0
327
329
  ? theme.styledSymbol("status.warning", "warning")
328
- : theme.styledSymbol("status.success", "success");
330
+ : theme.styledSymbol("tool.lsp", "accent");
329
331
 
330
332
  const meta: string[] = [];
331
333
  if (errorCount > 0) meta.push(`${errorCount} error${errorCount !== 1 ? "s" : ""}`);
@@ -407,7 +409,7 @@ function renderDiagnostics(
407
409
  function renderReferences(refMatch: RegExpMatchArray, lines: string[], expanded: boolean, theme: Theme): string[] {
408
410
  const refCount = Number.parseInt(refMatch[1], 10);
409
411
  const icon =
410
- refCount > 0 ? theme.styledSymbol("status.success", "success") : theme.styledSymbol("status.warning", "warning");
412
+ refCount > 0 ? theme.styledSymbol("tool.lsp", "accent") : theme.styledSymbol("status.warning", "warning");
411
413
 
412
414
  const locLines = lines.filter(l => /^\s*\S+:\d+:\d+/.test(l));
413
415
 
@@ -598,7 +600,7 @@ function renderGeneric(text: string, lines: string[], expanded: boolean, theme:
598
600
  hasError && !hasSuccess
599
601
  ? theme.styledSymbol("status.error", "error")
600
602
  : hasSuccess && !hasError
601
- ? theme.styledSymbol("status.success", "success")
603
+ ? theme.styledSymbol("tool.lsp", "accent")
602
604
  : theme.styledSymbol("status.info", "accent");
603
605
 
604
606
  if (expanded) {
@@ -5,9 +5,9 @@
5
5
  * by providing authorization URL, token URL, and client credentials.
6
6
  */
7
7
 
8
- import type { OAuthCallbackFlowOptions } from "@oh-my-pi/pi-ai/utils/oauth/callback-server";
9
- import { OAuthCallbackFlow } from "@oh-my-pi/pi-ai/utils/oauth/callback-server";
10
- import type { OAuthController, OAuthCredentials } from "@oh-my-pi/pi-ai/utils/oauth/types";
8
+ import type { OAuthCallbackFlowOptions } from "@oh-my-pi/pi-ai/oauth/callback-server";
9
+ import { OAuthCallbackFlow } from "@oh-my-pi/pi-ai/oauth/callback-server";
10
+ import type { OAuthController, OAuthCredentials } from "@oh-my-pi/pi-ai/oauth/types";
11
11
 
12
12
  const DEFAULT_PORT = 3000;
13
13
  const CALLBACK_PATH = "/callback";
package/src/mcp/render.ts CHANGED
@@ -53,7 +53,13 @@ export function renderMCPResult(
53
53
  const lines: string[] = [];
54
54
  const isError = result.isError ?? result.details?.isError ?? false;
55
55
  const title = result.details ? `${result.details.serverName}/${result.details.mcpToolName}` : "MCP";
56
- lines.push(renderStatusLine({ icon: isError ? "error" : "success", title }, theme));
56
+ const success = !isError;
57
+ lines.push(
58
+ renderStatusLine(
59
+ success ? { iconOverride: theme.styledSymbol("tool.mcp", "accent"), title } : { icon: "error", title },
60
+ theme,
61
+ ),
62
+ );
57
63
 
58
64
  // Args section (when expanded)
59
65
  if (expanded && args && typeof args === "object" && Object.keys(args).length > 0) {
@@ -649,6 +649,11 @@ export class AgentDashboard extends Container {
649
649
  this.#buildLayout();
650
650
  }
651
651
 
652
+ #shouldSubmitCreateDescription(data: string): boolean {
653
+ if (matchesKey(data, "ctrl+enter")) return true;
654
+ return process.platform === "win32" && data === "\n" && this.#createDescription.trim().length > 0;
655
+ }
656
+
652
657
  async #generateAgentFromDescription(rawDescription: string): Promise<void> {
653
658
  const description = rawDescription.trim();
654
659
  this.#createDescription = description;
@@ -1094,10 +1099,7 @@ export class AgentDashboard extends Container {
1094
1099
  }
1095
1100
  return;
1096
1101
  }
1097
- if (
1098
- !this.#createGenerating &&
1099
- (matchesKey(data, "ctrl+enter") || (data.charCodeAt(0) === 10 && data.length > 1))
1100
- ) {
1102
+ if (!this.#createGenerating && this.#shouldSubmitCreateDescription(data)) {
1101
1103
  this.#submitCreateDescription();
1102
1104
  return;
1103
1105
  }
@@ -1,6 +1,6 @@
1
1
  import { addKeyAliases, canonicalKeyId, Editor, type KeyId, parseKey, parseKittySequence } from "@oh-my-pi/pi-tui";
2
2
  import type { AppKeybinding } from "../../config/keybindings";
3
- import { imageReferenceHyperlink, renderImageReferences } from "../image-references";
3
+ import { imageReferenceHyperlink, PLACEHOLDER_REGEX, renderPlaceholders } from "../image-references";
4
4
  import { highlightMagicKeywords } from "../magic-keywords";
5
5
  import { theme } from "../theme/theme";
6
6
 
@@ -76,16 +76,22 @@ export function extractBracketedImagePastePath(data: string): string | undefined
76
76
  export class CustomEditor extends Editor {
77
77
  imageLinks?: readonly (string | undefined)[];
78
78
 
79
+ /** Treat image/paste markers as indivisible: a stray backspace deletes the whole token
80
+ * instead of corrupting `[Paste #1, +30 lines]` into plain text. */
81
+ override atomicTokenPattern = PLACEHOLDER_REGEX;
82
+
79
83
  /** Gradient-highlight the "ultrathink" / "orchestrate" / "workflowz" keywords as the user types
80
84
  * them, skipping any occurrence inside code spans, fenced blocks, or XML sections. Also make
81
85
  * pasted image placeholders visually distinct and hyperlink them once their blob file exists. */
82
86
  decorateText = (text: string): string =>
83
- renderImageReferences(text, {
87
+ renderPlaceholders(text, {
84
88
  renderText: value => highlightMagicKeywords(value),
85
- renderReference: (value, index) =>
86
- imageReferenceHyperlink(value, index, this.imageLinks, label =>
87
- theme.fg("accent", `\x1b[1m\x1b[4m${label}\x1b[24m\x1b[22m`),
88
- ),
89
+ renderReference: (value, kind, index) =>
90
+ kind === "image"
91
+ ? imageReferenceHyperlink(value, index, this.imageLinks, label =>
92
+ theme.fg("accent", `\x1b[1m\x1b[4m${label}\x1b[24m\x1b[22m`),
93
+ )
94
+ : theme.fg("accent", `\x1b[1m${value}\x1b[22m`),
89
95
  });
90
96
  onEscape?: () => void;
91
97
  onClear?: () => void;
@@ -1,4 +1,4 @@
1
- import { getOAuthProviders } from "@oh-my-pi/pi-ai/utils/oauth";
1
+ import { getOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
2
2
  import { Container, getKeybindings, Input, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
3
3
  import { theme } from "../../modes/theme/theme";
4
4
  import { openPath } from "../../utils/open";
@@ -1,5 +1,5 @@
1
- import { getOAuthProviders } from "@oh-my-pi/pi-ai/utils/oauth";
2
- import type { OAuthProviderInfo } from "@oh-my-pi/pi-ai/utils/oauth/types";
1
+ import { getOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
2
+ import type { OAuthProviderInfo } from "@oh-my-pi/pi-ai/oauth/types";
3
3
  import {
4
4
  Container,
5
5
  extractPrintableText,
@@ -179,10 +179,10 @@ export class OAuthSelectorComponent extends Container {
179
179
  return theme.fg("error", ` ${theme.status.error} invalid`) + source;
180
180
  }
181
181
  if (state === "valid") {
182
- return theme.fg("success", ` ${theme.status.success} logged in`) + source;
182
+ return theme.fg("success", ` ${theme.status.enabled} logged in`) + source;
183
183
  }
184
184
  return this.#hasSelectableAuth(providerId)
185
- ? theme.fg("success", ` ${theme.status.success} logged in`) + source
185
+ ? theme.fg("success", ` ${theme.status.enabled} logged in`) + source
186
186
  : "";
187
187
  }
188
188
 
@@ -54,6 +54,10 @@ type ReadToolResultDetails = {
54
54
  };
55
55
  conflictCount?: number;
56
56
  displayReadTargets?: unknown;
57
+ displayContent?: {
58
+ text?: string;
59
+ startLine?: number;
60
+ };
57
61
  meta?: {
58
62
  source?: {
59
63
  type?: string;
@@ -373,10 +377,13 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
373
377
  typeof details?.conflictCount === "number" && details.conflictCount > 0 ? details.conflictCount : undefined;
374
378
  entry.conflictCount = conflictCount;
375
379
  entry.status = result.isError ? "error" : suffixResolution ? "warning" : "success";
376
- // Store the text content for preview/expanded display
380
+ // Store clean display content for preview/expanded display when the read
381
+ // tool provides it; fall back to model-facing text for legacy results.
382
+ const displayContent =
383
+ typeof details?.displayContent?.text === "string" ? details.displayContent.text : undefined;
377
384
  const textContent = result.content?.find(c => c.type === "text")?.text;
378
- if (textContent !== undefined) {
379
- entry.contentText = textContent;
385
+ if (displayContent !== undefined || textContent !== undefined) {
386
+ entry.contentText = displayContent ?? textContent;
380
387
  }
381
388
  this.#updateDisplay();
382
389
  }
@@ -4,46 +4,24 @@ import { estimateTokens } from "@oh-my-pi/pi-agent-core/compaction";
4
4
  import { type Component, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
5
5
  import { formatCount, getProjectDir } from "@oh-my-pi/pi-utils";
6
6
  import { $ } from "bun";
7
- import { settings } from "../../config/settings";
8
- import type { StatusLinePreset, StatusLineSegmentId, StatusLineSeparatorStyle } from "../../config/settings-schema";
9
- import { theme } from "../../modes/theme/theme";
10
- import type { AgentSession } from "../../session/agent-session";
11
- import * as git from "../../utils/git";
12
- import { getSessionAccentAnsi, getSessionAccentHex } from "../../utils/session-color";
13
- import { sanitizeStatusText } from "../shared";
14
- import { computeNonMessageTokens } from "../utils/context-usage";
15
- import {
16
- canReuseCachedPr,
17
- createPrCacheContext,
18
- isSamePrCacheContext,
19
- type PrCacheContext,
20
- } from "./status-line/git-utils";
21
- import { getPreset } from "./status-line/presets";
22
- import { renderSegment, type SegmentContext } from "./status-line/segments";
23
- import { getSeparator } from "./status-line/separators";
24
- import { calculateTokensPerSecond } from "./status-line/token-rate";
25
-
26
- export interface StatusLineSegmentOptions {
27
- model?: { showThinkingLevel?: boolean };
28
- path?: { abbreviate?: boolean; maxLength?: number; stripWorkPrefix?: boolean };
29
- git?: { showBranch?: boolean; showStaged?: boolean; showUnstaged?: boolean; showUntracked?: boolean };
30
- time?: { format?: "12h" | "24h"; showSeconds?: boolean };
31
- }
32
-
33
- export interface StatusLineSettings {
34
- preset?: StatusLinePreset;
35
- leftSegments?: StatusLineSegmentId[];
36
- rightSegments?: StatusLineSegmentId[];
37
- separator?: StatusLineSeparatorStyle;
38
- segmentOptions?: StatusLineSegmentOptions;
39
- showHookStatus?: boolean;
40
- sessionAccent?: boolean;
41
- }
42
-
43
- export type EffectiveStatusLineSettings = Required<
44
- Pick<StatusLineSettings, "leftSegments" | "rightSegments" | "separator" | "segmentOptions">
45
- > &
46
- StatusLineSettings;
7
+ import { settings } from "../../../config/settings";
8
+ import type { AgentSession } from "../../../session/agent-session";
9
+ import * as git from "../../../utils/git";
10
+ import { getSessionAccentAnsi, getSessionAccentHex } from "../../../utils/session-color";
11
+ import { sanitizeStatusText } from "../../shared";
12
+ import { theme } from "../../theme/theme";
13
+ import { computeNonMessageTokens } from "../../utils/context-usage";
14
+ import { canReuseCachedPr, createPrCacheContext, isSamePrCacheContext, type PrCacheContext } from "./git-utils";
15
+ import { getPreset } from "./presets";
16
+ import { renderSegment, type SegmentContext } from "./segments";
17
+ import { getSeparator } from "./separators";
18
+ import { calculateTokensPerSecond } from "./token-rate";
19
+ import type {
20
+ EffectiveStatusLineSettings,
21
+ StatusLineSegmentId,
22
+ StatusLineSegmentOptions,
23
+ StatusLineSettings,
24
+ } from "./types";
47
25
 
48
26
  // ═══════════════════════════════════════════════════════════════════════════
49
27
  // Per-message token cache
@@ -1,3 +1,4 @@
1
+ export * from "./component";
1
2
  export * from "./presets";
2
3
  export * from "./segments";
3
4
  export * from "./separators";
@@ -1,14 +1,29 @@
1
1
  import type { StatusLinePreset, StatusLineSegmentId, StatusLineSeparatorStyle } from "../../../config/settings-schema";
2
2
  import type { AgentSession } from "../../../session/agent-session";
3
- import type { StatusLineSegmentOptions, StatusLineSettings } from "../status-line";
4
3
 
5
- export type {
6
- StatusLinePreset,
7
- StatusLineSegmentId,
8
- StatusLineSegmentOptions,
9
- StatusLineSeparatorStyle,
10
- StatusLineSettings,
11
- };
4
+ export type { StatusLinePreset, StatusLineSegmentId, StatusLineSeparatorStyle };
5
+
6
+ export interface StatusLineSegmentOptions {
7
+ model?: { showThinkingLevel?: boolean };
8
+ path?: { abbreviate?: boolean; maxLength?: number; stripWorkPrefix?: boolean };
9
+ git?: { showBranch?: boolean; showStaged?: boolean; showUnstaged?: boolean; showUntracked?: boolean };
10
+ time?: { format?: "12h" | "24h"; showSeconds?: boolean };
11
+ }
12
+
13
+ export interface StatusLineSettings {
14
+ preset?: StatusLinePreset;
15
+ leftSegments?: StatusLineSegmentId[];
16
+ rightSegments?: StatusLineSegmentId[];
17
+ separator?: StatusLineSeparatorStyle;
18
+ segmentOptions?: StatusLineSegmentOptions;
19
+ showHookStatus?: boolean;
20
+ sessionAccent?: boolean;
21
+ }
22
+
23
+ export type EffectiveStatusLineSettings = Required<
24
+ Pick<StatusLineSettings, "leftSegments" | "rightSegments" | "separator" | "segmentOptions">
25
+ > &
26
+ StatusLineSettings;
12
27
 
13
28
  // ═══════════════════════════════════════════════════════════════════════════
14
29
  // Segment Rendering
@@ -914,7 +914,7 @@ export class ToolExecutionComponent extends Container {
914
914
  */
915
915
  #formatToolExecution(): string {
916
916
  const lines: string[] = [];
917
- const icon = this.#isPartial ? "pending" : this.#result?.isError ? "error" : "success";
917
+ const icon = this.#isPartial ? "pending" : this.#result?.isError ? "error" : "done";
918
918
  lines.push(renderStatusLine({ icon, title: this.#toolLabel }, theme));
919
919
 
920
920
  const argsObject = this.#args && typeof this.#args === "object" ? (this.#args as Record<string, unknown>) : null;
@@ -71,9 +71,7 @@ function commonPrefixLength(prev: string[], cur: string[]): number {
71
71
  }
72
72
 
73
73
  function commonSuffixLength(prev: string[], cur: string[], prefixLength: number): number {
74
- const prevLimit = prev.length - prefixLength;
75
- const curLimit = cur.length - prefixLength;
76
- const limit = Math.min(prevLimit, curLimit);
74
+ const limit = Math.min(prev.length - prefixLength, cur.length - prefixLength);
77
75
  let i = 0;
78
76
  while (i < limit && prev[prev.length - 1 - i] === cur[cur.length - 1 - i]) i++;
79
77
  return i;
@@ -95,14 +93,23 @@ function deriveLiveCommitState(
95
93
  const staticRender = prefixLength === previous.lines.length && prefixLength === current.length;
96
94
  if (!staticRender) {
97
95
  const suffixLength = commonSuffixLength(previous.lines, current, prefixLength);
98
- const stablePreviousLength = prefixLength + suffixLength;
99
- const appendGrew =
100
- previous.lines.length > 0 &&
101
- current.length > previous.lines.length &&
102
- stablePreviousLength >= previous.lines.length;
103
- if (appendGrew && !volatile) {
96
+ // Append-only growth never rewrites a row that may already have scrolled
97
+ // into native scrollback; it only grows the block at/near its tail. Three
98
+ // shapes qualify: a pure bottom append, an insertion above stable trailing
99
+ // chrome (a streaming tool's footer/border), and an in-place extension of
100
+ // the current line by one streamed token (line count unchanged). The first
101
+ // two preserve every previous row across a matching prefix + suffix; the
102
+ // last leaves a single divergent previous row that the current row merely
103
+ // lengthens. A divergent interior row that is genuinely rewritten means the
104
+ // block re-laid-out committed content — volatile, and never committed.
105
+ const preservedEveryRow = prefixLength + suffixLength >= previous.lines.length;
106
+ const tailExtendedInPlace =
107
+ prefixLength + suffixLength === previous.lines.length - 1 &&
108
+ prefixLength < current.length &&
109
+ current[prefixLength]!.startsWith(previous.lines[prefixLength]!);
110
+ if ((preservedEveryRow || tailExtendedInPlace) && current.length >= previous.lines.length && !volatile) {
104
111
  appendOnly = true;
105
- } else if (stablePreviousLength < previous.lines.length) {
112
+ } else if (!preservedEveryRow && !tailExtendedInPlace) {
106
113
  volatile = true;
107
114
  appendOnly = false;
108
115
  }
@@ -1,6 +1,6 @@
1
1
  import { Container, Markdown } from "@oh-my-pi/pi-tui";
2
2
  import { getMarkdownTheme, theme } from "../../modes/theme/theme";
3
- import { imageReferenceHyperlink, renderImageReferences } from "../image-references";
3
+ import { imageReferenceHyperlink, renderPlaceholders } from "../image-references";
4
4
  import { highlightMagicKeywords } from "../magic-keywords";
5
5
 
6
6
  // OSC 133 shell integration: marks prompt zones for terminal multiplexers
@@ -26,9 +26,12 @@ export class UserMessageComponent extends Container {
26
26
  : (value: string) => theme.fg("userMessageText", highlightMagicKeywords(value, keywordReset));
27
27
  const imageLabel = (value: string) => theme.fg("accent", `\x1b[1m\x1b[4m${value}\x1b[24m\x1b[22m`);
28
28
  const color = (value: string) =>
29
- renderImageReferences(value, {
29
+ renderPlaceholders(value, {
30
30
  renderText: baseText,
31
- renderReference: (label, index) => imageReferenceHyperlink(label, index, imageLinks, imageLabel),
31
+ renderReference: (label, kind, index) =>
32
+ kind === "image"
33
+ ? imageReferenceHyperlink(label, index, imageLinks, imageLabel)
34
+ : theme.fg("accent", `\x1b[1m${label}\x1b[22m`),
32
35
  });
33
36
  this.addChild(
34
37
  new Markdown(text, 1, 1, getMarkdownTheme(), {
@@ -185,7 +185,7 @@ export class WelcomeComponent implements Component {
185
185
  for (const server of this.lspServers) {
186
186
  const icon =
187
187
  server.status === "ready"
188
- ? theme.styledSymbol("status.success", "success")
188
+ ? theme.styledSymbol("status.enabled", "success")
189
189
  : server.status === "connecting"
190
190
  ? theme.styledSymbol("status.pending", "muted")
191
191
  : theme.styledSymbol("status.error", "error");
@@ -798,7 +798,15 @@ export class EventController {
798
798
  );
799
799
  } else if (isShakeAction) {
800
800
  // Shake produces no CompactionResult; rebuild on success, suppress benign skips.
801
+ // The fallback path (`errorMessage` set, `skipped` false) means shake reclaimed
802
+ // some tokens before deciding the threshold still wasn't cleared — rebuild so
803
+ // the chat reflects the dropped regions even though a context-full pass follows.
801
804
  if (event.errorMessage) {
805
+ if (!event.skipped) {
806
+ this.ctx.rebuildChatFromMessages();
807
+ this.ctx.statusLine.invalidate();
808
+ this.ctx.updateEditorTopBorder();
809
+ }
802
810
  this.ctx.showWarning(event.errorMessage);
803
811
  } else if (!event.skipped) {
804
812
  this.ctx.rebuildChatFromMessages();