@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
package/src/tools/gh.ts CHANGED
@@ -2179,7 +2179,7 @@ function formatPrFiles(files: GhPrFile[] | undefined): string[] {
2179
2179
  }
2180
2180
 
2181
2181
  if (files.length > FILE_PREVIEW_LIMIT) {
2182
- lines.push(`- ... ${files.length - FILE_PREVIEW_LIMIT} more files`);
2182
+ lines.push(`[…${files.length - FILE_PREVIEW_LIMIT} files elided…]`);
2183
2183
  }
2184
2184
 
2185
2185
  return lines;
@@ -312,6 +312,10 @@ export interface ToolSession {
312
312
  steer?(message: { customType: string; content: string; details?: unknown }): void;
313
313
  /** Peek the currently in-flight tool-choice queue directive's invocation handler. Used by the `resolve` tool to dispatch to the pending action. */
314
314
  peekQueueInvoker?(): ((input: unknown) => Promise<unknown> | unknown) | undefined;
315
+ /** Peek the most-recently registered non-forcing pending preview invoker. The `resolve`
316
+ * tool dispatches to it so a staged preview resolves WITHOUT forcing tool_choice — the
317
+ * agent-loop's SoftToolRequirement lifecycle owns reminder injection and escalation. */
318
+ peekPendingInvoker?(): ((input: unknown) => Promise<unknown> | unknown) | undefined;
315
319
  /** Peek the long-lived "standing" resolve handler registered by a mode (e.g. plan mode).
316
320
  * Consulted by the `resolve` tool as a fallback when no queue invoker is in flight,
317
321
  * letting modes accept `resolve` invocations without forcing the tool choice every turn. */
package/src/tools/irc.ts CHANGED
@@ -182,7 +182,7 @@ export class IrcTool implements AgentTool<typeof ircSchema, IrcDetails> {
182
182
  const bus = IrcBus.global();
183
183
  const peers = registry
184
184
  .list()
185
- .filter(ref => ref.id !== senderId && ref.status !== "aborted")
185
+ .filter(ref => ref.id !== senderId && ref.status !== "aborted" && ref.kind !== "advisor")
186
186
  .map(ref => ({
187
187
  id: ref.id,
188
188
  displayName: ref.displayName,
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * JSON tree rendering utilities shared across tool renderers.
3
3
  */
4
- import { INTENT_FIELD } from "@oh-my-pi/pi-agent-core";
4
+ import { INTENT_FIELD } from "@oh-my-pi/pi-wire";
5
5
  import type { Theme } from "../modes/theme/theme";
6
6
  import { truncateToWidth } from "./render-utils";
7
7
 
@@ -19,6 +19,8 @@ const ARGS_INLINE_PAIR_SEP = ", ";
19
19
  const ARGS_INLINE_PAIR_SEP_WIDTH = Bun.stringWidth(ARGS_INLINE_PAIR_SEP);
20
20
  const ARGS_INLINE_MORE = "…";
21
21
  const ARGS_INLINE_MORE_WIDTH = Bun.stringWidth(ARGS_INLINE_MORE);
22
+ /** Minimal value footprint (quotes + a couple chars) reserved for each not-yet-rendered key. */
23
+ const ARGS_INLINE_TAIL_VALUE_RESERVE = 4;
22
24
 
23
25
  function isRecord(value: unknown): value is Record<string, unknown> {
24
26
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -49,10 +51,15 @@ export function formatScalar(value: unknown, maxLen: number): string {
49
51
  * Format args inline for collapsed view.
50
52
  */
51
53
  export function formatArgsInline(args: Record<string, unknown>, maxWidth: number): string {
52
- let result = "";
53
- let width = 0;
54
+ const keys: string[] = [];
54
55
  for (const key in args) {
55
56
  if (key in HIDDEN_ARG_KEYS) continue;
57
+ keys.push(key);
58
+ }
59
+ let result = "";
60
+ let width = 0;
61
+ for (let i = 0; i < keys.length; i++) {
62
+ const key = keys[i];
56
63
  const value = args[key];
57
64
  const sep = width > 0 ? ARGS_INLINE_PAIR_SEP : "";
58
65
  const sepW = width > 0 ? ARGS_INLINE_PAIR_SEP_WIDTH : 0;
@@ -61,11 +68,21 @@ export function formatArgsInline(args: Record<string, unknown>, maxWidth: number
61
68
  if (cap <= 0) {
62
69
  return `${result}${ARGS_INLINE_MORE}`;
63
70
  }
64
- const valueMaxLen = Math.min(maxWidth - current, 24);
71
+ // Reserve each still-pending key's minimal footprint (sep + name + `=` +
72
+ // a short value) so a long value can't starve the keys that follow it.
73
+ let tailReserve = 0;
74
+ for (let j = i + 1; j < keys.length; j++) {
75
+ tailReserve += ARGS_INLINE_PAIR_SEP_WIDTH + Bun.stringWidth(keys[j]) + 1 + ARGS_INLINE_TAIL_VALUE_RESERVE;
76
+ }
77
+ // Budget the whole `key=value` piece against the width left after the
78
+ // tail reserve, then back out the value's share. The last key reserves
79
+ // nothing and fills the line.
80
+ const pieceBudget = Math.min(cap, maxWidth - current - tailReserve);
81
+ const valueMaxLen = Math.max(1, pieceBudget - Bun.stringWidth(key) - 3);
65
82
  const valueStr = formatScalar(value, valueMaxLen);
66
83
  const piece = `${key}=${valueStr}`;
67
84
  const pieceW = Bun.stringWidth(piece);
68
- if (pieceW > cap) {
85
+ if (pieceW > pieceBudget) {
69
86
  return `${result}${sep}${truncateToWidth(piece, cap)}`;
70
87
  }
71
88
  result += sep + piece;
package/src/tools/read.ts CHANGED
@@ -275,7 +275,7 @@ function formatMergedBraceLine(
275
275
  shouldAddHashLines: boolean,
276
276
  shouldAddLineNumbers: boolean,
277
277
  ): { model: string; display: string } {
278
- const merged = `${headText.trimEnd()} .. ${tailText.trim()}`;
278
+ const merged = `${headText.trimEnd()} ${tailText.trim()}`;
279
279
  if (shouldAddHashLines) {
280
280
  return { model: `${startLine}-${endLine}:${merged}`, display: merged };
281
281
  }
@@ -315,7 +315,7 @@ const FOOTER_RANGE_SAMPLES = 2;
315
315
 
316
316
  /**
317
317
  * Footer appended to summarized reads telling the model how to recover the
318
- * elided body. Without this hint, agents either ignore the `...`/`{ .. }`
318
+ * elided body. Without this hint, agents either ignore the `…`/`{ }`
319
319
  * markers or burn a turn guessing the right selector (see issue #1046). The
320
320
  * footer demonstrates the multi-range selector syntax with concrete sample
321
321
  * ranges drawn from the actual elision so the model re-reads only what it
@@ -327,7 +327,6 @@ function formatSummaryElisionFooter(
327
327
  elidedLines: number,
328
328
  ): string {
329
329
  if (elidedRanges.length === 0) return "";
330
- const lineWord = elidedLines === 1 ? "line" : "lines";
331
330
  const sampleCount = Math.min(elidedRanges.length, FOOTER_RANGE_SAMPLES);
332
331
  const selector = elidedRanges
333
332
  .slice(0, sampleCount)
@@ -335,7 +334,7 @@ function formatSummaryElisionFooter(
335
334
  .join(",");
336
335
  const example = `${readPath}:${selector}`;
337
336
  const tail = elidedRanges.length > sampleCount ? `, e.g. ${example}` : ` with ${example}`;
338
- return `[${elidedLines} ${lineWord} elided; re-read needed ranges${tail}]`;
337
+ return `[…${elidedLines}ln elided; re-read needed ranges${tail}]`;
339
338
  }
340
339
  const READ_CHUNK_SIZE = 8 * 1024;
341
340
 
@@ -1904,8 +1903,8 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
1904
1903
  let elidedLines = 0;
1905
1904
  for (const unit of units) {
1906
1905
  if (unit.kind === "elided") {
1907
- modelParts.push("...");
1908
- displayParts.push("...");
1906
+ modelParts.push("");
1907
+ displayParts.push("");
1909
1908
  elidedRanges.push({ start: unit.startLine, end: unit.endLine });
1910
1909
  elidedLines += unit.endLine - unit.startLine + 1;
1911
1910
  continue;
@@ -1,4 +1,10 @@
1
- import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
1
+ import type {
2
+ AgentTool,
3
+ AgentToolContext,
4
+ AgentToolResult,
5
+ AgentToolUpdateCallback,
6
+ CustomMessage,
7
+ } from "@oh-my-pi/pi-agent-core";
2
8
  import type { Component } from "@oh-my-pi/pi-tui";
3
9
  import { Text } from "@oh-my-pi/pi-tui";
4
10
  import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
@@ -28,11 +34,18 @@ export interface ResolveToolDetails {
28
34
  sourceResultDetails?: unknown;
29
35
  }
30
36
 
37
+ /** Monotonic suffix making each staged preview's pending-invoker id UNIQUE, so
38
+ * stacked previews never clobber one another by label. */
39
+ let pendingPreviewSeq = 0;
40
+
31
41
  /**
32
- * Queue a resolve-protocol handler on the tool-choice queue. Forces the next
33
- * LLM call to invoke the hidden `resolve` tool, wraps the caller's apply/reject
34
- * callbacks into an onInvoked closure that matches the resolve schema, and
35
- * steers a preview reminder so the model understands why.
42
+ * Register a non-forcing resolve-protocol handler for a staged preview. Wraps the
43
+ * caller's apply/reject into an onInvoked closure (matching the resolve schema) and
44
+ * stores it on the tool-choice queue's pending-invoker registry under a UNIQUE id.
45
+ * The `resolve` tool dispatches to it; the agent-loop's SoftToolRequirement
46
+ * lifecycle injects the preview reminder and escalates to a forced `resolve` only
47
+ * if the model declines — so a compliant turn pays ZERO tool_choice change (no
48
+ * prompt-cache messages-cache invalidation).
36
49
  *
37
50
  * This is the canonical entry point for any tool that wants preview/apply
38
51
  * semantics. No session-level abstraction is needed: callers pass their
@@ -48,46 +61,55 @@ export function queueResolveHandler(
48
61
  },
49
62
  ): void {
50
63
  const queue = session.getToolChoiceQueue?.();
51
- const forced = session.buildToolChoice?.("resolve");
52
- if (!queue || !forced || typeof forced === "string") return;
64
+ if (!queue) return;
53
65
 
54
- const steerReminder = (): void => {
55
- session.steer?.({
56
- customType: "resolve-reminder",
57
- content: [
58
- "<system-reminder>",
59
- "This is a preview. Call the `resolve` tool to apply or discard these changes.",
60
- "</system-reminder>",
61
- ].join("\n"),
62
- details: { toolName: options.sourceToolName },
63
- });
64
- };
66
+ // Unique per preview: stacked/sequential previews each get their own entry.
67
+ const id = `pending-action:${options.sourceToolName}:${pendingPreviewSeq++}`;
65
68
 
66
- const pushDirective = (): void => {
67
- queue.pushOnce(forced, {
68
- label: `pending-action:${options.sourceToolName}`,
69
- now: true,
70
- onRejected: () => "requeue",
71
- onInvoked: async (input: unknown) =>
72
- runResolveInvocation(input as ResolveParams, {
73
- sourceToolName: options.sourceToolName,
74
- label: options.label,
75
- apply: options.apply,
76
- reject: options.reject,
77
- onApplyError: () => {
78
- // Apply threw (e.g. ast_edit overlapping replacements). Re-push the
79
- // same directive so the preview remains pending and the model can
80
- // `discard` or fix-and-retry on the next turn instead of being
81
- // stranded with no pending action to address.
82
- pushDirective();
83
- steerReminder();
84
- },
85
- }),
69
+ const onInvoked = async (input: unknown): Promise<AgentToolResult<unknown>> => {
70
+ const result = await runResolveInvocation(input as ResolveParams, {
71
+ sourceToolName: options.sourceToolName,
72
+ label: options.label,
73
+ apply: options.apply,
74
+ reject: options.reject,
75
+ onApplyError: () => {
76
+ // Apply threw (e.g. ast_edit overlapping replacements). Keep the preview
77
+ // pending under the SAME id so the model can `discard` or fix-and-retry;
78
+ // runResolveInvocation rethrows, so the success-path removal below is skipped.
79
+ queue.registerPendingInvoker(id, options.sourceToolName, onInvoked);
80
+ },
86
81
  });
82
+ // Resolved (apply succeeded, or discard): consume the staged action exactly once.
83
+ queue.removePendingInvoker(id);
84
+ return result;
87
85
  };
88
86
 
89
- pushDirective();
90
- steerReminder();
87
+ // NON-FORCING: register so `resolve` can dispatch here WITHOUT changing
88
+ // tool_choice. The agent-loop injects the reminder (from the SoftToolRequirement
89
+ // the session builds) and forces a resolve turn only on non-compliance.
90
+ queue.registerPendingInvoker(id, options.sourceToolName, onInvoked);
91
+ }
92
+
93
+ /**
94
+ * The canonical preview reminder. The resolve mechanism owns the wording; the
95
+ * agent-loop delivers it via the session's `SoftToolRequirement.reminder` (injected
96
+ * once per pending-preview head) instead of a host-side steer, so it lands as a
97
+ * stable mid-history append and never churns the cached prefix.
98
+ */
99
+ export function buildResolveReminderMessage(sourceToolName: string): CustomMessage {
100
+ return {
101
+ role: "custom",
102
+ customType: "resolve-reminder",
103
+ content: [
104
+ "<system-reminder>",
105
+ "This is a preview. Call the `resolve` tool to apply or discard these changes.",
106
+ "</system-reminder>",
107
+ ].join("\n"),
108
+ display: false,
109
+ details: { toolName: sourceToolName },
110
+ attribution: "agent",
111
+ timestamp: Date.now(),
112
+ };
91
113
  }
92
114
 
93
115
  /**
@@ -185,7 +207,10 @@ export class ResolveTool implements AgentTool<typeof resolveSchema, ResolveToolD
185
207
  _context?: AgentToolContext,
186
208
  ): Promise<AgentToolResult<ResolveToolDetails>> {
187
209
  return untilAborted(signal, async () => {
188
- const invoker = this.session.peekQueueInvoker?.() ?? this.session.peekStandingResolveHandler?.();
210
+ const invoker =
211
+ this.session.peekQueueInvoker?.() ??
212
+ this.session.peekPendingInvoker?.() ??
213
+ this.session.peekStandingResolveHandler?.();
189
214
  if (!invoker) {
190
215
  // `discard` is a request to cancel/abort a staged action. When nothing is
191
216
  // pending, the desired end-state (no staged change) already holds, so honor
@@ -48,8 +48,8 @@ function normalizeContentPaddingLeft(value: number | undefined): number {
48
48
 
49
49
  export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): string[] {
50
50
  const { header, headerMeta, state, sections = [], width, applyBg = true } = options;
51
- const h = theme.boxSharp.horizontal;
52
- const v = theme.boxSharp.vertical;
51
+ const h = theme.boxRound.horizontal;
52
+ const v = theme.boxRound.vertical;
53
53
  const cap = h.repeat(3);
54
54
  const lineWidth = Math.max(0, width);
55
55
  // Border colors: running/pending use accent, success uses dim (gray), error/warning keep their colors
@@ -84,8 +84,8 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
84
84
  const rows: BlockRow[] = [];
85
85
  rows.push({
86
86
  kind: "bar",
87
- leftChar: theme.boxSharp.topLeft,
88
- rightChar: theme.boxSharp.topRight,
87
+ leftChar: theme.boxRound.topLeft,
88
+ rightChar: theme.boxRound.topRight,
89
89
  label: header,
90
90
  meta: headerMeta,
91
91
  });
@@ -99,15 +99,15 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
99
99
  if (section.label) {
100
100
  rows.push({
101
101
  kind: "bar",
102
- leftChar: theme.boxSharp.teeRight,
103
- rightChar: theme.boxSharp.teeLeft,
102
+ leftChar: theme.boxRound.teeRight,
103
+ rightChar: theme.boxRound.teeLeft,
104
104
  label: section.label,
105
105
  });
106
106
  } else if (section.separator && sectionIndex > 0) {
107
107
  rows.push({
108
108
  kind: "bar",
109
- leftChar: theme.boxSharp.teeRight,
110
- rightChar: theme.boxSharp.teeLeft,
109
+ leftChar: theme.boxRound.teeRight,
110
+ rightChar: theme.boxRound.teeLeft,
111
111
  });
112
112
  }
113
113
  const allLines = section.lines.flatMap(l => l.split("\n"));
@@ -126,7 +126,7 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
126
126
  }
127
127
  }
128
128
 
129
- rows.push({ kind: "bottom", leftChar: theme.boxSharp.bottomLeft, rightChar: theme.boxSharp.bottomRight });
129
+ rows.push({ kind: "bottom", leftChar: theme.boxRound.bottomLeft, rightChar: theme.boxRound.bottomRight });
130
130
 
131
131
  const H = rows.length;
132
132
 
@@ -173,7 +173,7 @@ export const handleFirefoxAddons: SpecialHandler = async (
173
173
  md += `- ${permission}\n`;
174
174
  }
175
175
  if (permissions.length > preview.length) {
176
- md += `\n*...and ${permissions.length - preview.length} more*\n`;
176
+ md += `\n[…${permissions.length - preview.length} permissions elided…]\n`;
177
177
  }
178
178
  }
179
179
 
@@ -468,7 +468,7 @@ async function renderGitHubRepo(
468
468
  md += `${prefix}${item.path}\n`;
469
469
  }
470
470
  if (tree.length > 100) {
471
- md += `... and ${tree.length - 100} more files\n`;
471
+ md += `[…${tree.length - 100} files elided…]\n`;
472
472
  }
473
473
  md += "```\n\n";
474
474
  }
@@ -211,7 +211,7 @@ export const handleGoPkg: SpecialHandler = async (
211
211
  sections.push(exported.slice(0, 50).join("\n"));
212
212
  if (exported.length > 50) {
213
213
  notes.push(`showing 50 of ${exported.length} exports`);
214
- sections.push(`\n... and ${exported.length - 50} more`);
214
+ sections.push(`\n[…${exported.length - 50} exports elided…]`);
215
215
  }
216
216
  sections.push("");
217
217
  }
@@ -240,7 +240,7 @@ export const handleGoPkg: SpecialHandler = async (
240
240
  sections.push(imports.slice(0, 20).join("\n"));
241
241
  if (imports.length > 20) {
242
242
  notes.push(`showing 20 of ${imports.length} imports`);
243
- sections.push(`\n... and ${imports.length - 20} more`);
243
+ sections.push(`\n[…${imports.length - 20} imports elided…]`);
244
244
  }
245
245
  sections.push("");
246
246
  }
@@ -162,7 +162,7 @@ function formatModuleMarkdown(module: ModuleResponse, release: ReleaseResponse |
162
162
  md += "\n";
163
163
  }
164
164
  if (runtimeDeps.length > 20) {
165
- md += `\n*...and ${runtimeDeps.length - 20} more*\n`;
165
+ md += `\n[…${runtimeDeps.length - 20} dependencies elided…]\n`;
166
166
  }
167
167
  }
168
168
  }
@@ -212,7 +212,7 @@ function formatReleaseMarkdown(release: ReleaseResponse): string {
212
212
  md += "\n";
213
213
  }
214
214
  if (runtimeDeps.length > 20) {
215
- md += `\n*...and ${runtimeDeps.length - 20} more*\n`;
215
+ md += `\n[…${runtimeDeps.length - 20} dependencies elided…]\n`;
216
216
  }
217
217
  }
218
218
 
@@ -182,7 +182,7 @@ export const handleNvd: SpecialHandler = async (
182
182
  md += `- \`${cpe}\`\n`;
183
183
  }
184
184
  if (cpes.length > 20) {
185
- md += `\n*...and ${cpes.length - 20} more*\n`;
185
+ md += `\n[…${cpes.length - 20} CPEs elided…]\n`;
186
186
  }
187
187
  md += "\n";
188
188
  }
@@ -195,7 +195,7 @@ export const handleNvd: SpecialHandler = async (
195
195
  md += `- ${ref.url}${tags}\n`;
196
196
  }
197
197
  if (vuln.references.length > 15) {
198
- md += `\n*...and ${vuln.references.length - 15} more references*\n`;
198
+ md += `\n[…${vuln.references.length - 15} references elided…]\n`;
199
199
  }
200
200
  }
201
201
 
@@ -139,7 +139,7 @@ function formatTagList(tags: string[], maxItems: number): string {
139
139
  const limited = tags.slice(0, maxItems);
140
140
  const formatted = limited.map(tag => `\`${tag}\``).join(", ");
141
141
  if (tags.length > maxItems) {
142
- return `${formatted} (and ${tags.length - maxItems} more)`;
142
+ return `${formatted} […${tags.length - maxItems} tags elided…]`;
143
143
  }
144
144
  return formatted;
145
145
  }
@@ -216,7 +216,7 @@ export const handleOpenCorporates: SpecialHandler = async (
216
216
  md += "\n";
217
217
  }
218
218
  if (inactiveOfficers.length > 10) {
219
- md += `\n*...and ${inactiveOfficers.length - 10} more former officers*\n`;
219
+ md += `\n[…${inactiveOfficers.length - 10} former officers elided…]\n`;
220
220
  }
221
221
  md += "\n";
222
222
  }
@@ -109,7 +109,7 @@ export const handlePubDev: SpecialHandler = async (url: string, timeout: number,
109
109
  md += "\n";
110
110
  }
111
111
  if (deps.length > 20) {
112
- md += `\n*...and ${deps.length - 20} more*\n`;
112
+ md += `\n[…${deps.length - 20} dependencies elided…]\n`;
113
113
  }
114
114
  md += "\n";
115
115
  }
@@ -239,7 +239,7 @@ export const handleRepology: SpecialHandler = async (
239
239
  }
240
240
 
241
241
  if (packages.length > 15) {
242
- md += `\n*...and ${packages.length - 15} more repositories*\n`;
242
+ md += `\n[…${packages.length - 15} repositories elided…]\n`;
243
243
  }
244
244
 
245
245
  md += `\n---\n\n[View on Repology](${url})\n`;
@@ -294,7 +294,7 @@ async function renderSearch(
294
294
  }
295
295
 
296
296
  if (results.length > maxResults) {
297
- md += `... and ${results.length - maxResults} more results\n`;
297
+ md += `[…${results.length - maxResults} results elided…]\n`;
298
298
  }
299
299
 
300
300
  return { content: md, ok: true };
@@ -150,7 +150,7 @@ async function handleModuleUrl(
150
150
  md += `| ${input.name} | \`${type}\` | ${required} | ${desc} |\n`;
151
151
  }
152
152
  if (inputs.length > 30) {
153
- md += `\n*... and ${inputs.length - 30} more inputs*\n`;
153
+ md += `\n[…${inputs.length - 30} inputs elided…]\n`;
154
154
  }
155
155
  md += "\n";
156
156
  }
@@ -165,7 +165,7 @@ async function handleModuleUrl(
165
165
  md += "\n";
166
166
  }
167
167
  if (outputs.length > 20) {
168
- md += `\n*... and ${outputs.length - 20} more outputs*\n`;
168
+ md += `\n[…${outputs.length - 20} outputs elided…]\n`;
169
169
  }
170
170
  md += "\n";
171
171
  }
@@ -180,7 +180,7 @@ async function handleModuleUrl(
180
180
  md += "\n";
181
181
  }
182
182
  if (deps.length > 15) {
183
- md += `\n*... and ${deps.length - 15} more dependencies*\n`;
183
+ md += `\n[…${deps.length - 15} dependencies elided…]\n`;
184
184
  }
185
185
  md += "\n";
186
186
  }
@@ -193,7 +193,7 @@ async function handleModuleUrl(
193
193
  md += `- \`${res.type}\` (${res.name})\n`;
194
194
  }
195
195
  if (resources.length > 20) {
196
- md += `\n*... and ${resources.length - 20} more resources*\n`;
196
+ md += `\n[…${resources.length - 20} resources elided…]\n`;
197
197
  }
198
198
  md += "\n";
199
199
  }
@@ -205,7 +205,7 @@ async function handleModuleUrl(
205
205
  md += `- **${sub.name}**: \`${sub.path}\`\n`;
206
206
  }
207
207
  if (mod.submodules.length > 10) {
208
- md += `\n*... and ${mod.submodules.length - 10} more submodules*\n`;
208
+ md += `\n[…${mod.submodules.length - 10} submodules elided…]\n`;
209
209
  }
210
210
  }
211
211
 
@@ -267,7 +267,7 @@ async function handleProviderUrl(
267
267
  md += `- [${doc.title}](https://registry.terraform.io/providers/${namespace}/${type}/latest/docs/${doc.category}/${doc.slug})\n`;
268
268
  }
269
269
  if (docs.length > 15) {
270
- md += `\n*... and ${docs.length - 15} more*\n`;
270
+ md += `\n[…${docs.length - 15} documents elided…]\n`;
271
271
  }
272
272
  md += "\n";
273
273
  }
@@ -169,7 +169,7 @@ export const handleWikidata: SpecialHandler = async (
169
169
  if (values.length > 0) {
170
170
  // Limit values shown per property
171
171
  const displayValues = values.slice(0, 10);
172
- const overflow = values.length > 10 ? ` (+${values.length - 10} more)` : "";
172
+ const overflow = values.length > 10 ? ` […${values.length - 10} values elided…]` : "";
173
173
  processedProperties.push(`- **${propLabel}:** ${displayValues.join(", ")}${overflow}`);
174
174
  }
175
175
  }
@@ -187,7 +187,7 @@ export const handleWikidata: SpecialHandler = async (
187
187
  const maxProps = 50;
188
188
  md += processedProperties.slice(0, maxProps).join("\n");
189
189
  if (processedProperties.length > maxProps) {
190
- md += `\n\n*...and ${processedProperties.length - maxProps} more properties*`;
190
+ md += `\n\n[…${processedProperties.length - maxProps} properties elided…]`;
191
191
  }
192
192
  md += "\n";
193
193
  }
@@ -298,7 +298,7 @@ function applyLineCap(
298
298
  const removed = new Set(removable.map(item => item.index));
299
299
  const kept = lines.filter((_, index) => !removed.has(index));
300
300
  kept.push({
301
- label: `… (${removable.length} lines elided beyond depth/cap)`,
301
+ label: `[…${removable.length}ln elided…]`,
302
302
  depth: 0,
303
303
  isRoot: false,
304
304
  });
@@ -1,13 +0,0 @@
1
- import { Box } from "@oh-my-pi/pi-tui";
2
- import type { BranchSummaryMessage } from "../../session/messages";
3
- /**
4
- * Component that renders a branch summary message with collapsed/expanded state.
5
- * Uses same background color as hook messages for visual consistency.
6
- */
7
- export declare class BranchSummaryMessageComponent extends Box {
8
- #private;
9
- private readonly message;
10
- constructor(message: BranchSummaryMessage);
11
- setExpanded(expanded: boolean): void;
12
- invalidate(): void;
13
- }
@@ -1,46 +0,0 @@
1
- import { Box, Markdown, Spacer, Text } from "@oh-my-pi/pi-tui";
2
- import { getMarkdownTheme, theme } from "../../modes/theme/theme";
3
- import type { BranchSummaryMessage } from "../../session/messages";
4
-
5
- /**
6
- * Component that renders a branch summary message with collapsed/expanded state.
7
- * Uses same background color as hook messages for visual consistency.
8
- */
9
- export class BranchSummaryMessageComponent extends Box {
10
- #expanded = false;
11
-
12
- constructor(private readonly message: BranchSummaryMessage) {
13
- super(1, 1, t => theme.bg("customMessageBg", t));
14
- this.setIgnoreTight(true);
15
- this.#updateDisplay();
16
- }
17
-
18
- setExpanded(expanded: boolean): void {
19
- this.#expanded = expanded;
20
- this.#updateDisplay();
21
- }
22
-
23
- override invalidate(): void {
24
- super.invalidate();
25
- this.#updateDisplay();
26
- }
27
-
28
- #updateDisplay(): void {
29
- this.clear();
30
-
31
- const label = theme.fg("customMessageLabel", theme.bold("[branch]"));
32
- this.addChild(new Text(label, 0, 0));
33
- this.addChild(new Spacer(1));
34
-
35
- if (this.#expanded) {
36
- const header = "**Branch Summary**\n\n";
37
- this.addChild(
38
- new Markdown(header + this.message.summary, 0, 0, getMarkdownTheme(), {
39
- color: (text: string) => theme.fg("customMessageText", text),
40
- }),
41
- );
42
- } else {
43
- this.addChild(new Text(theme.fg("customMessageText", "Branch summary (ctrl+o to expand)"), 0, 0));
44
- }
45
- }
46
- }