pi-one-ui 0.2.2 → 0.3.1

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 (61) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.en.md +256 -0
  3. package/README.md +26 -25
  4. package/extensions/app/commands/settings-previews.ts +3 -3
  5. package/extensions/app/config/renderer.ts +12 -23
  6. package/extensions/app/config/shell.ts +122 -426
  7. package/extensions/app/config/store.ts +23 -92
  8. package/extensions/app/overlay/selector-border.ts +5 -5
  9. package/extensions/app/ownership.ts +1 -1
  10. package/extensions/app/panel.ts +18 -19
  11. package/extensions/app/presets.ts +1 -2
  12. package/extensions/app/runtime/tui-runtime.ts +31 -31
  13. package/extensions/features/context-inspector/index.ts +3 -3
  14. package/extensions/features/session-reference/index.ts +5 -5
  15. package/extensions/features/subagent-autocomplete.ts +1 -1
  16. package/extensions/layouts/context/index.ts +9 -12
  17. package/extensions/layouts/context/renderer/compact-mode.ts +24 -24
  18. package/extensions/layouts/context/renderer/default-mode.ts +21 -16
  19. package/extensions/layouts/context/renderer/index.ts +24 -24
  20. package/extensions/layouts/context/renderer/mouse/hover.ts +6 -6
  21. package/extensions/layouts/context/renderer/mouse/interaction.ts +35 -35
  22. package/extensions/layouts/context/renderer/mouse/packets.ts +3 -2
  23. package/extensions/layouts/context/renderer/mouse/scroll.ts +2 -2
  24. package/extensions/layouts/context/renderer/tool/diff/diff-component.ts +2 -1
  25. package/extensions/layouts/context/renderer/tool/diff/diff-edit-render.ts +21 -21
  26. package/extensions/layouts/context/renderer/tool/diff/diff-header.ts +4 -4
  27. package/extensions/layouts/context/renderer/tool/diff/diff-highlight.ts +6 -6
  28. package/extensions/layouts/context/renderer/tool/diff/diff-inline.ts +1 -1
  29. package/extensions/layouts/context/renderer/tool/diff/diff-layout.ts +18 -18
  30. package/extensions/layouts/context/renderer/tool/diff/diff-limits.ts +5 -5
  31. package/extensions/layouts/context/renderer/tool/diff/diff-palette.ts +3 -3
  32. package/extensions/layouts/context/renderer/tool/diff/diff-presentation.ts +1 -1
  33. package/extensions/layouts/context/renderer/tool/diff/diff-renderer.ts +2 -1
  34. package/extensions/layouts/context/renderer/tool/diff/diff-write-render.ts +31 -31
  35. package/extensions/layouts/context/renderer/tool/diff/index.ts +4 -4
  36. package/extensions/layouts/context/renderer/tool/diff/line-width-safety.ts +1 -1
  37. package/extensions/layouts/context/renderer/tool/diff/write-execution.ts +1 -1
  38. package/extensions/layouts/context/renderer/tool/grouping.ts +12 -12
  39. package/extensions/layouts/context/renderer/tool/message-display.ts +2 -2
  40. package/extensions/layouts/context/renderer/tool/result.ts +199 -19
  41. package/extensions/layouts/context/summary/core.ts +1 -0
  42. package/extensions/layouts/context/summary/index.ts +3 -3
  43. package/extensions/layouts/context/thinking/compact-thinking.ts +12 -13
  44. package/extensions/layouts/editor/accent-rail-editor.ts +5 -5
  45. package/extensions/layouts/editor/controller.ts +4 -4
  46. package/extensions/layouts/editor/editor-metadata-format.ts +4 -4
  47. package/extensions/layouts/editor/factory.ts +3 -3
  48. package/extensions/layouts/editor/index.ts +7 -7
  49. package/extensions/layouts/editor/minimalist-editor.ts +1 -1
  50. package/extensions/layouts/editor/ui.ts +7 -7
  51. package/extensions/layouts/footer/controller.ts +3 -3
  52. package/extensions/layouts/footer/footer.ts +21 -21
  53. package/extensions/layouts/footer/index.ts +8 -8
  54. package/extensions/layouts/header/index.ts +1 -1
  55. package/extensions/layouts/header/startup-header.ts +3 -2
  56. package/extensions/layouts/working-line/controller.ts +6 -6
  57. package/extensions/services/project-refresh.ts +2 -2
  58. package/extensions/services/session-state.ts +1 -1
  59. package/extensions/shared/format.ts +2 -2
  60. package/package.json +17 -10
  61. package/extensions/features/legacy/working-message.ts +0 -226
@@ -19,20 +19,12 @@ export type ConfigRecord = Record<string, unknown>;
19
19
 
20
20
  export type ConfigStorePaths = {
21
21
  canonical: string;
22
- legacyUnified: string;
23
- legacyShell: string;
24
- legacyRenderer: string;
25
22
  };
26
23
 
27
- /**
28
- * Returns canonical and legacy configuration locations for an agent directory.
29
- */
24
+ /** Returns the canonical configuration location for an agent directory. */
30
25
  export function configPaths(agentDir = getAgentDir()): ConfigStorePaths {
31
26
  return {
32
27
  canonical: join(agentDir, "pi-one-ui.json"),
33
- legacyUnified: join(agentDir, "pi-mine-ui.json"),
34
- legacyShell: join(agentDir, "zentui.json"),
35
- legacyRenderer: join(agentDir, "claude-code-style.json"),
36
28
  };
37
29
  }
38
30
 
@@ -138,7 +130,7 @@ function configReadError(path: string, error: unknown): Error {
138
130
  }
139
131
 
140
132
  /**
141
- * Builds the compatibility error used when a config write is unsafe.
133
+ * Builds the safety error used when a config write is unsafe.
142
134
  */
143
135
  function configWriteError(path: string, label: string, error: unknown): Error {
144
136
  const detail = error instanceof Error ? ` (${error.message})` : "";
@@ -147,79 +139,15 @@ function configWriteError(path: string, label: string, error: unknown): Error {
147
139
  );
148
140
  }
149
141
 
150
- type SelectedConfig = {
151
- record: ConfigRecord;
152
- writePath: string;
153
- mode?: number;
154
- materialize: boolean;
155
- };
156
-
157
- /**
158
- * Reads an optional config source and turns corruption into an explicit error.
159
- */
160
- function optionalConfig(
161
- path: string,
162
- ): { record: ConfigRecord; writePath: string; mode: number } | undefined {
163
- const state = readConfigFileState(path);
164
- if (state.kind === "missing") return undefined;
165
- if (state.kind === "corrupt") throw configReadError(path, state.error);
166
- return state;
167
- }
168
-
169
- /**
170
- * Selects the active source and the destination used by a later update.
171
- */
172
- function selectUnifiedConfig(paths: ConfigStorePaths): SelectedConfig {
173
- const canonical = optionalConfig(paths.canonical);
174
- if (canonical) return { ...canonical, materialize: false };
175
-
176
- const legacyUnified = optionalConfig(paths.legacyUnified);
177
- if (legacyUnified)
178
- return {
179
- ...legacyUnified,
180
- writePath: paths.canonical,
181
- materialize: true,
182
- };
183
-
184
- const shell = optionalConfig(paths.legacyShell);
185
- const renderer = optionalConfig(paths.legacyRenderer);
186
- if (!shell && !renderer) {
187
- return {
188
- record: {},
189
- writePath: paths.canonical,
190
- materialize: false,
191
- };
192
- }
193
-
194
- return {
195
- record: {
196
- ...(shell?.record ?? {}),
197
- version: 1,
198
- renderer: renderer?.record ?? {},
199
- },
200
- writePath: paths.canonical,
201
- mode: shell?.mode ?? renderer?.mode,
202
- materialize: true,
203
- };
204
- }
205
-
206
- /**
207
- * Read the one raw unified record used by all configuration domains.
208
- * Legacy sources are best-effort materialized through the same atomic writer;
209
- * the in-memory record remains usable if the agent directory is read-only.
210
- */
142
+ /** Reads the raw canonical record used by every configuration domain. */
211
143
  export function readUnifiedConfigRecord(
212
144
  paths: ConfigStorePaths = defaultConfigPaths,
213
145
  ): ConfigRecord {
214
- const selected = selectUnifiedConfig(paths);
215
- if (selected.materialize) {
216
- try {
217
- writeConfigAtomically(selected.writePath, selected.record, selected.mode);
218
- } catch {
219
- // Keep using the selected legacy record in memory when migration cannot be written.
220
- }
221
- }
222
- return selected.record;
146
+ const state = readConfigFileState(paths.canonical);
147
+ if (state.kind === "missing") return {};
148
+ if (state.kind === "corrupt")
149
+ throw configReadError(paths.canonical, state.error);
150
+ return state.record;
223
151
  }
224
152
 
225
153
  /**
@@ -247,22 +175,18 @@ export type ConfigStoreListener = (record: ConfigRecord) => void;
247
175
 
248
176
  /**
249
177
  * Process-wide raw configuration store. Domain config modules own parsing and
250
- * selectors; this module owns source selection, migration and persistence.
178
+ * selectors; this module owns canonical persistence.
251
179
  */
252
180
  export class ConfigStore {
253
181
  readonly paths: ConfigStorePaths;
254
182
  private readonly listeners = new Set<ConfigStoreListener>();
255
183
 
256
- /**
257
- * Creates a store for canonical and legacy paths.
258
- */
184
+ /** Creates a store for the canonical path. */
259
185
  constructor(paths: ConfigStorePaths = defaultConfigPaths) {
260
186
  this.paths = paths;
261
187
  }
262
188
 
263
- /**
264
- * Reads the current canonical or legacy-backed raw configuration record.
265
- */
189
+ /** Reads the current canonical raw configuration record. */
266
190
  read(): ConfigRecord {
267
191
  return readUnifiedConfigRecord(this.paths);
268
192
  }
@@ -271,11 +195,18 @@ export class ConfigStore {
271
195
  * Mutates and atomically persists the active raw configuration record.
272
196
  */
273
197
  update(mutate: (record: ConfigRecord) => void): ConfigRecord {
274
- const selected = selectUnifiedConfig(this.paths);
275
- mutate(selected.record);
276
- writeConfigAtomically(selected.writePath, selected.record, selected.mode);
277
- for (const listener of this.listeners) listener(selected.record);
278
- return selected.record;
198
+ const state = readConfigFileState(this.paths.canonical);
199
+ if (state.kind === "corrupt")
200
+ throw configReadError(this.paths.canonical, state.error);
201
+
202
+ mutate(state.record);
203
+ writeConfigAtomically(
204
+ state.writePath,
205
+ state.record,
206
+ state.kind === "valid" ? state.mode : undefined,
207
+ );
208
+ for (const listener of this.listeners) listener(state.record);
209
+ return state.record;
279
210
  }
280
211
 
281
212
  /**
@@ -3,16 +3,16 @@ import {
3
3
  SettingsSelectorComponent,
4
4
  type Theme,
5
5
  } from "@earendil-works/pi-coding-agent";
6
- import type { ZentuiConfig } from "../config/shell.ts";
7
- import {
8
- installPrototypePatch,
9
- removePrototypePatch,
10
- } from "../ownership/prototype-patch-registry.ts";
11
6
  import {
12
7
  EDITOR_BORDER_STYLE,
13
8
  renderChromeBorder,
14
9
  renderEditorBorder,
15
10
  } from "../../shared/style.ts";
11
+ import type { ZentuiConfig } from "../config/shell.ts";
12
+ import {
13
+ installPrototypePatch,
14
+ removePrototypePatch,
15
+ } from "../ownership/prototype-patch-registry.ts";
16
16
 
17
17
  type PatchableSelectorPrototype = {
18
18
  render: (width: number) => string[];
@@ -26,7 +26,7 @@ export function ownerFor(slot: UiSlot): UiOwner {
26
26
 
27
27
  export {
28
28
  LAYOUT_OWNERSHIP,
29
- LayoutRegistry,
30
29
  type LayoutId,
31
30
  type LayoutOwner,
31
+ LayoutRegistry,
32
32
  } from "./ownership/layout-registry.ts";
@@ -413,6 +413,13 @@ function saveNotice(ctx: any, label: string, value: string): void {
413
413
  );
414
414
  }
415
415
 
416
+ function notifyUpdateError(ctx: any, error: unknown): void {
417
+ ctx.ui?.notify?.(
418
+ `Could not update /oneui setting: ${error instanceof Error ? error.message : String(error)}`,
419
+ "error",
420
+ );
421
+ }
422
+
416
423
  /**
417
424
  * Persists one panel setting and reconciles its owning runtime layout.
418
425
  *
@@ -605,27 +612,19 @@ export async function showOneUiPanel(
605
612
  10,
606
613
  getSettingsListTheme(),
607
614
  (id, value) => {
608
- // Replacing Pi's editor synchronously moves focus away from
609
- // this overlay. Restore the overlay handle after the editor
610
- // has been reconciled so the panel remains interactive.
611
- if (id === "editorEnabled") {
612
- try {
613
- updateSetting(id, value, ctx, deps);
614
- list.updateValue(id, value);
615
- } catch (error) {
616
- ctx.ui?.notify?.(
617
- `Could not update Editor setting: ${error instanceof Error ? error.message : String(error)}`,
618
- "error",
619
- );
620
- } finally {
615
+ try {
616
+ updateSetting(id, value, ctx, deps);
617
+ list.updateValue(id, value);
618
+ } catch (error) {
619
+ notifyUpdateError(ctx, error);
620
+ createList();
621
+ list.selectItem(id);
622
+ } finally {
623
+ if (id === "editorEnabled" || id === "editorStyle") {
621
624
  panelHandle?.focus();
622
- tui.requestRender();
623
625
  }
624
- return;
626
+ tui.requestRender();
625
627
  }
626
- updateSetting(id, value, ctx, deps);
627
- list.updateValue(id, value);
628
- tui.requestRender();
629
628
  },
630
629
  () => done(undefined),
631
630
  );
@@ -687,7 +686,7 @@ export async function showOneUiPanel(
687
686
  width: "85%",
688
687
  maxHeight: "90%",
689
688
  margin: {
690
- top: 10,
689
+ top: 6,
691
690
  right: 1,
692
691
  bottom: 1,
693
692
  left: 1,
@@ -21,7 +21,7 @@ export function applyPreset(preset: Preset): void {
21
21
  saveUserMessagesComponentPatch({ enabled: false });
22
22
  saveWorkingLineComponentPatch({ enabled: false });
23
23
  saveFooterComponentPatch({ style: "native" });
24
- updateRendererConfig({ mode: "off", enableWorkingMessage: false });
24
+ updateRendererConfig({ mode: "off" });
25
25
  return;
26
26
  }
27
27
 
@@ -31,6 +31,5 @@ export function applyPreset(preset: Preset): void {
31
31
  saveFooterComponentPatch({ style: "starship" });
32
32
  updateRendererConfig({
33
33
  mode: preset === "compact" ? "compact" : "on",
34
- enableWorkingMessage: false,
35
34
  });
36
35
  }
@@ -3,56 +3,56 @@ import type {
3
3
  ExtensionContext,
4
4
  Theme,
5
5
  } from "@earendil-works/pi-coding-agent";
6
- import { showOneUiPanel, type TuiPanelRuntime } from "../panel.ts";
7
- import {
8
- createPiExtensionPort,
9
- type PiExtensionPort,
10
- } from "../host/pi-extension-port.ts";
11
- import { createPiUiPort, type PiUiPort } from "../host/pi-ui-port.ts";
12
- import { LayoutRegistry } from "../ownership/layout-registry.ts";
13
- import registerHeaderLayout from "../../layouts/header/index.ts";
14
- import { EventCoordinator } from "./event-coordinator.ts";
15
- import { RenderScheduler } from "./render-scheduler.ts";
16
- import { RuntimeStateStore } from "./runtime-state.ts";
6
+ import registerContext, {
7
+ type ContextExtensionOptions,
8
+ type ContextRuntimeController,
9
+ } from "../../layouts/context/index.ts";
10
+ import type { AccentRailLayoutPatchDiagnostic } from "../../layouts/editor/accent-rail-layout-patch.ts";
17
11
  import { EditorLayoutController } from "../../layouts/editor/controller.ts";
18
12
  import { FooterLayoutController } from "../../layouts/footer/controller.ts";
13
+ import registerHeaderLayout from "../../layouts/header/index.ts";
19
14
  import {
20
15
  WorkingLineLayoutController,
21
16
  type WorkingLineMessage,
22
17
  type WorkingLineMessageEnd,
23
18
  } from "../../layouts/working-line/controller.ts";
24
- import registerContext, {
25
- type ContextExtensionOptions,
26
- type ContextRuntimeController,
27
- } from "../../layouts/context/index.ts";
28
- import type { AccentRailLayoutPatchDiagnostic } from "../../layouts/editor/accent-rail-layout-patch.ts";
19
+ import {
20
+ renderTurnSummaryEntry,
21
+ TURN_SUMMARY_ENTRY_TYPE,
22
+ } from "../../layouts/working-line/interaction-summary.ts";
23
+ import { LiveContextController } from "../../services/live-context.ts";
24
+ import {
25
+ ProjectRefreshService,
26
+ type ScheduleProjectRefreshOptions,
27
+ } from "../../services/project-refresh.ts";
28
+ import {
29
+ SessionStateService,
30
+ syncState,
31
+ } from "../../services/session-state.ts";
32
+ import { resolveFooterTelemetry } from "../../services/telemetry.ts";
29
33
  import {
30
34
  ensureConfigExists,
31
35
  hasUnsupportedComponentStyle,
32
36
  loadConfig,
33
37
  mergeConfig,
38
+ type PolishedTuiConfig,
34
39
  saveEditorComponentPatch,
35
40
  saveFooterComponentPatch,
36
41
  saveWorkingLineComponentPatch,
37
- type PolishedTuiConfig,
38
42
  } from "../config/shell.ts";
39
- import { configStore, type ConfigRecord } from "../config/store.ts";
40
- import {
41
- SessionStateService,
42
- syncState,
43
- } from "../../services/session-state.ts";
44
- import {
45
- renderTurnSummaryEntry,
46
- TURN_SUMMARY_ENTRY_TYPE,
47
- } from "../../layouts/working-line/interaction-summary.ts";
48
- import { LiveContextController } from "../../services/live-context.ts";
43
+ import { type ConfigRecord, configStore } from "../config/store.ts";
49
44
  import {
50
- ProjectRefreshService,
51
- type ScheduleProjectRefreshOptions,
52
- } from "../../services/project-refresh.ts";
45
+ createPiExtensionPort,
46
+ type PiExtensionPort,
47
+ } from "../host/pi-extension-port.ts";
48
+ import { createPiUiPort, type PiUiPort } from "../host/pi-ui-port.ts";
53
49
  import { SelectorController } from "../overlay/selector-controller.ts";
50
+ import { LayoutRegistry } from "../ownership/layout-registry.ts";
51
+ import { showOneUiPanel, type TuiPanelRuntime } from "../panel.ts";
52
+ import { EventCoordinator } from "./event-coordinator.ts";
53
+ import { RenderScheduler } from "./render-scheduler.ts";
54
+ import { RuntimeStateStore } from "./runtime-state.ts";
54
55
  import { SessionLifecycle } from "./session-lifecycle.ts";
55
- import { resolveFooterTelemetry } from "../../services/telemetry.ts";
56
56
 
57
57
  export type TuiRuntimeContext = {
58
58
  readonly extensions: PiExtensionPort;
@@ -2,10 +2,10 @@ import {
2
2
  type BuildSystemPromptOptions,
3
3
  type ExtensionAPI,
4
4
  type ExtensionCommandContext,
5
- type ToolInfo,
6
5
  estimateTokens,
7
6
  formatSkillsForPrompt,
8
7
  getMarkdownTheme,
8
+ type ToolInfo,
9
9
  } from "@earendil-works/pi-coding-agent";
10
10
  import {
11
11
  Key,
@@ -13,9 +13,9 @@ import {
13
13
  matchesKey,
14
14
  visibleWidth,
15
15
  } from "@earendil-works/pi-tui";
16
- import { mouseBaseButton, parseSgrMousePacket } from "../../tools/sgr-mouse.ts";
17
- import { padLine } from "../../tools/format.ts";
18
16
  import { overlayManager } from "../../app/overlay/overlay-manager.ts";
17
+ import { padLine } from "../../tools/format.ts";
18
+ import { mouseBaseButton, parseSgrMousePacket } from "../../tools/sgr-mouse.ts";
19
19
 
20
20
  export type ContextPart = {
21
21
  label: string;
@@ -1,25 +1,25 @@
1
1
  import { resolve } from "node:path";
2
2
  import {
3
- SessionManager,
4
3
  type ExtensionAPI,
5
4
  type SessionInfo,
5
+ SessionManager,
6
6
  } from "@earendil-works/pi-coding-agent";
7
7
  import {
8
- Text,
9
8
  type AutocompleteItem,
10
9
  type AutocompleteProvider,
11
10
  type AutocompleteSuggestions,
12
11
  fuzzyFilter,
12
+ Text,
13
13
  } from "@earendil-works/pi-tui";
14
14
  import {
15
- SESSION_REFERENCE_CUSTOM_TYPE,
16
- SESSION_REFERENCE_PREFIX,
17
15
  buildReferenceContentFromSections,
18
16
  extractSessionReferenceIds,
19
17
  formatReferenceSession,
20
- sessionTitle,
21
18
  type ReferenceSessionInfo,
22
19
  type ReferenceSource,
20
+ SESSION_REFERENCE_CUSTOM_TYPE,
21
+ SESSION_REFERENCE_PREFIX,
22
+ sessionTitle,
23
23
  } from "./session.ts";
24
24
 
25
25
  const MAX_SESSION_SUGGESTIONS = 3;
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readdirSync, readFileSync } from "node:fs";
2
- import { join } from "node:path";
3
2
  import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
4
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
5
5
  import {
6
6
  type AutocompleteItem,
@@ -4,7 +4,6 @@ import type {
4
4
  Theme,
5
5
  } from "@earendil-works/pi-coding-agent";
6
6
  import { config } from "../../app/config/renderer.ts";
7
- import { configStore, type ConfigRecord } from "../../app/config/store.ts";
8
7
  import {
9
8
  hasUnsupportedComponentStyle,
10
9
  loadConfig,
@@ -13,27 +12,25 @@ import {
13
12
  type UserMessagesComponentConfig,
14
13
  type ZentuiConfig,
15
14
  } from "../../app/config/shell.ts";
16
- import {
17
- installUserMessageStyle,
18
- removeUserMessageStyle,
19
- } from "./message/user-message.ts";
20
-
15
+ import { type ConfigRecord, configStore } from "../../app/config/store.ts";
21
16
  // shell
22
17
  import piAliases from "../../features/aliases.ts";
18
+ import context from "../../features/context-inspector/index.ts";
23
19
  import { installFlushDockedBash } from "../../features/flush-docked-bash.ts";
24
-
20
+ import sessionReference from "../../features/session-reference/index.ts";
25
21
  // feature
26
22
  import agentAutocomplete from "../../features/subagent-autocomplete.ts";
27
- import agentSummary from "./summary/index.ts";
28
- import context from "../../features/context-inspector/index.ts";
29
- import sessionReference from "../../features/session-reference/index.ts";
30
- import { installCompactThinking } from "./thinking/compact-thinking.ts";
31
-
23
+ import {
24
+ installUserMessageStyle,
25
+ removeUserMessageStyle,
26
+ } from "./message/user-message.ts";
32
27
  // renderer
33
28
  import registerContextRenderer, {
34
29
  getCompactThinkingConfig,
35
30
  } from "./renderer/index.ts";
36
31
  import markdownEnhance from "./renderer/markdown-enhance.ts";
32
+ import agentSummary from "./summary/index.ts";
33
+ import { installCompactThinking } from "./thinking/compact-thinking.ts";
37
34
 
38
35
  export type ContextRuntimeController = {
39
36
  setMode: (mode: "on" | "compact" | "off", ctx: ExtensionContext) => void;
@@ -17,41 +17,22 @@
17
17
  */
18
18
  import {
19
19
  AssistantMessageComponent,
20
- ToolExecutionComponent,
21
20
  type Theme,
21
+ ToolExecutionComponent,
22
22
  } from "@earendil-works/pi-coding-agent";
23
23
  import {
24
- truncateToWidth,
25
- visibleWidth,
26
24
  Box,
27
25
  Spacer,
26
+ truncateToWidth,
27
+ visibleWidth,
28
28
  } from "@earendil-works/pi-tui";
29
29
  import { config, getToolDisplayConfig } from "../../../app/config/renderer.ts";
30
- import { toolLoadingIcon } from "../../../tools/tool-loading-icon.ts";
31
- import { sanitizeToolResultText } from "../../../tools/tool-result-sanitize.ts";
32
- import { refreshContextComponent } from "./context-refresh.ts";
33
- import { getMessageDisplayTheme } from "./tool/message-display.ts";
34
- import { showMoreHintText } from "./tool/show-more-hint.ts";
35
- import {
36
- countEditDiffStats,
37
- countWriteDiffStats,
38
- isRichDiffComponent,
39
- } from "./tool/diff/diff-renderer.ts";
40
- import { renderRichToolResult } from "./tool/diff/index.ts";
41
- import type { WriteExecutionMetadataStore } from "./tool/diff/write-execution.ts";
42
- import { isToolCallHovered } from "./mouse/hover.ts";
43
- import {
44
- insetComponent,
45
- renderExpandedToolResult,
46
- scheduleAnimation,
47
- } from "./tool/result.ts";
48
- import { oneLine } from "../../../tools/format.ts";
49
- import { paddedBackgroundRow } from "./tool/grouping.ts";
50
30
  import {
51
31
  hasVisibleText,
52
32
  stripBackgroundAnsi,
53
33
  } from "../../../tools/ansi-text.ts";
54
34
  import { walkComponentTree } from "../../../tools/component-tree.ts";
35
+ import { oneLine } from "../../../tools/format.ts";
55
36
  import {
56
37
  ASSISTANT_REENTRY_DESCRIPTION,
57
38
  ASSISTANT_REENTRY_KEY,
@@ -61,9 +42,28 @@ import {
61
42
  ASSISTANT_TOGGLE_ROUND_KEY,
62
43
  COMPACT_MODE_PATCH_KEY,
63
44
  COMPACT_THINKING_PATCH_KEY,
64
- patchRegistry,
65
45
  PROTOTYPE_ORIGINAL_KEY,
46
+ patchRegistry,
66
47
  } from "../../../tools/patch-keys.ts";
48
+ import { toolLoadingIcon } from "../../../tools/tool-loading-icon.ts";
49
+ import { sanitizeToolResultText } from "../../../tools/tool-result-sanitize.ts";
50
+ import { refreshContextComponent } from "./context-refresh.ts";
51
+ import { isToolCallHovered } from "./mouse/hover.ts";
52
+ import {
53
+ countEditDiffStats,
54
+ countWriteDiffStats,
55
+ isRichDiffComponent,
56
+ } from "./tool/diff/diff-renderer.ts";
57
+ import { renderRichToolResult } from "./tool/diff/index.ts";
58
+ import type { WriteExecutionMetadataStore } from "./tool/diff/write-execution.ts";
59
+ import { paddedBackgroundRow } from "./tool/grouping.ts";
60
+ import { getMessageDisplayTheme } from "./tool/message-display.ts";
61
+ import {
62
+ insetComponent,
63
+ renderExpandedToolResult,
64
+ scheduleAnimation,
65
+ } from "./tool/result.ts";
66
+ import { showMoreHintText } from "./tool/show-more-hint.ts";
67
67
 
68
68
  /** compact 渲染层对 compact-thinking 的只读查询面(不建第二套计时器)。 */
69
69
  export type CompactThinkingQuery = {
@@ -7,10 +7,11 @@
7
7
  import { ToolExecutionComponent } from "@earendil-works/pi-coding-agent";
8
8
  import { Text, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
9
9
  import {
10
+ type CompactStyleMode,
10
11
  config,
11
12
  getToolDisplayConfig,
12
- type CompactStyleMode,
13
13
  } from "../../../app/config/renderer.ts";
14
+ import { oneLine } from "../../../tools/format.ts";
14
15
  import {
15
16
  COMPONENT_TOOL_RENDER_MODE,
16
17
  GLOBAL_TOOL_RENDER_PATCH,
@@ -18,6 +19,13 @@ import {
18
19
  TOOL_EXPANDED_BACKGROUND_PATCH,
19
20
  } from "../../../tools/patch-keys.ts";
20
21
  import { isToolCallHovered } from "./mouse/hover.ts";
22
+ import { countWriteDiffStats } from "./tool/diff/diff-renderer.ts";
23
+ import {
24
+ renderRichToolResult,
25
+ type WriteExecutionMetadataStore,
26
+ } from "./tool/diff/index.ts";
27
+ import { getMessageDisplayTheme } from "./tool/message-display.ts";
28
+ import { humanizeToolLabel, toolCallSummary } from "./tool/names.ts";
21
29
  import {
22
30
  countLines,
23
31
  hasExpandableDetail,
@@ -30,28 +38,20 @@ import {
30
38
  renderExpandedToolResult,
31
39
  resolveToolVisualState,
32
40
  scheduleAnimation,
33
- settledIcon,
34
41
  setToolVisualState,
42
+ settledIcon,
35
43
  textFromResult,
36
44
  toolIconColor,
37
45
  toolViewportWidth,
38
46
  } from "./tool/result.ts";
39
- import { oneLine } from "../../../tools/format.ts";
40
47
  import { showMoreHintText } from "./tool/show-more-hint.ts";
41
- import { countWriteDiffStats } from "./tool/diff/diff-renderer.ts";
42
- import {
43
- renderRichToolResult,
44
- type WriteExecutionMetadataStore,
45
- } from "./tool/diff/index.ts";
46
- import { getMessageDisplayTheme } from "./tool/message-display.ts";
47
- import { humanizeToolLabel, toolCallSummary } from "./tool/names.ts";
48
48
 
49
49
  // 成功勾:亮绿 truecolor(与 message-display 一致)
50
50
  const BRIGHT_GREEN = "\x1b[38;2;80;220;100m";
51
51
  const ANSI_FG_RESET = "\x1b[39m";
52
52
 
53
- // pi-subagents 等扩展为 Agent 提供专用渲染器,ccstyle 必须保留。
54
- const DEDICATED_RENDERER_TOOLS = new Set(["Agent"]);
53
+ // Preserve rich renderers owned by legacy Agent tools and modern pi-subagents.
54
+ const DEDICATED_RENDERER_TOOLS = new Set(["Agent", "subagent"]);
55
55
 
56
56
  export type DefaultModeHooks = {
57
57
  /** 本安装是否仍持有全局工具渲染补丁。 */
@@ -148,14 +148,14 @@ function renderDefault(
148
148
  tool: any,
149
149
  slot: "renderCall" | "renderResult",
150
150
  args: any[],
151
- fallback = "",
151
+ fallback: string | (() => string) = "",
152
152
  ) {
153
153
  try {
154
154
  if (typeof tool?.[slot] === "function") return tool[slot](...args);
155
155
  } catch {
156
156
  // Fall through to raw fallback.
157
157
  }
158
- return new Text(fallback, 0, 0);
158
+ return new Text(typeof fallback === "function" ? fallback() : fallback, 0, 0);
159
159
  }
160
160
 
161
161
  type ParsedTask = { id: string; status: string; subject: string };
@@ -320,7 +320,7 @@ function createCcstyleTool(
320
320
  originalTool,
321
321
  "renderResult",
322
322
  [result, options, theme, context],
323
- textFromResult(result),
323
+ () => textFromResult(result),
324
324
  );
325
325
  }
326
326
 
@@ -421,7 +421,12 @@ function createCcstyleTool(
421
421
  isToolCallHovered(toolCallId) ? cachedHoveredLine! : cachedLine,
422
422
  ];
423
423
  },
424
- invalidate() {},
424
+ invalidate() {
425
+ cachedWidth = undefined;
426
+ cachedLine = undefined;
427
+ cachedHoveredLine = undefined;
428
+ context?.state?.ccstyleExpandedIoView?.invalidate?.();
429
+ },
425
430
  };
426
431
  },
427
432
  };