@oh-my-pi/pi-coding-agent 16.1.23 → 16.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +70 -0
- package/dist/cli.js +3817 -3735
- package/dist/types/advisor/__tests__/emission-guard.test.d.ts +1 -0
- package/dist/types/advisor/emission-guard.d.ts +73 -0
- package/dist/types/advisor/index.d.ts +1 -0
- package/dist/types/advisor/runtime.d.ts +7 -0
- package/dist/types/advisor/watchdog.d.ts +2 -0
- package/dist/types/cli/flag-tables.d.ts +1 -0
- package/dist/types/cli/gallery-fixtures/search.d.ts +1 -1
- package/dist/types/collab/display-name.d.ts +3 -0
- package/dist/types/collab/host.d.ts +0 -2
- package/dist/types/commands/worktree.d.ts +0 -3
- package/dist/types/config/inline-tool-descriptors-mode.d.ts +13 -0
- package/dist/types/config/model-discovery.d.ts +6 -1
- package/dist/types/config/model-registry.d.ts +6 -2
- package/dist/types/config/model-resolver.d.ts +12 -0
- package/dist/types/config/models-config-schema.d.ts +29 -2
- package/dist/types/config/models-config.d.ts +22 -1
- package/dist/types/config/settings-schema.d.ts +91 -21
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/dap/config.d.ts +1 -1
- package/dist/types/edit/hashline/filesystem.d.ts +4 -2
- package/dist/types/edit/renderer.d.ts +4 -0
- package/dist/types/extensibility/extensions/types.d.ts +17 -17
- package/dist/types/extensibility/hooks/types.d.ts +10 -10
- package/dist/types/extensibility/plugins/marketplace/types.d.ts +1 -0
- package/dist/types/internal-urls/__tests__/ssh-protocol.test.d.ts +1 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/registry-helpers.d.ts +2 -0
- package/dist/types/internal-urls/router.d.ts +1 -1
- package/dist/types/internal-urls/ssh-protocol.d.ts +10 -0
- package/dist/types/internal-urls/types.d.ts +19 -2
- package/dist/types/irc/bus.d.ts +6 -0
- package/dist/types/modes/components/__tests__/move-overlay.test.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +7 -0
- package/dist/types/modes/components/move-overlay.d.ts +23 -0
- package/dist/types/modes/components/plugin-selector.d.ts +2 -1
- package/dist/types/modes/components/queue-mode-selector.d.ts +2 -1
- package/dist/types/modes/components/select-list-mouse-routing.d.ts +2 -0
- package/dist/types/modes/components/settings-defs.d.ts +4 -1
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/show-images-selector.d.ts +2 -1
- package/dist/types/modes/components/status-line/component.d.ts +2 -0
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/theme-selector.d.ts +2 -1
- package/dist/types/modes/components/thinking-selector.d.ts +2 -1
- package/dist/types/modes/controllers/command-controller.d.ts +10 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +10 -1
- package/dist/types/modes/internal-url-autocomplete.d.ts +1 -1
- package/dist/types/modes/running-subagent-badge.d.ts +6 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +9 -1
- package/dist/types/sdk.d.ts +2 -2
- package/dist/types/session/agent-session.d.ts +2 -1
- package/dist/types/session/session-manager.d.ts +13 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
- package/dist/types/ssh/__tests__/connection-manager-args.test.d.ts +1 -0
- package/dist/types/ssh/__tests__/file-transfer-posix-guard.test.d.ts +1 -0
- package/dist/types/ssh/connection-manager.d.ts +2 -0
- package/dist/types/ssh/file-transfer.d.ts +79 -0
- package/dist/types/ssh/utils.d.ts +6 -0
- package/dist/types/system-prompt.d.ts +3 -0
- package/dist/types/task/executor.d.ts +16 -0
- package/dist/types/tiny/text.d.ts +1 -1
- package/dist/types/tools/builtin-names.d.ts +5 -1
- package/dist/types/tools/{find.d.ts → glob.d.ts} +15 -15
- package/dist/types/tools/{search.d.ts → grep.d.ts} +14 -14
- package/dist/types/tools/index.d.ts +3 -3
- package/dist/types/tools/path-utils.d.ts +29 -0
- package/dist/types/tools/read.d.ts +2 -2
- package/dist/types/tools/render-utils.d.ts +8 -0
- package/dist/types/tools/renderers.d.ts +11 -0
- package/dist/types/tools/ssh.d.ts +2 -0
- package/dist/types/tools/todo.d.ts +0 -16
- package/dist/types/tools/write.d.ts +2 -2
- package/dist/types/utils/active-repo-context.d.ts +8 -0
- package/dist/types/utils/image-resize.d.ts +1 -0
- package/dist/types/utils/markit-cache.d.ts +23 -0
- package/dist/types/utils/markit.d.ts +5 -1
- package/dist/types/utils/prompt-path.d.ts +1 -0
- package/dist/types/web/search/providers/duckduckgo.d.ts +14 -0
- package/dist/types/web/search/providers/firecrawl.d.ts +28 -0
- package/dist/types/web/search/providers/tinyfish.d.ts +29 -0
- package/dist/types/web/search/providers/xai.d.ts +13 -0
- package/dist/types/web/search/types.d.ts +18 -2
- package/package.json +30 -15
- package/scripts/build-binary.ts +9 -9
- package/scripts/bundle-dist.ts +2 -2
- package/src/advisor/__tests__/advisor.test.ts +40 -4
- package/src/advisor/__tests__/emission-guard.test.ts +147 -0
- package/src/advisor/advise-tool.ts +1 -1
- package/src/advisor/emission-guard.ts +172 -0
- package/src/advisor/index.ts +1 -0
- package/src/advisor/runtime.ts +11 -0
- package/src/advisor/watchdog.ts +12 -1
- package/src/cli/args.ts +5 -2
- package/src/cli/auth-broker-cli.ts +17 -0
- package/src/cli/config-cli.ts +4 -0
- package/src/cli/flag-tables.ts +7 -4
- package/src/cli/gallery-cli.ts +14 -2
- package/src/cli/gallery-fixtures/edit.ts +60 -0
- package/src/cli/gallery-fixtures/fs.ts +17 -17
- package/src/cli/gallery-fixtures/search.ts +4 -4
- package/src/cli/usage-cli.ts +20 -1
- package/src/cli/web-search-cli.ts +1 -1
- package/src/collab/display-name.ts +13 -0
- package/src/collab/guest.ts +5 -1
- package/src/collab/host.ts +2 -13
- package/src/commands/worktree.ts +6 -0
- package/src/config/inline-tool-descriptors-mode.ts +27 -0
- package/src/config/model-discovery.ts +77 -8
- package/src/config/model-registry.ts +89 -11
- package/src/config/model-resolver.ts +39 -1
- package/src/config/models-config-schema.ts +41 -5
- package/src/config/models-config.ts +4 -1
- package/src/config/settings-schema.ts +85 -27
- package/src/config/settings.ts +172 -1
- package/src/cursor.ts +1 -1
- package/src/dap/config.ts +152 -8
- package/src/dap/session.ts +1 -1
- package/src/discovery/helpers.ts +3 -1
- package/src/edit/hashline/diff.ts +14 -3
- package/src/edit/hashline/execute.ts +42 -6
- package/src/edit/hashline/filesystem.ts +38 -2
- package/src/edit/index.ts +1 -0
- package/src/edit/modes/patch.ts +11 -1
- package/src/edit/renderer.ts +140 -13
- package/src/eval/__tests__/agent-bridge.test.ts +101 -0
- package/src/eval/agent-bridge.ts +26 -3
- package/src/eval/js/tool-bridge.ts +2 -2
- package/src/export/html/index.ts +1 -1
- package/src/export/html/template.js +3 -1
- package/src/export/html/tool-views.generated.js +20 -20
- package/src/extensibility/extensions/types.ts +22 -22
- package/src/extensibility/hooks/types.ts +11 -11
- package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +8 -3
- package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +25 -10
- package/src/extensibility/plugins/marketplace/manager.ts +22 -0
- package/src/extensibility/plugins/marketplace/types.ts +1 -0
- package/src/internal-urls/__tests__/ssh-protocol.test.ts +331 -0
- package/src/internal-urls/docs-index.generated.txt +2 -2
- package/src/internal-urls/docs-index.ts +2 -3
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/registry-helpers.ts +19 -4
- package/src/internal-urls/router.ts +5 -3
- package/src/internal-urls/ssh-protocol.ts +367 -0
- package/src/internal-urls/types.ts +19 -2
- package/src/irc/bus.ts +11 -3
- package/src/mcp/tool-bridge.ts +32 -2
- package/src/memories/index.ts +1 -1
- package/src/modes/acp/acp-event-mapper.ts +2 -2
- package/src/modes/components/__tests__/move-overlay.test.ts +166 -0
- package/src/modes/components/agent-hub.ts +3 -0
- package/src/modes/components/agent-transcript-viewer.ts +9 -7
- package/src/modes/components/custom-editor.ts +90 -5
- package/src/modes/components/move-overlay.ts +282 -0
- package/src/modes/components/plan-review-overlay.ts +35 -35
- package/src/modes/components/plugin-selector.ts +6 -1
- package/src/modes/components/queue-mode-selector.ts +6 -1
- package/src/modes/components/select-list-mouse-routing.ts +35 -0
- package/src/modes/components/session-selector.ts +11 -10
- package/src/modes/components/settings-defs.ts +14 -1
- package/src/modes/components/settings-selector.ts +196 -29
- package/src/modes/components/show-images-selector.ts +6 -1
- package/src/modes/components/status-line/component.ts +108 -28
- package/src/modes/components/status-line/segments.ts +5 -1
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/theme-selector.ts +6 -1
- package/src/modes/components/thinking-selector.ts +6 -1
- package/src/modes/components/tool-execution.ts +25 -9
- package/src/modes/components/tree-selector.ts +5 -5
- package/src/modes/controllers/command-controller.ts +140 -47
- package/src/modes/controllers/event-controller.ts +59 -3
- package/src/modes/controllers/input-controller.ts +68 -2
- package/src/modes/controllers/selector-controller.ts +5 -2
- package/src/modes/controllers/streaming-reveal.ts +17 -0
- package/src/modes/controllers/tool-args-reveal.ts +1 -1
- package/src/modes/interactive-mode.ts +116 -50
- package/src/modes/internal-url-autocomplete.ts +17 -2
- package/src/modes/prompt-action-autocomplete.ts +3 -1
- package/src/modes/running-subagent-badge.ts +13 -0
- package/src/modes/setup-wizard/scenes/glyph.ts +2 -13
- package/src/modes/setup-wizard/scenes/providers.ts +2 -1
- package/src/modes/setup-wizard/scenes/theme.ts +6 -12
- package/src/modes/setup-wizard/scenes/web-search.ts +8 -13
- package/src/modes/setup-wizard/wizard-overlay.ts +6 -5
- package/src/modes/theme/theme.ts +9 -1
- package/src/modes/types.ts +9 -1
- package/src/modes/utils/interactive-context-helpers.ts +1 -1
- package/src/priority.json +15 -0
- package/src/prompts/advisor/active-repo-watchdog.md +6 -0
- package/src/prompts/advisor/system.md +21 -7
- package/src/prompts/agents/designer.md +1 -1
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/agents/librarian.md +2 -2
- package/src/prompts/agents/plan.md +2 -2
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/agents/task.md +2 -2
- package/src/prompts/system/active-repo-context.md +4 -0
- package/src/prompts/system/gemini-tool-call-reminder.md +9 -0
- package/src/prompts/system/plan-mode-active.md +3 -3
- package/src/prompts/system/project-prompt.md +2 -2
- package/src/prompts/system/system-prompt.md +6 -6
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/checkpoint.md +1 -1
- package/src/prompts/tools/{find.md → glob.md} +1 -1
- package/src/prompts/tools/{search.md → grep.md} +3 -3
- package/src/prompts/tools/read.md +4 -2
- package/src/sdk.ts +55 -25
- package/src/session/agent-session.ts +508 -132
- package/src/session/session-history-format.ts +2 -2
- package/src/session/session-manager.ts +46 -0
- package/src/slash-commands/builtin-registry.ts +138 -20
- package/src/slash-commands/helpers/usage-report.ts +23 -2
- package/src/ssh/__tests__/connection-manager-args.test.ts +69 -0
- package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +68 -0
- package/src/ssh/connection-manager.ts +12 -15
- package/src/ssh/file-transfer.ts +209 -0
- package/src/ssh/utils.ts +24 -0
- package/src/system-prompt.ts +56 -28
- package/src/task/executor.ts +96 -36
- package/src/task/index.ts +3 -3
- package/src/task/render.ts +14 -13
- package/src/task/worktree.ts +38 -1
- package/src/tiny/text.ts +49 -4
- package/src/tiny/worker.ts +3 -3
- package/src/tools/bash.ts +2 -2
- package/src/tools/builtin-names.ts +26 -2
- package/src/tools/{find.ts → glob.ts} +48 -42
- package/src/tools/{search.ts → grep.ts} +311 -129
- package/src/tools/index.ts +13 -14
- package/src/tools/irc.ts +6 -2
- package/src/tools/path-utils.ts +64 -1
- package/src/tools/read.ts +35 -6
- package/src/tools/render-utils.ts +14 -0
- package/src/tools/renderers.ts +15 -4
- package/src/tools/ssh.ts +17 -2
- package/src/tools/todo.ts +4 -26
- package/src/tools/write.ts +25 -9
- package/src/utils/active-repo-context.ts +143 -0
- package/src/utils/image-resize.ts +88 -7
- package/src/utils/lang-from-path.ts +3 -3
- package/src/utils/markit-cache.ts +166 -0
- package/src/utils/markit.ts +86 -18
- package/src/utils/prompt-path.ts +3 -0
- package/src/utils/title-generator.ts +1 -1
- package/src/web/search/provider.ts +54 -34
- package/src/web/search/providers/duckduckgo.ts +140 -0
- package/src/web/search/providers/firecrawl.ts +144 -0
- package/src/web/search/providers/tinyfish.ts +159 -0
- package/src/web/search/providers/xai.ts +292 -0
- package/src/web/search/providers/zai.ts +142 -56
- package/src/web/search/types.ts +6 -2
package/src/edit/renderer.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Edit tool renderer and LSP batching helpers.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { HL_FILE_PREFIX, HL_FILE_SUFFIX } from "@oh-my-pi/hashline";
|
|
5
|
+
import { HL_FILE_PREFIX, HL_FILE_SUFFIX, HL_MOVE_KEYWORD, HL_REM_KEYWORD } from "@oh-my-pi/hashline";
|
|
6
6
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
7
7
|
import { sliceWithWidth, visibleWidth, wrapTextWithAnsi } from "@oh-my-pi/pi-tui";
|
|
8
8
|
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
@@ -28,7 +28,15 @@ import {
|
|
|
28
28
|
shortenPath,
|
|
29
29
|
truncateDiffByHunk,
|
|
30
30
|
} from "../tools/render-utils";
|
|
31
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
fileHyperlink,
|
|
33
|
+
framedBlock,
|
|
34
|
+
Hasher,
|
|
35
|
+
type RenderCache,
|
|
36
|
+
renderStatusLine,
|
|
37
|
+
truncateToWidth,
|
|
38
|
+
WidthAwareText,
|
|
39
|
+
} from "../tui";
|
|
32
40
|
import type { EditMode } from "../utils/edit-mode";
|
|
33
41
|
import type { DiffError, DiffResult } from "./diff";
|
|
34
42
|
import { type ApplyPatchEntry, expandApplyPatchToEntries, expandApplyPatchToPreviewEntries } from "./modes/apply-patch";
|
|
@@ -62,6 +70,8 @@ export interface EditToolPerFileResult {
|
|
|
62
70
|
oldText?: string;
|
|
63
71
|
/** Source-of-truth content after the edit; `undefined` for delete operations. */
|
|
64
72
|
newText?: string;
|
|
73
|
+
/** Pre-move source path; set only when the edit moved/renamed the file. The header renders `sourcePath → path`. */
|
|
74
|
+
sourcePath?: string;
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
export interface EditToolDetails {
|
|
@@ -85,6 +95,8 @@ export interface EditToolDetails {
|
|
|
85
95
|
oldText?: string;
|
|
86
96
|
/** Source-of-truth content after the edit; `undefined` for delete operations. */
|
|
87
97
|
newText?: string;
|
|
98
|
+
/** Pre-move source path; set only when the edit moved/renamed the file. The header renders `sourcePath → path`. */
|
|
99
|
+
sourcePath?: string;
|
|
88
100
|
}
|
|
89
101
|
|
|
90
102
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -119,8 +131,16 @@ type EditRenderEntry = {
|
|
|
119
131
|
op?: Operation;
|
|
120
132
|
};
|
|
121
133
|
|
|
134
|
+
interface HashlineInputEntry {
|
|
135
|
+
path: string;
|
|
136
|
+
op?: Operation;
|
|
137
|
+
rename?: string;
|
|
138
|
+
/** A SWAP/DEL/INS line-editing op precedes the file op — keeps a move framed. */
|
|
139
|
+
hasLineEdits?: boolean;
|
|
140
|
+
}
|
|
141
|
+
|
|
122
142
|
interface HashlineInputRenderSummary {
|
|
123
|
-
entries:
|
|
143
|
+
entries: HashlineInputEntry[];
|
|
124
144
|
}
|
|
125
145
|
|
|
126
146
|
interface ApplyPatchRenderSummary {
|
|
@@ -293,9 +313,10 @@ function renderEditHeader(
|
|
|
293
313
|
linkPath?: string;
|
|
294
314
|
statsSuffix?: string;
|
|
295
315
|
extraSuffix?: string;
|
|
316
|
+
title?: string;
|
|
296
317
|
},
|
|
297
318
|
): string {
|
|
298
|
-
const title = getOperationTitle(options.op);
|
|
319
|
+
const title = options.title ?? getOperationTitle(options.op);
|
|
299
320
|
const descriptionOptions: EditPathDisplayOptions = {
|
|
300
321
|
rename: options.rename,
|
|
301
322
|
firstChangedLine: options.firstChangedLine,
|
|
@@ -327,6 +348,51 @@ function renderEditHeader(
|
|
|
327
348
|
return buildHeader(fitted.description);
|
|
328
349
|
}
|
|
329
350
|
|
|
351
|
+
/**
|
|
352
|
+
* Inline status row for delete / move-only edits — they carry no diff, so they
|
|
353
|
+
* render as a single line instead of an empty framed container. The completed
|
|
354
|
+
* result uses the eraser/move glyph; a still-streaming call uses the shared
|
|
355
|
+
* pending hourglass like every other tool.
|
|
356
|
+
*/
|
|
357
|
+
function renderInlineEditRow(
|
|
358
|
+
uiTheme: Theme,
|
|
359
|
+
opts: { op?: Operation; rename?: string; rawPath: string; linkPath?: string; pending: boolean },
|
|
360
|
+
): Component {
|
|
361
|
+
const isDelete = opts.op === "delete";
|
|
362
|
+
return new WidthAwareText(
|
|
363
|
+
width =>
|
|
364
|
+
renderEditHeader(width, uiTheme, {
|
|
365
|
+
icon: opts.pending ? "pending" : undefined,
|
|
366
|
+
iconOverride: opts.pending
|
|
367
|
+
? undefined
|
|
368
|
+
: uiTheme.styledSymbol(isDelete ? "tool.delete" : "tool.move", "accent"),
|
|
369
|
+
op: opts.op,
|
|
370
|
+
title: isDelete ? "Delete" : "Move",
|
|
371
|
+
rawPath: opts.rawPath,
|
|
372
|
+
rename: opts.rename,
|
|
373
|
+
linkPath: opts.linkPath,
|
|
374
|
+
}),
|
|
375
|
+
0,
|
|
376
|
+
0,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Whether a streaming edit call carries any payload worth boxing (a diff
|
|
382
|
+
* preview, replacement text, or a non-empty edits array). Used to keep a
|
|
383
|
+
* move-with-edits framed while a payload-less move/delete folds to an inline
|
|
384
|
+
* row — gated on args, not the async preview, so it can't flash inline before
|
|
385
|
+
* the diff arrives.
|
|
386
|
+
*/
|
|
387
|
+
function hasEditCallPayload(args: EditRenderArgs, renderContext: EditRenderContext | undefined): boolean {
|
|
388
|
+
const multi = renderContext?.perFileDiffPreview;
|
|
389
|
+
if (multi && multi.length > 1 && multi.some(p => p.diff || p.error)) return true;
|
|
390
|
+
if (args.previewDiff || args.diff || args.newText || args.patch) return true;
|
|
391
|
+
if (Array.isArray(args.edits) && args.edits.length > 0) return true;
|
|
392
|
+
if (renderContext?.editStreamingFallback) return true;
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
|
|
330
396
|
function renderPlainTextPreview(text: string, uiTheme: Theme, _filePath?: string): string {
|
|
331
397
|
const previewLines = sanitizeText(text).split("\n");
|
|
332
398
|
let preview = "\n\n";
|
|
@@ -491,15 +557,39 @@ function parseHashlineInputPreviewHeader(line: string): string | null {
|
|
|
491
557
|
return previewPath.length > 0 ? previewPath : null;
|
|
492
558
|
}
|
|
493
559
|
|
|
494
|
-
|
|
560
|
+
// Line-editing op headers (SWAP/DEL/INS family), distinct from the file-level
|
|
561
|
+
// REM/MV ops. Body rows are always `+TEXT`, so this only matches real headers.
|
|
562
|
+
const HL_LINE_OP_HEADER = /^(?:SWAP|DEL|INS)\b/;
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Walk a (possibly mid-stream) hashline payload into per-section descriptors:
|
|
566
|
+
* the target path plus any file-level op (`REM` → delete, `MV dest` → rename)
|
|
567
|
+
* and whether a line edit precedes it. Tolerant of partial input so the call
|
|
568
|
+
* preview can label a delete/move before the payload finishes streaming.
|
|
569
|
+
*/
|
|
570
|
+
function getHashlineInputSections(input: string): HashlineInputEntry[] {
|
|
495
571
|
const stripped = input.startsWith("\uFEFF") ? input.slice(1) : input;
|
|
496
|
-
const
|
|
572
|
+
const entries: HashlineInputEntry[] = [];
|
|
573
|
+
let current: HashlineInputEntry | undefined;
|
|
497
574
|
for (const rawLine of stripped.split("\n")) {
|
|
498
575
|
const line = rawLine.replace(/\r$/, "");
|
|
499
|
-
const
|
|
500
|
-
if (
|
|
576
|
+
const headerPath = parseHashlineInputPreviewHeader(line);
|
|
577
|
+
if (headerPath) {
|
|
578
|
+
current = { path: headerPath };
|
|
579
|
+
entries.push(current);
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
if (!current) continue;
|
|
583
|
+
const trimmed = line.trim();
|
|
584
|
+
if (trimmed === HL_REM_KEYWORD) {
|
|
585
|
+
current.op = "delete";
|
|
586
|
+
} else if (trimmed.startsWith(`${HL_MOVE_KEYWORD} `)) {
|
|
587
|
+
current.rename = normalizeHashlineInputPreviewPath(trimmed.slice(HL_MOVE_KEYWORD.length + 1));
|
|
588
|
+
} else if (HL_LINE_OP_HEADER.test(trimmed)) {
|
|
589
|
+
current.hasLineEdits = true;
|
|
590
|
+
}
|
|
501
591
|
}
|
|
502
|
-
return
|
|
592
|
+
return entries;
|
|
503
593
|
}
|
|
504
594
|
|
|
505
595
|
function getHashlineInputRenderSummary(
|
|
@@ -509,7 +599,7 @@ function getHashlineInputRenderSummary(
|
|
|
509
599
|
if (editMode !== "hashline" || typeof args.input !== "string") {
|
|
510
600
|
return undefined;
|
|
511
601
|
}
|
|
512
|
-
return { entries:
|
|
602
|
+
return { entries: getHashlineInputSections(args.input) };
|
|
513
603
|
}
|
|
514
604
|
|
|
515
605
|
function getApplyPatchRenderSummary(
|
|
@@ -627,12 +717,24 @@ export const editToolRenderer = {
|
|
|
627
717
|
firstHashlineInputEntry?.path ||
|
|
628
718
|
firstApplyPatchEntry?.path ||
|
|
629
719
|
"";
|
|
630
|
-
const rename =
|
|
631
|
-
|
|
720
|
+
const rename =
|
|
721
|
+
editArgs.rename ||
|
|
722
|
+
firstEdit?.rename ||
|
|
723
|
+
firstEdit?.move ||
|
|
724
|
+
firstApplyPatchEntry?.rename ||
|
|
725
|
+
firstHashlineInputEntry?.rename;
|
|
726
|
+
const op = editArgs.op || firstEdit?.op || firstApplyPatchEntry?.op || firstHashlineInputEntry?.op;
|
|
632
727
|
let fileCount = hashlineInputSummary?.entries.length ?? applyPatchSummary?.entries.length ?? 0;
|
|
633
728
|
if (Array.isArray(editArgs.edits)) {
|
|
634
729
|
fileCount = countEditFiles(editArgs.edits);
|
|
635
730
|
}
|
|
731
|
+
// Delete / payload-less move calls render as an inline pending row (no
|
|
732
|
+
// empty framed container), mirroring the completed result but with the
|
|
733
|
+
// shared hourglass instead of the eraser/move glyph.
|
|
734
|
+
const hasPayload = hasEditCallPayload(editArgs, renderContext) || Boolean(firstHashlineInputEntry?.hasLineEdits);
|
|
735
|
+
if (fileCount <= 1 && !applyPatchSummary?.error && (op === "delete" || (rename !== undefined && !hasPayload))) {
|
|
736
|
+
return renderInlineEditRow(uiTheme, { op, rename, rawPath, pending: true });
|
|
737
|
+
}
|
|
636
738
|
const callPreviewCaches: RenderedStringCache[] = [];
|
|
637
739
|
return framedBlock(uiTheme, width => {
|
|
638
740
|
// No status icon on the head row: it's the head of the framed block,
|
|
@@ -702,7 +804,9 @@ function renderSingleFileResult(
|
|
|
702
804
|
const firstEdit = args?.edits?.[0];
|
|
703
805
|
const hashlineInputSummary = getHashlineInputRenderSummary(args ?? {}, options.renderContext?.editMode);
|
|
704
806
|
const firstHashlineInputEntry = hashlineInputSummary?.entries[0];
|
|
807
|
+
const moveSource = details && "sourcePath" in details ? details.sourcePath : undefined;
|
|
705
808
|
const rawPath =
|
|
809
|
+
moveSource ||
|
|
706
810
|
args?.file_path ||
|
|
707
811
|
args?.path ||
|
|
708
812
|
filePathFromEditEntry(firstEdit?.path) ||
|
|
@@ -719,12 +823,26 @@ function renderSingleFileResult(
|
|
|
719
823
|
(result.content?.find(c => c.type === "text")?.text ?? "")
|
|
720
824
|
: "";
|
|
721
825
|
|
|
826
|
+
// Delete and move-only results carry no diff to box. Per design these render
|
|
827
|
+
// as an inline status row (eraser / move glyph) rather than an empty framed
|
|
828
|
+
// container. Errors, no-ops, creates, move-with-edits, and anything with
|
|
829
|
+
// diagnostics keep the framed block below.
|
|
830
|
+
if (!isError && !details?.diff && !details?.diagnostics && (op === "delete" || rename)) {
|
|
831
|
+
const linkPath = details && "path" in details ? details.path : undefined;
|
|
832
|
+
return renderInlineEditRow(uiTheme, { op, rename, rawPath, linkPath, pending: false });
|
|
833
|
+
}
|
|
834
|
+
|
|
722
835
|
let diffSectionRenderDiffFn: ((t: string, o?: { filePath?: string }) => string) | undefined;
|
|
723
836
|
const diffSectionCache = createRenderedStringCache();
|
|
724
837
|
|
|
725
838
|
return framedBlock(uiTheme, width => {
|
|
726
839
|
const { expanded, renderContext } = options;
|
|
727
|
-
|
|
840
|
+
// A finalized result is authoritative: its `details` describe exactly
|
|
841
|
+
// what happened. The shared streaming `editDiffPreview` is a call-phase
|
|
842
|
+
// artifact (in a batch it reflects only the first file), so consulting it
|
|
843
|
+
// for an empty-diff delete/move/no-op result mislabels the card. Fall
|
|
844
|
+
// back to the preview only when no details exist yet.
|
|
845
|
+
const editDiffPreview = details ? undefined : renderContext?.editDiffPreview;
|
|
728
846
|
const renderDiffFn = renderContext?.renderDiff ?? plainDiffRender;
|
|
729
847
|
|
|
730
848
|
if (diffSectionRenderDiffFn !== renderDiffFn) {
|
|
@@ -756,6 +874,15 @@ function renderSingleFileResult(
|
|
|
756
874
|
if (errorText) body = uiTheme.fg("error", replaceTabs(errorText));
|
|
757
875
|
} else if (details?.diff) {
|
|
758
876
|
body = renderDiffSection(details.diff, rawPath, expanded, uiTheme, renderDiffFn, diffSectionCache);
|
|
877
|
+
} else if (details) {
|
|
878
|
+
// Authoritative result with no textual diff: a delete, a move-only
|
|
879
|
+
// rename, or a genuine no-op. The header already names the op
|
|
880
|
+
// (Delete / `src → dst`); only a true no-op needs an explanatory
|
|
881
|
+
// body so an empty card isn't mistaken for a stalled edit.
|
|
882
|
+
if (op !== "delete" && op !== "create" && !rename) {
|
|
883
|
+
const noChangePath = linkPath ? shortenPath(linkPath) : rawPath ? shortenPath(rawPath) : "";
|
|
884
|
+
body = uiTheme.fg("dim", `No changes were made${noChangePath ? ` to ${noChangePath}` : ""}.`);
|
|
885
|
+
}
|
|
759
886
|
} else if (editDiffPreview) {
|
|
760
887
|
if ("error" in editDiffPreview) body = uiTheme.fg("error", replaceTabs(editDiffPreview.error));
|
|
761
888
|
else if (editDiffPreview.diff)
|
|
@@ -3,7 +3,11 @@ import * as fs from "node:fs/promises";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { TempDir } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import { Settings } from "../../config/settings";
|
|
6
|
+
import { AgentProtocolHandler } from "../../internal-urls/agent-protocol";
|
|
7
|
+
import { resetRegisteredArtifactDirsForTests } from "../../internal-urls/registry-helpers";
|
|
6
8
|
import type { PlanModeState } from "../../plan-mode/state";
|
|
9
|
+
import { AgentRegistry } from "../../registry/agent-registry";
|
|
10
|
+
import type { AgentSession } from "../../session/agent-session";
|
|
7
11
|
import * as taskDiscovery from "../../task/discovery";
|
|
8
12
|
import type { ExecutorOptions } from "../../task/executor";
|
|
9
13
|
import * as taskExecutor from "../../task/executor";
|
|
@@ -154,6 +158,8 @@ function spyConcurrencyBarrier(limit: number): { maxInFlight: () => number } {
|
|
|
154
158
|
describe("runEvalAgent", () => {
|
|
155
159
|
afterEach(() => {
|
|
156
160
|
vi.restoreAllMocks();
|
|
161
|
+
AgentRegistry.resetGlobalForTests();
|
|
162
|
+
resetRegisteredArtifactDirsForTests();
|
|
157
163
|
});
|
|
158
164
|
|
|
159
165
|
it("resolves the default task agent and agent overrides", async () => {
|
|
@@ -246,6 +252,62 @@ describe("runEvalAgent", () => {
|
|
|
246
252
|
const options = runSpy.mock.calls[0]?.[0];
|
|
247
253
|
if (!options) throw new Error("runSubprocess was not called");
|
|
248
254
|
expect(options.enableLsp).toBe(false);
|
|
255
|
+
expect(options.keepAlive).toBe(false);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("registers temp artifact dirs for in-memory handle results so agent URLs resolve", async () => {
|
|
259
|
+
mockAgents();
|
|
260
|
+
vi.spyOn(taskExecutor, "runSubprocess").mockImplementation(async options => {
|
|
261
|
+
if (!options.artifactsDir) throw new Error("artifactsDir missing");
|
|
262
|
+
await fs.mkdir(options.artifactsDir, { recursive: true });
|
|
263
|
+
await fs.writeFile(path.join(options.artifactsDir, `${options.id}.md`), "recoverable output");
|
|
264
|
+
return singleResult(options, { output: "recoverable output" });
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const result = await runEvalAgent({ prompt: "hello", handle: true }, { session: makeSession() });
|
|
268
|
+
const resource = await new AgentProtocolHandler().resolve(new URL(`agent://${result.details.id}`) as never);
|
|
269
|
+
|
|
270
|
+
expect(resource.content).toBe("recoverable output");
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("unregisters eval subagents through the bridge cleanup path", async () => {
|
|
274
|
+
AgentRegistry.resetGlobalForTests();
|
|
275
|
+
mockAgents();
|
|
276
|
+
let disposed = false;
|
|
277
|
+
const cleanupSession = {
|
|
278
|
+
dispose: async () => {
|
|
279
|
+
disposed = true;
|
|
280
|
+
},
|
|
281
|
+
} as unknown as AgentSession;
|
|
282
|
+
vi.spyOn(taskExecutor, "runSubprocess").mockImplementation(async options => {
|
|
283
|
+
AgentRegistry.global().register({
|
|
284
|
+
id: options.id,
|
|
285
|
+
displayName: options.id,
|
|
286
|
+
kind: "sub",
|
|
287
|
+
session: cleanupSession,
|
|
288
|
+
status: "idle",
|
|
289
|
+
});
|
|
290
|
+
await taskExecutor.finalizeSubagentLifecycle({
|
|
291
|
+
id: options.id,
|
|
292
|
+
session: cleanupSession,
|
|
293
|
+
aborted: false,
|
|
294
|
+
keepAlive: options.keepAlive !== false,
|
|
295
|
+
isolated: options.worktree !== undefined,
|
|
296
|
+
agentIdleTtlMs: 0,
|
|
297
|
+
reviveSession: null,
|
|
298
|
+
});
|
|
299
|
+
return singleResult(options);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
await runEvalAgent({ prompt: "hello", label: "Cleanup" }, { session: makeSession() });
|
|
303
|
+
|
|
304
|
+
expect(disposed).toBe(true);
|
|
305
|
+
expect(AgentRegistry.global().get("Cleanup")).toBeUndefined();
|
|
306
|
+
expect(
|
|
307
|
+
AgentRegistry.global()
|
|
308
|
+
.listVisibleTo("Main")
|
|
309
|
+
.map(ref => ref.id),
|
|
310
|
+
).not.toContain("Cleanup");
|
|
249
311
|
});
|
|
250
312
|
|
|
251
313
|
it("maps successful and failed subagent results", async () => {
|
|
@@ -1096,6 +1158,45 @@ describe("runEvalAgent isolation", () => {
|
|
|
1096
1158
|
await fs.rm(path.dirname(persistedPath!), { recursive: true, force: true });
|
|
1097
1159
|
});
|
|
1098
1160
|
|
|
1161
|
+
it("throws schema calls when nested patch application reports a warning", async () => {
|
|
1162
|
+
mockAgents();
|
|
1163
|
+
mockIsolationContext();
|
|
1164
|
+
const nestedPatch = "diff --git a/file b/file\n";
|
|
1165
|
+
vi.spyOn(isolationRunner, "runIsolatedSubprocess").mockImplementation(async opts =>
|
|
1166
|
+
singleResult(opts.baseOptions, {
|
|
1167
|
+
output: JSON.stringify({ status: "ok" }),
|
|
1168
|
+
patchPath: `/artifacts/${opts.agentId}.patch`,
|
|
1169
|
+
nestedPatches: [{ relativePath: "sub/nested", patch: nestedPatch }],
|
|
1170
|
+
}),
|
|
1171
|
+
);
|
|
1172
|
+
vi.spyOn(isolationRunner, "mergeIsolatedChanges").mockResolvedValue({
|
|
1173
|
+
summary: "\n\nApplied patches: yes",
|
|
1174
|
+
changesApplied: true,
|
|
1175
|
+
hadAnyChanges: true,
|
|
1176
|
+
mergedBranchForNestedPatches: false,
|
|
1177
|
+
});
|
|
1178
|
+
vi.spyOn(isolationRunner, "applyEligibleNestedPatches").mockResolvedValue(
|
|
1179
|
+
"\n\n<system-notification>Some nested repository patches failed to apply.</system-notification>",
|
|
1180
|
+
);
|
|
1181
|
+
|
|
1182
|
+
await expect(
|
|
1183
|
+
runEvalAgent(
|
|
1184
|
+
{
|
|
1185
|
+
prompt: "structured",
|
|
1186
|
+
isolated: true,
|
|
1187
|
+
schema: {
|
|
1188
|
+
type: "object",
|
|
1189
|
+
properties: { status: { type: "string" } },
|
|
1190
|
+
required: ["status"],
|
|
1191
|
+
},
|
|
1192
|
+
},
|
|
1193
|
+
{ session: isolatedSession() },
|
|
1194
|
+
),
|
|
1195
|
+
).rejects.toThrow(
|
|
1196
|
+
/nested patch apply failed.*Some nested repository patches failed to apply.*nested-0-sub_nested\.patch/s,
|
|
1197
|
+
);
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1099
1200
|
it("skips the merge phase when apply=false and surfaces the patch artifact instead", async () => {
|
|
1100
1201
|
mockAgents();
|
|
1101
1202
|
mockIsolationContext();
|
package/src/eval/agent-bridge.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { prompt, Snowflake } from "@oh-my-pi/pi-utils";
|
|
|
8
8
|
import { type } from "arktype";
|
|
9
9
|
import { resolveAgentModelPatterns } from "../config/model-resolver";
|
|
10
10
|
import type { LocalProtocolOptions } from "../internal-urls";
|
|
11
|
+
import { registerArtifactsDir } from "../internal-urls/registry-helpers";
|
|
11
12
|
import { MCPManager } from "../mcp/manager";
|
|
12
13
|
import subagentUserPromptTemplate from "../prompts/system/subagent-user-prompt.md" with { type: "text" };
|
|
13
14
|
import { MAIN_AGENT_ID } from "../registry/agent-registry";
|
|
@@ -189,6 +190,7 @@ function getOutputManager(session: ToolSession): AgentOutputManager {
|
|
|
189
190
|
interface ArtifactPaths {
|
|
190
191
|
sessionFile: string | null;
|
|
191
192
|
artifactsDir: string;
|
|
193
|
+
unregisterArtifactsDir?: () => void;
|
|
192
194
|
/**
|
|
193
195
|
* True when `artifactsDir` was created off the session path (no session
|
|
194
196
|
* file). Caller is then free to `rm -rf` it once all isolated patch
|
|
@@ -203,7 +205,8 @@ async function getArtifacts(session: ToolSession): Promise<ArtifactPaths> {
|
|
|
203
205
|
const tempArtifactsDir = sessionArtifactsDir === null;
|
|
204
206
|
const artifactsDir = sessionArtifactsDir ?? path.join(os.tmpdir(), `omp-eval-agent-${Snowflake.next()}`);
|
|
205
207
|
await fs.mkdir(artifactsDir, { recursive: true });
|
|
206
|
-
|
|
208
|
+
const unregisterArtifactsDir = tempArtifactsDir ? registerArtifactsDir(artifactsDir) : undefined;
|
|
209
|
+
return { sessionFile, artifactsDir, unregisterArtifactsDir, tempArtifactsDir };
|
|
207
210
|
}
|
|
208
211
|
|
|
209
212
|
/**
|
|
@@ -229,6 +232,10 @@ async function persistNestedPatches(
|
|
|
229
232
|
return written;
|
|
230
233
|
}
|
|
231
234
|
|
|
235
|
+
function plainIsolationSummary(summary: string): string {
|
|
236
|
+
return summary.replace(/<\/?system-notification>/g, "").trim();
|
|
237
|
+
}
|
|
238
|
+
|
|
232
239
|
function emitProgressStatus(emitStatus: ((event: JsStatusEvent) => void) | undefined, progress: AgentProgress): void {
|
|
233
240
|
if (!emitStatus) return;
|
|
234
241
|
const preview = (progress.assignment ?? progress.task ?? "").split("\n")[0]?.slice(0, 120);
|
|
@@ -324,7 +331,7 @@ export async function runEvalAgent(args: unknown, options: EvalAgentBridgeOption
|
|
|
324
331
|
};
|
|
325
332
|
const parentArtifactManager = options.session.getArtifactManager?.() ?? undefined;
|
|
326
333
|
const mcpManager = options.session.mcpManager ?? MCPManager.instance();
|
|
327
|
-
const { sessionFile, artifactsDir, tempArtifactsDir } = await getArtifacts(options.session);
|
|
334
|
+
const { sessionFile, artifactsDir, unregisterArtifactsDir, tempArtifactsDir } = await getArtifacts(options.session);
|
|
328
335
|
const outputManager = getOutputManager(options.session);
|
|
329
336
|
const id = await outputManager.allocate(outputIdBase(parsed.label, agentName));
|
|
330
337
|
const assignment = parsed.prompt.trim();
|
|
@@ -385,6 +392,7 @@ export async function runEvalAgent(args: unknown, options: EvalAgentBridgeOption
|
|
|
385
392
|
// must not be killed by `task.maxRuntimeMs`. Force the limit off
|
|
386
393
|
// regardless of the inherited session setting.
|
|
387
394
|
maxRuntimeMs: 0,
|
|
395
|
+
keepAlive: false,
|
|
388
396
|
mcpManager,
|
|
389
397
|
contextFiles,
|
|
390
398
|
skills: availableSkills,
|
|
@@ -494,7 +502,7 @@ export async function runEvalAgent(args: unknown, options: EvalAgentBridgeOption
|
|
|
494
502
|
);
|
|
495
503
|
}
|
|
496
504
|
|
|
497
|
-
|
|
505
|
+
const nestedSummary = await applyEligibleNestedPatches({
|
|
498
506
|
result,
|
|
499
507
|
repoRoot: isolationContext.repoRoot,
|
|
500
508
|
mergeMode,
|
|
@@ -502,6 +510,20 @@ export async function runEvalAgent(args: unknown, options: EvalAgentBridgeOption
|
|
|
502
510
|
mergedBranchForNestedPatches: outcome.mergedBranchForNestedPatches,
|
|
503
511
|
commitMessage: buildCommitMessage(),
|
|
504
512
|
});
|
|
513
|
+
mergeSummary += nestedSummary;
|
|
514
|
+
if (structured && nestedSummary.trim()) {
|
|
515
|
+
const recoveryParts: string[] = [];
|
|
516
|
+
if (result.nestedPatches?.length) {
|
|
517
|
+
const nestedPaths = await persistNestedPatches(artifactsDir, result.id, result.nestedPatches);
|
|
518
|
+
recoveryParts.push(
|
|
519
|
+
`Captured nested repository patches (${result.nestedPatches.length}) preserved at: ${nestedPaths.join(", ")}.`,
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
const recoveryHint = recoveryParts.length > 0 ? ` ${recoveryParts.join(" ")}` : "";
|
|
523
|
+
throw new ToolError(
|
|
524
|
+
`agent() isolated nested patch apply failed for ${result.id}: ${plainIsolationSummary(nestedSummary)}${recoveryHint}`,
|
|
525
|
+
);
|
|
526
|
+
}
|
|
505
527
|
} else if (result.branchName) {
|
|
506
528
|
mergeSummary = `\n\nIsolation: changes captured on branch \`${result.branchName}\` (apply=false). Not merged.`;
|
|
507
529
|
} else if (result.patchPath) {
|
|
@@ -526,6 +548,7 @@ export async function runEvalAgent(args: unknown, options: EvalAgentBridgeOption
|
|
|
526
548
|
const shouldCleanupTempArtifacts = tempArtifactsDir && !parsed.handle && (!isIsolated || changesApplied === true);
|
|
527
549
|
if (shouldCleanupTempArtifacts) {
|
|
528
550
|
await fs.rm(artifactsDir, { recursive: true, force: true });
|
|
551
|
+
unregisterArtifactsDir?.();
|
|
529
552
|
}
|
|
530
553
|
|
|
531
554
|
options.session.recordEvalSubagentUsage?.(result.usage?.output ?? 0);
|
|
@@ -88,9 +88,9 @@ function summarizeToolResult(
|
|
|
88
88
|
path: record.path,
|
|
89
89
|
count: details.matchCount ?? undefined,
|
|
90
90
|
});
|
|
91
|
-
case "
|
|
91
|
+
case "glob":
|
|
92
92
|
return withError({
|
|
93
|
-
op: "
|
|
93
|
+
op: "glob",
|
|
94
94
|
pattern: record.pattern,
|
|
95
95
|
count: details.fileCount ?? undefined,
|
|
96
96
|
matches: Array.isArray(details.files) ? details.files.slice(0, 20) : undefined,
|
package/src/export/html/index.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { SessionManager } from "../../session/session-manager";
|
|
|
9
9
|
import templateCss from "./template.css" with { type: "text" };
|
|
10
10
|
import templateHtml from "./template.html" with { type: "text" };
|
|
11
11
|
import templateJs from "./template.js" with { type: "text" };
|
|
12
|
-
// Pre-built React tool renderers: built by `bun
|
|
12
|
+
// Pre-built React tool renderers: built by `gen:tool-views` (`bun run gen:tool-views`),
|
|
13
13
|
// run automatically by root `prepare` on install and by `prepack` at publish.
|
|
14
14
|
import toolViewsJs from "./tool-views.generated.js" with { type: "text" };
|
|
15
15
|
import { webExportThemeVars } from "./web-palette";
|
|
@@ -433,10 +433,12 @@
|
|
|
433
433
|
const cmd = rawCmd.replace(/[\n\t]/g, ' ').trim().slice(0, 50);
|
|
434
434
|
return `[bash: ${cmd}${rawCmd.length > 50 ? '...' : ''}]`;
|
|
435
435
|
}
|
|
436
|
+
case 'search':
|
|
436
437
|
case 'grep':
|
|
437
438
|
return `[grep: /${args.pattern || ''}/ in ${shortenPath(String((args.paths || [args.path || '.']).join(', ')))}]`;
|
|
438
439
|
case 'find':
|
|
439
|
-
|
|
440
|
+
case 'glob':
|
|
441
|
+
return `[glob: ${shortenPath(String((args.paths || [args.pattern || '.']).join(', ')))}]`;
|
|
440
442
|
case 'ls':
|
|
441
443
|
return `[ls: ${shortenPath(String(args.path || '.'))}]`;
|
|
442
444
|
default: {
|