@oh-my-pi/pi-coding-agent 15.10.0 → 15.10.2
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/CHANGELOG.md +142 -1
- package/dist/types/cli/dry-balance-cli.d.ts +15 -1
- package/dist/types/cli/startup-cwd.d.ts +2 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/commit/analysis/conventional.d.ts +2 -2
- package/dist/types/commit/analysis/summary.d.ts +2 -2
- package/dist/types/commit/changelog/generate.d.ts +2 -2
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/index.d.ts +3 -3
- package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
- package/dist/types/commit/model-selection.d.ts +10 -4
- package/dist/types/config/api-key-resolver.d.ts +34 -0
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-provider-priority.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +17 -1
- package/dist/types/config/model-resolver.d.ts +4 -1
- package/dist/types/config/settings-schema.d.ts +9 -0
- package/dist/types/config/settings.d.ts +7 -2
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/debug/report-bundle.d.ts +3 -0
- package/dist/types/edit/file-snapshot-store.d.ts +18 -10
- package/dist/types/eval/py/__tests__/prelude.test.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +4 -1
- package/dist/types/lsp/client.d.ts +10 -0
- package/dist/types/lsp/utils.d.ts +3 -2
- package/dist/types/main.d.ts +3 -9
- package/dist/types/mcp/tool-bridge.d.ts +2 -0
- package/dist/types/modes/components/chat-block.d.ts +64 -0
- package/dist/types/modes/components/custom-editor.d.ts +4 -1
- package/dist/types/modes/components/overlay-box.d.ts +17 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
- package/dist/types/modes/components/plan-toc.d.ts +41 -0
- package/dist/types/modes/components/read-tool-group.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +2 -0
- package/dist/types/modes/components/transcript-container.d.ts +11 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +17 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
- package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +16 -5
- package/dist/types/modes/magic-keywords.d.ts +1 -1
- package/dist/types/modes/markdown-prose.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +21 -5
- package/dist/types/modes/utils/copy-targets.d.ts +21 -1
- package/dist/types/modes/workflow.d.ts +3 -3
- package/dist/types/plan-mode/approved-plan.d.ts +27 -8
- package/dist/types/plan-mode/plan-protection.d.ts +4 -4
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +21 -0
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/messages.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +8 -3
- package/dist/types/slash-commands/types.d.ts +4 -6
- package/dist/types/task/executor.d.ts +17 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/render.d.ts +3 -2
- package/dist/types/tools/archive-reader.d.ts +5 -0
- package/dist/types/tools/ast-edit.d.ts +3 -0
- package/dist/types/tools/ast-grep.d.ts +3 -0
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/eval.d.ts +8 -0
- package/dist/types/tools/find.d.ts +8 -4
- package/dist/types/tools/gh-cache-invalidation.d.ts +6 -0
- package/dist/types/tools/github-cache.d.ts +12 -0
- package/dist/types/tools/grouped-file-output.d.ts +95 -12
- package/dist/types/tools/memory-render.d.ts +4 -1
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/plan-mode-guard.d.ts +8 -9
- package/dist/types/tools/render-utils.d.ts +5 -9
- package/dist/types/tools/search.d.ts +6 -2
- package/dist/types/tools/sqlite-reader.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +3 -2
- package/dist/types/tools/write.d.ts +3 -0
- package/dist/types/tools/yield.d.ts +8 -0
- package/dist/types/tui/output-block.d.ts +16 -4
- package/dist/types/tui/status-line.d.ts +3 -0
- package/dist/types/utils/enhanced-paste.d.ts +20 -0
- package/dist/types/web/search/providers/kimi.d.ts +1 -1
- package/package.json +9 -9
- package/src/auto-thinking/classifier.ts +5 -1
- package/src/cli/args.ts +3 -1
- package/src/cli/dry-balance-cli.ts +54 -21
- package/src/cli/gallery-cli.ts +4 -1
- package/src/cli/gallery-fixtures/misc.ts +29 -0
- package/src/cli/startup-cwd.ts +68 -0
- package/src/commands/launch.ts +3 -0
- package/src/commit/analysis/conventional.ts +2 -2
- package/src/commit/analysis/summary.ts +2 -2
- package/src/commit/changelog/generate.ts +2 -2
- package/src/commit/changelog/index.ts +2 -2
- package/src/commit/map-reduce/index.ts +3 -3
- package/src/commit/map-reduce/map-phase.ts +2 -2
- package/src/commit/map-reduce/reduce-phase.ts +2 -2
- package/src/commit/model-selection.ts +36 -11
- package/src/commit/pipeline.ts +4 -4
- package/src/config/api-key-resolver.ts +58 -0
- package/src/config/model-provider-priority.ts +55 -0
- package/src/config/model-registry.ts +29 -24
- package/src/config/model-resolver.ts +39 -7
- package/src/config/settings-schema.ts +10 -0
- package/src/config/settings.ts +106 -43
- package/src/dap/config.ts +41 -2
- package/src/dap/defaults.json +1 -0
- package/src/dap/session.ts +1 -0
- package/src/dap/types.ts +10 -0
- package/src/debug/index.ts +47 -53
- package/src/debug/raw-sse-buffer.ts +7 -4
- package/src/debug/report-bundle.ts +9 -0
- package/src/edit/file-snapshot-store.ts +33 -1
- package/src/edit/hashline/filesystem.ts +2 -1
- package/src/edit/renderer.ts +82 -78
- package/src/eval/__tests__/llm-bridge.test.ts +110 -31
- package/src/eval/js/context-manager.ts +32 -15
- package/src/eval/llm-bridge.ts +22 -6
- package/src/eval/py/__tests__/prelude.test.ts +19 -0
- package/src/eval/py/executor.ts +23 -11
- package/src/eval/py/prelude.py +1 -1
- package/src/extensibility/extensions/types.ts +10 -1
- package/src/goals/tools/goal-tool.ts +36 -26
- package/src/internal-urls/docs-index.generated.ts +8 -8
- package/src/lsp/client.ts +23 -11
- package/src/lsp/config.ts +11 -1
- package/src/lsp/index.ts +61 -9
- package/src/lsp/utils.ts +3 -2
- package/src/main.ts +100 -72
- package/src/mcp/tool-bridge.ts +2 -0
- package/src/memories/index.ts +14 -7
- package/src/mnemopi/backend.ts +5 -1
- package/src/modes/acp/acp-agent.ts +33 -26
- package/src/modes/components/assistant-message.ts +2 -9
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/copy-selector.ts +1 -44
- package/src/modes/components/custom-editor.ts +164 -109
- package/src/modes/components/custom-message.ts +1 -3
- package/src/modes/components/execution-shared.ts +1 -2
- package/src/modes/components/hook-message.ts +1 -3
- package/src/modes/components/model-selector.ts +59 -13
- package/src/modes/components/oauth-selector.ts +33 -7
- package/src/modes/components/overlay-box.ts +108 -0
- package/src/modes/components/plan-review-overlay.ts +799 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/read-tool-group.ts +20 -4
- package/src/modes/components/skill-message.ts +0 -1
- package/src/modes/components/status-line.ts +19 -4
- package/src/modes/components/tips.txt +2 -1
- package/src/modes/components/todo-reminder.ts +0 -2
- package/src/modes/components/tool-execution.ts +68 -88
- package/src/modes/components/transcript-container.ts +84 -24
- package/src/modes/components/user-message.ts +2 -3
- package/src/modes/controllers/command-controller-shared.ts +7 -6
- package/src/modes/controllers/command-controller.ts +57 -55
- package/src/modes/controllers/event-controller.ts +67 -40
- package/src/modes/controllers/extension-ui-controller.ts +10 -73
- package/src/modes/controllers/input-controller.ts +170 -126
- package/src/modes/controllers/mcp-command-controller.ts +69 -60
- package/src/modes/controllers/selector-controller.ts +23 -25
- package/src/modes/controllers/streaming-reveal.ts +212 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/interactive-mode.ts +274 -112
- package/src/modes/magic-keywords.ts +1 -1
- package/src/modes/markdown-prose.ts +1 -1
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/shimmer.ts +20 -9
- package/src/modes/theme/theme-schema.json +1 -1
- package/src/modes/theme/theme.ts +8 -4
- package/src/modes/types.ts +21 -7
- package/src/modes/utils/copy-targets.ts +133 -27
- package/src/modes/utils/ui-helpers.ts +44 -46
- package/src/modes/workflow.ts +10 -10
- package/src/plan-mode/approved-plan.ts +66 -43
- package/src/plan-mode/plan-protection.ts +4 -4
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/plan-mode-active.md +67 -58
- package/src/prompts/system/plan-mode-approved.md +1 -1
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/bash.md +9 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/eval.md +2 -1
- package/src/prompts/tools/read.md +2 -2
- package/src/sdk.ts +37 -46
- package/src/session/agent-session.ts +119 -18
- package/src/session/auth-storage.ts +2 -0
- package/src/session/messages.ts +26 -0
- package/src/session/session-manager.ts +109 -28
- package/src/slash-commands/builtin-registry.ts +36 -9
- package/src/slash-commands/types.ts +4 -6
- package/src/task/executor.ts +76 -38
- package/src/task/index.ts +4 -0
- package/src/task/render.ts +211 -147
- package/src/tools/archive-reader.ts +64 -0
- package/src/tools/ask.ts +119 -164
- package/src/tools/ast-edit.ts +98 -71
- package/src/tools/ast-grep.ts +37 -43
- package/src/tools/bash.ts +57 -6
- package/src/tools/browser/tab-supervisor.ts +13 -1
- package/src/tools/browser/tab-worker.ts +33 -4
- package/src/tools/debug.ts +20 -8
- package/src/tools/eval.ts +13 -2
- package/src/tools/fetch.ts +297 -7
- package/src/tools/find.ts +51 -30
- package/src/tools/gh-cache-invalidation.ts +200 -0
- package/src/tools/gh-renderer.ts +81 -42
- package/src/tools/github-cache.ts +25 -0
- package/src/tools/grouped-file-output.ts +272 -48
- package/src/tools/image-gen.ts +150 -103
- package/src/tools/inspect-image-renderer.ts +63 -41
- package/src/tools/inspect-image.ts +10 -3
- package/src/tools/job.ts +3 -4
- package/src/tools/memory-render.ts +4 -1
- package/src/tools/path-utils.ts +28 -2
- package/src/tools/plan-mode-guard.ts +66 -39
- package/src/tools/read.ts +48 -28
- package/src/tools/render-utils.ts +21 -37
- package/src/tools/resolve.ts +14 -0
- package/src/tools/search-tool-bm25.ts +36 -23
- package/src/tools/search.ts +118 -81
- package/src/tools/sqlite-reader.ts +9 -12
- package/src/tools/todo.ts +118 -52
- package/src/tools/write.ts +83 -64
- package/src/tools/yield.ts +10 -1
- package/src/tui/output-block.ts +60 -13
- package/src/tui/status-line.ts +5 -1
- package/src/utils/commit-message-generator.ts +11 -3
- package/src/utils/enhanced-paste.ts +230 -0
- package/src/utils/title-generator.ts +2 -1
- package/src/web/search/providers/anthropic.ts +25 -19
- package/src/web/search/providers/codex.ts +37 -8
- package/src/web/search/providers/exa.ts +11 -3
- package/src/web/search/providers/kimi.ts +28 -17
- package/src/web/search/providers/parallel.ts +35 -24
- package/src/web/search/providers/synthetic.ts +8 -6
- package/src/web/search/providers/tavily.ts +9 -8
- package/src/web/search/providers/zai.ts +8 -6
package/src/task/render.ts
CHANGED
|
@@ -6,18 +6,20 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
9
|
-
import { Container, Text } from "@oh-my-pi/pi-tui";
|
|
9
|
+
import { Container, Markdown, Text } from "@oh-my-pi/pi-tui";
|
|
10
10
|
import { formatNumber } from "@oh-my-pi/pi-utils";
|
|
11
11
|
import { settings } from "../config/settings";
|
|
12
12
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
13
13
|
import { formatContextUsage } from "../modes/components/status-line/context-thresholds";
|
|
14
|
-
import
|
|
14
|
+
import { shimmerEnabled, shimmerText } from "../modes/theme/shimmer";
|
|
15
|
+
import { getMarkdownTheme, type Theme } from "../modes/theme/theme";
|
|
15
16
|
import {
|
|
16
17
|
formatBadge,
|
|
17
18
|
formatDuration,
|
|
18
19
|
formatMoreItems,
|
|
19
20
|
formatStatusIcon,
|
|
20
21
|
replaceTabs,
|
|
22
|
+
type ToolUIStatus,
|
|
21
23
|
truncateToWidth,
|
|
22
24
|
} from "../tools/render-utils";
|
|
23
25
|
import {
|
|
@@ -28,7 +30,8 @@ import {
|
|
|
28
30
|
type ReportFindingDetails,
|
|
29
31
|
type SubmitReviewDetails,
|
|
30
32
|
} from "../tools/review";
|
|
31
|
-
import {
|
|
33
|
+
import { framedBlock, renderStatusLine } from "../tui";
|
|
34
|
+
import { repairDoubleEncodedJsonString } from "./repair-args";
|
|
32
35
|
import { subprocessToolRegistry } from "./subprocess-tool-registry";
|
|
33
36
|
import type { AgentProgress, SingleResult, TaskItem, TaskParams, TaskToolDetails } from "./types";
|
|
34
37
|
|
|
@@ -506,28 +509,20 @@ function formatOutputInline(data: unknown, theme: Theme, maxWidth = 80): string
|
|
|
506
509
|
* preview. The args stream in token by token, so the array grows over time and
|
|
507
510
|
* trailing entries may be partially parsed — every field access is defensive.
|
|
508
511
|
*/
|
|
509
|
-
function renderTaskItemLines(
|
|
510
|
-
tasks: TaskItem[] | undefined,
|
|
511
|
-
contPrefix: string,
|
|
512
|
-
expanded: boolean,
|
|
513
|
-
theme: Theme,
|
|
514
|
-
): string[] {
|
|
512
|
+
function renderTaskItemLines(tasks: TaskItem[] | undefined, expanded: boolean, theme: Theme): string[] {
|
|
515
513
|
const items = tasks ?? [];
|
|
516
514
|
if (items.length === 0) return [];
|
|
517
515
|
|
|
518
|
-
const
|
|
519
|
-
const last = theme.fg("dim", theme.tree.last);
|
|
516
|
+
const bullet = theme.fg("dim", "•");
|
|
520
517
|
const cap = expanded ? items.length : Math.min(items.length, 12);
|
|
521
518
|
const truncated = cap < items.length;
|
|
522
519
|
|
|
523
520
|
const lines: string[] = [];
|
|
524
521
|
for (let i = 0; i < cap; i++) {
|
|
525
522
|
const task = items[i] as Partial<TaskItem> | undefined;
|
|
526
|
-
const isLastLine = !truncated && i === items.length - 1;
|
|
527
|
-
const connector = isLastLine ? last : branch;
|
|
528
523
|
const rawId = task?.id?.trim();
|
|
529
524
|
const idLabel = rawId ? formatTaskId(rawId) : `#${i + 1}`;
|
|
530
|
-
let line = `${
|
|
525
|
+
let line = `${bullet} ${theme.fg("accent", theme.bold(idLabel))}`;
|
|
531
526
|
const desc = task?.description?.trim();
|
|
532
527
|
if (desc) {
|
|
533
528
|
line += `: ${theme.fg("muted", truncateToWidth(replaceTabs(desc), 64))}`;
|
|
@@ -535,11 +530,42 @@ function renderTaskItemLines(
|
|
|
535
530
|
lines.push(line);
|
|
536
531
|
}
|
|
537
532
|
if (truncated) {
|
|
538
|
-
lines.push(`${
|
|
533
|
+
lines.push(`${bullet} ${theme.fg("dim", formatMoreItems(items.length - cap, "agent"))}`);
|
|
539
534
|
}
|
|
540
535
|
return lines;
|
|
541
536
|
}
|
|
542
537
|
|
|
538
|
+
/**
|
|
539
|
+
* Build the shared-context section (the `# Goal / # Constraints` background
|
|
540
|
+
* passed to every subagent). Rendered in both the streaming call preview and
|
|
541
|
+
* the merged result frame so the brief stays visible for the whole task
|
|
542
|
+
* lifecycle — not just until the first progress snapshot replaces the call view.
|
|
543
|
+
*/
|
|
544
|
+
type TaskRenderSection = { lines: string[] };
|
|
545
|
+
type ContextSectionRenderer = (width: number) => TaskRenderSection;
|
|
546
|
+
|
|
547
|
+
// Default output-block layout is: left border + one-cell content inset + right
|
|
548
|
+
// border. Render markdown at that inner width so the output block does not need
|
|
549
|
+
// to rewrap already-rendered context lines.
|
|
550
|
+
const CONTEXT_FRAME_INSET = 3;
|
|
551
|
+
|
|
552
|
+
function contextMarkdownWidth(frameWidth: number): number {
|
|
553
|
+
return Math.max(1, frameWidth - CONTEXT_FRAME_INSET);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function createContextSectionRenderer(args: TaskParams | undefined, theme: Theme): ContextSectionRenderer | undefined {
|
|
557
|
+
// `renderResult` receives the raw tool args (unlike `renderCall`, which is
|
|
558
|
+
// fed through `repairTaskParams`), so undo any per-field double-encoding here
|
|
559
|
+
// too. The repair is idempotent on already-clean text.
|
|
560
|
+
const context = repairDoubleEncodedJsonString(args?.context ?? "").trim();
|
|
561
|
+
if (!context) return undefined;
|
|
562
|
+
|
|
563
|
+
const markdown = new Markdown(context, 0, 0, getMarkdownTheme(), {
|
|
564
|
+
color: text => theme.fg("muted", text),
|
|
565
|
+
});
|
|
566
|
+
return width => ({ lines: markdown.render(contextMarkdownWidth(width)) });
|
|
567
|
+
}
|
|
568
|
+
|
|
543
569
|
/**
|
|
544
570
|
* Render the tool call arguments.
|
|
545
571
|
*/
|
|
@@ -548,44 +574,34 @@ export function renderCall(
|
|
|
548
574
|
options: RenderResultOptions & { renderContext?: { hasResult?: boolean } },
|
|
549
575
|
theme: Theme,
|
|
550
576
|
): Component {
|
|
551
|
-
const lines: string[] = [];
|
|
552
|
-
lines.push(renderStatusLine({ icon: "pending", title: "Task", description: args.agent }, theme));
|
|
553
|
-
|
|
554
|
-
const context = (args.context ?? "").trim();
|
|
555
|
-
const hasContext = context.length > 0;
|
|
556
|
-
const branch = theme.fg("dim", theme.tree.branch);
|
|
557
|
-
const last = theme.fg("dim", theme.tree.last);
|
|
558
|
-
const vertical = theme.fg("dim", theme.tree.vertical);
|
|
559
577
|
const showIsolated = "isolated" in args && args.isolated === true;
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
lines
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
// the call args stream in. Once a result snapshot exists, `renderResult`
|
|
578
|
-
// draws the same agents as progress/result lines (id + description), so
|
|
579
|
-
// emitting the preview here would render every task twice.
|
|
580
|
-
if (!options.renderContext?.hasResult) {
|
|
581
|
-
lines.push(...renderTaskItemLines(args.tasks, tasksContPrefix, options.expanded, theme));
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
if (showIsolated) {
|
|
585
|
-
lines.push(` ${last} ${theme.fg("dim", "Isolated")}: ${theme.fg("muted", "true")}`);
|
|
586
|
-
}
|
|
578
|
+
const header = renderStatusLine({ icon: "pending", title: "Task", description: args.agent }, theme);
|
|
579
|
+
const contextSectionRenderer = createContextSectionRenderer(args, theme);
|
|
580
|
+
return framedBlock(theme, width => {
|
|
581
|
+
const sections: Array<{ label?: string; lines: string[]; separator?: boolean }> = [];
|
|
582
|
+
|
|
583
|
+
if (contextSectionRenderer) sections.push(contextSectionRenderer(width));
|
|
584
|
+
|
|
585
|
+
// The per-task preview list only exists to surface dispatched agents while
|
|
586
|
+
// the call args stream in. Once a result snapshot exists, `renderResult`
|
|
587
|
+
// draws the same agents as progress/result lines, so showing the Tasks
|
|
588
|
+
// section here would just repeat the count the result frame already shows.
|
|
589
|
+
if (!options.renderContext?.hasResult) {
|
|
590
|
+
sections.push({
|
|
591
|
+
separator: true,
|
|
592
|
+
lines: renderTaskItemLines(args.tasks, options.expanded, theme),
|
|
593
|
+
});
|
|
594
|
+
}
|
|
587
595
|
|
|
588
|
-
|
|
596
|
+
return {
|
|
597
|
+
header,
|
|
598
|
+
headerMeta: showIsolated ? "isolated" : undefined,
|
|
599
|
+
sections,
|
|
600
|
+
state: "pending",
|
|
601
|
+
borderColor: "borderMuted",
|
|
602
|
+
width,
|
|
603
|
+
};
|
|
604
|
+
});
|
|
589
605
|
}
|
|
590
606
|
|
|
591
607
|
/**
|
|
@@ -593,14 +609,13 @@ export function renderCall(
|
|
|
593
609
|
*/
|
|
594
610
|
function renderAgentProgress(
|
|
595
611
|
progress: AgentProgress,
|
|
596
|
-
|
|
612
|
+
prefix: string,
|
|
613
|
+
continuePrefix: string,
|
|
597
614
|
expanded: boolean,
|
|
598
615
|
theme: Theme,
|
|
599
616
|
spinnerFrame?: number,
|
|
600
617
|
): string[] {
|
|
601
618
|
const lines: string[] = [];
|
|
602
|
-
const prefix = isLast ? theme.fg("dim", theme.tree.last) : theme.fg("dim", theme.tree.branch);
|
|
603
|
-
const continuePrefix = isLast ? " " : `${theme.fg("dim", theme.tree.vertical)} `;
|
|
604
619
|
|
|
605
620
|
const icon = getStatusIcon(progress.status, theme, spinnerFrame);
|
|
606
621
|
const iconColor =
|
|
@@ -614,11 +629,23 @@ function renderAgentProgress(
|
|
|
614
629
|
const description = progress.description?.trim();
|
|
615
630
|
const displayId = formatTaskId(progress.id);
|
|
616
631
|
const titlePart = description ? `${theme.bold(displayId)}: ${description}` : displayId;
|
|
617
|
-
|
|
632
|
+
const indent = prefix ? `${prefix} ` : "";
|
|
633
|
+
let statusLine: string;
|
|
634
|
+
if (progress.status === "running") {
|
|
635
|
+
const bullet = theme.fg("accent", "•");
|
|
636
|
+
const name = theme.fg("accent", description ? theme.bold(displayId) : displayId);
|
|
637
|
+
statusLine = `${indent}${bullet} ${name}`;
|
|
638
|
+
if (description) {
|
|
639
|
+
const desc = shimmerEnabled() ? shimmerText(description, theme) : theme.fg("accent", description);
|
|
640
|
+
statusLine += `${theme.fg("accent", ":")} ${desc}`;
|
|
641
|
+
}
|
|
642
|
+
} else {
|
|
643
|
+
statusLine = `${indent}${theme.fg(iconColor, icon)} ${theme.fg("accent", titlePart)}`;
|
|
644
|
+
}
|
|
618
645
|
|
|
619
646
|
// Show retry-blocked badge so the parent immediately sees that a child
|
|
620
647
|
// is sleeping on a provider 429, not silently progressing. Wins over the
|
|
621
|
-
// generic running
|
|
648
|
+
// generic running marker because "we're waiting on a quota window" is
|
|
622
649
|
// the operationally meaningful state.
|
|
623
650
|
if (progress.retryState && progress.status === "running") {
|
|
624
651
|
statusLine += ` ${formatBadge("retrying", "warning", theme)}`;
|
|
@@ -867,10 +894,14 @@ function renderFindings(
|
|
|
867
894
|
/**
|
|
868
895
|
* Render final result for a single agent.
|
|
869
896
|
*/
|
|
870
|
-
function renderAgentResult(
|
|
897
|
+
function renderAgentResult(
|
|
898
|
+
result: SingleResult,
|
|
899
|
+
prefix: string,
|
|
900
|
+
continuePrefix: string,
|
|
901
|
+
expanded: boolean,
|
|
902
|
+
theme: Theme,
|
|
903
|
+
): string[] {
|
|
871
904
|
const lines: string[] = [];
|
|
872
|
-
const prefix = isLast ? theme.fg("dim", theme.tree.last) : theme.fg("dim", theme.tree.branch);
|
|
873
|
-
const continuePrefix = isLast ? " " : `${theme.fg("dim", theme.tree.vertical)} `;
|
|
874
905
|
|
|
875
906
|
const { warning: missingCompleteWarning, rest: outputWithoutWarning } = extractMissingYieldWarning(result.output);
|
|
876
907
|
const aborted = result.aborted ?? false;
|
|
@@ -899,7 +930,7 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
|
|
899
930
|
const description = result.description?.trim();
|
|
900
931
|
const displayId = formatTaskId(result.id);
|
|
901
932
|
const titlePart = description ? `${theme.bold(displayId)}: ${description}` : displayId;
|
|
902
|
-
let statusLine = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", titlePart)} ${formatBadge(
|
|
933
|
+
let statusLine = `${prefix ? `${prefix} ` : ""}${theme.fg(iconColor, icon)} ${theme.fg("accent", titlePart)} ${formatBadge(
|
|
903
934
|
statusText,
|
|
904
935
|
iconColor,
|
|
905
936
|
theme,
|
|
@@ -1043,101 +1074,123 @@ export function renderResult(
|
|
|
1043
1074
|
result: { content: Array<{ type: string; text?: string }>; details?: TaskToolDetails },
|
|
1044
1075
|
options: RenderResultOptions,
|
|
1045
1076
|
theme: Theme,
|
|
1077
|
+
args?: TaskParams,
|
|
1046
1078
|
): Component {
|
|
1047
1079
|
const fallbackText = result.content.find(c => c.type === "text")?.text ?? "";
|
|
1048
1080
|
const details = result.details;
|
|
1081
|
+
const contextSectionRenderer = createContextSectionRenderer(args, theme);
|
|
1049
1082
|
|
|
1050
1083
|
if (!details) {
|
|
1051
1084
|
const text = result.content.find(c => c.type === "text")?.text || "";
|
|
1052
|
-
|
|
1085
|
+
const header = renderStatusLine({ icon: "success", title: "Task" }, theme);
|
|
1086
|
+
return framedBlock(theme, width => ({
|
|
1087
|
+
header,
|
|
1088
|
+
sections: [
|
|
1089
|
+
...(contextSectionRenderer ? [contextSectionRenderer(width)] : []),
|
|
1090
|
+
...(text ? [{ separator: true, lines: [theme.fg("dim", truncateToWidth(text, width))] }] : []),
|
|
1091
|
+
],
|
|
1092
|
+
state: "success",
|
|
1093
|
+
borderColor: "borderMuted",
|
|
1094
|
+
width,
|
|
1095
|
+
}));
|
|
1053
1096
|
}
|
|
1054
1097
|
|
|
1055
|
-
|
|
1098
|
+
const hasResults = Boolean(details.results && details.results.length > 0);
|
|
1099
|
+
const aborted = hasResults && details.results.some(r => r.aborted);
|
|
1100
|
+
const failed = hasResults && details.results.some(r => !r.aborted && r.exitCode !== 0);
|
|
1101
|
+
const mergeFailed = hasResults && details.results.some(r => !r.aborted && r.exitCode === 0 && Boolean(r.error));
|
|
1102
|
+
const isError = aborted || failed;
|
|
1103
|
+
const agentCount = hasResults ? details.results.length : (details.progress?.length ?? 0);
|
|
1104
|
+
const icon: ToolUIStatus = options.isPartial ? "running" : isError ? "error" : mergeFailed ? "warning" : "success";
|
|
1105
|
+
const header = renderStatusLine(
|
|
1106
|
+
{
|
|
1107
|
+
icon,
|
|
1108
|
+
title: "Task",
|
|
1109
|
+
meta: agentCount > 0 ? [`${agentCount} ${agentCount === 1 ? "agent" : "agents"}`] : undefined,
|
|
1110
|
+
},
|
|
1111
|
+
theme,
|
|
1112
|
+
);
|
|
1056
1113
|
|
|
1057
|
-
return {
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
const key = new Hasher()
|
|
1061
|
-
.bool(expanded)
|
|
1062
|
-
.bool(isPartial)
|
|
1063
|
-
.u32(spinnerFrame ?? 0)
|
|
1064
|
-
.u32(width)
|
|
1065
|
-
.digest();
|
|
1066
|
-
if (cached?.key === key) return cached.lines;
|
|
1067
|
-
|
|
1068
|
-
const lines: string[] = [];
|
|
1069
|
-
|
|
1070
|
-
const shouldRenderProgress =
|
|
1071
|
-
Boolean(details.progress && details.progress.length > 0) && (isPartial || details.results.length === 0);
|
|
1072
|
-
if (shouldRenderProgress && details.progress) {
|
|
1073
|
-
details.progress.forEach((progress, i) => {
|
|
1074
|
-
const isLast = i === details.progress!.length - 1;
|
|
1075
|
-
lines.push(...renderAgentProgress(progress, isLast, expanded, theme, spinnerFrame));
|
|
1076
|
-
});
|
|
1077
|
-
} else if (details.results && details.results.length > 0) {
|
|
1078
|
-
details.results.forEach((res, i) => {
|
|
1079
|
-
const isLast = i === details.results.length - 1;
|
|
1080
|
-
lines.push(...renderAgentResult(res, isLast, expanded, theme));
|
|
1081
|
-
});
|
|
1082
|
-
|
|
1083
|
-
const abortedCount = details.results.filter(r => r.aborted).length;
|
|
1084
|
-
const mergeFailedCount = details.results.filter(r => !r.aborted && r.exitCode === 0 && r.error).length;
|
|
1085
|
-
const successCount = details.results.filter(r => !r.aborted && r.exitCode === 0 && !r.error).length;
|
|
1086
|
-
const failCount = details.results.length - successCount - mergeFailedCount - abortedCount;
|
|
1087
|
-
let summary = `${theme.fg("dim", "Total:")} `;
|
|
1088
|
-
if (abortedCount > 0) {
|
|
1089
|
-
summary += theme.fg("error", `${abortedCount} aborted`);
|
|
1090
|
-
if (successCount > 0 || mergeFailedCount > 0 || failCount > 0) summary += theme.sep.dot;
|
|
1091
|
-
}
|
|
1092
|
-
if (successCount > 0) {
|
|
1093
|
-
summary += theme.fg("success", `${successCount} succeeded`);
|
|
1094
|
-
if (mergeFailedCount > 0 || failCount > 0) summary += theme.sep.dot;
|
|
1095
|
-
}
|
|
1096
|
-
if (mergeFailedCount > 0) {
|
|
1097
|
-
summary += theme.fg("warning", `${mergeFailedCount} merge failed`);
|
|
1098
|
-
if (failCount > 0) summary += theme.sep.dot;
|
|
1099
|
-
}
|
|
1100
|
-
if (failCount > 0) {
|
|
1101
|
-
summary += theme.fg("error", `${failCount} failed`);
|
|
1102
|
-
}
|
|
1103
|
-
summary += `${theme.sep.dot}${theme.fg("dim", formatDuration(details.totalDurationMs))}`;
|
|
1104
|
-
lines.push(summary);
|
|
1105
|
-
}
|
|
1114
|
+
return framedBlock(theme, width => {
|
|
1115
|
+
const { expanded, isPartial, spinnerFrame } = options;
|
|
1116
|
+
const lines: string[] = [];
|
|
1106
1117
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
}
|
|
1118
|
+
const shouldRenderProgress =
|
|
1119
|
+
Boolean(details.progress && details.progress.length > 0) && (isPartial || details.results.length === 0);
|
|
1120
|
+
if (shouldRenderProgress && details.progress) {
|
|
1121
|
+
details.progress.forEach(progress => {
|
|
1122
|
+
lines.push(...renderAgentProgress(progress, "", " ", expanded, theme, spinnerFrame));
|
|
1123
|
+
});
|
|
1124
|
+
} else if (details.results && details.results.length > 0) {
|
|
1125
|
+
details.results.forEach(res => {
|
|
1126
|
+
lines.push(...renderAgentResult(res, "", " ", expanded, theme));
|
|
1127
|
+
});
|
|
1113
1128
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1129
|
+
const abortedCount = details.results.filter(r => r.aborted).length;
|
|
1130
|
+
const mergeFailedCount = details.results.filter(r => !r.aborted && r.exitCode === 0 && r.error).length;
|
|
1131
|
+
const successCount = details.results.filter(r => !r.aborted && r.exitCode === 0 && !r.error).length;
|
|
1132
|
+
const failCount = details.results.length - successCount - mergeFailedCount - abortedCount;
|
|
1133
|
+
const summaryParts: string[] = [];
|
|
1134
|
+
if (abortedCount > 0) summaryParts.push(theme.fg("error", `${abortedCount} aborted`));
|
|
1135
|
+
if (successCount > 0) summaryParts.push(theme.fg("success", `${successCount} succeeded`));
|
|
1136
|
+
if (mergeFailedCount > 0) summaryParts.push(theme.fg("warning", `${mergeFailedCount} merge failed`));
|
|
1137
|
+
if (failCount > 0) summaryParts.push(theme.fg("error", `${failCount} failed`));
|
|
1138
|
+
summaryParts.push(theme.fg("dim", formatDuration(details.totalDurationMs)));
|
|
1139
|
+
// Wrap the run summary in the theme's bracket glyphs (dim chrome, colored
|
|
1140
|
+
// counts) to match the bash tool's `[Wall: … | Exit: …]` footer.
|
|
1141
|
+
lines.push(
|
|
1142
|
+
theme.fg("dim", theme.format.bracketLeft) +
|
|
1143
|
+
summaryParts.join(theme.fg("dim", theme.sep.dot)) +
|
|
1144
|
+
theme.fg("dim", theme.format.bracketRight),
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
const state = isPartial ? "running" : isError ? "error" : mergeFailed ? "warning" : "success";
|
|
1149
|
+
const borderColor = isError ? "error" : "borderMuted";
|
|
1150
|
+
|
|
1151
|
+
if (lines.length === 0) {
|
|
1152
|
+
const text = fallbackText.trim() ? fallbackText : "No results";
|
|
1153
|
+
return {
|
|
1154
|
+
header,
|
|
1155
|
+
sections: [
|
|
1156
|
+
...(contextSectionRenderer ? [contextSectionRenderer(width)] : []),
|
|
1157
|
+
{ separator: true, lines: [theme.fg("dim", truncateToWidth(text, width))] },
|
|
1158
|
+
],
|
|
1159
|
+
state,
|
|
1160
|
+
borderColor,
|
|
1161
|
+
width,
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
if (fallbackText.trim()) {
|
|
1166
|
+
const summaryLines = fallbackText.split("\n");
|
|
1167
|
+
const markerIndex = summaryLines.findIndex(
|
|
1168
|
+
line =>
|
|
1169
|
+
line.includes("<system-notification>") ||
|
|
1170
|
+
line.startsWith("Applied patches:") ||
|
|
1171
|
+
line.startsWith("No changes to apply."),
|
|
1172
|
+
);
|
|
1173
|
+
if (markerIndex >= 0) {
|
|
1174
|
+
const extra = summaryLines.slice(markerIndex);
|
|
1175
|
+
for (const line of extra) {
|
|
1176
|
+
if (!line.trim()) continue;
|
|
1177
|
+
lines.push(theme.fg("dim", line));
|
|
1128
1178
|
}
|
|
1129
1179
|
}
|
|
1180
|
+
}
|
|
1130
1181
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1182
|
+
while (lines.length > 0 && lines[0].trim() === "") lines.shift();
|
|
1183
|
+
return {
|
|
1184
|
+
header,
|
|
1185
|
+
sections: [
|
|
1186
|
+
...(contextSectionRenderer ? [contextSectionRenderer(width)] : []),
|
|
1187
|
+
...(lines.length > 0 ? [{ separator: true, lines }] : []),
|
|
1188
|
+
],
|
|
1189
|
+
state,
|
|
1190
|
+
borderColor,
|
|
1191
|
+
width,
|
|
1192
|
+
};
|
|
1193
|
+
});
|
|
1141
1194
|
}
|
|
1142
1195
|
|
|
1143
1196
|
function isTaskToolDetails(value: unknown): value is TaskToolDetails {
|
|
@@ -1149,13 +1202,23 @@ function isTaskToolDetails(value: unknown): value is TaskToolDetails {
|
|
|
1149
1202
|
);
|
|
1150
1203
|
}
|
|
1151
1204
|
|
|
1205
|
+
// Nested subagent snapshots sit one or more levels below the frame border, so
|
|
1206
|
+
// they keep tree guides to convey depth (the parent prepends its own continue
|
|
1207
|
+
// prefix). Only the top-level agent list drops guides (the frame is its box).
|
|
1208
|
+
function nestedMarkers(isLast: boolean, theme: Theme): { prefix: string; continuePrefix: string } {
|
|
1209
|
+
return {
|
|
1210
|
+
prefix: isLast ? theme.fg("dim", theme.tree.last) : theme.fg("dim", theme.tree.branch),
|
|
1211
|
+
continuePrefix: isLast ? " " : `${theme.fg("dim", theme.tree.vertical)} `,
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1152
1215
|
function renderNestedTaskResults(detailsList: TaskToolDetails[], expanded: boolean, theme: Theme): string[] {
|
|
1153
1216
|
const lines: string[] = [];
|
|
1154
1217
|
for (const details of detailsList) {
|
|
1155
1218
|
if (!details.results || details.results.length === 0) continue;
|
|
1156
1219
|
details.results.forEach((result, index) => {
|
|
1157
|
-
const
|
|
1158
|
-
lines.push(...renderAgentResult(result,
|
|
1220
|
+
const { prefix, continuePrefix } = nestedMarkers(index === details.results.length - 1, theme);
|
|
1221
|
+
lines.push(...renderAgentResult(result, prefix, continuePrefix, expanded, theme));
|
|
1159
1222
|
});
|
|
1160
1223
|
}
|
|
1161
1224
|
return lines;
|
|
@@ -1177,16 +1240,16 @@ function renderNestedTaskTree(
|
|
|
1177
1240
|
const hasResults = Boolean(details.results && details.results.length > 0);
|
|
1178
1241
|
if (hasResults) {
|
|
1179
1242
|
details.results.forEach((result, index) => {
|
|
1180
|
-
const
|
|
1181
|
-
lines.push(...renderAgentResult(result,
|
|
1243
|
+
const { prefix, continuePrefix } = nestedMarkers(index === details.results.length - 1, theme);
|
|
1244
|
+
lines.push(...renderAgentResult(result, prefix, continuePrefix, expanded, theme));
|
|
1182
1245
|
});
|
|
1183
1246
|
continue;
|
|
1184
1247
|
}
|
|
1185
1248
|
const inflight = details.progress;
|
|
1186
1249
|
if (inflight && inflight.length > 0) {
|
|
1187
1250
|
inflight.forEach((prog, index) => {
|
|
1188
|
-
const
|
|
1189
|
-
lines.push(...renderAgentProgress(prog,
|
|
1251
|
+
const { prefix, continuePrefix } = nestedMarkers(index === inflight.length - 1, theme);
|
|
1252
|
+
lines.push(...renderAgentProgress(prog, prefix, continuePrefix, expanded, theme, spinnerFrame));
|
|
1190
1253
|
});
|
|
1191
1254
|
}
|
|
1192
1255
|
}
|
|
@@ -1207,4 +1270,5 @@ subprocessToolRegistry.register<TaskToolDetails>("task", {
|
|
|
1207
1270
|
export const taskToolRenderer = {
|
|
1208
1271
|
renderCall,
|
|
1209
1272
|
renderResult,
|
|
1273
|
+
mergeCallAndResult: true,
|
|
1210
1274
|
};
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
1
4
|
import { inflateSync, strFromU8 } from "fflate";
|
|
2
5
|
|
|
6
|
+
import { formatBytes } from "./render-utils";
|
|
3
7
|
import { ToolError } from "./tool-errors";
|
|
4
8
|
|
|
5
9
|
export type ArchiveFormat = "zip" | "tar" | "tar.gz";
|
|
@@ -123,11 +127,21 @@ function getArchiveFormatFromPath(filePath: string): ArchiveFormat | undefined {
|
|
|
123
127
|
return undefined;
|
|
124
128
|
}
|
|
125
129
|
|
|
130
|
+
export function formatArchiveEntryLines(entries: readonly ArchiveDirectoryEntry[]): string[] {
|
|
131
|
+
return entries.map(entry => {
|
|
132
|
+
if (entry.isDirectory) return `${entry.name}/`;
|
|
133
|
+
|
|
134
|
+
const sizeSuffix = entry.size > 0 ? ` (${formatBytes(entry.size)})` : "";
|
|
135
|
+
return `${entry.name}${sizeSuffix}`;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
126
139
|
const ZIP_LOCAL_FILE_HEADER_SIGNATURE = 0x04034b50;
|
|
127
140
|
const ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE = 0x02014b50;
|
|
128
141
|
const ZIP64_EOCD_SIGNATURE = 0x06064b50;
|
|
129
142
|
const ZIP64_EOCD_LOCATOR_SIGNATURE = 0x07064b50;
|
|
130
143
|
const ZIP_EOCD_SIGNATURE = 0x06054b50;
|
|
144
|
+
const ZIP_DATA_DESCRIPTOR_SIGNATURE = 0x08074b50;
|
|
131
145
|
const ZIP_EOCD_MIN_LENGTH = 22;
|
|
132
146
|
const ZIP_EOCD_MAX_COMMENT_LENGTH = 0xffff;
|
|
133
147
|
const ZIP64_EOCD_LOCATOR_LENGTH = 20;
|
|
@@ -167,6 +181,37 @@ function readUInt32LE(bytes: Uint8Array, offset: number): number {
|
|
|
167
181
|
return (bytes[offset]! | (bytes[offset + 1]! << 8) | (bytes[offset + 2]! << 16) | (bytes[offset + 3]! << 24)) >>> 0;
|
|
168
182
|
}
|
|
169
183
|
|
|
184
|
+
function bytesMatchAscii(bytes: Uint8Array, offset: number, value: string): boolean {
|
|
185
|
+
if (bytes.byteLength < offset + value.length) return false;
|
|
186
|
+
for (let index = 0; index < value.length; index++) {
|
|
187
|
+
if (bytes[offset + index] !== value.charCodeAt(index)) return false;
|
|
188
|
+
}
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function sniffArchiveFormat(bytes: Uint8Array): ArchiveFormat | undefined {
|
|
193
|
+
if (bytes.byteLength >= 4) {
|
|
194
|
+
const signature = readUInt32LE(bytes, 0);
|
|
195
|
+
if (
|
|
196
|
+
signature === ZIP_LOCAL_FILE_HEADER_SIGNATURE ||
|
|
197
|
+
signature === ZIP_EOCD_SIGNATURE ||
|
|
198
|
+
signature === ZIP_DATA_DESCRIPTOR_SIGNATURE
|
|
199
|
+
) {
|
|
200
|
+
return "zip";
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (bytes.byteLength >= 2 && bytes[0] === 0x1f && bytes[1] === 0x8b) {
|
|
205
|
+
return "tar.gz";
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (bytesMatchAscii(bytes, 257, "ustar")) {
|
|
209
|
+
return "tar";
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
|
|
170
215
|
function readUInt64LEAsNumber(bytes: Uint8Array, offset: number): number {
|
|
171
216
|
const value = readUInt32LE(bytes, offset) + readUInt32LE(bytes, offset + 4) * ZIP_UINT32_RANGE;
|
|
172
217
|
if (!Number.isSafeInteger(value)) {
|
|
@@ -627,3 +672,22 @@ export async function openArchive(filePath: string): Promise<ArchiveReader> {
|
|
|
627
672
|
format === "zip" ? await readZipEntries(filePath) : await readTarEntries(await Bun.file(filePath).bytes());
|
|
628
673
|
return new ArchiveReader(format, entries);
|
|
629
674
|
}
|
|
675
|
+
|
|
676
|
+
export async function listArchiveRoot(
|
|
677
|
+
bytes: Uint8Array,
|
|
678
|
+
format: ArchiveFormat,
|
|
679
|
+
opts: { limit?: number } = {},
|
|
680
|
+
): Promise<string> {
|
|
681
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "omp-archive-"));
|
|
682
|
+
const tempPath = path.join(tempDir, `payload.${format}`);
|
|
683
|
+
try {
|
|
684
|
+
await Bun.write(tempPath, bytes);
|
|
685
|
+
const archive = await openArchive(tempPath);
|
|
686
|
+
const entries = archive.listDirectory("");
|
|
687
|
+
const limitedEntries = opts.limit !== undefined && opts.limit > 0 ? entries.slice(0, opts.limit) : entries;
|
|
688
|
+
const lines = formatArchiveEntryLines(limitedEntries);
|
|
689
|
+
return lines.length > 0 ? lines.join("\n") : "(empty archive directory)";
|
|
690
|
+
} finally {
|
|
691
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
692
|
+
}
|
|
693
|
+
}
|