@quandev104/pi-style 0.1.2 → 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.
Files changed (36) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +1 -2
  3. package/dist/extensions/pi-style.js +5919 -4966
  4. package/dist/extensions/pi-style.js.map +1 -1
  5. package/extension-src/pi-style/app/command-service.ts +2 -2
  6. package/extension-src/pi-style/app/index.ts +0 -1
  7. package/extension-src/pi-style/domain/config-authorization.ts +1 -2
  8. package/extension-src/pi-style/domain/config-normalization.ts +3 -3
  9. package/extension-src/pi-style/domain/config-types.ts +2 -2
  10. package/extension-src/pi-style/domain/status.ts +1 -1
  11. package/extension-src/pi-style/domain/theme.ts +3 -0
  12. package/extension-src/pi-style/features/messages/index.ts +2 -8
  13. package/extension-src/pi-style/features/tools/boxed/bash.ts +663 -45
  14. package/extension-src/pi-style/features/tools/boxed/batch.ts +459 -0
  15. package/extension-src/pi-style/features/tools/boxed/edit.ts +28 -2
  16. package/extension-src/pi-style/features/tools/boxed/fallback.ts +47 -4
  17. package/extension-src/pi-style/features/tools/boxed/find.ts +48 -48
  18. package/extension-src/pi-style/features/tools/boxed/grep.ts +161 -89
  19. package/extension-src/pi-style/features/tools/boxed/index.ts +4 -0
  20. package/extension-src/pi-style/features/tools/boxed/ls.ts +39 -47
  21. package/extension-src/pi-style/features/tools/boxed/output-tree.ts +368 -0
  22. package/extension-src/pi-style/features/tools/boxed/quick-edit.ts +25 -4
  23. package/extension-src/pi-style/features/tools/boxed/read.ts +32 -189
  24. package/extension-src/pi-style/features/tools/boxed/session-config.ts +70 -4
  25. package/extension-src/pi-style/features/tools/boxed/shared.ts +41 -1
  26. package/extension-src/pi-style/features/tools/boxed/write.ts +105 -47
  27. package/extension-src/pi-style/features/tools/index.ts +14 -0
  28. package/extension-src/pi-style/pi/compatibility-coordinator.ts +4 -26
  29. package/extension-src/pi-style/pi/compatibility-probe.ts +3 -33
  30. package/extension-src/pi-style/pi/compatibility-registry.ts +0 -1
  31. package/extension-src/pi-style/pi/config-session.ts +0 -2
  32. package/extension-src/pi-style/pi/index.ts +35 -1
  33. package/extension-src/pi-style/pi/session-coordinator.ts +47 -3
  34. package/extension-src/pi-style/shared/box.ts +117 -26
  35. package/extension-src/pi-style/shared/theme-extras.ts +0 -2
  36. package/package.json +1 -1
@@ -1,65 +1,65 @@
1
1
  // Boxed find tool renderer.
2
+ //
3
+ // find calls render as a boxless tree panel — a lone find shows its parsed
4
+ // output as a flat `Glob: <pattern> <N> files · in <path>` tree; consecutive
5
+ // find calls group into one panel with per-member nested subtrees (see
6
+ // batch.ts). Pending/failed calls without output fall back to a path row.
2
7
 
3
8
  import { stripAnsi } from "../../../shared/ansi.js";
9
+ import { getTextOutput, shortenPath } from "../../../shared/box.js";
4
10
  import {
5
- boxedToolWidthKey,
6
- countLines,
7
- getTextOutput,
8
- renderBoxedToolResult,
9
- shortenPath,
10
- stripTrailingNotice,
11
- } from "../../../shared/box.js";
12
- import {
13
- type BoxedToolDefinition,
14
- clearFooterState,
15
- compactCall,
16
- compactFooterWithState,
17
- noteExecutionStart,
18
- resultFooterLines,
19
- truncationOutputLines,
20
- } from "./shared.js";
11
+ type BatchToolMeta,
12
+ EMPTY_BATCH_COMPONENT,
13
+ emptyBatchResult,
14
+ registerBatchCall,
15
+ registerBatchResult,
16
+ renderBatchAwareCall,
17
+ } from "./batch.js";
18
+ import { parseFindOutput } from "./output-tree.js";
19
+ import { type BoxedToolDefinition, noteExecutionStart } from "./shared.js";
21
20
 
22
- function queryDetail(pattern: string, rawPath: string): string {
21
+ const FIND_META: BatchToolMeta = Object.freeze({
22
+ toolName: "find",
23
+ label: "Find",
24
+ headerLabel: "Glob",
25
+ });
26
+
27
+ function pathLabel(rawPath: string): string {
23
28
  const displayPath = String(rawPath ?? ".");
24
- const path = displayPath === "." || displayPath === "" ? "current directory" : shortenPath(displayPath);
29
+ return displayPath === "." || displayPath === "" ? "current directory" : shortenPath(displayPath);
30
+ }
31
+
32
+ function queryDetail(pattern: string, rawPath: string): string {
33
+ const path = pathLabel(rawPath);
25
34
  return pattern ? `${pattern} in ${path}` : path;
26
35
  }
27
36
 
28
37
  export const findTool: BoxedToolDefinition = {
29
38
  call(args, theme, context) {
30
39
  noteExecutionStart(context);
31
- const detail = queryDetail(String(args?.pattern ?? ""), String(args?.path ?? "."));
32
- return compactCall(theme, "Find", `${theme.fg("dim", "Query: ")}${detail}`, {
33
- detailKey: detail,
34
- context,
40
+ const pattern = String(args?.pattern ?? "");
41
+ const rawPath = String(args?.path ?? ".");
42
+ const detail = queryDetail(pattern, rawPath);
43
+ const { isLeader, batch } = registerBatchCall(FIND_META, detail, context, {
44
+ pattern,
45
+ pathLabel: pathLabel(rawPath),
35
46
  });
47
+ if (!isLeader) return EMPTY_BATCH_COMPONENT;
48
+ return renderBatchAwareCall(theme, batch);
36
49
  },
37
- result(result, options, theme, context) {
38
- clearFooterState(context);
50
+ result(result, options, _theme, context) {
39
51
  const output = stripAnsi(getTextOutput(result)).trimEnd();
40
- const detail = queryDetail(String(context?.args?.pattern ?? ""), String(context?.args?.path ?? "."));
41
- const widthKey = boxedToolWidthKey("Find", detail);
42
-
43
- if (context.isError) {
44
- return renderBoxedToolResult(theme, () => [theme.fg("error", output || "Error")], {
45
- widthKey,
46
- footerLines: resultFooterLines(theme, result, context),
47
- isError: true,
48
- });
49
- }
50
-
51
- if (!options.expanded) return compactFooterWithState(theme, result, context);
52
-
53
- let fileCount = 0;
54
- if (output && output !== "No files found matching pattern") {
55
- const stripped = stripTrailingNotice(output);
56
- fileCount = truncationOutputLines(result) ?? countLines(stripped);
57
- }
58
-
59
- const summary = `↳ Found ${fileCount} ${fileCount === 1 ? "file" : "files"}.`;
60
- return renderBoxedToolResult(theme, () => [theme.fg("dim", summary)], {
61
- widthKey,
62
- footerLines: resultFooterLines(theme, result, context),
63
- });
52
+ const entries = context.isError ? undefined : parseFindOutput(output);
53
+ registerBatchResult(
54
+ FIND_META,
55
+ {
56
+ isPartial: Boolean(options.isPartial),
57
+ isError: Boolean(context.isError),
58
+ errorText: context.isError ? output || undefined : undefined,
59
+ ...(entries !== undefined ? { entries } : {}),
60
+ },
61
+ context,
62
+ );
63
+ return emptyBatchResult();
64
64
  },
65
65
  };
@@ -1,108 +1,180 @@
1
1
  // Boxed grep/search tool renderer.
2
+ //
3
+ // grep renders a **boxless tree panel**: a summary header
4
+ // (`Grep: <pattern> <N> matches · <M> files · in <path>`) followed by match rows
5
+ // grouped by file (`├─ *line│content`). Like the quiet-tool batch panel, the
6
+ // whole panel lives in the call component and reads a live registry on every
7
+ // render, so the result's match data is picked up without cross-component
8
+ // invalidation. grep does not batch (each call owns its own panel).
9
+ //
10
+ // Lifecycle: panels are keyed by toolCallId and cleared on session reset and new
11
+ // message boundaries (see resetGrepRegistry wiring in session-coordinator.ts and
12
+ // pi/index.ts), mirroring the batch registry.
2
13
 
14
+ import type { Component } from "@earendil-works/pi-tui";
3
15
  import { stripAnsi } from "../../../shared/ansi.js";
16
+ import { type BoxTheme, getTextOutput, shortenPath } from "../../../shared/box.js";
17
+ import { safeTruncateToWidth } from "../../../shared/render-budget.js";
4
18
  import {
5
- boxedToolWidthKey,
6
- countLines,
7
- getTextOutput,
8
- renderBoxedToolResult,
9
- renderLines,
10
- shortenPath,
11
- stripTrailingNotice,
12
- } from "../../../shared/box.js";
19
+ type GrepMatch,
20
+ groupMatchesByFile,
21
+ parseGrepOutput,
22
+ pluralForm,
23
+ renderGrepTree,
24
+ SEARCH_ICON,
25
+ TREE_INDENT,
26
+ } from "./output-tree.js";
13
27
  import { getToolsRenderConfig } from "./session-config.js";
14
- import {
15
- type BoxedToolDefinition,
16
- clearFooterState,
17
- compactCall,
18
- compactFooterWithState,
19
- matchLimitReached,
20
- noteExecutionStart,
21
- resultFooterLines,
22
- } from "./shared.js";
28
+ import { type BoxedToolDefinition, noteExecutionStart } from "./shared.js";
29
+
30
+ const GREP_HEAD_LIMIT = 6;
31
+ const GREP_ERROR_LINES = 2;
32
+
33
+ interface GrepPanelState {
34
+ pattern: string;
35
+ pathLabel: string;
36
+ /** `undefined` until the result arrives; an empty array means zero matches. */
37
+ matches: GrepMatch[] | undefined;
38
+ isError: boolean;
39
+ errorText: string | undefined;
40
+ isPartial: boolean;
41
+ }
23
42
 
24
- const MAX_GREP_PREVIEW_LINES = 10;
43
+ const grepPanels = new Map<string, GrepPanelState>();
25
44
 
26
- function queryDetail(pattern: string, rawPath: string): string {
45
+ /** Reset all grep panel state (session start/shutdown). */
46
+ export function resetGrepRegistry(): void {
47
+ grepPanels.clear();
48
+ }
49
+
50
+ function pathLabel(rawPath: string): string {
27
51
  const displayPath = String(rawPath ?? ".");
28
- const path = displayPath === "." || displayPath === "" ? "current directory" : shortenPath(displayPath);
29
- return pattern ? `/${pattern}/ in ${path}` : path;
52
+ return displayPath === "." || displayPath === "" ? "current directory" : shortenPath(displayPath);
30
53
  }
31
54
 
32
- export const grepTool: BoxedToolDefinition = {
33
- call(args, theme, context) {
34
- noteExecutionStart(context);
35
- const detail = queryDetail(String(args?.pattern ?? ""), String(args?.path ?? "."));
36
- return compactCall(theme, "Search", `${theme.fg("dim", "Query: ")}${detail}`, {
37
- detailKey: detail,
38
- context,
39
- });
40
- },
41
- result(result, options, theme, context) {
42
- clearFooterState(context);
43
- const output = stripAnsi(getTextOutput(result)).trimEnd();
44
- const stripped = stripTrailingNotice(output);
45
- const detail = queryDetail(String(context?.args?.pattern ?? ""), String(context?.args?.path ?? "."));
46
- const widthKey = boxedToolWidthKey("Search", detail);
47
- const cfg = getToolsRenderConfig();
55
+ function registerGrepCall(toolCallId: string, pattern: string, label: string): void {
56
+ const existing = grepPanels.get(toolCallId);
57
+ if (existing) {
58
+ existing.pattern = pattern;
59
+ existing.pathLabel = label;
60
+ return;
61
+ }
62
+ grepPanels.set(toolCallId, {
63
+ pattern,
64
+ pathLabel: label,
65
+ matches: undefined,
66
+ isError: false,
67
+ errorText: undefined,
68
+ isPartial: true,
69
+ });
70
+ }
71
+
72
+ function registerGrepResult(
73
+ toolCallId: string,
74
+ data: { matches: GrepMatch[]; isError: boolean; errorText: string | undefined; isPartial: boolean },
75
+ ): void {
76
+ const state = grepPanels.get(toolCallId);
77
+ if (!state) return;
78
+ state.matches = data.matches;
79
+ state.isError = data.isError;
80
+ state.errorText = data.errorText;
81
+ state.isPartial = data.isPartial;
82
+ }
48
83
 
49
- if (context.isError) {
50
- return renderBoxedToolResult(
51
- theme,
52
- (width) => {
53
- const body = renderLines(theme, stripped || output || "Error", options, {
54
- maxLines: MAX_GREP_PREVIEW_LINES,
55
- color: "error",
56
- width,
57
- });
58
- return body ? body.split("\n") : [];
59
- },
60
- {
61
- widthKey,
62
- footerLines: resultFooterLines(theme, result, context),
63
- isError: true,
64
- },
65
- );
66
- }
84
+ function bold(theme: BoxTheme, text: string): string {
85
+ return typeof theme?.bold === "function" ? theme.bold(text) : text;
86
+ }
67
87
 
68
- if (!options.expanded) return compactFooterWithState(theme, result, context);
88
+ /** `Grep: <pattern> <N> matches · <M> files · in <path>` (done) /
89
+ * `Grep: <pattern> · in <path>` (pending). */
90
+ function formatGrepHeader(theme: BoxTheme, state: GrepPanelState): string {
91
+ const icon = getToolsRenderConfig().nerdFonts ? `${SEARCH_ICON} ` : "";
92
+ const label = bold(theme, "Grep:");
93
+ const patternPart = state.pattern ? ` ${theme.fg("text", state.pattern)}` : "";
94
+ const pathPart = state.pathLabel ? theme.fg("dim", ` · in ${state.pathLabel}`) : "";
95
+ if (state.isError) {
96
+ return `${icon}${theme.fg("error", bold(theme, "✗ Grep:"))}${state.pattern ? ` ${theme.fg("error", state.pattern)}` : ""}${pathPart}`;
97
+ }
98
+ if (state.matches === undefined) {
99
+ return `${icon}${label}${patternPart}${pathPart}`;
100
+ }
101
+ const matchCount = state.matches.length;
102
+ const fileCount = groupMatchesByFile(state.matches).length;
103
+ const matchesPart = theme.fg("accent", `${matchCount} ${pluralForm("match", matchCount)}`);
104
+ const filesPart = theme.fg("dim", ` · ${fileCount} ${pluralForm("file", fileCount)}`);
105
+ return `${icon}${label}${patternPart} ${matchesPart}${filesPart}${pathPart}`;
106
+ }
69
107
 
70
- let matchCount = 0;
71
- if (stripped && stripped !== "No matches found") {
72
- const lines = stripped.split("\n");
73
- matchCount = lines.filter((line) => /:\d+:/.test(line)).length;
108
+ function renderErrorLines(theme: BoxTheme, errorText: string, width: number): string[] {
109
+ const raw = stripAnsi(errorText)
110
+ .split("\n")
111
+ .map((line) => line.trim())
112
+ .filter((line) => line.length > 0);
113
+ if (raw.length === 0) return [];
114
+ const prefix = `${TREE_INDENT}${theme.fg("borderMuted", "└─")} `;
115
+ const out = raw
116
+ .slice(0, GREP_ERROR_LINES)
117
+ .map((line) => safeTruncateToWidth(`${prefix}${theme.fg("error", line)}`, Math.max(1, width), "…"));
118
+ if (raw.length > GREP_ERROR_LINES)
119
+ out.push(safeTruncateToWidth(`${prefix}${theme.fg("error", "…")}`, Math.max(1, width), "…"));
120
+ return out;
121
+ }
74
122
 
75
- if (matchCount === 0) {
76
- matchCount = countLines(stripped);
77
- }
123
+ function renderGrepPanelLines(theme: BoxTheme, state: GrepPanelState, width: number): string[] {
124
+ const safeWidth = Math.max(1, width);
125
+ const header = safeTruncateToWidth(formatGrepHeader(theme, state), safeWidth, "…");
126
+ if (state.isError) {
127
+ return [header, ...(state.errorText ? renderErrorLines(theme, state.errorText, width) : [])];
128
+ }
129
+ if (state.matches === undefined) return [header];
130
+ return renderGrepTree(theme, header, state.matches, safeWidth, {
131
+ headLimit: GREP_HEAD_LIMIT,
132
+ withIcons: getToolsRenderConfig().nerdFonts,
133
+ });
134
+ }
78
135
 
79
- if (matchLimitReached(result) !== undefined) {
80
- matchCount = Math.max(matchCount, matchLimitReached(result) as number);
81
- }
82
- }
136
+ /** Live panel component reading the registry on every render pass. The state
137
+ * reference is captured at creation (like the batch panel): a registry clear
138
+ * on session reset/resume must not blank already-rendered panels — the result
139
+ * renderer mutates this same object, so live updates still flow. */
140
+ function renderGrepPanel(theme: BoxTheme, toolCallId: string): Component {
141
+ const state = grepPanels.get(toolCallId);
142
+ return {
143
+ invalidate() {},
144
+ render(width: number): string[] {
145
+ if (!state) return [safeTruncateToWidth(bold(theme, "Grep:"), Math.max(1, width), "…")];
146
+ return renderGrepPanelLines(theme, state, width);
147
+ },
148
+ };
149
+ }
83
150
 
84
- const summary = theme.fg("dim", `↳ Found ${matchCount} ${matchCount === 1 ? "match" : "matches"}.`);
85
- if (!stripped || stripped === "No matches found") {
86
- return renderBoxedToolResult(theme, () => [summary], {
87
- widthKey,
88
- footerLines: resultFooterLines(theme, result, context),
89
- });
90
- }
151
+ /** Empty result component the panel lives in the call component, which
152
+ * re-renders when the result arrives (Pi re-renders the tool execution
153
+ * component on tool_execution_end), picking up the stored matches. */
154
+ const EMPTY_GREP_RESULT: Component = {
155
+ invalidate() {},
156
+ render() {
157
+ return [];
158
+ },
159
+ };
91
160
 
92
- return renderBoxedToolResult(
93
- theme,
94
- (width) => {
95
- const body = renderLines(theme, stripped, options, {
96
- maxLines: options.expanded ? cfg.maxExpandedLines : MAX_GREP_PREVIEW_LINES,
97
- color: "toolOutput",
98
- width,
99
- });
100
- return [summary, ...(body ? body.split("\n") : [])];
101
- },
102
- {
103
- widthKey,
104
- footerLines: resultFooterLines(theme, result, context),
105
- },
106
- );
161
+ export const grepTool: BoxedToolDefinition = {
162
+ call(args, theme, context) {
163
+ noteExecutionStart(context);
164
+ const pattern = String(args?.pattern ?? "");
165
+ registerGrepCall(context.toolCallId, pattern, pathLabel(String(args?.path ?? ".")));
166
+ return renderGrepPanel(theme, context.toolCallId);
167
+ },
168
+ result(result, options, _theme, context) {
169
+ const output = stripAnsi(getTextOutput(result)).trimEnd();
170
+ const isError = Boolean(context.isError);
171
+ const matches = isError ? [] : parseGrepOutput(output);
172
+ registerGrepResult(context.toolCallId, {
173
+ matches,
174
+ isError,
175
+ errorText: isError ? output || undefined : undefined,
176
+ isPartial: Boolean(options.isPartial),
177
+ });
178
+ return EMPTY_GREP_RESULT;
107
179
  },
108
180
  };
@@ -7,6 +7,7 @@
7
7
  import type { Component } from "@earendil-works/pi-tui";
8
8
  import type { BoxTheme } from "../../../shared/box.js";
9
9
  import { bashTool } from "./bash.js";
10
+ import { closeActiveBatch, isBatchableTool } from "./batch.js";
10
11
  import { editTool } from "./edit.js";
11
12
  import { renderFallbackCall, renderFallbackResult } from "./fallback.js";
12
13
  import { findTool } from "./find.js";
@@ -46,6 +47,9 @@ export function renderBoxedToolCall(
46
47
  theme: BoxTheme,
47
48
  context: BoxedToolContext,
48
49
  ): Component {
50
+ // Any non-batchable tool call is a batch boundary: the next quiet call starts
51
+ // a fresh batch instead of joining the previous one.
52
+ if (!isBatchableTool(toolName)) closeActiveBatch();
49
53
  const tool = typeof toolName === "string" ? REGISTRY[toolName] : undefined;
50
54
  if (tool) return tool.call(args, theme, context);
51
55
  return renderFallbackCall(toolName, args, theme, context);
@@ -1,23 +1,28 @@
1
1
  // Boxed ls tool renderer.
2
+ //
3
+ // ls calls render as a boxless tree panel — a lone ls shows its parsed output as
4
+ // a flat `List: <N> files · in <path>` tree; consecutive ls calls group into one
5
+ // panel with per-member nested subtrees (see batch.ts). Pending/failed calls
6
+ // without output fall back to a path row.
2
7
 
3
8
  import { stripAnsi } from "../../../shared/ansi.js";
9
+ import { getTextOutput, shortenPath } from "../../../shared/box.js";
4
10
  import {
5
- boxedToolWidthKey,
6
- countLines,
7
- getTextOutput,
8
- renderBoxedToolResult,
9
- shortenPath,
10
- stripTrailingNotice,
11
- } from "../../../shared/box.js";
12
- import {
13
- type BoxedToolDefinition,
14
- clearFooterState,
15
- compactCall,
16
- compactFooterWithState,
17
- noteExecutionStart,
18
- resultFooterLines,
19
- truncationOutputLines,
20
- } from "./shared.js";
11
+ type BatchToolMeta,
12
+ EMPTY_BATCH_COMPONENT,
13
+ emptyBatchResult,
14
+ registerBatchCall,
15
+ registerBatchResult,
16
+ renderBatchAwareCall,
17
+ } from "./batch.js";
18
+ import { parseLsOutput } from "./output-tree.js";
19
+ import { type BoxedToolDefinition, noteExecutionStart } from "./shared.js";
20
+
21
+ const LIST_META: BatchToolMeta = Object.freeze({
22
+ toolName: "ls",
23
+ label: "List",
24
+ headerLabel: "List",
25
+ });
21
26
 
22
27
  function displayPath(rawPath: string): string {
23
28
  const path = String(rawPath ?? ".");
@@ -28,38 +33,25 @@ function displayPath(rawPath: string): string {
28
33
  export const lsTool: BoxedToolDefinition = {
29
34
  call(args, theme, context) {
30
35
  noteExecutionStart(context);
31
- const detail = displayPath(String(args?.path ?? "."));
32
- return compactCall(theme, "List", `${theme.fg("dim", "Path: ")}${detail}`, {
33
- detailKey: detail,
34
- context,
35
- });
36
+ const rawPath = String(args?.path ?? ".");
37
+ const detail = displayPath(rawPath);
38
+ const { isLeader, batch } = registerBatchCall(LIST_META, detail, context, { pathLabel: detail });
39
+ if (!isLeader) return EMPTY_BATCH_COMPONENT;
40
+ return renderBatchAwareCall(theme, batch);
36
41
  },
37
- result(result, options, theme, context) {
38
- clearFooterState(context);
42
+ result(result, options, _theme, context) {
39
43
  const output = stripAnsi(getTextOutput(result)).trimEnd();
40
- const detail = displayPath(String(context?.args?.path ?? "."));
41
- const widthKey = boxedToolWidthKey("List", detail);
42
-
43
- if (context.isError) {
44
- return renderBoxedToolResult(theme, () => [theme.fg("error", output || "Error")], {
45
- widthKey,
46
- footerLines: resultFooterLines(theme, result, context),
47
- isError: true,
48
- });
49
- }
50
-
51
- if (!options.expanded) return compactFooterWithState(theme, result, context);
52
-
53
- let itemCount = 0;
54
- if (output && output !== "(empty directory)") {
55
- const stripped = stripTrailingNotice(output);
56
- itemCount = truncationOutputLines(result) ?? countLines(stripped);
57
- }
58
-
59
- const summary = `↳ Listed ${itemCount} ${itemCount === 1 ? "item" : "items"}.`;
60
- return renderBoxedToolResult(theme, () => [theme.fg("dim", summary)], {
61
- widthKey,
62
- footerLines: resultFooterLines(theme, result, context),
63
- });
44
+ const entries = context.isError ? undefined : parseLsOutput(output);
45
+ registerBatchResult(
46
+ LIST_META,
47
+ {
48
+ isPartial: Boolean(options.isPartial),
49
+ isError: Boolean(context.isError),
50
+ errorText: context.isError ? output || undefined : undefined,
51
+ ...(entries !== undefined ? { entries } : {}),
52
+ },
53
+ context,
54
+ );
55
+ return emptyBatchResult();
64
56
  },
65
57
  };