@oh-my-pi/pi-tui 18.3.1 → 18.3.3

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 (116) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/types/app-keybindings.d.ts +2 -17
  3. package/dist/types/autocomplete.d.ts +18 -3
  4. package/dist/types/chrome/keybinding-hints.d.ts +16 -10
  5. package/dist/types/components/editor.d.ts +11 -0
  6. package/dist/types/key-hint-format.d.ts +29 -0
  7. package/dist/types/keys.d.ts +2 -1
  8. package/dist/types/overlays/agent-hub.d.ts +1 -1
  9. package/dist/types/overlays/agent-transcript-viewer.d.ts +1 -1
  10. package/dist/types/overlays/model-picker.d.ts +1 -3
  11. package/dist/types/overlays/rewind-selector.d.ts +4 -2
  12. package/dist/types/prompt/composer-hints.d.ts +42 -0
  13. package/dist/types/prompt/composer.d.ts +2 -1
  14. package/dist/types/prompt/custom-editor.d.ts +8 -2
  15. package/dist/types/prompt/macos-spelling.d.ts +7 -16
  16. package/dist/types/prompt/prompt-action-autocomplete.d.ts +4 -1
  17. package/dist/types/prompt/prose-gate.d.ts +24 -0
  18. package/dist/types/prompt/word-completion.d.ts +46 -0
  19. package/dist/types/render/render-utils.d.ts +1 -1
  20. package/dist/types/status-line/host.d.ts +1 -1
  21. package/dist/types/theme/active-symbols.d.ts +12 -0
  22. package/dist/types/theme/symbols.d.ts +1 -1
  23. package/dist/types/theme/theme-class.d.ts +0 -1
  24. package/dist/types/tools/eval.d.ts +1 -1
  25. package/dist/types/tools/mcp.d.ts +3 -1
  26. package/dist/types/tools/task-yield-assembly.d.ts +10 -2
  27. package/dist/types/tools/task.d.ts +4 -0
  28. package/package.json +11 -10
  29. package/src/app-keybindings.ts +11 -104
  30. package/src/apps/autoresearch-dashboard.ts +10 -3
  31. package/src/apps/debug/log-viewer.ts +3 -2
  32. package/src/apps/debug/raw-sse.ts +2 -1
  33. package/src/apps/git/git-tui.ts +9 -5
  34. package/src/apps/live-visualizer.ts +2 -1
  35. package/src/apps/ps-top.ts +13 -4
  36. package/src/autocomplete.ts +42 -7
  37. package/src/chat/compaction-summary-message.ts +2 -1
  38. package/src/chat/execution-shared.ts +4 -2
  39. package/src/chat/tool-execution.ts +3 -1
  40. package/src/chat/ttsr-notification.ts +4 -3
  41. package/src/chat/user-message.ts +2 -2
  42. package/src/chrome/keybinding-hints.ts +38 -13
  43. package/src/components/editor.ts +180 -58
  44. package/src/components/settings-list.ts +10 -3
  45. package/src/components/tab-bar.ts +2 -1
  46. package/src/debug-server.ts +4 -1
  47. package/src/hotkeys-markdown.ts +14 -17
  48. package/src/key-hint-format.ts +122 -0
  49. package/src/keys.ts +2 -1
  50. package/src/overlays/advisor-config.ts +28 -6
  51. package/src/overlays/agent-hub.ts +8 -5
  52. package/src/overlays/agent-transcript-viewer.ts +6 -4
  53. package/src/overlays/agents-hub.ts +22 -8
  54. package/src/overlays/annotation-overlay.ts +31 -17
  55. package/src/overlays/ask-dialog.ts +14 -19
  56. package/src/overlays/bordered-loader.ts +2 -1
  57. package/src/overlays/btw-history-panel.ts +15 -12
  58. package/src/overlays/btw-panel.ts +12 -7
  59. package/src/overlays/cleanse-panel.ts +9 -6
  60. package/src/overlays/codex-reset-fireworks.ts +5 -3
  61. package/src/overlays/copy-selector.ts +13 -5
  62. package/src/overlays/extensions/extension-dashboard.ts +6 -1
  63. package/src/overlays/history-search.ts +3 -2
  64. package/src/overlays/hook-editor.ts +7 -2
  65. package/src/overlays/hook-input.ts +2 -1
  66. package/src/overlays/hook-selector.ts +5 -1
  67. package/src/overlays/login-dialog.ts +17 -4
  68. package/src/overlays/logout-account-selector.ts +10 -1
  69. package/src/overlays/mcp-add-wizard.ts +32 -20
  70. package/src/overlays/model-hub.ts +66 -27
  71. package/src/overlays/model-picker.ts +27 -12
  72. package/src/overlays/move-overlay.ts +5 -1
  73. package/src/overlays/omfg-panel.ts +8 -5
  74. package/src/overlays/pause-screen.ts +7 -3
  75. package/src/overlays/plan-review-overlay.ts +40 -14
  76. package/src/overlays/plan-save-overlay.ts +3 -1
  77. package/src/overlays/plugin-settings.ts +15 -8
  78. package/src/overlays/reset-usage-selector.ts +10 -3
  79. package/src/overlays/rewind-selector.ts +193 -13
  80. package/src/overlays/session-info-overlay.ts +4 -3
  81. package/src/overlays/session-selector.ts +12 -2
  82. package/src/overlays/settings-selector.ts +18 -11
  83. package/src/overlays/tree-selector.ts +27 -4
  84. package/src/overlays/usage-dashboard.ts +8 -2
  85. package/src/prompt/composer-cache.ts +2 -1
  86. package/src/prompt/composer-hints.ts +75 -0
  87. package/src/prompt/composer.ts +3 -2
  88. package/src/prompt/custom-editor.ts +22 -4
  89. package/src/prompt/macos-spelling.ts +17 -158
  90. package/src/prompt/prompt-action-autocomplete.ts +9 -2
  91. package/src/prompt/prose-gate.ts +87 -0
  92. package/src/prompt/tips.txt +6 -6
  93. package/src/prompt/welcome.ts +35 -2
  94. package/src/prompt/word-completion.ts +205 -0
  95. package/src/render/render-utils.ts +4 -4
  96. package/src/setup/scenes/composer.ts +5 -1
  97. package/src/setup/scenes/model.ts +3 -1
  98. package/src/setup/scenes/providers.ts +4 -1
  99. package/src/setup/scenes/sign-in.ts +4 -2
  100. package/src/setup/scenes/splash.ts +9 -4
  101. package/src/setup/scenes/theme.ts +18 -5
  102. package/src/setup/wizard-overlay.ts +5 -4
  103. package/src/status-line/host.ts +1 -1
  104. package/src/status-line/segments.ts +2 -2
  105. package/src/theme/active-symbols.ts +20 -0
  106. package/src/theme/glyph-bundle.json +28 -1
  107. package/src/theme/symbols.ts +88 -6
  108. package/src/theme/theme-class.ts +0 -1
  109. package/src/theme/theme.ts +2 -0
  110. package/src/tools/bash-interactive.ts +2 -1
  111. package/src/tools/eval.ts +3 -3
  112. package/src/tools/mcp.ts +3 -1
  113. package/src/tools/task-yield-assembly.ts +34 -8
  114. package/src/tools/task.ts +14 -5
  115. package/dist/types/overlays/tiny-title-download-progress.d.ts +0 -21
  116. package/src/overlays/tiny-title-download-progress.ts +0 -102
@@ -99,7 +99,6 @@ export type SymbolKey =
99
99
  | "icon.advisorClosed"
100
100
  | "icon.time"
101
101
  | "icon.omp"
102
- | "icon.esc"
103
102
  | "icon.ghost"
104
103
  | "icon.agents"
105
104
  | "icon.job"
@@ -285,7 +284,28 @@ export type SymbolKey =
285
284
  | "tool.goal"
286
285
  | "tool.irc"
287
286
  | "tool.delete"
288
- | "tool.move";
287
+ | "tool.move"
288
+ // Keyboard keys (rendered by formatKeyHint). `*Mac` variants replace the
289
+ // generic modifier on macOS, whose keycaps are labelled with ⌃ ⌥ ⌘.
290
+ | "key.ctrl"
291
+ | "key.ctrlMac"
292
+ | "key.shift"
293
+ | "key.alt"
294
+ | "key.altMac"
295
+ | "key.super"
296
+ | "key.superMac"
297
+ // Gap after a glyph modifier (`⌃⇧C`); nerd icons render too small to abut
298
+ | "key.joiner"
299
+ | "key.enter"
300
+ | "key.esc"
301
+ | "key.tab"
302
+ | "key.backspace"
303
+ | "key.delete"
304
+ | "key.space"
305
+ | "key.up"
306
+ | "key.down"
307
+ | "key.left"
308
+ | "key.right";
289
309
 
290
310
  export type SymbolMap = Record<SymbolKey, string>;
291
311
  /**
@@ -459,7 +479,6 @@ const UNICODE_SYMBOLS: SymbolMap = {
459
479
  "icon.advisorClosed": "🙈",
460
480
  "icon.time": "⏱",
461
481
  "icon.omp": "π",
462
- "icon.esc": "⎋",
463
482
  "icon.ghost": "👻",
464
483
  "icon.agents": "👥",
465
484
  "icon.job": "⚙",
@@ -646,6 +665,25 @@ const UNICODE_SYMBOLS: SymbolMap = {
646
665
  "tool.irc": "✉",
647
666
  "tool.delete": "🗑",
648
667
  "tool.move": "➜",
668
+ // Keys: word modifiers off macOS (⌃/⌥/⌘ name no key on a PC keyboard)
669
+ "key.ctrl": "Ctrl",
670
+ "key.ctrlMac": "⌃",
671
+ "key.shift": "⇧",
672
+ "key.alt": "Alt",
673
+ "key.altMac": "⌥",
674
+ "key.super": "Super",
675
+ "key.superMac": "⌘",
676
+ "key.joiner": "",
677
+ "key.enter": "⏎",
678
+ "key.esc": "⎋",
679
+ "key.tab": "⇥",
680
+ "key.backspace": "⌫",
681
+ "key.delete": "⌦",
682
+ "key.space": "␣",
683
+ "key.up": "↑",
684
+ "key.down": "↓",
685
+ "key.left": "←",
686
+ "key.right": "→",
649
687
  };
650
688
 
651
689
  const NERD_SYMBOLS: SymbolMap = {
@@ -822,8 +860,6 @@ const NERD_SYMBOLS: SymbolMap = {
822
860
  // U+F03FF before (2ec52b8bdd) and reverted because it renders the wrong glyph;
823
861
  // do not swap it again.
824
862
  "icon.omp": "\u{f0d57}",
825
- // pick: 󱊷 (nf-md-keyboard_esc) | alt: ⎋
826
- "icon.esc": "\u{f12b7}",
827
863
  // pick: 󰊠 (nf-md-ghost) | alt: 👻
828
864
  "icon.ghost": "\u{f02a0}",
829
865
  // pick:  | alt: 
@@ -1098,6 +1134,35 @@ const NERD_SYMBOLS: SymbolMap = {
1098
1134
  "tool.irc": "\uF086",
1099
1135
  "tool.delete": "\uf12d",
1100
1136
  "tool.move": "\uf061",
1137
+ // Keys: word modifiers off macOS; arrows stay Unicode (nerd arrows add nothing)
1138
+ "key.ctrl": "Ctrl",
1139
+ // pick: 󰘴 (nf-md-apple_keyboard_control) | alt: ⌃
1140
+ "key.ctrlMac": "\u{f0634}",
1141
+ // pick: 󰘶 (nf-md-apple_keyboard_shift) | alt: ⇧
1142
+ "key.shift": "\u{f0636}",
1143
+ "key.alt": "Alt",
1144
+ // pick: 󰘵 (nf-md-apple_keyboard_option) | alt: ⌥
1145
+ "key.altMac": "\u{f0635}",
1146
+ "key.super": "Super",
1147
+ // pick: 󰘳 (nf-md-apple_keyboard_command) | alt: ⌘
1148
+ "key.superMac": "\u{f0633}",
1149
+ "key.joiner": " ",
1150
+ // pick: 󰌑 (nf-md-keyboard_return) | alt: ⏎
1151
+ "key.enter": "\u{f0311}",
1152
+ // pick: 󱊷 (nf-md-keyboard_esc) | alt: ⎋
1153
+ "key.esc": "\u{f12b7}",
1154
+ // pick: 󰌒 (nf-md-keyboard_tab) | alt: ⇥
1155
+ "key.tab": "\u{f0312}",
1156
+ // pick: 󰁮 (nf-md-backspace) | alt: ⌫
1157
+ "key.backspace": "\u{f006e}",
1158
+ // pick: 󰹾 (nf-md-backspace_reverse) | alt: ⌦
1159
+ "key.delete": "\u{f0e7e}",
1160
+ // pick: 󱁐 (nf-md-keyboard_space) | alt: ␣
1161
+ "key.space": "\u{f1050}",
1162
+ "key.up": "↑",
1163
+ "key.down": "↓",
1164
+ "key.left": "←",
1165
+ "key.right": "→",
1101
1166
  };
1102
1167
 
1103
1168
  const ASCII_SYMBOLS: SymbolMap = {
@@ -1192,7 +1257,6 @@ const ASCII_SYMBOLS: SymbolMap = {
1192
1257
  "icon.advisorClosed": "(adv)",
1193
1258
  "icon.time": "t:",
1194
1259
  "icon.omp": "pi",
1195
- "icon.esc": "esc",
1196
1260
  "icon.ghost": "@",
1197
1261
  "icon.agents": "AG",
1198
1262
  "icon.job": "bg",
@@ -1374,6 +1438,24 @@ const ASCII_SYMBOLS: SymbolMap = {
1374
1438
  "tool.irc": "#",
1375
1439
  "tool.delete": "rm",
1376
1440
  "tool.move": "mv",
1441
+ "key.ctrl": "Ctrl",
1442
+ "key.ctrlMac": "Ctrl",
1443
+ "key.shift": "Shift",
1444
+ "key.alt": "Alt",
1445
+ "key.altMac": "Option",
1446
+ "key.super": "Super",
1447
+ "key.superMac": "Cmd",
1448
+ "key.joiner": "+",
1449
+ "key.enter": "Enter",
1450
+ "key.esc": "Esc",
1451
+ "key.tab": "Tab",
1452
+ "key.backspace": "Backspace",
1453
+ "key.delete": "Delete",
1454
+ "key.space": "Space",
1455
+ "key.up": "Up",
1456
+ "key.down": "Down",
1457
+ "key.left": "Left",
1458
+ "key.right": "Right",
1377
1459
  };
1378
1460
 
1379
1461
  export const SYMBOL_PRESETS: Record<SymbolPreset, SymbolMap> = {
@@ -592,7 +592,6 @@ export class Theme {
592
592
  advisorClosed: this.#symbols["icon.advisorClosed"],
593
593
  time: this.#symbols["icon.time"],
594
594
  omp: this.#symbols["icon.omp"],
595
- esc: this.#symbols["icon.esc"],
596
595
  ghost: this.#symbols["icon.ghost"],
597
596
  agents: this.#symbols["icon.agents"],
598
597
  job: this.#symbols["icon.job"],
@@ -5,6 +5,7 @@ import type { Terminal, TerminalAppearance } from "../terminal";
5
5
  import { colorLuma } from "@oh-my-pi/pi-utils/color";
6
6
  import { getCustomThemesDir } from "@oh-my-pi/pi-utils/dirs";
7
7
  import * as logger from "@oh-my-pi/pi-utils/logger";
8
+ import { setActiveSymbolTheme } from "./active-symbols";
8
9
  import { ansi256ToHex, resolveThemeColors, resolveVarRefs } from "./color";
9
10
  import { type CreateThemeOptions, getBuiltinThemes, loadTheme, loadThemeJson, loadThemeSync } from "./loader";
10
11
  import type { ThemeColor, ThemeJson } from "./schema";
@@ -99,6 +100,7 @@ export function bindTheme(binding: ThemeBinding): () => void {
99
100
 
100
101
  function assignTheme(value: Theme): void {
101
102
  theme = value;
103
+ setActiveSymbolTheme(value);
102
104
  if (themeBindings) {
103
105
  for (const binding of themeBindings) binding(value);
104
106
  }
@@ -4,6 +4,7 @@ import type { Component } from "../tui";
4
4
  import { extractPrintableText, matchesKey, parseKey, parseKittySequence } from "../keys";
5
5
  import { padding, truncateToWidth, visibleWidth } from "../utils";
6
6
  import type { Theme } from "../theme/theme";
7
+ import { formatKeyHint } from "../app-keybindings";
7
8
  import { formatStatusIcon, replaceTabs } from "../render/render-utils";
8
9
  import { readTerminalRows, styleTerminalRow } from "./terminal-output";
9
10
 
@@ -267,7 +268,7 @@ export class BashInteractiveOverlayComponent implements Component {
267
268
  const footer =
268
269
  this.#state === "running"
269
270
  ? truncateToWidth(
270
- `${this.#uiTheme.fg("warning", "esc")} ${this.#uiTheme.fg("dim", "force-kill")} ${this.#uiTheme.fg("dim", "· input forwarded to PTY")}`,
271
+ `${this.#uiTheme.fg("warning", formatKeyHint("escape"))} ${this.#uiTheme.fg("dim", "force-kill")} ${this.#uiTheme.fg("dim", "· input forwarded to PTY")}`,
271
272
  innerWidth,
272
273
  )
273
274
  : truncateToWidth(this.#uiTheme.fg("dim", "session finished"), innerWidth);
package/src/tools/eval.ts CHANGED
@@ -30,7 +30,7 @@ import {
30
30
  } from "../render/render-utils";
31
31
  import type { ImageContent } from "@oh-my-pi/pi-ai";
32
32
  import type { OutputMeta } from "./output-meta";
33
- import type { ConfiguredThinkingLevel } from "../render/render-utils";
33
+ import { type ConfiguredThinkingLevel, expandKeyHint } from "../render/render-utils";
34
34
 
35
35
  /** Runtime backend that an eval cell dispatches to. */
36
36
  export type EvalLanguage = "python" | "js";
@@ -677,7 +677,7 @@ export const evalToolRenderer = {
677
677
  const outputLines = [...outputContent.lines];
678
678
  if (!expanded && outputContent.hiddenCount > 0) {
679
679
  outputLines.push(
680
- uiTheme.fg("dim", `… ${outputContent.hiddenCount} more lines (ctrl+o to expand)`),
680
+ uiTheme.fg("dim", `… ${outputContent.hiddenCount} more lines (${expandKeyHint()} to expand)`),
681
681
  );
682
682
  }
683
683
  if (statusLines.length > 0) {
@@ -811,7 +811,7 @@ export const evalToolRenderer = {
811
811
  outputLines.push("");
812
812
  const skippedLine = uiTheme.fg(
813
813
  "dim",
814
- `… (${cachedSkipped} earlier lines, showing ${cachedLines.length} of ${cachedSkipped + cachedLines.length}) (ctrl+o to expand)`,
814
+ `… (${cachedSkipped} earlier lines, showing ${cachedLines.length} of ${cachedSkipped + cachedLines.length}) (${expandKeyHint()} to expand)`,
815
815
  );
816
816
  outputLines.push(truncateToWidth(skippedLine, width));
817
817
  }
package/src/tools/mcp.ts CHANGED
@@ -266,7 +266,7 @@ export interface MCPResourceContent {
266
266
  /** Supported MCP result content blocks retained in display metadata. */
267
267
  export type MCPContent = MCPTextContent | MCPImageContent | MCPResourceContent;
268
268
 
269
- /** Details included in MCP tool results for rendering */
269
+ /** MCP result details shared by renderers and programmatic tool consumers. */
270
270
  export interface MCPToolDetails {
271
271
  /** Server name */
272
272
  serverName: string;
@@ -276,6 +276,8 @@ export interface MCPToolDetails {
276
276
  isError?: boolean;
277
277
  /** Raw content from MCP response */
278
278
  rawContent?: MCPContent[];
279
+ /** Server-supplied structured data, independent of the model-facing text rendering. */
280
+ structuredContent?: Record<string, unknown>;
279
281
  /** Structured metadata from the MCP response */
280
282
  mcpMeta?: Record<string, unknown>;
281
283
  /** Provider ID (e.g., "claude", "mcp-json") */
@@ -1,4 +1,13 @@
1
1
  import type { YieldItem } from "./task";
2
+
3
+ /**
4
+ * Output-schema shape of each declared top-level property, keyed by incremental yield label.
5
+ * `array` sections accumulate into a list (even a lone yield); `scalar` sections keep the
6
+ * latest yield, since the schema admits exactly one value. Undeclared labels accumulate
7
+ * into a list only once repeated.
8
+ */
9
+ export type YieldSectionShapes = ReadonlyMap<string, "array" | "scalar">;
10
+
2
11
  /** Outcome of folding a run's yield calls into one payload, with provenance flags. */
3
12
  interface AssembledYieldResult {
4
13
  data: unknown;
@@ -52,12 +61,14 @@ function appendYieldSection(
52
61
  sectionCounts: Map<string, number>,
53
62
  label: string,
54
63
  value: unknown,
55
- forceArray: boolean,
64
+ shape: "array" | "scalar" | undefined,
56
65
  ): void {
57
66
  const count = sectionCounts.get(label) ?? 0;
58
67
  const existing = sections[label];
59
- if (count === 0) {
60
- sections[label] = forceArray ? [value] : value;
68
+ if (shape === "scalar") {
69
+ sections[label] = value;
70
+ } else if (count === 0) {
71
+ sections[label] = shape === "array" ? [value] : value;
61
72
  } else if (Array.isArray(existing)) {
62
73
  existing.push(value);
63
74
  } else {
@@ -74,12 +85,13 @@ function appendYieldSection(
74
85
  * assistant turn the raw terminal result. Other string-typed yields contribute
75
86
  * the terminal labelled section. Untyped terminal yields keep the historical
76
87
  * "last yield wins" behavior unless no terminal yield exists, in which case
77
- * accumulated typed sections finalize on idle.
88
+ * accumulated typed sections finalize on idle. Repeated sections merge per
89
+ * `sectionShapes` (see {@link YieldSectionShapes}).
78
90
  */
79
91
  export function assembleYieldResult(
80
92
  yieldItems: YieldItem[],
81
93
  lastAssistantText?: string,
82
- arrayLabels?: ReadonlySet<string>,
94
+ sectionShapes?: YieldSectionShapes,
83
95
  ): AssembledYieldResult | undefined {
84
96
  if (yieldItems.length === 0) return undefined;
85
97
 
@@ -101,18 +113,27 @@ export function assembleYieldResult(
101
113
  // level deep and made output-schema validation report every field missing.
102
114
  const sections: Record<string, unknown> = {};
103
115
  const sectionCounts = new Map<string, number>();
116
+ const overriddenScalars = new Set<string>();
104
117
  let schemaOverridden = false;
105
118
  let missingData = false;
106
119
  let hasSections = false;
107
120
  for (const item of yieldItems) {
108
121
  if (item.status === "aborted") continue;
109
122
  if (!isIncrementalYieldType(item.type)) continue;
110
- schemaOverridden ||= item.schemaOverridden === true;
123
+ const overridden = item.schemaOverridden === true;
111
124
  const labels = getYieldLabels(item.type);
112
125
  const resolved = resolveYieldPayload(item, lastAssistantText, labels);
113
126
  missingData ||= resolved.missingData;
127
+ if (labels.length === 0) schemaOverridden ||= overridden;
114
128
  for (const label of labels) {
115
- appendYieldSection(sections, sectionCounts, label, resolved.value, arrayLabels?.has(label) ?? false);
129
+ const shape = sectionShapes?.get(label);
130
+ appendYieldSection(sections, sectionCounts, label, resolved.value, shape);
131
+ if (shape === "scalar") {
132
+ if (overridden) overriddenScalars.add(label);
133
+ else overriddenScalars.delete(label);
134
+ } else {
135
+ schemaOverridden ||= overridden;
136
+ }
116
137
  hasSections = true;
117
138
  }
118
139
  }
@@ -133,7 +154,12 @@ export function assembleYieldResult(
133
154
  // A data-less terminal finalize keeps accumulated sections; only when none
134
155
  // exist does the last assistant turn become the raw result.
135
156
  if (hasSections) {
136
- return { data: sections, schemaOverridden, rawText: false, missingData };
157
+ return {
158
+ data: sections,
159
+ schemaOverridden: schemaOverridden || overriddenScalars.size > 0,
160
+ rawText: false,
161
+ missingData,
162
+ };
137
163
  }
138
164
 
139
165
  if (!terminalItem) return undefined;
package/src/tools/task.ts CHANGED
@@ -42,7 +42,7 @@ import { framedToolCard } from "../render/tool-card";
42
42
  import { formatOutputInline, renderJsonTreeLines } from "./json-tree";
43
43
  import { repairDoubleEncodedJsonString } from "./task-repair-args";
44
44
  import { getSubprocessToolRenderer } from "./subprocess";
45
- import { assembleYieldResult } from "./task-yield-assembly";
45
+ import { assembleYieldResult, type YieldSectionShapes } from "./task-yield-assembly";
46
46
 
47
47
  /** Render context threaded in from `ToolExecutionComponent.#buildRenderContext`. */
48
48
  interface TaskRenderContext {
@@ -98,8 +98,13 @@ function normalizeFindings(value: unknown): FindingDetails[] {
98
98
  return findings;
99
99
  }
100
100
 
101
- /** Reviewer output declares `findings` as an array, so a lone finding section still assembles as a list. */
102
- const REVIEWER_ARRAY_LABELS: ReadonlySet<string> = new Set(["findings"]);
101
+ /** Reviewer output shapes: `findings` is an array (a lone finding still assembles as a list); the verdict fields are scalars. */
102
+ const REVIEWER_SECTION_SHAPES: YieldSectionShapes = new Map([
103
+ ["findings", "array"],
104
+ ["overall_correctness", "scalar"],
105
+ ["explanation", "scalar"],
106
+ ["confidence", "scalar"],
107
+ ]);
103
108
 
104
109
  function extractIncrementalReviewResult(
105
110
  items: RenderYieldItem[],
@@ -110,7 +115,7 @@ function extractIncrementalReviewResult(
110
115
  status: item.status === "aborted" ? "aborted" : item.status === "success" ? "success" : undefined,
111
116
  useLastTurn: item.useLastTurn,
112
117
  }));
113
- const assembled = assembleYieldResult(yieldItems, undefined, REVIEWER_ARRAY_LABELS);
118
+ const assembled = assembleYieldResult(yieldItems, undefined, REVIEWER_SECTION_SHAPES);
114
119
  const data = assembled?.data;
115
120
  if (!data || typeof data !== "object" || Array.isArray(data)) return undefined;
116
121
  const record = data as Record<string, unknown>;
@@ -553,7 +558,7 @@ function createAssignmentSectionRenderer(
553
558
  }
554
559
 
555
560
  /**
556
- * Build the shared-context section (the `# Goal / # Constraints` background a
561
+ * Build the shared-context section (the `# Goal / # Contract` background a
557
562
  * batch call hands every subagent). Rendered like the assignment brief so the
558
563
  * shared background stays visible for the whole task lifecycle.
559
564
  */
@@ -1726,6 +1731,8 @@ export interface TaskItem {
1726
1731
  agent?: string;
1727
1732
  /** The work; required by the schema. */
1728
1733
  task?: string;
1734
+ /** Caller's terse rationale for why the work is simple or complex; required by the schema and fed to the child's `auto` thinking classifier. */
1735
+ complexity?: string;
1729
1736
  /** Per-spawn thinking effort: lowest/middle/highest level the resolved model supports. Overrides the agent's default selector (e.g. `auto`). */
1730
1737
  effort?: "lo" | "med" | "hi";
1731
1738
  /** Caller-provided output schema; its presence overrides the selected agent's schema. */
@@ -1751,6 +1758,8 @@ export interface TaskParams {
1751
1758
  agent?: string;
1752
1759
  /** The work (flat form). */
1753
1760
  task?: string;
1761
+ /** Caller's difficulty rationale (flat form); see {@link TaskItem.complexity}. */
1762
+ complexity?: string;
1754
1763
  /** Per-spawn thinking effort (flat form): lowest/middle/highest level the resolved model supports. */
1755
1764
  effort?: "lo" | "med" | "hi";
1756
1765
  /** Caller-provided output schema; its presence overrides the selected agent's schema. */
@@ -1,21 +0,0 @@
1
- import { type Component } from "../index.js";
2
- /** Download progress fields consumed by the status display. */
3
- export interface TinyTitleDownloadProgress {
4
- status: "initiate" | "download" | "progress" | "progress_total" | "done" | "ready" | "error";
5
- file?: string;
6
- progress?: number;
7
- loaded?: number;
8
- total?: number;
9
- files?: Record<string, {
10
- loaded: number;
11
- total: number;
12
- }>;
13
- }
14
- export declare class TinyTitleDownloadProgressComponent implements Component {
15
- #private;
16
- constructor(modelLabel: string);
17
- update(event: TinyTitleDownloadProgress): void;
18
- isComplete(): boolean;
19
- invalidate(): void;
20
- render(width: number): readonly string[];
21
- }
@@ -1,102 +0,0 @@
1
- import { type Component, truncateToWidth, visibleWidth } from "../index";
2
- import { formatBytes } from "@oh-my-pi/pi-utils";
3
- import { ProgressBar } from "../components/progress-bar";
4
- /** Download progress fields consumed by the status display. */
5
- export interface TinyTitleDownloadProgress {
6
- status: "initiate" | "download" | "progress" | "progress_total" | "done" | "ready" | "error";
7
- file?: string;
8
- progress?: number;
9
- loaded?: number;
10
- total?: number;
11
- files?: Record<string, { loaded: number; total: number }>;
12
- }
13
- import { theme } from "../theme/theme";
14
-
15
- const DEFAULT_BAR_WIDTH = 24;
16
-
17
- function padLine(line: string, width: number): string {
18
- const visible = visibleWidth(line);
19
- return visible >= width ? truncateToWidth(line, width) : `${line}${" ".repeat(width - visible)}`;
20
- }
21
-
22
- function currentFile(event: TinyTitleDownloadProgress | undefined): string | undefined {
23
- if (!event) return undefined;
24
- if (event.file) return event.file.split("/").at(-1) ?? event.file;
25
- if (event.files) {
26
- let largestFile: string | undefined;
27
- let largestLoaded = -1;
28
- for (const file in event.files) {
29
- const state = event.files[file];
30
- if (state.loaded <= largestLoaded || state.loaded >= state.total) continue;
31
- largestFile = file;
32
- largestLoaded = state.loaded;
33
- }
34
- return largestFile?.split("/").at(-1) ?? largestFile;
35
- }
36
- return undefined;
37
- }
38
-
39
- function statusLabel(event: TinyTitleDownloadProgress | undefined): string {
40
- if (!event) return "Preparing";
41
- if (event.status === "error") return "Failed";
42
- if (event.status === "ready") return "Ready";
43
- if (event.status === "done") return "Downloaded";
44
- if (event.status === "download") return "Downloading";
45
- if (event.status === "progress" || event.status === "progress_total") return "Downloading";
46
- return "Preparing";
47
- }
48
-
49
- function byteLabel(event: TinyTitleDownloadProgress | undefined): string | undefined {
50
- if (!event?.loaded || !event.total) return undefined;
51
- return `${formatBytes(event.loaded)} / ${formatBytes(event.total)}`;
52
- }
53
-
54
- export class TinyTitleDownloadProgressComponent implements Component {
55
- #modelLabel: string;
56
- #event: TinyTitleDownloadProgress | undefined;
57
- #bar = new ProgressBar(undefined, {
58
- min: 0,
59
- max: 100,
60
- minWidth: 8,
61
- maxWidth: DEFAULT_BAR_WIDTH,
62
- style: {
63
- filled: "█",
64
- empty: "░",
65
- styleFilled: text => theme.fg("accent", text),
66
- styleEmpty: text => theme.fg("muted", text),
67
- styleIndeterminate: text => theme.fg("muted", text),
68
- },
69
- });
70
-
71
- constructor(modelLabel: string) {
72
- this.#modelLabel = modelLabel;
73
- }
74
-
75
- update(event: TinyTitleDownloadProgress): void {
76
- this.#event = event;
77
- this.#bar.setValue(event.progress);
78
- }
79
-
80
- isComplete(): boolean {
81
- return this.#event?.status === "ready" || this.#event?.status === "error";
82
- }
83
-
84
- invalidate(): void {
85
- this.#bar.invalidate();
86
- }
87
-
88
- render(width: number): readonly string[] {
89
- width = Math.max(1, width);
90
- const border = theme.fg("border", theme.boxRound.horizontal.repeat(width));
91
- const status = statusLabel(this.#event);
92
- const file = currentFile(this.#event);
93
- const pct =
94
- this.#event?.progress === undefined ? "" : `${Math.floor(this.#event.progress).toString().padStart(3, " ")}%`;
95
- const bytes = byteLabel(this.#event);
96
- const title = `${theme.fg("accent", "Tiny model")} ${theme.fg("muted", status)} ${this.#modelLabel}`;
97
- const bar = this.#bar.render(Math.max(8, width - 36))[0] ?? "";
98
- const details = [bar, pct, bytes, file].filter((part): part is string => Boolean(part)).join(" ");
99
-
100
- return [border, padLine(` ${title}`, width), padLine(` ${details}`, width), border];
101
- }
102
- }