@hheei/pi-ext-ui 0.8.1 → 0.8.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.
package/dist/extension.js CHANGED
@@ -14,6 +14,9 @@ export default function (pi) {
14
14
  key: "@hheei/pi-ext-ui",
15
15
  async start(context) {
16
16
  let patch;
17
+ // The host owns this setting; a message view that draws its own shell applies the same
18
+ // value the host applies to its own rows, including the default when it is unset.
19
+ const outputPad = () => (context.pi.getSettings().outputPad === 0 ? 0 : 1);
17
20
  const apply = (enabled) => {
18
21
  if (context.extension.mode !== "tui")
19
22
  return;
@@ -26,7 +29,7 @@ export default function (pi) {
26
29
  if (patch !== undefined)
27
30
  return;
28
31
  try {
29
- patch = installHostToolGroups(context.extension, () => runtime.backgrounds);
32
+ patch = installHostToolGroups(context.extension, () => runtime.backgrounds, outputPad);
30
33
  redrawGroups = patch.requestRender;
31
34
  }
32
35
  catch (error) {
@@ -70,7 +73,7 @@ export default function (pi) {
70
73
  });
71
74
  const toolRenderers = registerExtensionHook(pi, toolRendererPoint, createOptionalToolRendererFactory(pi, runtime));
72
75
  context.resources.add("tool-renderer", () => toolRenderers.dispose());
73
- const handle = registerExtensionHook(context.pi, messageViewPoint, createMessageView);
76
+ const handle = registerExtensionHook(context.pi, messageViewPoint, (spec) => createMessageView({ ...spec, outputPad: spec.outputPad ?? outputPad() }));
74
77
  context.resources.add("message-view", () => handle.dispose());
75
78
  try {
76
79
  const state = await provider.storage.load({
@@ -1,6 +1,6 @@
1
1
  import { type ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  /** Private to pi-ext-ui: two process hooks, scoped by the actual host UI facade. */
3
- export declare function installHostToolGroups(context: ExtensionContext, background: () => boolean): {
3
+ export declare function installHostToolGroups(context: ExtensionContext, background: () => boolean, outputPad: () => number): {
4
4
  dispose(): void;
5
5
  requestRender(): void;
6
6
  beginRun(): void;
@@ -1,4 +1,4 @@
1
- import { AssistantMessageComponent, InteractiveMode, ToolExecutionComponent, VERSION, } from "@earendil-works/pi-coding-agent";
1
+ import { AssistantMessageComponent, InteractiveMode, ToolExecutionComponent, } from "@earendil-works/pi-coding-agent";
2
2
  import { Container, stripTerminalSequences, Text } from "@earendil-works/pi-tui";
3
3
  import { MessageView } from "./message-view.js";
4
4
  import { memberParts, TailUnit, ToolGroupView, } from "./tool-group-view.js";
@@ -7,8 +7,8 @@ const SEAMS = ["getRegisteredToolDefinition", "showExtensionNotify"];
7
7
  function record(value) {
8
8
  return typeof value === "object" && value !== null;
9
9
  }
10
- // Audited Pi >=1.0.1 compiles these TS-private members to ordinary JS properties.
11
- // All host-private access stays here; VERSION comes from the running host virtual module.
10
+ // Audited Pi >=1.5.0 compiles these TS-private members to ordinary JS properties.
11
+ // All host-private access stays here.
12
12
  function readHost(value) {
13
13
  if (!record(value))
14
14
  throw new Error("Invalid InteractiveMode instance");
@@ -147,12 +147,14 @@ function noticeMember(component) {
147
147
  * semantic color. The group draws these rows once, so a plain notice has no expansion of its own.
148
148
  */
149
149
  class NotifyView {
150
- #text = new Text("", 1, 0);
150
+ #text = new Text("", 0, 0);
151
151
  #ui;
152
+ #outputPad;
152
153
  #message;
153
154
  #type;
154
- constructor(message, type, ui) {
155
+ constructor(message, type, ui, outputPad) {
155
156
  this.#ui = ui;
157
+ this.#outputPad = outputPad;
156
158
  this.#message = message;
157
159
  this.#type = type;
158
160
  }
@@ -166,6 +168,8 @@ class NotifyView {
166
168
  }
167
169
  /** The notice body as it appears both in the transcript and inside the activity group. */
168
170
  rows(width) {
171
+ // The pad setting can change while a notice is on screen, so it is read per render.
172
+ this.#text.setPaddingX(this.#outputPad());
169
173
  // info is the terminal's own readable text; a warning or error keeps its status token and
170
174
  // its written prefix, so the level never depends on color alone.
171
175
  this.#text.setText(this.#ui.theme.fg(this.#type === "info" ? "text" : this.#type, this.#content));
@@ -182,7 +186,7 @@ class NotifyView {
182
186
  this.#text.invalidate();
183
187
  }
184
188
  }
185
- function attach(host, run, background) {
189
+ function attach(host, run, background, outputPad) {
186
190
  const chat = host.chat;
187
191
  const original = {
188
192
  render: chat.render,
@@ -399,7 +403,7 @@ function attach(host, run, background) {
399
403
  notify(message, type) {
400
404
  if (!active)
401
405
  return false;
402
- chat.addChild(new NotifyView(message, type, host.ui));
406
+ chat.addChild(new NotifyView(message, type, host.ui, outputPad));
403
407
  host.requestRender();
404
408
  return true;
405
409
  },
@@ -513,13 +517,7 @@ function patchState() {
513
517
  return state;
514
518
  }
515
519
  /** Private to pi-ext-ui: two process hooks, scoped by the actual host UI facade. */
516
- export function installHostToolGroups(context, background) {
517
- const parts = VERSION.split(".").map(Number);
518
- const [major = 0, minor = 0, patch = 0] = parts;
519
- if (parts.length !== 3 ||
520
- !parts.every(Number.isInteger) ||
521
- !(major > 1 || (major === 1 && (minor > 0 || (minor === 0 && patch >= 1)))))
522
- throw new Error(`Tool grouping requires Pi >=1.0.1; running ${VERSION}`);
520
+ export function installHostToolGroups(context, background, outputPad) {
523
521
  const state = patchState();
524
522
  const ui = context.ui;
525
523
  if (state.owners.has(ui))
@@ -545,7 +543,7 @@ export function installHostToolGroups(context, background) {
545
543
  ui,
546
544
  hosts: new Map(),
547
545
  failed: new WeakSet(),
548
- attach: (host) => attach(host, run, background),
546
+ attach: (host) => attach(host, run, background, outputPad),
549
547
  };
550
548
  state.owners.set(ui, owner);
551
549
  // Reload rebuilds history before session_start; reconnect already discovered instances.
@@ -3,8 +3,9 @@ import { resolve } from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
4
  import { getLanguageFromPath, } from "@earendil-works/pi-coding-agent";
5
5
  import { getCapabilities, hyperlink, stripTerminalSequences } from "@earendil-works/pi-tui";
6
+ import { isRecord } from "@hheei/pi-ext-core";
6
7
  import { diffStats } from "./diff-view.js";
7
- import { formatTimeoutFact, isRecord, parseCallArgs, } from "./payload-parser.js";
8
+ import { formatTimeoutFact, parseCallArgs } from "./payload-parser.js";
8
9
  /**
9
10
  * The single owner of Pi's own tool vocabulary in this UI. Concrete tool packages state their own
10
11
  * semantics through ext-core's presentation point; this module is the explicit adapter for the tools
@@ -1,6 +1,6 @@
1
1
  import { type Theme, type ToolRenderers } from "@earendil-works/pi-coding-agent";
2
2
  import { type Component, type TuiMouseEvent, type TuiMouseEventResult } from "@earendil-works/pi-tui";
3
- import type { ToolPresentationProvider } from "@hheei/pi-ext-core";
3
+ import { type ToolPresentationProvider } from "@hheei/pi-ext-core";
4
4
  import type { NestedCallRecord, ToolRenderContext } from "./renderer.js";
5
5
  import { type Collapsible } from "./tool-view.js";
6
6
  import type { CapturedToolCall } from "./tracker.js";
@@ -1,13 +1,12 @@
1
1
  import { copyToClipboard as copyToSystemClipboard, getLanguageFromPath, highlightCode, } from "@earendil-works/pi-coding-agent";
2
2
  import { stripTerminalSequences, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "@earendil-works/pi-tui";
3
+ import { isRecord } from "@hheei/pi-ext-core";
3
4
  import { extractChildToolCopyText, inlinePresentation, nativeRequestCopy } from "./native-tools.js";
5
+ import { formatDurationMs } from "./payload-parser.js";
4
6
  import { formatToolHeaderText, TOOL_SPINNER_INTERVAL_MS, ToolView, toContentRows, toolSpinnerFrame, transitionFoldLevel, } from "./tool-view.js";
5
7
  function truncate(text, width) {
6
8
  return truncateToWidth(text, Math.max(1, width));
7
9
  }
8
- function isRecord(value) {
9
- return typeof value === "object" && value !== null && !Array.isArray(value);
10
- }
11
10
  /**
12
11
  * A call without a final result is still running: its row carries the live spinner frame. A persisted
13
12
  * record states its own status, so a recovered board never animates a call that already ended.
@@ -27,11 +26,6 @@ function copyText(text, request) {
27
26
  }
28
27
  return request + stripTerminalSequences(text.slice(request.length));
29
28
  }
30
- function formatDuration(ms) {
31
- if (ms === undefined || !Number.isFinite(ms))
32
- return "";
33
- return ms < 1000 ? `${String(Math.round(ms))}ms` : `${(ms / 1000).toFixed(1)}s`;
34
- }
35
29
  export const NESTED_CALLS_EXPANDED_KEY = "__pi_ext_ui_nested_expanded__";
36
30
  export const NESTED_CALLS_COPIED_KEY = "__pi_ext_ui_nested_copied__";
37
31
  export class NestedCallsView {
@@ -140,6 +134,9 @@ export class NestedCallsView {
140
134
  lastComponent: undefined,
141
135
  args: "args" in call ? call.args : undefined,
142
136
  toolCallId,
137
+ // The container frame already indented these rows by the output pad; a child body that
138
+ // applied it again would count the same setting twice.
139
+ outputPad: 0,
143
140
  durationMs: call.durationMs,
144
141
  isError: Boolean(call.isError || ("status" in call && call.status === "error")),
145
142
  isPartial: false,
@@ -261,7 +258,7 @@ export class NestedCallsView {
261
258
  title: toolName,
262
259
  summary: childSummary,
263
260
  ...(ownSummary === undefined || ownSummary === "" ? {} : { summaryStyled: true }),
264
- meta: formatDuration(call.durationMs),
261
+ meta: formatDurationMs(call.durationMs),
265
262
  }, theme);
266
263
  const isCopied = this.#copiedIndex === i;
267
264
  const copyIcon = this.#failedIndex === i
@@ -20,7 +20,6 @@ export interface NestedCallRecord {
20
20
  readonly cost?: number | undefined;
21
21
  readonly error?: string | undefined;
22
22
  }
23
- export declare function isRecord(value: unknown): value is Record<string, unknown>;
24
23
  export declare function estimateStreamingTokens(args: unknown): number;
25
24
  export declare function formatDurationMs(ms: number | undefined): string;
26
25
  export declare function formatCostUsd(cost: number | undefined): string;
@@ -1,7 +1,5 @@
1
+ import { isRecord } from "@hheei/pi-ext-core";
1
2
  export const RESOLVED_CALLS_KEY = "__piExtUiResolvedCalls";
2
- export function isRecord(value) {
3
- return typeof value === "object" && value !== null && !Array.isArray(value);
4
- }
5
3
  export function estimateStreamingTokens(args) {
6
4
  if (args === undefined || args === null)
7
5
  return 0;
@@ -1,10 +1,7 @@
1
1
  import { type ExtensionAPI, type ToolRendererResolver, type ToolRenderers } from "@earendil-works/pi-coding-agent";
2
2
  import type { ToolUiRuntime } from "./runtime.js";
3
- /** Optional host metadata is absent on early supported Pi versions. */
4
- export type ToolRenderContext = Parameters<NonNullable<ToolRenderers["renderCall"]>>[2] & {
5
- readonly durationMs?: number | undefined;
6
- readonly outputPad?: number | undefined;
7
- };
3
+ /** Everything the host hands a tool renderer for one call. */
4
+ export type ToolRenderContext = Parameters<NonNullable<ToolRenderers["renderCall"]>>[2];
8
5
  /**
9
6
  * Upstream tools whose call and result views pi-ext-ui still frames itself. Concrete tool packages
10
7
  * own their own presentation through ext-core's optional renderer point, so their names never
package/dist/renderer.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { highlightCode, } from "@earendil-works/pi-coding-agent";
2
2
  import { stripTerminalSequences } from "@earendil-works/pi-tui";
3
- import { agentResultText, getRegisteredToolPresentationProvider, getRegisteredToolRenderers, getToolPresentationProvider, hasOptionalToolRenderers, } from "@hheei/pi-ext-core";
3
+ import { agentResultText, getRegisteredToolPresentationProvider, getRegisteredToolRenderers, getToolPresentationProvider, hasOptionalToolRenderers, isRecord, } from "@hheei/pi-ext-core";
4
4
  import { DiffView } from "./diff-view.js";
5
5
  import { NestedCallsView } from "./nested-calls-view.js";
6
6
  import { formatToolHeaderText, ToolView, } from "./tool-view.js";
@@ -11,7 +11,7 @@ import { formatToolHeaderText, ToolView, } from "./tool-view.js";
11
11
  */
12
12
  export const SPECIALIZED_TOOL_RENDERERS = ["codemode"];
13
13
  import { inlinePresentation, nativeHeaderSummary, nativeHeavyRequest, nativeOutputLanguage, nativeRequestBlock, nativeResultText, nativeTimeoutFact, } from "./native-tools.js";
14
- import { callStatsParts, estimateStreamingTokens, extractNestedCalls, formatCostUsd, formatDurationMs, isRecord, RESOLVED_CALLS_KEY, resolveContainerStatus, } from "./payload-parser.js";
14
+ import { callStatsParts, estimateStreamingTokens, extractNestedCalls, formatCostUsd, formatDurationMs, RESOLVED_CALLS_KEY, resolveContainerStatus, } from "./payload-parser.js";
15
15
  export { extractChildToolCopyText, inlinePresentation } from "./native-tools.js";
16
16
  export { estimateStreamingTokens, } from "./payload-parser.js";
17
17
  const SPECIALIZED_TOOL_SET = new Set(SPECIALIZED_TOOL_RENDERERS);
@@ -415,6 +415,9 @@ function renderGenericResult(params) {
415
415
  const upstreamContext = {
416
416
  ...context,
417
417
  lastComponent: lastUpstreamComponent,
418
+ // This frame indents the rows it embeds, so the renderer it wraps must not indent
419
+ // them again: the pad is applied once, by the outermost shell.
420
+ outputPad: 0,
418
421
  };
419
422
  const comp = originalRenderResult(result, effectiveOptions, theme, upstreamContext);
420
423
  lastUpstreamComponent = comp;
@@ -486,19 +489,34 @@ function renderOwnedPresentation(params) {
486
489
  const isError = context.isError || result?.isError === true;
487
490
  const pending = result === undefined || context.isPartial;
488
491
  const isDiff = !isError && !pending && presentation.kind === "diff";
492
+ // A container's children are facts: the provider states the ones it finished, and the tracker
493
+ // already holds the one still running, so the row can show work the tool has not reported yet.
494
+ const captured = runtime.tracker.getCaptured(context.toolCallId) ??
495
+ (result !== undefined &&
496
+ isRecord(result.details) &&
497
+ Array.isArray(result.details.__piExtUiCapturedCalls)
498
+ ? result.details.__piExtUiCapturedCalls
499
+ : undefined);
500
+ const ownCalls = presentation.children === undefined
501
+ ? extractNestedCalls(result)
502
+ : presentationChildCalls(presentation.children);
503
+ const childCalls = withSessionArgs(captured ?? ownCalls, runtime);
489
504
  // The tool owns its status: a container says "none" for a child's failure and a partial patch
490
505
  // says "warn" even though the host marks the result failed. Only a provider without an opinion
491
506
  // falls back to the frame's own reading of the execution.
492
507
  const status = presentation.status ??
493
508
  (isError
494
509
  ? "error"
495
- : pending
496
- ? context.argsComplete
497
- ? "running"
498
- : "streaming"
499
- : isDiff
510
+ : // Child rows carry the work, so the container's own glyph is the fold pin only.
511
+ childCalls.length > 0
500
512
  ? "none"
501
- : "ok");
513
+ : pending
514
+ ? context.argsComplete
515
+ ? "running"
516
+ : "streaming"
517
+ : isDiff
518
+ ? "none"
519
+ : "ok");
502
520
  const railToken = isError ? "error" : pending ? "accent" : "success";
503
521
  const output = isError && result !== undefined
504
522
  ? agentResultText(result)
@@ -563,18 +581,6 @@ function renderOwnedPresentation(params) {
563
581
  if (presentation.collapsedRows !== undefined) {
564
582
  return presentation.collapsedRows;
565
583
  }
566
- const captured = result !== undefined
567
- ? (runtime.tracker.getCaptured(context.toolCallId) ??
568
- (isRecord(result.details) && Array.isArray(result.details.__piExtUiCapturedCalls)
569
- ? result.details.__piExtUiCapturedCalls
570
- : undefined))
571
- : undefined;
572
- const nestedCalls = extractNestedCalls(result);
573
- // The owner's own child facts win over parsing another package's details.
574
- const ownCalls = presentation.children === undefined
575
- ? nestedCalls
576
- : presentationChildCalls(presentation.children);
577
- const childCalls = withSessionArgs(captured ?? ownCalls, runtime);
578
584
  if (childCalls.length > 0) {
579
585
  return new NestedCallsView({
580
586
  calls: childCalls,
@@ -659,6 +665,7 @@ function createRendererResolver(runtime, options, resolvePresentation, resolveNa
659
665
  return {
660
666
  renderShell: "self",
661
667
  renderCall(args, theme, context) {
668
+ runtime.rememberRow(context.toolCallId, context.invalidate);
662
669
  if (provider !== undefined) {
663
670
  const callContext = { ...context, args };
664
671
  const view = renderOwnedPresentation({
@@ -722,6 +729,7 @@ function createRendererResolver(runtime, options, resolvePresentation, resolveNa
722
729
  return view;
723
730
  },
724
731
  renderResult(result, renderOptions, theme, context) {
732
+ runtime.rememberRow(context.toolCallId, context.invalidate);
725
733
  const resultContext = {
726
734
  ...context,
727
735
  expanded: renderOptions.expanded,
@@ -787,9 +795,16 @@ export function registerCollapsibleToolRenderer(pi, runtime, options) {
787
795
  const disposers = [
788
796
  pi.on("tool_execution_start", (event) => {
789
797
  runtime.tracker.recordStart(event);
798
+ // A nested call has no row of its own until the container redraws, so the container is
799
+ // rebuilt the moment the call starts rather than when its result arrives.
800
+ runtime.repaintParent(event.parentToolCallId);
790
801
  }),
791
802
  pi.on("tool_execution_end", (event) => {
792
803
  runtime.tracker.recordEnd(event);
804
+ if (event.parentToolCallId === undefined)
805
+ runtime.forgetRow(event.toolCallId);
806
+ else
807
+ runtime.repaintParent(event.parentToolCallId);
793
808
  }),
794
809
  pi.on("tool_call", (event) => {
795
810
  runtime.tracker.recordResolved(event);
package/dist/runtime.d.ts CHANGED
@@ -5,9 +5,14 @@ import { NestedCallArgsIndex, ToolExecutionTracker } from "./tracker.js";
5
5
  * restart, cleanup or settings change in one cannot reach the other.
6
6
  */
7
7
  export declare class ToolUiRuntime {
8
+ #private;
8
9
  readonly tracker: ToolExecutionTracker;
9
10
  readonly nestedCalls: NestedCallArgsIndex;
10
11
  /** Pi-native per-tool backgrounds are opt-in; the default is the quiet transparent row. */
11
12
  backgrounds: boolean;
13
+ rememberRow(toolCallId: string, invalidate: () => void): void;
14
+ /** The host rebuilds the row from fresh facts, so its child board shows the call that started. */
15
+ repaintParent(parentToolCallId: string | undefined): void;
16
+ forgetRow(toolCallId: string): void;
12
17
  clear(): void;
13
18
  }
package/dist/runtime.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { NestedCallArgsIndex, ToolExecutionTracker } from "./tracker.js";
2
+ /** Bounded like the capture table: only rows the session can still repaint are worth keeping. */
3
+ const MAX_LIVE_ROWS = 200;
2
4
  /**
3
5
  * The mutable state one extension runtime owns: nested-call captures, the session-branch argument
4
6
  * index, and the tool background preference. Two Pi runtimes in one process never share it, so a
@@ -7,10 +9,30 @@ import { NestedCallArgsIndex, ToolExecutionTracker } from "./tracker.js";
7
9
  export class ToolUiRuntime {
8
10
  tracker = new ToolExecutionTracker();
9
11
  nestedCalls = new NestedCallArgsIndex();
12
+ /** A container row's own invalidator, so a live nested call can repaint the row that made it. */
13
+ #invalidators = new Map();
10
14
  /** Pi-native per-tool backgrounds are opt-in; the default is the quiet transparent row. */
11
15
  backgrounds = false;
16
+ rememberRow(toolCallId, invalidate) {
17
+ if (!this.#invalidators.has(toolCallId) && this.#invalidators.size >= MAX_LIVE_ROWS) {
18
+ const oldest = this.#invalidators.keys().next().value;
19
+ if (oldest !== undefined)
20
+ this.#invalidators.delete(oldest);
21
+ }
22
+ this.#invalidators.set(toolCallId, invalidate);
23
+ }
24
+ /** The host rebuilds the row from fresh facts, so its child board shows the call that started. */
25
+ repaintParent(parentToolCallId) {
26
+ if (parentToolCallId === undefined)
27
+ return;
28
+ this.#invalidators.get(parentToolCallId)?.();
29
+ }
30
+ forgetRow(toolCallId) {
31
+ this.#invalidators.delete(toolCallId);
32
+ }
12
33
  clear() {
13
34
  this.tracker.clear();
14
35
  this.nestedCalls.setSource(undefined);
36
+ this.#invalidators.clear();
15
37
  }
16
38
  }
package/dist/tool-view.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Buffer } from "node:buffer";
2
2
  import { copyToClipboard as copyToSystemClipboard, } from "@earendil-works/pi-coding-agent";
3
3
  import { truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "@earendil-works/pi-tui";
4
+ import { isRecord } from "@hheei/pi-ext-core";
4
5
  import { surfaceBlock } from "./background.js";
5
6
  /** Pi native tool surfaces: a failure outranks work in progress, which outranks a settled tool. */
6
7
  function nativeSurface(status) {
@@ -28,9 +29,6 @@ const DEFAULT_MAX_SECTION_LINES = 9;
28
29
  export const MAX_LEVEL2_SECTION_LINES = 500;
29
30
  export const MAX_LEVEL2_BYTES = 64 * 1024;
30
31
  const DEFAULT_COPY_FEEDBACK_MS = 1200;
31
- function isRecord(value) {
32
- return typeof value === "object" && value !== null && !Array.isArray(value);
33
- }
34
32
  export function transitionFoldLevel(current, ctrlKey) {
35
33
  if (current === 0) {
36
34
  return ctrlKey ? 2 : 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hheei/pi-ext-ui",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
4
4
  "description": "Collapsible tool rendering frame and tool renderer pilot for Pi.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -35,15 +35,15 @@
35
35
  "access": "public"
36
36
  },
37
37
  "dependencies": {
38
- "@hheei/pi-ext-core": "^0.8.0"
38
+ "@hheei/pi-ext-core": "^0.8.1"
39
39
  },
40
40
  "peerDependencies": {
41
- "@earendil-works/pi-coding-agent": ">=1.0.1",
42
- "@earendil-works/pi-tui": ">=1.0.1"
41
+ "@earendil-works/pi-coding-agent": ">=1.5.0",
42
+ "@earendil-works/pi-tui": ">=1.5.0"
43
43
  },
44
44
  "devDependencies": {
45
- "@earendil-works/pi-coding-agent": "^1.4.0",
46
- "@earendil-works/pi-tui": "^1.4.0"
45
+ "@earendil-works/pi-coding-agent": "^1.5.0",
46
+ "@earendil-works/pi-tui": "^1.5.0"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "node -e \"import { rmSync } from 'node:fs'; rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json"