@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
@@ -3558,7 +3558,7 @@ export class SessionManager {
3558
3558
  }
3559
3559
  const relocated = sourceCwdGone && (mostRecent === null || (mostRecentIsBreadcrumb && !hasCurrentCwdSession));
3560
3560
  if (relocated) {
3561
- process.stderr.write(`Re-rooting moved session from ${resolvedBreadcrumbCwd} to ${resolvedCwd}.\n`);
3561
+ logger.info("Re-rooting moved session", { from: resolvedBreadcrumbCwd, to: resolvedCwd });
3562
3562
  const manager = await SessionManager.open(breadcrumb.sessionFile, undefined, storage);
3563
3563
  await manager.moveTo(cwd, sessionDir);
3564
3564
  return manager;
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
- import { getOAuthProviders } from "@oh-my-pi/pi-ai/utils/oauth";
4
+ import { getOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
5
5
  import { Snowflake, setProjectDir } from "@oh-my-pi/pi-utils";
6
6
  import { $ } from "bun";
7
7
  import type { SettingPath, SettingValue } from "../config/settings";
@@ -100,6 +100,14 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
100
100
  runtime.ctx.editor.setText("");
101
101
  },
102
102
  },
103
+ {
104
+ name: "plan-review",
105
+ description: "Re-open the plan review for the latest plan (plan mode only)",
106
+ handleTui: async (_command, runtime) => {
107
+ await runtime.ctx.openPlanReview();
108
+ runtime.ctx.editor.setText("");
109
+ },
110
+ },
103
111
  {
104
112
  name: "goal",
105
113
  description: "Toggle goal mode (persistent autonomous objective for this session)",
@@ -10,6 +10,7 @@ import { contextFileCapability } from "./capability/context-file";
10
10
  import { systemPromptCapability } from "./capability/system-prompt";
11
11
  import type { SkillsSettings } from "./config/settings";
12
12
  import { type ContextFile, loadCapability, type SystemPrompt as SystemPromptFile } from "./discovery";
13
+ import { expandAtImports } from "./discovery/at-imports";
13
14
  import { loadSkills, type Skill } from "./extensibility/skills";
14
15
  import { hasObsidian } from "./internal-urls/vault-protocol";
15
16
  import customSystemPromptTemplate from "./prompts/system/custom-system-prompt.md" with { type: "text" };
@@ -254,15 +255,20 @@ export async function loadProjectContextFiles(
254
255
 
255
256
  const result = await loadCapability(contextFileCapability.id, { cwd: resolvedCwd });
256
257
 
257
- // Convert ContextFile items and preserve depth info
258
- const files = result.items.map(item => {
259
- const contextFile = item as ContextFile;
260
- return {
261
- path: contextFile.path,
262
- content: contextFile.content,
263
- depth: contextFile.depth,
264
- };
265
- });
258
+ // Materialize ContextFile items, expanding any `@path/to/file` includes
259
+ // in their content. The expansion uses the file's own directory as the
260
+ // resolution base so relative imports work the same way Claude Code,
261
+ // Goose, and other tools document.
262
+ const files = await Promise.all(
263
+ result.items.map(async item => {
264
+ const contextFile = item as ContextFile;
265
+ return {
266
+ path: contextFile.path,
267
+ content: await expandAtImports(contextFile.content, contextFile.path),
268
+ depth: contextFile.depth,
269
+ };
270
+ }),
271
+ );
266
272
 
267
273
  // Sort by depth (descending): higher depth (farther from cwd) comes first,
268
274
  // so files closer to cwd appear later and are more prominent
package/src/task/index.ts CHANGED
@@ -158,6 +158,8 @@ export const READ_ONLY_TOOL_NAMES: ReadonlySet<string> = new Set([
158
158
  "search_tool_bm25",
159
159
  ]);
160
160
 
161
+ const PLAN_MODE_AGENT_TOOL_ALLOWLIST: ReadonlySet<string> = new Set(["ast_grep", "report_finding"]);
162
+
161
163
  export function isReadOnlyAgent(agent: AgentDefinition): boolean {
162
164
  return !!agent.tools?.length && agent.tools.every(tool => READ_ONLY_TOOL_NAMES.has(tool));
163
165
  }
@@ -677,7 +679,13 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
677
679
  }
678
680
 
679
681
  const planModeState = this.session.getPlanModeState?.();
680
- const planModeTools = ["read", "search", "find", "lsp", "web_search"];
682
+ const planModeBaseTools = ["read", "search", "find", "lsp", "web_search"];
683
+ const planModeTools = [
684
+ ...planModeBaseTools,
685
+ ...(agent.tools ?? []).filter(
686
+ tool => PLAN_MODE_AGENT_TOOL_ALLOWLIST.has(tool) && !planModeBaseTools.includes(tool),
687
+ ),
688
+ ];
681
689
  const effectiveAgent: typeof agent = planModeState?.enabled
682
690
  ? {
683
691
  ...agent,
@@ -632,7 +632,7 @@ function renderAgentProgress(
632
632
  const indent = prefix ? `${prefix} ` : "";
633
633
  let statusLine: string;
634
634
  if (progress.status === "running") {
635
- const bullet = theme.fg("accent", "");
635
+ const bullet = theme.styledSymbol("status.done", "text");
636
636
  const name = theme.fg("accent", description ? theme.bold(displayId) : displayId);
637
637
  statusLine = `${indent}${bullet} ${name}`;
638
638
  if (description) {
@@ -640,7 +640,9 @@ function renderAgentProgress(
640
640
  statusLine += `${theme.fg("accent", ":")} ${desc}`;
641
641
  }
642
642
  } else {
643
- statusLine = `${indent}${theme.fg(iconColor, icon)} ${theme.fg("accent", titlePart)}`;
643
+ const glyph =
644
+ progress.status === "completed" ? theme.styledSymbol("status.done", "accent") : theme.fg(iconColor, icon);
645
+ statusLine = `${indent}${glyph} ${theme.fg("accent", titlePart)}`;
644
646
  }
645
647
 
646
648
  // Show retry-blocked badge so the parent immediately sees that a child
@@ -807,12 +809,15 @@ function renderReviewResult(
807
809
 
808
810
  // Verdict line
809
811
  const verdictColor = summary.overall_correctness === "correct" ? "success" : "error";
810
- const verdictIcon = summary.overall_correctness === "correct" ? theme.status.success : theme.status.error;
812
+ const isCorrect = summary.overall_correctness === "correct";
813
+ const verdictIcon = isCorrect
814
+ ? theme.styledSymbol("status.done", "accent")
815
+ : theme.fg(verdictColor, theme.status.error);
811
816
  lines.push(
812
- `${continuePrefix} Patch is ${theme.fg(verdictColor, summary.overall_correctness)} ${theme.fg(
813
- verdictColor,
814
- verdictIcon,
815
- )} ${theme.fg("dim", `(${(summary.confidence * 100).toFixed(0)}% confidence)`)}`,
817
+ `${continuePrefix} Patch is ${theme.fg(verdictColor, summary.overall_correctness)} ${verdictIcon} ${theme.fg(
818
+ "dim",
819
+ `(${(summary.confidence * 100).toFixed(0)}% confidence)`,
820
+ )}`,
816
821
  );
817
822
 
818
823
  // Explanation preview (first ~80 chars when collapsed, full when expanded)
@@ -913,7 +918,7 @@ function renderAgentResult(
913
918
  : needsWarning
914
919
  ? theme.status.warning
915
920
  : success
916
- ? theme.status.success
921
+ ? theme.styledSymbol("status.done", "accent")
917
922
  : theme.status.error;
918
923
  const iconColor = needsWarning ? "warning" : success ? "success" : mergeFailed ? "warning" : "error";
919
924
  const statusText = aborted
@@ -1071,7 +1076,7 @@ function renderAgentResult(
1071
1076
  * Render the tool result.
1072
1077
  */
1073
1078
  export function renderResult(
1074
- result: { content: Array<{ type: string; text?: string }>; details?: TaskToolDetails },
1079
+ result: { content: Array<{ type: string; text?: string }>; details?: TaskToolDetails; isError?: boolean },
1075
1080
  options: RenderResultOptions,
1076
1081
  theme: Theme,
1077
1082
  args?: TaskParams,
@@ -1082,15 +1087,25 @@ export function renderResult(
1082
1087
 
1083
1088
  if (!details) {
1084
1089
  const text = result.content.find(c => c.type === "text")?.text || "";
1085
- const header = renderStatusLine({ icon: "success", title: "Task" }, theme);
1090
+ const errored = result.isError === true;
1091
+ const header = errored
1092
+ ? renderStatusLine({ icon: "error", title: "Task", description: args?.agent }, theme)
1093
+ : renderStatusLine(
1094
+ {
1095
+ iconOverride: theme.styledSymbol("status.done", "accent"),
1096
+ title: "Task",
1097
+ description: args?.agent,
1098
+ },
1099
+ theme,
1100
+ );
1086
1101
  return framedBlock(theme, width => ({
1087
1102
  header,
1088
1103
  sections: [
1089
1104
  ...(contextSectionRenderer ? [contextSectionRenderer(width)] : []),
1090
1105
  ...(text ? [{ separator: true, lines: [theme.fg("dim", truncateToWidth(text, width))] }] : []),
1091
1106
  ],
1092
- state: "success",
1093
- borderColor: "borderMuted",
1107
+ state: errored ? "error" : "success",
1108
+ borderColor: errored ? "error" : "borderMuted",
1094
1109
  width,
1095
1110
  }));
1096
1111
  }
@@ -1102,11 +1117,18 @@ export function renderResult(
1102
1117
  const isError = aborted || failed;
1103
1118
  const agentCount = hasResults ? details.results.length : (details.progress?.length ?? 0);
1104
1119
  const icon: ToolUIStatus = options.isPartial ? "running" : isError ? "error" : mergeFailed ? "warning" : "success";
1120
+ // Surface the dispatched agent type (e.g. `Reviewer`) alongside the count so
1121
+ // the header reads `Task 16 agents: Reviewer`. All tasks in one call share a
1122
+ // single `agent` type (top-level param), so one label covers the whole batch.
1123
+ const agentName = args?.agent?.trim();
1124
+ const countLabel = agentCount > 0 ? `${agentCount} ${agentCount === 1 ? "agent" : "agents"}` : undefined;
1125
+ const metaLabel = countLabel ? (agentName ? `${countLabel}: ${agentName}` : countLabel) : agentName;
1105
1126
  const header = renderStatusLine(
1106
1127
  {
1107
- icon,
1128
+ icon: icon === "success" ? undefined : icon,
1129
+ iconOverride: icon === "success" ? theme.styledSymbol("status.done", "accent") : undefined,
1108
1130
  title: "Task",
1109
- meta: agentCount > 0 ? [`${agentCount} ${agentCount === 1 ? "agent" : "agents"}`] : undefined,
1131
+ meta: metaLabel ? [metaLabel] : undefined,
1110
1132
  },
1111
1133
  theme,
1112
1134
  );
package/src/tools/ask.ts CHANGED
@@ -96,7 +96,7 @@ const OTHER_OPTION = "Other (type your own)";
96
96
  const RECOMMENDED_SUFFIX = " (Recommended)";
97
97
 
98
98
  function getDoneOptionLabel(): string {
99
- return `${theme.status.success} Done selecting`;
99
+ return `${theme.symbol("tool.ask")} Done selecting`;
100
100
  }
101
101
 
102
102
  /** Add "(Recommended)" suffix to the option at the given index if not already present */
@@ -407,6 +407,12 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
407
407
  readonly description: string;
408
408
  readonly parameters = askSchema;
409
409
  readonly strict = true;
410
+ // Run alone in its tool batch. The interactive selector/editor is a single
411
+ // shared UI surface (`ExtensionUiController.showHookSelector` has no queue and
412
+ // overwrites `ctx.hookSelector` on each call), so two concurrent `ask` calls
413
+ // would clobber each other: the second steals focus and orphans the first,
414
+ // whose promise then hangs until the user aborts the whole turn.
415
+ readonly concurrency = "exclusive";
410
416
  readonly loadMode = "discoverable";
411
417
 
412
418
  constructor(private readonly session: ToolSession) {
@@ -621,9 +627,7 @@ interface AskRenderArgs {
621
627
  /** Render a custom free-text answer as a status line plus indented continuation rows. */
622
628
  function renderCustomInputLines(uiTheme: Theme, customInput: string): string[] {
623
629
  const lines = customInput.split("\n");
624
- const out: string[] = [
625
- ` ${uiTheme.styledSymbol("status.success", "success")} ${uiTheme.fg("toolOutput", lines[0] ?? "")}`,
626
- ];
630
+ const out: string[] = [` ${uiTheme.styledSymbol("tool.ask", "accent")} ${uiTheme.fg("toolOutput", lines[0] ?? "")}`];
627
631
  for (let i = 1; i < lines.length; i++) out.push(` ${uiTheme.fg("toolOutput", lines[i])}`);
628
632
  return out;
629
633
  }
@@ -814,7 +818,12 @@ export const askToolRenderer = {
814
818
  const question = details.question;
815
819
  const hasSelection =
816
820
  details.customInput !== undefined || (details.selectedOptions && details.selectedOptions.length > 0);
817
- const header = renderStatusLine({ icon: hasSelection ? "success" : "warning", title: "Ask" }, uiTheme);
821
+ const header = renderStatusLine(
822
+ hasSelection
823
+ ? { iconOverride: uiTheme.styledSymbol("tool.ask", "accent"), title: "Ask" }
824
+ : { icon: "warning", title: "Ask" },
825
+ uiTheme,
826
+ );
818
827
  const dOptions = details.options;
819
828
  const dSelected = details.selectedOptions;
820
829
  const dMulti = details.multi;
@@ -246,7 +246,7 @@ class BashInteractiveOverlayComponent implements Component {
246
246
  this.#state === "running"
247
247
  ? formatStatusIcon("running", this.uiTheme)
248
248
  : this.#state === "complete" && this.#exitCode === 0
249
- ? formatStatusIcon("success", this.uiTheme)
249
+ ? this.uiTheme.styledSymbol("tool.bash", "accent")
250
250
  : formatStatusIcon("warning", this.uiTheme);
251
251
  const title = this.uiTheme.fg("accent", "Console");
252
252
  const statusBadge = `${this.uiTheme.fg("dim", this.uiTheme.format.bracketLeft)}${this.#stateText()}${this.uiTheme.fg("dim", this.uiTheme.format.bracketRight)}`;
package/src/tools/bash.ts CHANGED
@@ -1151,11 +1151,23 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
1151
1151
  const renderArgs = toBashRenderArgs(args, config);
1152
1152
  const cmdLines = args ? formatBashCommandLines(renderArgs, uiTheme) : undefined;
1153
1153
  const isError = result.isError === true;
1154
- const icon = options.isPartial ? "pending" : isError ? "error" : "success";
1154
+ const isPartial = options.isPartial === true;
1155
+ const success = !isPartial && !isError;
1155
1156
  const header =
1156
1157
  config.showHeader === false
1157
1158
  ? undefined
1158
- : renderStatusLine({ icon, title: config.resolveTitle(args, options) }, uiTheme);
1159
+ : renderStatusLine(
1160
+ success
1161
+ ? {
1162
+ iconOverride: uiTheme.styledSymbol("tool.bash", "accent"),
1163
+ title: config.resolveTitle(args, options),
1164
+ }
1165
+ : {
1166
+ icon: isPartial ? "pending" : "error",
1167
+ title: config.resolveTitle(args, options),
1168
+ },
1169
+ uiTheme,
1170
+ );
1159
1171
  const details = result.details;
1160
1172
  const outputBlock = new CachedOutputBlock();
1161
1173
 
@@ -146,7 +146,7 @@ function renderOpenOrCloseLine(
146
146
  const action = (details?.action ?? args.action ?? "open") as "open" | "close" | "run";
147
147
  const status = cellStatus(isPartial, isError);
148
148
  const icon =
149
- status === "complete" ? "success" : status === "error" ? "error" : status === "running" ? "running" : "pending";
149
+ status === "complete" ? "done" : status === "error" ? "error" : status === "running" ? "running" : "pending";
150
150
 
151
151
  let title: string;
152
152
  if (action === "close") {
@@ -163,7 +163,10 @@ function renderOpenOrCloseLine(
163
163
  const url = details?.url ?? args.url;
164
164
  if (url) meta.push(shortenPath(url));
165
165
 
166
- const header = renderStatusLine({ icon, title, meta }, theme);
166
+ const header =
167
+ status === "complete"
168
+ ? renderStatusLine({ iconOverride: theme.styledSymbol("tool.browser", "accent"), title, meta }, theme)
169
+ : renderStatusLine({ icon, title, meta }, theme);
167
170
  if (!output) return new Text(header, 0, 0);
168
171
  const outputLines = output.split("\n").map(line => theme.fg("toolOutput", replaceTabs(line)));
169
172
  return new Text([header, ...outputLines].join("\n"), 0, 0);