@quandev104/pi-style 0.1.3 → 0.1.4

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.
@@ -858,10 +858,14 @@ function formatBoxedStatusIcon(theme, isError) {
858
858
  function formatToolTitlePrefix(theme, name) {
859
859
  return colorFromExtra(theme, "bashPromptColor", "bashMode", `\u2794 ${name}`);
860
860
  }
861
- function formatBoxedToolTitle(theme, name, isError) {
862
- const coloredTitle = isError ? theme.fg("error", `\u2794 ${name} \u2717`) : `${formatToolTitlePrefix(theme, name)} ${formatBoxedStatusIcon(theme, false)}`;
861
+ function formatBoxedToolTitle(theme, name, isError, status) {
862
+ const coloredTitle = isError ? theme.fg("error", `\u2794 ${name} \u2717`) : status === "running" ? `${formatToolTitlePrefix(theme, name)} ${theme.fg("text", "\u25CC")}` : status === "pending" ? formatToolTitlePrefix(theme, name) : `${formatToolTitlePrefix(theme, name)} ${formatBoxedStatusIcon(theme, false)}`;
863
863
  return typeof theme?.bold === "function" ? theme.bold(coloredTitle) : coloredTitle;
864
864
  }
865
+ function formatBoxedRunningStatus(theme, elapsedMs) {
866
+ const elapsed = elapsedMs === void 0 ? "" : `${theme.fg("text", ` \xB7 ${(elapsedMs / 1e3).toFixed(1)}s`)}`;
867
+ return `${theme.fg("dim", "\u25CC Running")}${elapsed}`;
868
+ }
865
869
  function boxFrameText(theme, text) {
866
870
  return `${RESET_INTENSITY}${theme.fg("border", text)}`;
867
871
  }
@@ -970,7 +974,12 @@ function renderBoxedToolCall(theme, toolName, detailLines, options = {}) {
970
974
  },
971
975
  render(width) {
972
976
  if (cache?.width === width) return cache.lines;
973
- const title = formatBoxedToolTitle(theme, toolName, options.isError);
977
+ const title = formatBoxedToolTitle(
978
+ theme,
979
+ toolName,
980
+ options.isError,
981
+ options.isPending ? options.running ? "running" : "pending" : void 0
982
+ );
974
983
  const headerLabel = options.headerDetail ? `${title} \xB7 ${options.headerDetail}` : title;
975
984
  const renderedWidth = boxWidth(width);
976
985
  const lines = [
@@ -978,15 +987,15 @@ function renderBoxedToolCall(theme, toolName, detailLines, options = {}) {
978
987
  boxBlankLine(theme, renderedWidth),
979
988
  ...detailLines.flatMap((line) => boxedWrappedLines(theme, line, renderedWidth))
980
989
  ];
981
- if (options.isPending) {
982
- const pendingText = options.pendingText ?? "Waiting for output\u2026";
990
+ if (options.isPending && !options.resultSeen) {
991
+ const pendingLabel = options.pendingLabel ?? theme.fg("dim", `\u2026 ${options.pendingText ?? "Waiting for output\u2026"}`);
983
992
  lines.push(
984
993
  boxBlankLine(theme, renderedWidth),
985
994
  boxLabeledBorder(
986
995
  theme,
987
996
  BOX_ROUND_BOTTOM_LEFT,
988
997
  BOX_ROUND_BOTTOM_RIGHT,
989
- theme.fg("dim", `\u2026 ${pendingText}`),
998
+ pendingLabel,
990
999
  void 0,
991
1000
  renderedWidth
992
1001
  )
@@ -1014,7 +1023,12 @@ function renderCompactBoxedToolCall(theme, toolName, detailLine, options = {}) {
1014
1023
  },
1015
1024
  render(width) {
1016
1025
  const renderedWidth = boxWidth(width);
1017
- const title = formatBoxedToolTitle(theme, toolName, options.isError);
1026
+ const title = formatBoxedToolTitle(
1027
+ theme,
1028
+ toolName,
1029
+ options.isError,
1030
+ options.isPending ? options.running ? "running" : "pending" : void 0
1031
+ );
1018
1032
  const headerLabel = detailLine ? `${title} \xB7 ${detailLine}` : title;
1019
1033
  const compactFooter = typeof options.state?.[COMPACT_FOOTER_KEY] === "string" ? options.state[COMPACT_FOOTER_KEY] : "";
1020
1034
  const _footerIsError = Boolean(options.state?.[COMPACT_FOOTER_ERROR_KEY]);
@@ -1036,13 +1050,13 @@ function renderCompactBoxedToolCall(theme, toolName, detailLine, options = {}) {
1036
1050
  )
1037
1051
  );
1038
1052
  } else if (options.isPending) {
1039
- const pendingText = options.pendingText ?? "Waiting for output\u2026";
1053
+ const pendingLabel = options.pendingLabel ?? (options.running ? formatBoxedRunningStatus(theme, void 0) : theme.fg("dim", `\u2026 ${options.pendingText ?? "Waiting for output\u2026"}`));
1040
1054
  lines.push(
1041
1055
  boxLabeledBorder(
1042
1056
  theme,
1043
1057
  BOX_ROUND_BOTTOM_LEFT,
1044
1058
  BOX_ROUND_BOTTOM_RIGHT,
1045
- theme.fg("dim", `\u2026 ${pendingText}`),
1059
+ pendingLabel,
1046
1060
  options.bottomRightLabel,
1047
1061
  renderedWidth
1048
1062
  )
@@ -1065,19 +1079,21 @@ function renderBoxedToolResult(theme, body, options = {}) {
1065
1079
  const renderedWidth = boxWidth(width);
1066
1080
  const maxContentWidth = boxInnerWidth(renderedWidth);
1067
1081
  const bodyLines = typeof body === "function" ? body(maxContentWidth) : body.render(maxContentWidth);
1068
- const errorPrefix = options.isError ? [theme.fg("error", "\u2717 Error")] : [];
1082
+ const errorPrefix = options.isError ? [theme.fg("error", options.errorLabel ?? "\u2717 Error")] : [];
1069
1083
  const outputLines = bodyLines.length > 0 ? [...errorPrefix, ...bodyLines] : [theme.fg("muted", `\u2205 ${options.emptyText ?? "(no output)"}`)];
1070
1084
  const footerText = (options.footerLines ?? []).join(" \xB7 ");
1071
1085
  const dividerText = typeof options.dividerLabel === "function" ? options.dividerLabel(renderedWidth) : options.dividerLabel ?? "Response";
1072
1086
  const rendered = [
1073
- boxLabeledBorder(
1074
- theme,
1075
- BOX_DIVIDER_LEFT,
1076
- BOX_DIVIDER_RIGHT,
1077
- theme.fg("dim", dividerText),
1078
- options.dividerRightLabel ? theme.fg("dim", options.dividerRightLabel) : void 0,
1079
- renderedWidth
1080
- ),
1087
+ ...options.showDivider === false ? [] : [
1088
+ boxLabeledBorder(
1089
+ theme,
1090
+ BOX_DIVIDER_LEFT,
1091
+ BOX_DIVIDER_RIGHT,
1092
+ theme.fg("dim", dividerText),
1093
+ options.dividerRightLabel ? theme.fg("dim", options.dividerRightLabel) : void 0,
1094
+ renderedWidth
1095
+ )
1096
+ ],
1081
1097
  boxBlankLine(theme, renderedWidth),
1082
1098
  ...renderBoxedOutputLines(theme, outputLines, renderedWidth, options.renderLineBudget ?? outputLines.length),
1083
1099
  boxBlankLine(theme, renderedWidth),
@@ -1411,17 +1427,53 @@ function getToolsRenderConfig() {
1411
1427
  return sessionToolsConfig;
1412
1428
  }
1413
1429
  var STARTED_AT_KEY = "__piStyleStartedAt";
1414
- var ELAPSED_MS_KEY = "__piStyleElapsedMs";
1430
+ var ENDED_AT_KEY = "__piStyleEndedAt";
1431
+ var RESULT_SEEN_KEY = "__piStyleResultSeen";
1432
+ var TICKER_KEY = "__piStyleElapsedTicker";
1415
1433
  function recordExecutionStarted(state, executionStarted) {
1416
1434
  if (!executionStarted || !state || typeof state !== "object") return;
1417
1435
  if (typeof state[STARTED_AT_KEY] !== "number") state[STARTED_AT_KEY] = performance.now();
1418
1436
  }
1437
+ function recordExecutionEnded(state) {
1438
+ if (!state || typeof state !== "object") return;
1439
+ if (typeof state[ENDED_AT_KEY] !== "number") state[ENDED_AT_KEY] = performance.now();
1440
+ }
1419
1441
  function getStateElapsedMs(state) {
1420
1442
  if (!state || typeof state !== "object") return void 0;
1421
- if (typeof state[ELAPSED_MS_KEY] !== "number" && typeof state[STARTED_AT_KEY] === "number") {
1422
- state[ELAPSED_MS_KEY] = performance.now() - state[STARTED_AT_KEY];
1443
+ const started = state[STARTED_AT_KEY];
1444
+ if (typeof started !== "number") return void 0;
1445
+ const ended = state[ENDED_AT_KEY];
1446
+ if (typeof ended === "number") return Math.max(0, ended - started);
1447
+ return Math.max(0, performance.now() - started);
1448
+ }
1449
+ function isResultSeen(state) {
1450
+ return Boolean(state && typeof state === "object" && state[RESULT_SEEN_KEY] === true);
1451
+ }
1452
+ function markResultSeen(state) {
1453
+ if (!state || typeof state !== "object") return;
1454
+ state[RESULT_SEEN_KEY] = true;
1455
+ }
1456
+ var tickerStates = /* @__PURE__ */ new Set();
1457
+ function startElapsedTicker(state, invalidate) {
1458
+ if (!state || typeof state !== "object") return;
1459
+ if (state[TICKER_KEY] !== void 0) return;
1460
+ state[TICKER_KEY] = setInterval(() => invalidate(), 1e3);
1461
+ tickerStates.add(state);
1462
+ }
1463
+ function stopElapsedTicker(state) {
1464
+ if (!state || typeof state !== "object") return;
1465
+ const handle = state[TICKER_KEY];
1466
+ if (handle !== void 0) clearInterval(handle);
1467
+ delete state[TICKER_KEY];
1468
+ tickerStates.delete(state);
1469
+ }
1470
+ function stopAllElapsedTickers() {
1471
+ for (const state of tickerStates) {
1472
+ const handle = state[TICKER_KEY];
1473
+ if (handle !== void 0) clearInterval(handle);
1474
+ delete state[TICKER_KEY];
1423
1475
  }
1424
- return typeof state[ELAPSED_MS_KEY] === "number" ? state[ELAPSED_MS_KEY] : void 0;
1476
+ tickerStates.clear();
1425
1477
  }
1426
1478
 
1427
1479
  // extension-src/pi-style/features/tools/boxed/batch.ts
@@ -2846,7 +2898,7 @@ function createBuiltinSegments() {
2846
2898
  const token = contextBarToken(percent);
2847
2899
  const window = snapshot.context?.windowTokens;
2848
2900
  const label = `ctx${window !== void 0 ? ` (${formatTokens(window)})` : ""}:`;
2849
- const width = options["context_bar"]?.width ?? CONTEXT_BAR_WIDTH;
2901
+ const width = options.context_bar?.width ?? CONTEXT_BAR_WIDTH;
2850
2902
  return {
2851
2903
  visible: true,
2852
2904
  content: `${theme.apply("muted", label)} ${theme.apply(token, contextBar(percent, width))} ${theme.apply(token, `${Math.round(percent)}%`)}`,
@@ -5280,12 +5332,31 @@ function compactCall(theme, toolName, detailLine, options) {
5280
5332
  state: options.context.state,
5281
5333
  isError: Boolean(options.context.isError),
5282
5334
  isPartial: Boolean(options.context.isPartial),
5283
- isPending: pendingFlag(options.context)
5335
+ isPending: pendingFlag(options.context),
5336
+ running: Boolean(options.context.executionStarted)
5284
5337
  });
5285
5338
  }
5286
5339
  function noteExecutionStart(context) {
5287
5340
  recordExecutionStarted(context.state, context.executionStarted);
5288
5341
  }
5342
+ function noteBoxedCallState(context) {
5343
+ if (!context.executionStarted) return;
5344
+ if (context.isPartial) startElapsedTicker(context.state, context.invalidate);
5345
+ else {
5346
+ recordExecutionEnded(context.state);
5347
+ stopElapsedTicker(context.state);
5348
+ }
5349
+ }
5350
+ function noteBoxedResultPhase(context, isPartial) {
5351
+ const firstResultPass = !isResultSeen(context.state);
5352
+ markResultSeen(context.state);
5353
+ if (isPartial) startElapsedTicker(context.state, context.invalidate);
5354
+ else {
5355
+ recordExecutionEnded(context.state);
5356
+ stopElapsedTicker(context.state);
5357
+ }
5358
+ return firstResultPass;
5359
+ }
5289
5360
  function stateElapsedMs(context) {
5290
5361
  return getStateElapsedMs(context.state);
5291
5362
  }
@@ -5423,34 +5494,202 @@ function renderBoxedBashCall(theme, commandLines, context, widthKey) {
5423
5494
  if (commandLines.length > maxCommandLines + 1) {
5424
5495
  detailLines.push(theme.fg("muted", `... ${commandLines.length - maxCommandLines - 1} more lines`));
5425
5496
  }
5426
- return renderBoxedToolCall(theme, "Bash", detailLines, {
5497
+ const running = Boolean(context.executionStarted);
5498
+ const resultSeen = isResultSeen(context.state);
5499
+ const base = {
5427
5500
  widthKey,
5428
5501
  isError: Boolean(context.isError),
5429
5502
  isPartial: Boolean(context.isPartial),
5430
- isPending: Boolean(context.isPartial)
5431
- });
5503
+ isPending: Boolean(context.isPartial),
5504
+ running
5505
+ };
5506
+ if (running && context.isPartial && !resultSeen) {
5507
+ detailLines.push(theme.fg("dim", "No output received yet"));
5508
+ return renderBoxedToolCall(theme, "Bash", detailLines, {
5509
+ ...base,
5510
+ pendingLabel: formatBoxedRunningStatus(theme, getStateElapsedMs(context.state))
5511
+ });
5512
+ }
5513
+ return renderBoxedToolCall(theme, "Bash", detailLines, { ...base, resultSeen });
5432
5514
  }
5433
- function formatTimeout(context) {
5434
- const timeout = context?.args?.timeout ?? 300;
5435
- return `${timeout}s`;
5515
+ var BASH_STATUS_PATTERNS = [
5516
+ {
5517
+ re: /(?:^|\n\n)Command timed out after ([\d.]+) seconds$/i,
5518
+ build: (match) => ({ kind: "timeout", seconds: Number(match[1]) })
5519
+ },
5520
+ { re: /(?:^|\n\n)[^\n]*aborted$/i, build: () => ({ kind: "cancelled" }) },
5521
+ {
5522
+ re: /(?:^|\n\n)Command exited with code (\d+)$/i,
5523
+ build: (match) => ({ kind: "exit", exitCode: Number(match[1]) })
5524
+ }
5525
+ ];
5526
+ function parseBashTerminalStatus(text) {
5527
+ const clean = String(text ?? "").replace(/\r/g, "");
5528
+ for (const { re, build } of BASH_STATUS_PATTERNS) {
5529
+ const match = clean.match(re);
5530
+ if (match && match.index !== void 0) {
5531
+ return { status: build(match), body: clean.slice(0, match.index).trimEnd() };
5532
+ }
5533
+ }
5534
+ if (/^(?:operation )?aborted(?: after \d+ retry attempts?)?$/i.test(clean.trim())) {
5535
+ return { status: { kind: "cancelled" }, body: "" };
5536
+ }
5537
+ return { status: void 0, body: clean };
5538
+ }
5539
+ function bashErrorLabel(status) {
5540
+ if (status?.kind === "timeout") return "\u2717 Timed out";
5541
+ if (status?.kind === "cancelled") return "\u2717 Cancelled";
5542
+ return void 0;
5543
+ }
5544
+ function bashEmptyBodyText(status, isError) {
5545
+ if (status?.kind === "timeout") return "No output was received before the timeout";
5546
+ if (status?.kind === "cancelled") return "Command was cancelled without producing output";
5547
+ if (isError) return "Command failed without producing output";
5548
+ return "Command completed without producing output";
5549
+ }
5550
+ function bashFooter(theme, status, elapsedMs, bodyText, isError) {
5551
+ const elapsed = elapsedMs === void 0 ? "--" : `${(elapsedMs / 1e3).toFixed(2)}s`;
5552
+ const words = bodyText.trim() ? formatBoxedWords(bodyText) : "";
5553
+ if (status?.kind === "timeout") {
5554
+ const seconds = Number.isFinite(status.seconds) && status.seconds > 0 ? status.seconds : Number.NaN;
5555
+ return theme.fg(
5556
+ "warning",
5557
+ Number.isFinite(seconds) ? `Terminated after ${seconds.toFixed(1)}s` : "Terminated by timeout"
5558
+ );
5559
+ }
5560
+ if (status?.kind === "cancelled") {
5561
+ return [theme.fg("warning", "Cancelled"), theme.fg("text", elapsed)].join(theme.fg("dim", " \xB7 "));
5562
+ }
5563
+ const exitLabel = status?.kind === "exit" ? `Exit ${status.exitCode}` : isError ? "Failed" : "Exit 0";
5564
+ const exitColor = status?.kind === "exit" && status.exitCode !== 0 ? "error" : "text";
5565
+ const parts = [theme.fg(exitColor, exitLabel), theme.fg("text", elapsed)];
5566
+ if (words) parts.push(theme.fg("dim", words));
5567
+ return parts.join(theme.fg("dim", " \xB7 "));
5436
5568
  }
5437
- function renderBoxedBashResult(theme, inner, result, context, expandHint2) {
5569
+ var INTERACTIVE_COMMANDS = /* @__PURE__ */ new Set([
5570
+ "pi",
5571
+ "vim",
5572
+ "vi",
5573
+ "nvim",
5574
+ "nano",
5575
+ "less",
5576
+ "more",
5577
+ "man",
5578
+ "top",
5579
+ "htop",
5580
+ "btop",
5581
+ "ssh",
5582
+ "telnet",
5583
+ "python",
5584
+ "python3",
5585
+ "node",
5586
+ "sqlite3",
5587
+ "mysql",
5588
+ "psql",
5589
+ "redis-cli",
5590
+ "mongosh",
5591
+ "bc",
5592
+ "irssi"
5593
+ ]);
5594
+ function isInteractiveCommand(command) {
5595
+ const base = (String(command ?? "").trim().split(/\s+/)[0] ?? "").split("/").pop() ?? "";
5596
+ return INTERACTIVE_COMMANDS.has(base);
5597
+ }
5598
+ function bashBodyComponent(preview, emptyLines) {
5599
+ if (!emptyLines) return preview;
5600
+ return {
5601
+ invalidate: () => preview.invalidate(),
5602
+ render(width) {
5603
+ const lines = preview.render(width);
5604
+ return lines.length > 0 ? lines : emptyLines;
5605
+ }
5606
+ };
5607
+ }
5608
+ function renderBashStreamingResult(theme, raw, options, context) {
5609
+ const body = stripBashToolNoticeLines(stripAnsi(raw));
5610
+ const hasOutput = body.trim().length > 0;
5611
+ const elapsed = getStateElapsedMs(context.state);
5612
+ const emptyLines = [theme.fg("dim", "No output received yet")];
5613
+ if (!hasOutput && isInteractiveCommand(context?.args?.command) && (elapsed ?? 0) >= 1e3) {
5614
+ emptyLines.push(theme.fg("dim", "The process may be waiting for terminal input"));
5615
+ }
5616
+ const preview = createBashResultPreview(theme, body, options, "toolOutput");
5438
5617
  const rawCommand = String(context?.args?.command ?? "...");
5439
- const referenceLines = rawCommand.split("\n").map((line, index) => `${index === 0 ? "$ " : "> "}${line}`);
5440
- return renderBoxedToolResult(theme, inner, {
5618
+ return renderBoxedToolResult(theme, bashBodyComponent(preview, hasOutput ? void 0 : emptyLines), {
5441
5619
  widthKey: bashWidthKey(rawCommand, context?.args?.timeout),
5442
- referenceLines,
5443
- footerLines: [
5444
- formatBoxedFooter(theme, result, [`timeout ${formatTimeout(context)}`], getElapsed(context))
5445
- ],
5446
- ...expandHint2 ? { expandHint: expandHint2 } : {},
5447
- isError: context.isError,
5448
- isPartial: Boolean(context.isPartial)
5620
+ referenceLines: rawCommand.split("\n").map((line, index) => `${index === 0 ? "$ " : "> "}${line}`),
5621
+ dividerLabel: "Output",
5622
+ showDivider: hasOutput,
5623
+ footerLines: [formatBoxedRunningStatus(theme, elapsed)],
5624
+ isPartial: true
5449
5625
  });
5450
5626
  }
5451
- function getElapsed(context) {
5452
- return getStateElapsedMs(context.state);
5627
+ function renderBashFinalResult(theme, raw, options, context) {
5628
+ const isError = Boolean(context.isError);
5629
+ const clean = stripAnsi(raw);
5630
+ const { status, body: statusStripped } = parseBashTerminalStatus(clean);
5631
+ const output = stripBashToolNoticeLines(statusStripped);
5632
+ const elapsed = getStateElapsedMs(context.state);
5633
+ const outputColor = isError ? "error" : "toolOutput";
5634
+ const footer = bashFooter(theme, status, elapsed, output, isError);
5635
+ const errorLabel = isError ? bashErrorLabel(status) ?? "\u2717 Error" : void 0;
5636
+ const rawCommand = String(context?.args?.command ?? "...");
5637
+ const widthKey = bashWidthKey(rawCommand, context?.args?.timeout);
5638
+ const referenceLines = rawCommand.split("\n").map((line, index) => `${index === 0 ? "$ " : "> "}${line}`);
5639
+ if (!options.expanded) {
5640
+ const scanLines = getToolsRenderConfig().maxCollapsedLines + 10;
5641
+ let nlCount = 0;
5642
+ let tailStart = 0;
5643
+ for (let i = statusStripped.length - 1; i >= 0; i--) {
5644
+ if (statusStripped.charCodeAt(i) === 10) {
5645
+ nlCount++;
5646
+ if (nlCount >= scanLines) {
5647
+ tailStart = i + 1;
5648
+ break;
5649
+ }
5650
+ }
5651
+ }
5652
+ const tail = stripBashToolNoticeLines(stripAnsi(statusStripped.slice(tailStart)));
5653
+ const totalLinesBefore = tailStart > 0 ? countNewlines(statusStripped, 0, tailStart) : 0;
5654
+ const preview2 = createBashResultPreview(theme, tail, options, outputColor);
5655
+ return renderBoxedToolResult(
5656
+ theme,
5657
+ bashBodyComponent(
5658
+ preview2,
5659
+ statusStripped.trim() ? void 0 : [theme.fg("muted", bashEmptyBodyText(status, isError))]
5660
+ ),
5661
+ {
5662
+ widthKey,
5663
+ referenceLines,
5664
+ footerLines: [footer],
5665
+ ...totalLinesBefore > 0 ? { expandHint: "Ctrl+O for more" } : {},
5666
+ isError,
5667
+ isPartial: false,
5668
+ ...errorLabel ? { errorLabel } : {}
5669
+ }
5670
+ );
5671
+ }
5672
+ const preview = createBashResultPreview(theme, output, options, outputColor);
5673
+ return renderBoxedToolResult(
5674
+ theme,
5675
+ bashBodyComponent(preview, output.trim() ? void 0 : [theme.fg("muted", bashEmptyBodyText(status, isError))]),
5676
+ {
5677
+ widthKey,
5678
+ referenceLines,
5679
+ footerLines: [footer],
5680
+ isError,
5681
+ isPartial: false,
5682
+ ...errorLabel ? { errorLabel } : {}
5683
+ }
5684
+ );
5453
5685
  }
5686
+ var EMPTY_BASH_RESULT = Object.freeze({
5687
+ invalidate() {
5688
+ },
5689
+ render() {
5690
+ return [];
5691
+ }
5692
+ });
5454
5693
  function createBashResultPreview(theme, text, options, color) {
5455
5694
  let cacheKey = "";
5456
5695
  let cacheLines = null;
@@ -5792,14 +6031,21 @@ var bashTool = {
5792
6031
  bashTreeStates.set(context.toolCallId, { cls, command: String(args?.command ?? "") });
5793
6032
  return renderBashTreePanel(theme, context.toolCallId, context);
5794
6033
  }
6034
+ noteBoxedCallState(context);
5795
6035
  const rawCommand = String(args?.command ?? "...");
5796
6036
  return renderBoxedBashCall(theme, rawCommand.split("\n"), context, bashWidthKey(rawCommand, args?.timeout));
5797
6037
  },
5798
6038
  result(result, options, theme, context) {
6039
+ const firstResultPass = !isResultSeen(context.state);
6040
+ markResultSeen(context.state);
5799
6041
  const cls = classifyBashCommand(String(context?.args?.command ?? ""));
5800
6042
  if (cls && !context.isError) {
5801
- const output2 = stripBashToolNoticeLines(stripAnsi(getTextOutput(result)));
5802
- const parsed = parseBashTreeOutput(cls, output2);
6043
+ if (!options.isPartial) {
6044
+ recordExecutionEnded(context.state);
6045
+ stopElapsedTicker(context.state);
6046
+ }
6047
+ const output = stripBashToolNoticeLines(stripAnsi(getTextOutput(result)));
6048
+ const parsed = parseBashTreeOutput(cls, output);
5803
6049
  const state = bashTreeStates.get(context.toolCallId);
5804
6050
  if (parsed) {
5805
6051
  if (state) state.parsed = parsed;
@@ -5807,30 +6053,18 @@ var bashTool = {
5807
6053
  return EMPTY_BASH_TREE_RESULT;
5808
6054
  }
5809
6055
  if (state) state.fallback = true;
6056
+ } else if (options.isPartial) {
6057
+ startElapsedTicker(context.state, context.invalidate);
6058
+ } else {
6059
+ recordExecutionEnded(context.state);
6060
+ stopElapsedTicker(context.state);
5810
6061
  }
5811
6062
  const raw = getTextOutput(result);
5812
- const outputColor = context.isError ? "error" : "toolOutput";
5813
- if (!options.expanded) {
5814
- const scanLines = getToolsRenderConfig().maxCollapsedLines + 10;
5815
- let nlCount = 0;
5816
- let tailStart = 0;
5817
- for (let i = raw.length - 1; i >= 0; i--) {
5818
- if (raw.charCodeAt(i) === 10) {
5819
- nlCount++;
5820
- if (nlCount >= scanLines) {
5821
- tailStart = i + 1;
5822
- break;
5823
- }
5824
- }
5825
- }
5826
- const tail = stripBashToolNoticeLines(stripAnsi(raw.slice(tailStart)));
5827
- const totalLinesBefore = tailStart > 0 ? countNewlines(raw, 0, tailStart) : 0;
5828
- const inner2 = createBashResultPreview(theme, tail, options, outputColor);
5829
- return renderBoxedBashResult(theme, inner2, result, context, totalLinesBefore > 0 ? "Ctrl+O for more" : void 0);
6063
+ if (options.isPartial) {
6064
+ if (firstResultPass) return EMPTY_BASH_RESULT;
6065
+ return renderBashStreamingResult(theme, raw, options, context);
5830
6066
  }
5831
- const output = stripBashToolNoticeLines(stripAnsi(raw));
5832
- const inner = createBashResultPreview(theme, output, options, outputColor);
5833
- return renderBoxedBashResult(theme, inner, result, context);
6067
+ return renderBashFinalResult(theme, raw, options, context);
5834
6068
  }
5835
6069
  };
5836
6070
 
@@ -6857,6 +7091,13 @@ var AdaptiveDiffComponent = class {
6857
7091
  // extension-src/pi-style/features/tools/boxed/edit.ts
6858
7092
  var MAX_HIGHLIGHT_DIFF_CHARS = 12e3;
6859
7093
  var MAX_HIGHLIGHT_DIFF_ROWS = 120;
7094
+ var EMPTY_EDIT_RESULT = Object.freeze({
7095
+ invalidate() {
7096
+ },
7097
+ render() {
7098
+ return [];
7099
+ }
7100
+ });
6860
7101
  function diffDividerLabel(theme, stats) {
6861
7102
  const plus = stats.additions > 0 ? theme.fg("toolDiffAdded", `+${stats.additions}`) : theme.fg("dim", "+0");
6862
7103
  const minus = stats.removals > 0 ? theme.fg("toolDiffRemoved", `-${stats.removals}`) : theme.fg("dim", "-0");
@@ -6874,17 +7115,24 @@ function editDiffFooter(theme, result, context, stats) {
6874
7115
  var editTool = {
6875
7116
  call(args, theme, context) {
6876
7117
  noteExecutionStart(context);
7118
+ noteBoxedCallState(context);
6877
7119
  const detail = displayPath(String(args?.path ?? args?.file_path ?? ""), context);
6878
7120
  return renderBoxedToolCall(theme, "Edit", [], {
6879
7121
  headerDetail: detail,
6880
7122
  isError: Boolean(context.isError),
6881
7123
  isPartial: Boolean(context.isPartial),
6882
- isPending: Boolean(context.isPartial)
7124
+ isPending: Boolean(context.isPartial),
7125
+ running: Boolean(context.executionStarted),
7126
+ resultSeen: isResultSeen(context.state)
6883
7127
  });
6884
7128
  },
6885
7129
  result(result, options, theme, context) {
6886
7130
  if (options.isPartial) {
7131
+ const firstResultPass = noteBoxedResultPhase(context, options.isPartial);
7132
+ if (firstResultPass) return EMPTY_EDIT_RESULT;
6887
7133
  return renderBoxedToolResult(theme, () => [`${theme.fg("dim", "\u21B3")} ${theme.fg("muted", "Applying edit...")}`], {
7134
+ showDivider: false,
7135
+ footerLines: [formatBoxedRunningStatus(theme, stateElapsedMs(context))],
6888
7136
  isPartial: true
6889
7137
  });
6890
7138
  }
@@ -6938,19 +7186,41 @@ var editTool = {
6938
7186
  var MAX_FALLBACK_PREVIEW_LINES = 10;
6939
7187
  function renderFallbackCall(toolName, args, theme, context) {
6940
7188
  noteExecutionStart(context);
7189
+ noteBoxedCallState(context);
6941
7190
  return renderBoxedToolCall(theme, formatToolName(String(toolName ?? "Tool")), formatToolParamLines(args, theme), {
6942
7191
  isError: Boolean(context.isError),
6943
7192
  isPartial: Boolean(context.isPartial),
6944
- isPending: Boolean(context.isPartial)
7193
+ isPending: Boolean(context.isPartial),
7194
+ running: Boolean(context.executionStarted),
7195
+ resultSeen: isResultSeen(context.state)
6945
7196
  });
6946
7197
  }
6947
7198
  function renderFallbackResult(_toolName, result, options, theme, context) {
7199
+ const firstResultPass = noteBoxedResultPhase(context, options.isPartial);
6948
7200
  const isError = Boolean(context.isError);
6949
7201
  const expanded = Boolean(options.expanded);
6950
7202
  const maxLines = expanded ? getToolsRenderConfig().maxExpandedLines : MAX_FALLBACK_PREVIEW_LINES;
6951
7203
  const output = getTextOutput(result);
6952
7204
  const elapsedMs = getStateElapsedMs(context.state);
6953
7205
  const { lines, omitted } = selectRenderLines(output, maxLines);
7206
+ if (options.isPartial) {
7207
+ if (firstResultPass) return EMPTY_FALLBACK_RESULT;
7208
+ const hasOutput = output.trim().length > 0;
7209
+ return renderBoxedToolResult(
7210
+ theme,
7211
+ () => {
7212
+ const body = lines.map((line) => formatToolOutputLine(theme, line, "toolOutput"));
7213
+ if (!hasOutput) body.push(theme.fg("dim", "No output received yet"));
7214
+ return body;
7215
+ },
7216
+ {
7217
+ dividerLabel: "Output",
7218
+ showDivider: hasOutput,
7219
+ footerLines: [formatBoxedRunningStatus(theme, elapsedMs)],
7220
+ isPartial: true
7221
+ }
7222
+ );
7223
+ }
6954
7224
  return renderBoxedToolResult(
6955
7225
  theme,
6956
7226
  () => {
@@ -6961,7 +7231,7 @@ function renderFallbackResult(_toolName, result, options, theme, context) {
6961
7231
  return body;
6962
7232
  },
6963
7233
  {
6964
- footerLines: [formatBoxedFooter(theme, result, [], elapsedMs)],
7234
+ footerLines: [formatBoxedFooterWithElapsed(theme, elapsedMs, output)],
6965
7235
  renderLineBudget: maxLines,
6966
7236
  ...expanded || omitted <= 0 ? {} : { expandHint: "Ctrl+O for more" },
6967
7237
  isError,
@@ -6969,6 +7239,20 @@ function renderFallbackResult(_toolName, result, options, theme, context) {
6969
7239
  }
6970
7240
  );
6971
7241
  }
7242
+ var EMPTY_FALLBACK_RESULT = Object.freeze({
7243
+ invalidate() {
7244
+ },
7245
+ render() {
7246
+ return [];
7247
+ }
7248
+ });
7249
+ function formatBoxedFooterWithElapsed(theme, elapsedMs, output) {
7250
+ const elapsed = elapsedMs === void 0 ? "--" : `${(elapsedMs / 1e3).toFixed(2)}s`;
7251
+ const words = output.trim() ? formatBoxedWords(output) : "";
7252
+ const parts = [theme.fg("text", elapsed)];
7253
+ if (words) parts.push(theme.fg("dim", words));
7254
+ return parts.join(theme.fg("dim", " \xB7 "));
7255
+ }
6972
7256
 
6973
7257
  // extension-src/pi-style/features/tools/boxed/find.ts
6974
7258
  var FIND_META = Object.freeze({
@@ -7055,6 +7339,13 @@ var lsTool = {
7055
7339
  import { getLanguageFromPath as getLanguageFromPath2 } from "@earendil-works/pi-coding-agent";
7056
7340
  var MAX_HIGHLIGHT_DIFF_CHARS2 = 12e3;
7057
7341
  var MAX_HIGHLIGHT_DIFF_ROWS2 = 120;
7342
+ var EMPTY_QUICK_EDIT_RESULT = Object.freeze({
7343
+ invalidate() {
7344
+ },
7345
+ render() {
7346
+ return [];
7347
+ }
7348
+ });
7058
7349
  var QUICK_EDIT_TOOLS = {
7059
7350
  quick_edit: {
7060
7351
  toolLabel: "Quick Edit",
@@ -7137,10 +7428,12 @@ function quickEditDiffFooter(theme, result, context, stats) {
7137
7428
  }
7138
7429
  function renderQuickEditResult(_toolName, result, options, theme, context, config) {
7139
7430
  if (options.isPartial) {
7431
+ const firstResultPass = noteBoxedResultPhase(context, options.isPartial);
7432
+ if (firstResultPass) return EMPTY_QUICK_EDIT_RESULT;
7140
7433
  return renderBoxedToolResult(
7141
7434
  theme,
7142
7435
  () => [`${theme.fg("dim", "\u21B3")} ${theme.fg("muted", `Applying ${config.applyingLabel}...`)}`],
7143
- { isPartial: true }
7436
+ { showDivider: false, footerLines: [formatBoxedRunningStatus(theme, stateElapsedMs(context))], isPartial: true }
7144
7437
  );
7145
7438
  }
7146
7439
  const output = getTextOutput(result);
@@ -7194,12 +7487,15 @@ function quickEditTool(config) {
7194
7487
  return {
7195
7488
  call(args, theme, context) {
7196
7489
  noteExecutionStart(context);
7490
+ noteBoxedCallState(context);
7197
7491
  const detail = displayPath(String(args?.path ?? ""), context);
7198
7492
  return renderBoxedToolCall(theme, config.toolLabel, [], {
7199
7493
  headerDetail: detail,
7200
7494
  isError: Boolean(context.isError),
7201
7495
  isPartial: Boolean(context.isPartial),
7202
- isPending: Boolean(context.isPartial)
7496
+ isPending: Boolean(context.isPartial),
7497
+ running: Boolean(context.executionStarted),
7498
+ resultSeen: isResultSeen(context.state)
7203
7499
  });
7204
7500
  },
7205
7501
  result(result, options, theme, context) {
@@ -7239,6 +7535,13 @@ var readTool = {
7239
7535
 
7240
7536
  // extension-src/pi-style/features/tools/boxed/write.ts
7241
7537
  var WRITE_EXPAND_HINT = "Ctrl+O for more";
7538
+ var EMPTY_WRITE_RESULT = Object.freeze({
7539
+ invalidate() {
7540
+ },
7541
+ render() {
7542
+ return [];
7543
+ }
7544
+ });
7242
7545
  function numberedPreviewLines(content) {
7243
7546
  const normalized = replaceTabs(String(content ?? "")).replace(/\r/g, "");
7244
7547
  if (!normalized) return [];
@@ -7261,6 +7564,7 @@ function renderWritePreviewBox(theme, detailLine, content, options) {
7261
7564
  ...options.state ? { state: options.state } : {},
7262
7565
  isError: options.isError,
7263
7566
  isPending: options.isPending,
7567
+ running: Boolean(options.running),
7264
7568
  bodyLines: () => {
7265
7569
  if (preview.length === 0) return [];
7266
7570
  const shown = preview.slice(0, budget).map((line) => formatNumberedLine(theme, line));
@@ -7287,10 +7591,11 @@ var writeTool = {
7287
7591
  state: context.state,
7288
7592
  isError: Boolean(context.isError),
7289
7593
  isPending: Boolean(context.isPartial),
7594
+ running: Boolean(context.executionStarted),
7290
7595
  expanded: Boolean(context.expanded)
7291
7596
  });
7292
7597
  },
7293
- result(result, _options, theme, context) {
7598
+ result(result, options, theme, context) {
7294
7599
  clearFooterState(context);
7295
7600
  const output = getTextOutput(result);
7296
7601
  const detail = displayPath(String(context?.args?.path ?? context?.args?.file_path ?? ""), context);
@@ -7302,6 +7607,7 @@ var writeTool = {
7302
7607
  isError: true
7303
7608
  });
7304
7609
  }
7610
+ if (options.isPartial) return EMPTY_WRITE_RESULT;
7305
7611
  return compactFooterWithState(theme, result, context);
7306
7612
  }
7307
7613
  };
@@ -8662,6 +8968,7 @@ function createPiStyleSessionCoordinator(pi, hooks = {}) {
8662
8968
  resetBatchRegistry();
8663
8969
  resetGrepRegistry();
8664
8970
  resetBashTreeRegistry();
8971
+ stopAllElapsedTickers();
8665
8972
  active = false;
8666
8973
  await app.reload();
8667
8974
  productGate = app.productPolicy.corePatchGate;
@@ -8722,6 +9029,7 @@ function createPiStyleSessionCoordinator(pi, hooks = {}) {
8722
9029
  resetBatchRegistry();
8723
9030
  resetGrepRegistry();
8724
9031
  resetBashTreeRegistry();
9032
+ stopAllElapsedTickers();
8725
9033
  app.sessionShutdown();
8726
9034
  }
8727
9035
  };