@oh-my-pi/pi-coding-agent 16.0.10 → 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 (135) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/dist/cli.js +3344 -3371
  3. package/dist/types/advisor/index.d.ts +1 -0
  4. package/dist/types/advisor/transcript-recorder.d.ts +52 -0
  5. package/dist/types/commit/agentic/agent.d.ts +1 -1
  6. package/dist/types/config/settings-schema.d.ts +14 -8
  7. package/dist/types/edit/file-snapshot-store.d.ts +1 -1
  8. package/dist/types/extensibility/extensions/types.d.ts +7 -0
  9. package/dist/types/modes/components/__tests__/skill-message.test.d.ts +1 -0
  10. package/dist/types/modes/components/agent-hub.d.ts +6 -1
  11. package/dist/types/modes/components/agent-transcript-viewer.d.ts +39 -0
  12. package/dist/types/modes/components/assistant-message.d.ts +8 -0
  13. package/dist/types/modes/components/cache-invalidation-marker.d.ts +34 -0
  14. package/dist/types/modes/components/chat-transcript-builder.d.ts +42 -0
  15. package/dist/types/modes/components/compaction-summary-message.d.ts +14 -1
  16. package/dist/types/modes/components/index.d.ts +0 -1
  17. package/dist/types/modes/components/message-frame.d.ts +6 -4
  18. package/dist/types/modes/controllers/command-controller.d.ts +3 -2
  19. package/dist/types/modes/interactive-mode.d.ts +4 -2
  20. package/dist/types/modes/theme/theme.d.ts +7 -1
  21. package/dist/types/modes/types.d.ts +9 -2
  22. package/dist/types/registry/agent-registry.d.ts +10 -3
  23. package/dist/types/sdk.d.ts +1 -1
  24. package/dist/types/session/agent-session.d.ts +20 -1
  25. package/dist/types/session/compact-modes.d.ts +60 -0
  26. package/dist/types/session/session-context.d.ts +7 -0
  27. package/dist/types/session/session-dump-format.d.ts +1 -0
  28. package/dist/types/session/streaming-output.d.ts +0 -2
  29. package/dist/types/session/tool-choice-queue.d.ts +14 -0
  30. package/dist/types/system-prompt.d.ts +3 -3
  31. package/dist/types/tools/__tests__/json-tree.test.d.ts +1 -0
  32. package/dist/types/tools/index.d.ts +4 -0
  33. package/dist/types/tools/resolve.d.ts +15 -5
  34. package/package.json +12 -12
  35. package/src/advisor/index.ts +1 -0
  36. package/src/advisor/transcript-recorder.ts +136 -0
  37. package/src/cli/stats-cli.ts +2 -11
  38. package/src/collab/host.ts +25 -13
  39. package/src/commit/agentic/agent.ts +2 -1
  40. package/src/commit/agentic/tools/git-file-diff.ts +2 -2
  41. package/src/commit/changelog/index.ts +1 -1
  42. package/src/commit/map-reduce/map-phase.ts +1 -1
  43. package/src/commit/map-reduce/utils.ts +1 -1
  44. package/src/config/settings-schema.ts +16 -9
  45. package/src/config/settings.ts +0 -6
  46. package/src/debug/log-viewer.ts +4 -4
  47. package/src/debug/raw-sse.ts +4 -4
  48. package/src/edit/file-snapshot-store.ts +1 -1
  49. package/src/edit/renderer.ts +9 -9
  50. package/src/eval/js/tool-bridge.ts +3 -2
  51. package/src/eval/py/prelude.py +3 -2
  52. package/src/export/html/tool-views.generated.js +28 -28
  53. package/src/extensibility/extensions/types.ts +7 -0
  54. package/src/hindsight/mental-models.ts +1 -1
  55. package/src/internal-urls/docs-index.generated.txt +1 -1
  56. package/src/internal-urls/history-protocol.ts +8 -3
  57. package/src/irc/bus.ts +8 -0
  58. package/src/lsp/index.ts +2 -2
  59. package/src/lsp/render.ts +7 -7
  60. package/src/main.ts +4 -1
  61. package/src/modes/acp/acp-agent.ts +63 -0
  62. package/src/modes/components/__tests__/skill-message.test.ts +92 -0
  63. package/src/modes/components/agent-dashboard.ts +1 -1
  64. package/src/modes/components/agent-hub.ts +97 -920
  65. package/src/modes/components/agent-transcript-viewer.ts +461 -0
  66. package/src/modes/components/assistant-message.ts +21 -0
  67. package/src/modes/components/cache-invalidation-marker.ts +84 -0
  68. package/src/modes/components/chat-transcript-builder.ts +476 -0
  69. package/src/modes/components/compaction-summary-message.ts +29 -1
  70. package/src/modes/components/custom-message.ts +4 -1
  71. package/src/modes/components/diff.ts +12 -35
  72. package/src/modes/components/dynamic-border.ts +1 -1
  73. package/src/modes/components/extensions/extension-dashboard.ts +1 -1
  74. package/src/modes/components/extensions/inspector-panel.ts +5 -5
  75. package/src/modes/components/hook-selector.ts +2 -2
  76. package/src/modes/components/index.ts +0 -1
  77. package/src/modes/components/message-frame.ts +10 -6
  78. package/src/modes/components/model-selector.ts +2 -2
  79. package/src/modes/components/overlay-box.ts +10 -9
  80. package/src/modes/components/skill-message.ts +39 -19
  81. package/src/modes/components/tiny-title-download-progress.ts +1 -1
  82. package/src/modes/components/welcome.ts +1 -1
  83. package/src/modes/controllers/command-controller.ts +12 -2
  84. package/src/modes/controllers/event-controller.ts +15 -1
  85. package/src/modes/controllers/input-controller.ts +8 -1
  86. package/src/modes/controllers/selector-controller.ts +11 -1
  87. package/src/modes/interactive-mode.ts +13 -3
  88. package/src/modes/theme/theme.ts +14 -0
  89. package/src/modes/types.ts +9 -2
  90. package/src/modes/utils/ui-helpers.ts +20 -2
  91. package/src/prompts/steering/user-interjection.md +3 -4
  92. package/src/prompts/tools/read.md +1 -1
  93. package/src/registry/agent-registry.ts +13 -4
  94. package/src/sdk.ts +9 -7
  95. package/src/session/agent-session.ts +182 -16
  96. package/src/session/compact-modes.ts +105 -0
  97. package/src/session/messages.ts +7 -9
  98. package/src/session/session-context.ts +54 -7
  99. package/src/session/session-dump-format.ts +4 -2
  100. package/src/session/session-history-format.ts +1 -1
  101. package/src/session/snapcompact-inline.ts +2 -2
  102. package/src/session/streaming-output.ts +5 -5
  103. package/src/session/tool-choice-queue.ts +59 -0
  104. package/src/slash-commands/builtin-registry.ts +16 -4
  105. package/src/system-prompt.ts +10 -9
  106. package/src/task/executor.ts +1 -1
  107. package/src/task/output-manager.ts +5 -0
  108. package/src/tools/__tests__/json-tree.test.ts +35 -0
  109. package/src/tools/approval.ts +1 -1
  110. package/src/tools/bash-interactive.ts +4 -4
  111. package/src/tools/bash.ts +0 -1
  112. package/src/tools/browser.ts +0 -1
  113. package/src/tools/eval.ts +1 -1
  114. package/src/tools/gh.ts +1 -1
  115. package/src/tools/index.ts +4 -0
  116. package/src/tools/irc.ts +1 -1
  117. package/src/tools/json-tree.ts +22 -5
  118. package/src/tools/read.ts +5 -6
  119. package/src/tools/resolve.ts +66 -41
  120. package/src/tui/output-block.ts +9 -9
  121. package/src/web/scrapers/firefox-addons.ts +1 -1
  122. package/src/web/scrapers/github.ts +1 -1
  123. package/src/web/scrapers/go-pkg.ts +2 -2
  124. package/src/web/scrapers/metacpan.ts +2 -2
  125. package/src/web/scrapers/nvd.ts +2 -2
  126. package/src/web/scrapers/ollama.ts +1 -1
  127. package/src/web/scrapers/opencorporates.ts +1 -1
  128. package/src/web/scrapers/pub-dev.ts +1 -1
  129. package/src/web/scrapers/repology.ts +1 -1
  130. package/src/web/scrapers/sourcegraph.ts +1 -1
  131. package/src/web/scrapers/terraform.ts +6 -6
  132. package/src/web/scrapers/wikidata.ts +2 -2
  133. package/src/workspace-tree.ts +1 -1
  134. package/dist/types/modes/components/branch-summary-message.d.ts +0 -13
  135. package/src/modes/components/branch-summary-message.ts +0 -46
@@ -40,9 +40,12 @@ export class HistoryProtocolHandler implements ProtocolHandler {
40
40
  async resolve(url: InternalUrl): Promise<InternalResource> {
41
41
  const agentId = url.rawHost || url.hostname;
42
42
  const registry = AgentRegistry.global();
43
+ // Advisor transcripts are observability-only — surfaced in the Agent Hub, never
44
+ // in the agent-facing roster. Hide them from the index, lookup, and completions.
45
+ const visible = registry.list().filter(ref => ref.kind !== "advisor");
43
46
 
44
47
  if (!agentId) {
45
- const content = this.#renderIndex(registry.list());
48
+ const content = this.#renderIndex(visible);
46
49
  return {
47
50
  url: url.href,
48
51
  content,
@@ -52,13 +55,14 @@ export class HistoryProtocolHandler implements ProtocolHandler {
52
55
  }
53
56
 
54
57
  let ref = registry.get(agentId);
58
+ if (ref?.kind === "advisor") ref = undefined;
55
59
  if (!ref) {
56
60
  // Case-insensitive fallback: agent ids are human-typed (e.g. AuthLoader).
57
61
  const lower = agentId.toLowerCase();
58
- ref = registry.list().find(candidate => candidate.id.toLowerCase() === lower);
62
+ ref = visible.find(candidate => candidate.id.toLowerCase() === lower);
59
63
  }
60
64
  if (!ref) {
61
- const known = registry.list().map(candidate => candidate.id);
65
+ const known = visible.map(candidate => candidate.id);
62
66
  const knownStr = known.length > 0 ? known.join(", ") : "none";
63
67
  throw new Error(`Unknown agent: ${agentId}\nKnown agents: ${knownStr}\nList all with history://`);
64
68
  }
@@ -105,6 +109,7 @@ export class HistoryProtocolHandler implements ProtocolHandler {
105
109
  async complete(): Promise<UrlCompletion[]> {
106
110
  return AgentRegistry.global()
107
111
  .list()
112
+ .filter(ref => ref.kind !== "advisor")
108
113
  .map(ref => ({
109
114
  value: ref.id,
110
115
  description: `${ref.status} · ${ref.kind}${ref.parentId ? ` · parent ${ref.parentId}` : ""}`,
package/src/irc/bus.ts CHANGED
@@ -98,6 +98,14 @@ export class IrcBus {
98
98
  if (!ref || ref.status === "aborted") {
99
99
  return { to: message.to, outcome: "failed", error: `Unknown or terminated agent "${message.to}".` };
100
100
  }
101
+ // Advisor refs are observability-only transcripts, never messageable peers.
102
+ if (ref.kind === "advisor") {
103
+ return {
104
+ to: message.to,
105
+ outcome: "failed",
106
+ error: `Agent "${message.to}" is a read-only advisor transcript and cannot be messaged.`,
107
+ };
108
+ }
101
109
 
102
110
  let revived = false;
103
111
  if (ref.status === "parked") {
package/src/lsp/index.ts CHANGED
@@ -598,7 +598,7 @@ async function runWorkspaceDiagnostics(
598
598
  // Limit output length
599
599
  const lines = combined.split("\n");
600
600
  if (lines.length > 50) {
601
- return { output: `${lines.slice(0, 50).join("\n")}\n... and ${lines.length - 50} more lines`, projectType };
601
+ return { output: `${lines.slice(0, 50).join("\n")}\n[…${lines.length - 50}ln elided…]`, projectType };
602
602
  }
603
603
  return { output: combined, projectType };
604
604
  } catch (e) {
@@ -2035,7 +2035,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
2035
2035
  const lines = limitedSymbols.map(s => formatSymbolInformation(s, this.session.cwd));
2036
2036
  const truncationLine =
2037
2037
  dedupedSymbols.length > WORKSPACE_SYMBOL_LIMIT
2038
- ? `\n... ${dedupedSymbols.length - WORKSPACE_SYMBOL_LIMIT} additional symbol(s) omitted`
2038
+ ? `\n[…${dedupedSymbols.length - WORKSPACE_SYMBOL_LIMIT} symbols elided…]`
2039
2039
  : "";
2040
2040
  return {
2041
2041
  content: [
package/src/lsp/render.ts CHANGED
@@ -223,10 +223,10 @@ function renderHover(
223
223
  const langLabel = lang ? theme.fg("mdCodeBlockBorder", ` ${lang}`) : "";
224
224
 
225
225
  if (expanded) {
226
- const h = theme.boxSharp.horizontal;
227
- const v = theme.boxSharp.vertical;
228
- const top = `${theme.boxSharp.topLeft}${h.repeat(3)}`;
229
- const bottom = `${theme.boxSharp.bottomLeft}${h.repeat(3)}`;
226
+ const h = theme.boxRound.horizontal;
227
+ const v = theme.boxRound.vertical;
228
+ const top = `${theme.boxRound.topLeft}${h.repeat(3)}`;
229
+ const bottom = `${theme.boxRound.bottomLeft}${h.repeat(3)}`;
230
230
  let output = `${icon}${langLabel}`;
231
231
  if (beforeCode) {
232
232
  for (const line of beforeCode.split("\n")) {
@@ -254,9 +254,9 @@ function renderHover(
254
254
  const preview = truncateToWidth(beforeCode, TRUNCATE_LENGTHS.TITLE);
255
255
  output += `\n ${theme.fg("dim", theme.tree.branch)} ${theme.fg("muted", preview)}`;
256
256
  }
257
- const h = theme.boxSharp.horizontal;
258
- const v = theme.boxSharp.vertical;
259
- const bottom = `${theme.boxSharp.bottomLeft}${h.repeat(3)}`;
257
+ const h = theme.boxRound.horizontal;
258
+ const v = theme.boxRound.vertical;
259
+ const bottom = `${theme.boxRound.bottomLeft}${h.repeat(3)}`;
260
260
  output += `\n ${theme.fg("mdCodeBlockBorder", v)} ${firstCodeLine}`;
261
261
 
262
262
  if (codeLines.length > 1) {
package/src/main.ts CHANGED
@@ -133,9 +133,12 @@ const HOST_DEFAULTED_SETTING_PATHS: SettingPath[] = [
133
133
  "memory.backend",
134
134
  "memories.enabled",
135
135
  // Advisor is interactive-session assistance. Protocol hosts opt in explicitly
136
- // instead of inheriting a user's globally-enabled local preference.
136
+ // instead of inheriting a user's globally-enabled local preference, and when
137
+ // they do opt in they get the default tuning rather than the user's local tuning.
137
138
  "advisor.enabled",
138
139
  "advisor.subagents",
140
+ "advisor.syncBacklog",
141
+ "advisor.immuneTurns",
139
142
  ];
140
143
 
141
144
  const RPC_BACKGROUND_DEFAULTED_SETTING_PATHS: SettingPath[] = [
@@ -68,10 +68,17 @@ import type { SessionInfo as StoredSessionInfo } from "../../session/session-lis
68
68
  import { SessionManager } from "../../session/session-manager";
69
69
  import { executeAcpBuiltinSlashCommand } from "../../slash-commands/acp-builtins";
70
70
  import { buildAvailableSlashCommands, toAcpAvailableCommands } from "../../slash-commands/available-commands";
71
+ import { DEFAULT_STT_MODEL_KEY, STT_MODEL_OPTIONS } from "../../stt/models";
71
72
  import { AUTO_THINKING, parseConfiguredThinkingLevel } from "../../thinking";
72
73
  import { normalizeLocalScheme } from "../../tools/path-utils";
73
74
  import { runResolveInvocation } from "../../tools/resolve";
74
75
  import { ToolError } from "../../tools/tool-errors";
76
+ import {
77
+ DEFAULT_TTS_LOCAL_MODEL_KEY,
78
+ DEFAULT_TTS_VOICE,
79
+ TTS_LOCAL_MODELS,
80
+ TTS_LOCAL_VOICE_OPTIONS,
81
+ } from "../../tts/models";
75
82
  import { canonicalizeMessage } from "../../utils/thinking-display";
76
83
  import { createAcpClientBridge } from "./acp-client-bridge";
77
84
  import {
@@ -91,6 +98,7 @@ const MODEL_CONFIG_ID = "model";
91
98
  const THINKING_CONFIG_ID = "thinking";
92
99
  const THINKING_OFF = "off";
93
100
  const SESSION_PAGE_SIZE = 50;
101
+ const SPEECH_MODELS_LIST_METHOD = "speech.models.list";
94
102
  /**
95
103
  * Delay between `session/new` (or `session/load` / `session/resume` /
96
104
  * `unstable_session/fork`) returning and the agent firing the first
@@ -195,6 +203,59 @@ type MCPSourceMap = {
195
203
 
196
204
  type CreateAcpSession = (cwd: string) => Promise<AgentSession>;
197
205
 
206
+ type AcpSpeechOption = {
207
+ value: string;
208
+ label: string;
209
+ description?: string;
210
+ };
211
+
212
+ type AcpSpeechVoiceOption = {
213
+ value: string;
214
+ label: string;
215
+ };
216
+
217
+ type AcpSpeechTtsModelOption = AcpSpeechOption & {
218
+ voices: AcpSpeechVoiceOption[];
219
+ };
220
+
221
+ function buildAcpSpeechModelsCatalog(): Record<string, unknown> {
222
+ const voices = TTS_LOCAL_VOICE_OPTIONS.map(({ value, label }) => ({ value, label }));
223
+ return {
224
+ settings: {
225
+ speechToTextModel: "stt.modelName",
226
+ textToSpeechModel: "tts.localModel",
227
+ textToSpeechVoice: "tts.localVoice",
228
+ speechVoice: "speech.voice",
229
+ },
230
+ defaults: {
231
+ speechToTextModel: DEFAULT_STT_MODEL_KEY,
232
+ textToSpeechModel: DEFAULT_TTS_LOCAL_MODEL_KEY,
233
+ voice: DEFAULT_TTS_VOICE,
234
+ },
235
+ speechToText: {
236
+ setting: "stt.modelName",
237
+ defaultValue: DEFAULT_STT_MODEL_KEY,
238
+ models: STT_MODEL_OPTIONS.map(({ value, label, description }) => ({ value, label, description })),
239
+ },
240
+ textToSpeech: {
241
+ modelSetting: "tts.localModel",
242
+ voiceSetting: "tts.localVoice",
243
+ speechVoiceSetting: "speech.voice",
244
+ defaultModel: DEFAULT_TTS_LOCAL_MODEL_KEY,
245
+ defaultVoice: DEFAULT_TTS_VOICE,
246
+ models: TTS_LOCAL_MODELS.map(
247
+ ({ key, label, description, voices: modelVoices }): AcpSpeechTtsModelOption => ({
248
+ value: key,
249
+ label,
250
+ description,
251
+ voices: modelVoices.map(({ id, label: voiceLabel }) => ({ value: id, label: voiceLabel })),
252
+ }),
253
+ ),
254
+ voices,
255
+ },
256
+ };
257
+ }
258
+
198
259
  /**
199
260
  * Bridge a single ExtensionUIContext call to the ACP `unstable_createElicitation`
200
261
  * surface. Skills/extensions ask for one value at a time (a chosen option, a
@@ -850,6 +911,8 @@ export class AcpAgent implements Agent {
850
911
 
851
912
  async extMethod(method: string, params: { [key: string]: unknown }): Promise<{ [key: string]: unknown }> {
852
913
  switch (method) {
914
+ case SPEECH_MODELS_LIST_METHOD:
915
+ return buildAcpSpeechModelsCatalog();
853
916
  case "_omp/sessions/listAll": {
854
917
  const limit = typeof params.limit === "number" ? Math.max(1, Math.min(5000, params.limit as number)) : 1000;
855
918
  const sessions = await SessionManager.listAll();
@@ -0,0 +1,92 @@
1
+ import { beforeAll, describe, expect, it } from "bun:test";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { Settings } from "../../../config/settings";
5
+ import type { CustomMessage, SkillPromptDetails } from "../../../session/messages";
6
+ import { getThemeByName, setThemeInstance, type Theme } from "../../theme/theme";
7
+ import { SkillMessageComponent } from "../skill-message";
8
+
9
+ // Drop SGR colors and OSC 8 hyperlink wrappers so assertions see the visible text only.
10
+ const strip = (lines: readonly string[]): string =>
11
+ lines
12
+ .join("\n")
13
+ .replace(/\x1b\]8;[^\x1b\x07]*(?:\x07|\x1b\\)/g, "")
14
+ .replace(/\x1b\[[0-9;]*m/g, "");
15
+
16
+ function makeMessage(
17
+ details: SkillPromptDetails,
18
+ content = "Use the atomic-commit workflow.",
19
+ ): CustomMessage<SkillPromptDetails> {
20
+ return { role: "custom", customType: "skill-prompt", content, display: true, details, timestamp: Date.now() };
21
+ }
22
+
23
+ describe("SkillMessageComponent", () => {
24
+ let uiTheme: Theme;
25
+
26
+ beforeAll(async () => {
27
+ await Settings.init({ inMemory: true });
28
+ const loaded = await getThemeByName("dark");
29
+ if (!loaded) throw new Error("theme unavailable");
30
+ uiTheme = loaded;
31
+ setThemeInstance(uiTheme);
32
+ });
33
+
34
+ const skillPath = path.join(os.homedir(), ".agent/skills/atomic-commit/SKILL.md");
35
+
36
+ it("renders a compact, outlined card instead of the archaic key:value dump", () => {
37
+ const component = new SkillMessageComponent(
38
+ makeMessage({ name: "atomic-commit", path: skillPath, lineCount: 88 }),
39
+ );
40
+ const text = strip(component.render(80));
41
+
42
+ // New look: an icon-tagged "skill" header with the name and a single meta line.
43
+ expect(text).toContain("skill");
44
+ expect(text).toContain("atomic-commit");
45
+ expect(text).toContain("88 lines");
46
+
47
+ // The card is drawn with an outline.
48
+ expect(text).toContain(uiTheme.boxRound.topLeft);
49
+ expect(text).toContain(uiTheme.boxRound.bottomRight);
50
+
51
+ // Path is home-shortened and never leaks the absolute home dir.
52
+ expect(text).toContain("~/.agent/skills/atomic-commit/SKILL.md");
53
+ expect(text).not.toContain(os.homedir());
54
+
55
+ // The old archaic framing is gone.
56
+ expect(text).not.toContain("[skill]");
57
+ expect(text).not.toContain("Skill:");
58
+ expect(text).not.toContain("Path:");
59
+ expect(text).not.toContain("Prompt:");
60
+ });
61
+
62
+ it("flattens multi-line args onto the single-line header", () => {
63
+ const component = new SkillMessageComponent(
64
+ makeMessage({ name: "atomic-commit", path: skillPath, lineCount: 88, args: "stage all\nthen split" }),
65
+ );
66
+ const text = strip(component.render(80));
67
+ // Whitespace (including the newline) collapsed to single spaces so the header can't break.
68
+ expect(text).toContain("stage all then split");
69
+ expect(text).not.toContain("stage all\nthen split");
70
+ });
71
+
72
+ it("uses a singular unit for a one-line prompt", () => {
73
+ const component = new SkillMessageComponent(makeMessage({ name: "tiny", path: skillPath, lineCount: 1 }));
74
+ const text = strip(component.render(80));
75
+ expect(text).toContain("1 line");
76
+ expect(text).not.toContain("1 lines");
77
+ });
78
+
79
+ it("reveals the prompt body under a calm subheader only when expanded", () => {
80
+ const details: SkillPromptDetails = { name: "atomic-commit", path: skillPath, lineCount: 88 };
81
+ const body = "Step one: stage hunks.";
82
+
83
+ const collapsed = new SkillMessageComponent(makeMessage(details, body));
84
+ expect(strip(collapsed.render(80))).not.toContain(body);
85
+
86
+ const expanded = new SkillMessageComponent(makeMessage(details, body));
87
+ expanded.setExpanded(true);
88
+ const text = strip(expanded.render(80));
89
+ expect(text).toContain("prompt");
90
+ expect(text).toContain(body);
91
+ });
92
+ });
@@ -321,7 +321,7 @@ class TwoColumnBody implements Component {
321
321
  const rightLines = this.rightPane.render(rightWidth);
322
322
  const lineCount = this.maxHeight;
323
323
  const out: string[] = [];
324
- const separator = theme.fg("dim", ` ${theme.boxSharp.vertical} `);
324
+ const separator = theme.fg("dim", ` ${theme.boxRound.vertical} `);
325
325
 
326
326
  for (let i = 0; i < lineCount; i++) {
327
327
  const left = truncateToWidth(leftLines[i] ?? "", leftWidth);