@hheei/pi-ext-ui 0.7.1 → 0.8.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.
package/dist/extension.js CHANGED
@@ -4,7 +4,6 @@ import { createMessageView } from "./message-view.js";
4
4
  import { createOptionalToolRendererFactory, registerCollapsibleToolRenderer } from "./renderer.js";
5
5
  import { ToolUiRuntime } from "./runtime.js";
6
6
  import { activityGroupsEnabled, createToolGroupSettings, toolBackgroundsEnabled, } from "./settings.js";
7
- import { TOOL_SPINNER_INTERVAL_MS } from "./tool-view.js";
8
7
  export default function (pi) {
9
8
  // One runtime per extension instance: two Pi runtimes in one process never share these facts.
10
9
  const runtime = new ToolUiRuntime();
@@ -27,7 +26,7 @@ export default function (pi) {
27
26
  if (patch !== undefined)
28
27
  return;
29
28
  try {
30
- patch = installHostToolGroups(context.extension);
29
+ patch = installHostToolGroups(context.extension, () => runtime.backgrounds);
31
30
  redrawGroups = patch.requestRender;
32
31
  }
33
32
  catch (error) {
@@ -46,47 +45,14 @@ export default function (pi) {
46
45
  patch?.beginRun();
47
46
  redrawGroups?.();
48
47
  });
49
- // A live tool animates its status glyph. The ticker exists only while one is running, and the
50
- // frame itself comes from one shared clock, so a phase never restarts on a status change.
51
- let liveTools = 0;
52
- let ticker;
53
- const stopTicker = () => {
54
- if (ticker === undefined)
55
- return;
56
- clearInterval(ticker);
57
- ticker = undefined;
58
- };
59
48
  const onAgentEnd = pi.on("agent_end", () => {
60
49
  patch?.endRun();
61
- // No tool outlives the agent loop, so the animation cannot keep a ticker alive either.
62
- liveTools = 0;
63
- stopTicker();
64
50
  redrawGroups?.();
65
51
  });
66
- const onToolStart = pi.on("tool_execution_start", () => {
67
- liveTools += 1;
68
- if (ticker !== undefined)
69
- return;
70
- ticker = setInterval(() => {
71
- if (liveTools === 0 || redrawGroups === undefined) {
72
- stopTicker();
73
- return;
74
- }
75
- redrawGroups();
76
- }, TOOL_SPINNER_INTERVAL_MS);
77
- });
78
- const onToolEnd = pi.on("tool_execution_end", () => {
79
- liveTools = Math.max(0, liveTools - 1);
80
- if (liveTools === 0)
81
- stopTicker();
82
- });
52
+ // The live glyph clock lives with the rows that draw it, so nothing here ticks.
83
53
  context.resources.add("run-clock", () => {
84
54
  onAgentStart();
85
55
  onAgentEnd();
86
- onToolStart();
87
- onToolEnd();
88
- stopTicker();
89
- liveTools = 0;
90
56
  });
91
57
  context.resources.add("tool-group-settings", registerSettings(provider, getRuntimeSettingsRegistry(pi)));
92
58
  context.resources.add("tool-groups", () => {
@@ -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): {
3
+ export declare function installHostToolGroups(context: ExtensionContext, background: () => boolean): {
4
4
  dispose(): void;
5
5
  requestRender(): void;
6
6
  beginRun(): void;
@@ -182,7 +182,7 @@ class NotifyView {
182
182
  this.#text.invalidate();
183
183
  }
184
184
  }
185
- function attach(host, run) {
185
+ function attach(host, run, background) {
186
186
  const chat = host.chat;
187
187
  const original = {
188
188
  render: chat.render,
@@ -200,8 +200,6 @@ function attach(host, run) {
200
200
  throw new Error("Pi transcript cannot accept instance methods");
201
201
  const projection = new Container();
202
202
  let groups = new Map();
203
- /** One wrapper per tail component, so its reserved height survives a transcript rebuild. */
204
- let tails = new Map();
205
203
  let active = true;
206
204
  const disposeGroups = () => {
207
205
  for (const group of groups.values())
@@ -256,7 +254,6 @@ function attach(host, run) {
256
254
  };
257
255
  const rebuild = () => {
258
256
  const next = new Map();
259
- const nextTails = new Map();
260
257
  const children = [];
261
258
  const blocks = [];
262
259
  let calls = new Set();
@@ -269,6 +266,14 @@ function attach(host, run) {
269
266
  */
270
267
  const flush = (ended, boundary) => {
271
268
  const first = members[0]?.component;
269
+ // Empty assistant/tool-call placeholders do not replace the last visible tail.
270
+ const tail = ended
271
+ ? undefined
272
+ : [...members]
273
+ .reverse()
274
+ .find((member) => member.kind !== "thinking" ||
275
+ (member.thinking !== undefined && member.thinking !== ""));
276
+ const inside = members.filter((member) => member !== tail);
272
277
  let weight = 0;
273
278
  for (const member of members) {
274
279
  if (member.kind === "tool")
@@ -278,32 +283,19 @@ function attach(host, run) {
278
283
  weight += 0.5;
279
284
  }
280
285
  if (first !== undefined && weight >= 2) {
281
- const group = groups.get(first) ?? new ToolGroupView(host.ui, host.requestRender);
282
- const tail = ended ? undefined : members.at(-1);
283
- const inside = tail === undefined ? members : members.slice(0, -1);
286
+ const group = groups.get(first) ?? new ToolGroupView(host.ui, host.requestRender, background);
284
287
  const boundaryTime = ended && boundary !== undefined
285
288
  ? (run.observed.get(boundary)?.at ?? performance.now())
286
289
  : undefined;
287
- blocks.push({
288
- group,
289
- members: inside,
290
- tail,
291
- requests,
292
- clock: clockOf(members, boundaryTime),
293
- });
290
+ const clock = clockOf(members, boundaryTime);
291
+ blocks.push({ group, members: inside, tail, requests, clock });
294
292
  next.set(first, group);
295
293
  children.push(group);
296
- if (tail !== undefined) {
297
- const key = memberParts(tail)[0];
298
- const unit = (key === undefined ? undefined : tails.get(key)) ?? new TailUnit(tail);
299
- unit.update(tail);
300
- if (key !== undefined)
301
- nextTails.set(key, unit);
302
- children.push(unit);
303
- }
304
294
  }
305
295
  else
306
- children.push(...members.flatMap(memberParts));
296
+ children.push(...inside.flatMap(memberParts));
297
+ if (tail !== undefined)
298
+ children.push(new TailUnit(tail, background));
307
299
  members = [];
308
300
  requests = [];
309
301
  };
@@ -372,7 +364,6 @@ function attach(host, run) {
372
364
  group.dispose();
373
365
  }
374
366
  groups = next;
375
- tails = nextTails;
376
367
  projection.children = children;
377
368
  };
378
369
  const wrappers = {
@@ -522,7 +513,7 @@ function patchState() {
522
513
  return state;
523
514
  }
524
515
  /** Private to pi-ext-ui: two process hooks, scoped by the actual host UI facade. */
525
- export function installHostToolGroups(context) {
516
+ export function installHostToolGroups(context, background) {
526
517
  const parts = VERSION.split(".").map(Number);
527
518
  const [major = 0, minor = 0, patch = 0] = parts;
528
519
  if (parts.length !== 3 ||
@@ -554,7 +545,7 @@ export function installHostToolGroups(context) {
554
545
  ui,
555
546
  hosts: new Map(),
556
547
  failed: new WeakSet(),
557
- attach: (host) => attach(host, run),
548
+ attach: (host) => attach(host, run, background),
558
549
  };
559
550
  state.owners.set(ui, owner);
560
551
  // Reload rebuilds history before session_start; reconnect already discovered instances.
@@ -1,13 +1,22 @@
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
3
  import { extractChildToolCopyText, inlinePresentation, nativeRequestCopy } from "./native-tools.js";
4
- import { formatToolHeaderText, ToolView, toContentRows, transitionFoldLevel, } from "./tool-view.js";
4
+ import { formatToolHeaderText, TOOL_SPINNER_INTERVAL_MS, ToolView, toContentRows, toolSpinnerFrame, transitionFoldLevel, } from "./tool-view.js";
5
5
  function truncate(text, width) {
6
6
  return truncateToWidth(text, Math.max(1, width));
7
7
  }
8
8
  function isRecord(value) {
9
9
  return typeof value === "object" && value !== null && !Array.isArray(value);
10
10
  }
11
+ /**
12
+ * A call without a final result is still running: its row carries the live spinner frame. A persisted
13
+ * record states its own status, so a recovered board never animates a call that already ended.
14
+ */
15
+ function isLiveCall(call) {
16
+ return "status" in call
17
+ ? call.status === "running"
18
+ : call.result === undefined;
19
+ }
11
20
  /**
12
21
  * A copy keeps the request half byte-exact and strips only what follows it: the result is display
13
22
  * text, while the request is what the model sent and may legitimately contain escapes.
@@ -31,6 +40,7 @@ export class NestedCallsView {
31
40
  #copiedIndex = undefined;
32
41
  #failedIndex = undefined;
33
42
  #copiedTimer = undefined;
43
+ #liveTimer = undefined;
34
44
  #lineMapping = [];
35
45
  #renderCache;
36
46
  constructor(props) {
@@ -191,14 +201,30 @@ export class NestedCallsView {
191
201
  if (state !== undefined)
192
202
  delete state[NESTED_CALLS_COPIED_KEY];
193
203
  }
204
+ /** A live child row owns the next frame; a settled board asks for none. */
205
+ #requestNextFrame() {
206
+ if (this.#liveTimer !== undefined)
207
+ return;
208
+ this.#liveTimer = setTimeout(() => {
209
+ this.#liveTimer = undefined;
210
+ this.#props.invalidate?.();
211
+ }, TOOL_SPINNER_INTERVAL_MS);
212
+ this.#liveTimer.unref?.();
213
+ }
194
214
  render(width) {
195
215
  const safeWidth = Math.max(1, width);
196
216
  const levelsKey = this.#getLevelsKey();
217
+ // A live child row draws the shared spinner, so a cached board would freeze that glyph.
218
+ const frame = this.#props.calls.some(isLiveCall) ? toolSpinnerFrame() : "";
219
+ // A live child row draws the glyph, so this board owns the next frame for as long as it does.
220
+ if (frame !== "")
221
+ this.#requestNextFrame();
197
222
  if (this.#renderCache !== undefined &&
198
223
  this.#renderCache.width === safeWidth &&
199
224
  this.#renderCache.copiedIndex === this.#copiedIndex &&
200
225
  this.#renderCache.failedIndex === this.#failedIndex &&
201
- this.#renderCache.levelsKey === levelsKey) {
226
+ this.#renderCache.levelsKey === levelsKey &&
227
+ this.#renderCache.frame === frame) {
202
228
  this.#lineMapping = this.#renderCache.lineMapping;
203
229
  return this.#renderCache.lines;
204
230
  }
@@ -212,8 +238,7 @@ export class NestedCallsView {
212
238
  continue;
213
239
  const toolName = "toolName" in call ? call.toolName : call.name;
214
240
  const isError = Boolean(call.isError || ("status" in call && call.status === "error"));
215
- const isRunning = ("status" in call && call.status === "running") ||
216
- (!("status" in call) && call.result === undefined);
241
+ const isRunning = isLiveCall(call);
217
242
  const expansionLevel = this.#getExpansionLevel(i);
218
243
  const isExpanded = expansionLevel !== 0;
219
244
  const rawSummary = "summary" in call ? call.summary : undefined;
@@ -281,6 +306,7 @@ export class NestedCallsView {
281
306
  copiedIndex: this.#copiedIndex,
282
307
  failedIndex: this.#failedIndex,
283
308
  levelsKey,
309
+ frame,
284
310
  lines,
285
311
  lineMapping: this.#lineMapping,
286
312
  };
@@ -56,9 +56,7 @@ export interface ToolGroupClock {
56
56
  export declare function memberParts(member: Member): readonly Component[];
57
57
  export declare class TailUnit implements Component {
58
58
  #private;
59
- constructor(member: Member);
60
- /** The same tail with a newer fact: the component list can grow, the reserved height must not. */
61
- update(member: Member): void;
59
+ constructor(member: Member, background: () => boolean);
62
60
  render(width: number): string[];
63
61
  handleMouse(event: Parameters<NonNullable<Component["handleMouse"]>>[0]): import("@earendil-works/pi-tui").TuiMouseEventResult | undefined;
64
62
  invalidate(): void;
@@ -72,7 +70,7 @@ export declare class TailUnit implements Component {
72
70
  */
73
71
  export declare class ToolGroupView extends Container {
74
72
  #private;
75
- constructor(ui: ExtensionUIContext, redraw: () => void);
73
+ constructor(ui: ExtensionUIContext, redraw: () => void, background: () => boolean);
76
74
  /**
77
75
  * Release the block's own lifetime. The projection drops a block when it is evicted, when
78
76
  * grouping is disabled, or when the patch unloads; an idempotent dispose keeps that path honest,
@@ -1,58 +1,62 @@
1
- import { Box, Container, MouseRegion, Spacer, Text, truncateToWidth, wrapTextWithAnsi, } from "@earendil-works/pi-tui";
1
+ import { Box, Container, MouseRegion, Spacer, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "@earendil-works/pi-tui";
2
2
  import { fitRow } from "@hheei/pi-ext-core";
3
3
  import { backgroundFill } from "./background.js";
4
4
  import { isCollapsible, toContentRows, toSingleRow, } from "./tool-view.js";
5
5
  /** A settled block keeps its surface this long, then drops it so the transcript stays quiet. */
6
6
  const SURFACE_HOLD_MS = 5_000;
7
- /** A tool that has not received its final result yet: its own rows are still changing height. */
8
- function isLiveTail(member) {
9
- return member.kind === "tool" && member.completed !== true;
10
- }
11
7
  /** Every original component of a member, in transcript order. */
12
8
  export function memberParts(member) {
13
9
  return member.parts ?? [member.component];
14
10
  }
11
+ /**
12
+ * A row that draws nothing visible. A separator row is not always an empty string: Pi marks the start
13
+ * of a message zone with an OSC 133 row that paints no cells, and counting it as content would add a
14
+ * second blank row after the block.
15
+ */
16
+ function isBlankRow(row) {
17
+ return row === undefined || visibleWidth(row) === 0;
18
+ }
15
19
  export class TailUnit {
16
20
  #parts;
21
+ #background;
17
22
  #offsets = [];
18
- /** Rows already shown for a live member: a tool that grows may not shrink back and shift the block. */
19
- #reserved = 0;
20
- #live;
21
- constructor(member) {
22
- this.#parts = memberParts(member);
23
- this.#live = isLiveTail(member);
24
- }
25
- /** The same tail with a newer fact: the component list can grow, the reserved height must not. */
26
- update(member) {
23
+ constructor(member, background) {
27
24
  this.#parts = memberParts(member);
28
- this.#live = isLiveTail(member);
25
+ this.#background = background;
29
26
  }
30
27
  render(width) {
31
28
  const offsets = [];
32
29
  const rows = [];
30
+ let contentRows = 0;
33
31
  for (const part of this.#parts) {
34
32
  const all = part.render(width);
35
33
  const skip = all[0] === "" ? 1 : 0;
36
34
  const own = all.slice(skip);
37
35
  if (own.length === 0)
38
36
  continue;
39
- if (rows.length > 0)
37
+ if (rows.length > 0) {
40
38
  rows.push("");
39
+ contentRows++;
40
+ }
41
+ // A leading OSC-only separator stays in the output, but is not a content row.
42
+ contentRows += own.length - (isBlankRow(own[0]) ? 1 : 0);
41
43
  offsets.push({ part, top: rows.length, height: own.length, skip });
42
44
  rows.push(...own);
43
45
  }
44
- this.#offsets = offsets;
45
- if (rows.length === 0)
46
+ if (rows.length === 0) {
47
+ this.#offsets = offsets;
46
48
  return rows;
47
- const out = rows[0] === "" ? rows : ["", ...rows];
48
- // A live tail grows downward from a fixed top: the host keeps the transcript's bottom in view, so
49
- // a row count that falls back drags everything above it down. Reserve the tallest frame instead.
50
- if (this.#live)
51
- this.#reserved = Math.max(this.#reserved, out.length);
52
- else
53
- this.#reserved = 0;
54
- while (out.length < this.#reserved)
55
- out.push("");
49
+ }
50
+ const out = isBlankRow(rows[0]) ? rows : ["", ...rows];
51
+ // The separator above is a row of this unit too, so every part offset moves down with it.
52
+ const shift = out.length - rows.length;
53
+ this.#offsets = offsets.map((offset) => ({ ...offset, top: offset.top + shift }));
54
+ // Surface padding counts toward the minimum; added rows stay outside surfaces and hit areas.
55
+ const minimum = this.#background() ? 4 : 2;
56
+ if (contentRows > 0) {
57
+ for (let row = contentRows; row < minimum; row++)
58
+ out.push("");
59
+ }
56
60
  return out;
57
61
  }
58
62
  handleMouse(event) {
@@ -138,6 +142,8 @@ export class ToolGroupView extends Container {
138
142
  #header = new Text("", 0, 0);
139
143
  #ui;
140
144
  #redraw;
145
+ /** Pi-native block backgrounds are opt-in; while they are on, the fill replaces the rails. */
146
+ #background;
141
147
  /** The stable clock for this block; restored history stays unknown until live content joins. */
142
148
  #clock;
143
149
  /** The block's own members, drawn inside the group. */
@@ -160,10 +166,11 @@ export class ToolGroupView extends Container {
160
166
  // One extra colored row above and below: the block needs breathing room inside its background.
161
167
  #box = new Box(0, 1);
162
168
  #spacer = new Spacer(1);
163
- constructor(ui, redraw) {
169
+ constructor(ui, redraw, background) {
164
170
  super();
165
171
  this.#ui = ui;
166
172
  this.#redraw = redraw;
173
+ this.#background = background;
167
174
  this.#heading = new MouseRegion(this.#header, (event) => {
168
175
  if (event.type !== "click" || event.button !== "left")
169
176
  return undefined;
@@ -492,6 +499,13 @@ export class ToolGroupView extends Container {
492
499
  push(row, index === 0 ? { kind: "item", member: member.component } : { kind: "none" });
493
500
  }
494
501
  }
502
+ /**
503
+ * The cells a thinking row starts with. Thinking has no surface of its own, so it keeps the same
504
+ * rail as a tool at every level; once a block is filled, the fill delimits it and the rail goes.
505
+ */
506
+ #thinkingPrefix() {
507
+ return this.#background() ? " " : `${this.#ui.theme.fg("dim", "▎")} `;
508
+ }
495
509
  /** One preview row: the grapheme head plus an ellipsis, then cut to the cells the rail leaves. */
496
510
  #thinkingPreviewRow(text, width) {
497
511
  const theme = this.#ui.theme;
@@ -499,15 +513,16 @@ export class ToolGroupView extends Container {
499
513
  const room = Math.max(1, width - 2);
500
514
  const fitted = truncateToWidth(head, more ? Math.max(1, room - 1) : room, "…");
501
515
  const preview = more && fitted === head ? `${head}…` : fitted;
502
- return `${theme.fg("dim", "▎")} ${theme.fg("thinkingText", theme.italic(preview))}`;
516
+ return `${this.#thinkingPrefix()}${theme.fg("thinkingText", theme.italic(preview))}`;
503
517
  }
504
518
  /** Full thinking text: paragraphs keep their separation, every row wraps to the indent's width. */
505
519
  #thinkingRows(text, width) {
506
520
  const theme = this.#ui.theme;
521
+ const prefix = this.#thinkingPrefix();
507
522
  const inner = Math.max(1, width - 2);
508
523
  return toContentRows(text).flatMap((line) => {
509
524
  const wrapped = wrapTextWithAnsi(line, inner);
510
- return (wrapped.length > 0 ? wrapped : [""]).map((row) => row === "" ? "" : ` ${theme.fg("thinkingText", theme.italic(row))}`);
525
+ return (wrapped.length > 0 ? wrapped : [""]).map((row) => row === "" ? "" : `${prefix}${theme.fg("thinkingText", theme.italic(row))}`);
511
526
  });
512
527
  }
513
528
  /**
package/dist/tool-view.js CHANGED
@@ -245,6 +245,7 @@ export class ToolView {
245
245
  #localCopiedUntil = 0;
246
246
  #localCopyFailed = false;
247
247
  #localTimer;
248
+ #liveTimer;
248
249
  #lastWidth = 80;
249
250
  #lastHeight = 0;
250
251
  #lastNoRail = false;
@@ -386,6 +387,20 @@ export class ToolView {
386
387
  this.#spec.stateHolder[COPY_FEEDBACK_TIMER_KEY] = timer;
387
388
  }
388
389
  }
390
+ /**
391
+ * A live glyph needs someone to ask for the next frame, and no host event says "still running".
392
+ * The frame that drew one owns that timer: it fires once, the repaint renders again, and a settled
393
+ * row simply stops asking.
394
+ */
395
+ #requestNextFrame() {
396
+ if (this.#liveTimer !== undefined)
397
+ return;
398
+ this.#liveTimer = setTimeout(() => {
399
+ this.#liveTimer = undefined;
400
+ this.#spec.invalidate?.();
401
+ }, TOOL_SPINNER_INTERVAL_MS);
402
+ this.#liveTimer.unref?.();
403
+ }
389
404
  #renderHeader(width, level) {
390
405
  const { theme, header, copy } = this.#spec;
391
406
  const safeWidth = Math.max(1, width);
@@ -514,7 +529,15 @@ export class ToolView {
514
529
  // The glyph of a live tool changes on its own clock, so a cached frame would freeze it.
515
530
  const status = this.#spec.header.status;
516
531
  const glyph = status === "streaming" || status === "running" ? toolSpinnerFrame() : "";
517
- if (this.#cache !== undefined &&
532
+ // A live row at level 0 draws the glyph, so this frame owns the next one.
533
+ if (glyph !== "" && level === 0)
534
+ this.#requestNextFrame();
535
+ // A component body draws on a clock of its own: a container's child board animates a live child
536
+ // while the container itself already shows its fold pin, so a cached frame would freeze the
537
+ // child's glyph. Only a body of literal rows is fully known here.
538
+ const bodyOwnsItsClock = this.#spec.collapsedRows !== undefined && !Array.isArray(this.#spec.collapsedRows);
539
+ if (!bodyOwnsItsClock &&
540
+ this.#cache !== undefined &&
518
541
  this.#cache.width === width &&
519
542
  this.#cache.level === level &&
520
543
  this.#cache.noRail === noRail &&
@@ -547,7 +570,9 @@ export class ToolView {
547
570
  this.#lastTopPad = surface === undefined ? 0 : 1;
548
571
  const lines = surface === undefined ? safeLines : surfaceBlock(this.#spec.theme, surface, safeLines, width);
549
572
  this.#lastHeight = lines.length;
550
- this.#cache = { width, level, copied, noRail, surface, glyph, lines };
573
+ this.#cache = bodyOwnsItsClock
574
+ ? undefined
575
+ : { width, level, copied, noRail, surface, glyph, lines };
551
576
  return lines;
552
577
  }
553
578
  handleMouse(event) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hheei/pi-ext-ui",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "Collapsible tool rendering frame and tool renderer pilot for Pi.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -22,29 +22,28 @@
22
22
  "pi",
23
23
  "pi-package",
24
24
  "pi-extension",
25
- "hepi",
26
25
  "ui",
27
26
  "collapsible"
28
27
  ],
29
28
  "license": "MIT",
30
29
  "repository": {
31
30
  "type": "git",
32
- "url": "git+https://github.com/hheei/hepi-mono.git",
31
+ "url": "git+https://github.com/hheei/pi-mono.git",
33
32
  "directory": "packages/pi-ext-ui"
34
33
  },
35
34
  "publishConfig": {
36
35
  "access": "public"
37
36
  },
38
37
  "dependencies": {
39
- "@hheei/pi-ext-core": "^0.7.1"
38
+ "@hheei/pi-ext-core": "^0.8.0"
40
39
  },
41
40
  "peerDependencies": {
42
41
  "@earendil-works/pi-coding-agent": ">=1.0.1",
43
42
  "@earendil-works/pi-tui": ">=1.0.1"
44
43
  },
45
44
  "devDependencies": {
46
- "@earendil-works/pi-coding-agent": "1.3.0",
47
- "@earendil-works/pi-tui": "1.3.0"
45
+ "@earendil-works/pi-coding-agent": "^1.4.0",
46
+ "@earendil-works/pi-tui": "^1.4.0"
48
47
  },
49
48
  "scripts": {
50
49
  "build": "node -e \"import { rmSync } from 'node:fs'; rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json"