@oh-my-pi/pi-coding-agent 15.10.12 → 15.11.1
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 +90 -4
- package/dist/cli.js +869 -825
- package/dist/types/async/index.d.ts +0 -1
- package/dist/types/capability/mcp.d.ts +1 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +5 -0
- package/dist/types/config/keybindings.d.ts +6 -1
- package/dist/types/config/settings-schema.d.ts +66 -34
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/shared-events.d.ts +2 -2
- package/dist/types/internal-urls/history-protocol.d.ts +14 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/types.d.ts +1 -1
- package/dist/types/irc/bus.d.ts +66 -0
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/oauth-flow.d.ts +6 -1
- package/dist/types/mcp/transports/stdio.d.ts +1 -0
- package/dist/types/mcp/types.d.ts +2 -0
- package/dist/types/modes/components/agent-hub.d.ts +30 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -0
- package/dist/types/modes/components/compaction-summary-message.d.ts +10 -4
- package/dist/types/modes/components/custom-editor.d.ts +2 -0
- package/dist/types/modes/components/mcp-add-wizard.d.ts +2 -1
- package/dist/types/modes/components/settings-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +3 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/components/transcript-container.d.ts +3 -2
- package/dist/types/modes/components/ttsr-notification.d.ts +5 -1
- package/dist/types/modes/components/welcome.d.ts +3 -9
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/controllers/tool-args-reveal.d.ts +43 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -2
- package/dist/types/modes/theme/theme.d.ts +3 -1
- package/dist/types/modes/types.d.ts +3 -2
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -1
- package/dist/types/registry/agent-lifecycle.d.ts +51 -0
- package/dist/types/registry/agent-registry.d.ts +16 -5
- package/dist/types/session/agent-session.d.ts +35 -30
- package/dist/types/session/messages.d.ts +2 -4
- package/dist/types/session/session-history-format.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +21 -3
- package/dist/types/session/streaming-output.d.ts +23 -0
- package/dist/types/task/executor.d.ts +11 -2
- package/dist/types/task/index.d.ts +11 -4
- package/dist/types/task/output-manager.d.ts +0 -7
- package/dist/types/task/repair-args.d.ts +8 -7
- package/dist/types/task/types.d.ts +55 -51
- package/dist/types/tools/browser/tab-worker.d.ts +3 -1
- package/dist/types/tools/find.d.ts +0 -11
- package/dist/types/tools/grouped-file-output.d.ts +0 -49
- package/dist/types/tools/index.d.ts +1 -3
- package/dist/types/tools/irc.d.ts +76 -38
- package/dist/types/tools/job.d.ts +7 -1
- package/dist/types/tools/render-utils.d.ts +22 -0
- package/examples/extensions/with-deps/package.json +1 -0
- package/package.json +11 -10
- package/scripts/bundle-dist.ts +28 -19
- package/src/async/index.ts +0 -1
- package/src/capability/mcp.ts +1 -0
- package/src/cli/gallery-cli.ts +6 -5
- package/src/cli/gallery-fixtures/agentic.ts +230 -115
- package/src/cli/gallery-fixtures/types.ts +5 -0
- package/src/cli.ts +20 -6
- package/src/commit/agentic/tools/analyze-file.ts +38 -19
- package/src/config/keybindings.ts +6 -1
- package/src/config/mcp-schema.json +4 -0
- package/src/config/settings-schema.ts +68 -41
- package/src/config/settings.ts +7 -0
- package/src/edit/renderer.ts +96 -46
- package/src/eval/__tests__/agent-bridge.test.ts +5 -3
- package/src/eval/agent-bridge.ts +3 -16
- package/src/eval/js/shared/prelude.txt +1 -1
- package/src/eval/py/prelude.py +5 -6
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +44 -14
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/shared-events.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +9 -9
- package/src/internal-urls/history-protocol.ts +113 -0
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/router.ts +3 -1
- package/src/internal-urls/types.ts +1 -1
- package/src/irc/bus.ts +292 -0
- package/src/main.ts +8 -60
- package/src/mcp/manager.ts +3 -0
- package/src/mcp/oauth-discovery.ts +27 -2
- package/src/mcp/oauth-flow.ts +47 -1
- package/src/mcp/transports/stdio.ts +3 -0
- package/src/mcp/types.ts +2 -0
- package/src/modes/components/{session-observer-overlay.ts → agent-hub.ts} +586 -367
- package/src/modes/components/assistant-message.ts +15 -0
- package/src/modes/components/btw-panel.ts +5 -1
- package/src/modes/components/compaction-summary-message.ts +68 -32
- package/src/modes/components/custom-editor.ts +10 -0
- package/src/modes/components/mcp-add-wizard.ts +13 -0
- package/src/modes/components/settings-selector.ts +2 -0
- package/src/modes/components/status-line/component.ts +22 -12
- package/src/modes/components/status-line/types.ts +3 -0
- package/src/modes/components/tool-execution.ts +31 -1
- package/src/modes/components/transcript-container.ts +99 -18
- package/src/modes/components/tree-selector.ts +6 -1
- package/src/modes/components/ttsr-notification.ts +72 -30
- package/src/modes/components/welcome.ts +9 -33
- package/src/modes/controllers/event-controller.ts +93 -4
- package/src/modes/controllers/extension-ui-controller.ts +8 -8
- package/src/modes/controllers/input-controller.ts +18 -2
- package/src/modes/controllers/mcp-command-controller.ts +34 -2
- package/src/modes/controllers/selector-controller.ts +25 -17
- package/src/modes/controllers/tool-args-reveal.ts +174 -0
- package/src/modes/interactive-mode.ts +17 -15
- package/src/modes/theme/theme.ts +24 -5
- package/src/modes/types.ts +3 -5
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +51 -49
- package/src/prompts/system/irc-incoming.md +3 -4
- package/src/prompts/system/orchestrate-notice.md +2 -2
- package/src/prompts/system/subagent-system-prompt.md +0 -5
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/system/workflow-notice.md +2 -2
- package/src/prompts/tools/eval.md +3 -3
- package/src/prompts/tools/irc.md +29 -19
- package/src/prompts/tools/read.md +2 -2
- package/src/prompts/tools/task-summary.md +5 -16
- package/src/prompts/tools/task.md +43 -29
- package/src/registry/agent-lifecycle.ts +218 -0
- package/src/registry/agent-registry.ts +16 -5
- package/src/sdk.ts +29 -9
- package/src/session/agent-session.ts +268 -241
- package/src/session/messages.ts +11 -78
- package/src/session/session-history-format.ts +246 -0
- package/src/session/session-manager.ts +59 -5
- package/src/session/streaming-output.ts +60 -0
- package/src/task/executor.ts +855 -466
- package/src/task/index.ts +723 -794
- package/src/task/output-manager.ts +0 -11
- package/src/task/render.ts +142 -66
- package/src/task/repair-args.ts +21 -9
- package/src/task/types.ts +73 -66
- package/src/tools/ask.ts +4 -2
- package/src/tools/bash.ts +15 -5
- package/src/tools/browser/tab-worker.ts +26 -7
- package/src/tools/browser.ts +28 -1
- package/src/tools/find.ts +2 -27
- package/src/tools/grouped-file-output.ts +1 -118
- package/src/tools/index.ts +4 -12
- package/src/tools/irc.ts +596 -171
- package/src/tools/job.ts +41 -7
- package/src/tools/read.ts +57 -1
- package/src/tools/render-utils.ts +56 -0
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resolve.ts +4 -1
- package/src/tools/write.ts +65 -47
- package/src/web/search/providers/anthropic.ts +29 -4
- package/dist/types/async/support.d.ts +0 -2
- package/dist/types/modes/components/session-observer-overlay.d.ts +0 -11
- package/dist/types/task/simple-mode.d.ts +0 -8
- package/src/async/support.ts +0 -5
- package/src/task/simple-mode.ts +0 -27
package/src/tools/bash.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { truncateToVisualLines } from "../modes/components/visual-truncate";
|
|
|
17
17
|
import { highlightCode, type Theme } from "../modes/theme/theme";
|
|
18
18
|
import bashDescription from "../prompts/tools/bash.md" with { type: "text" };
|
|
19
19
|
import type { ClientBridgeTerminalExitStatus, ClientBridgeTerminalOutput } from "../session/client-bridge";
|
|
20
|
-
import { DEFAULT_MAX_BYTES, streamTailUpdates, TailBuffer } from "../session/streaming-output";
|
|
20
|
+
import { DEFAULT_MAX_BYTES, enforceInlineByteCap, streamTailUpdates, TailBuffer } from "../session/streaming-output";
|
|
21
21
|
import { renderStatusLine } from "../tui";
|
|
22
22
|
import { CachedOutputBlock, markFramedBlockComponent } from "../tui/output-block";
|
|
23
23
|
import { getSixelLineMask } from "../utils/sixel";
|
|
@@ -429,7 +429,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
429
429
|
}
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
-
#buildCompletedResult(
|
|
432
|
+
async #buildCompletedResult(
|
|
433
433
|
result: BashResult | BashInteractiveResult,
|
|
434
434
|
timeoutSec: number,
|
|
435
435
|
options: {
|
|
@@ -438,7 +438,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
438
438
|
terminalId?: string;
|
|
439
439
|
wallTimeMs?: number;
|
|
440
440
|
} = {},
|
|
441
|
-
): AgentToolResult<BashToolDetails
|
|
441
|
+
): Promise<AgentToolResult<BashToolDetails>> {
|
|
442
442
|
const exitCode = result.exitCode;
|
|
443
443
|
const failedExit = exitCode !== undefined && exitCode !== 0;
|
|
444
444
|
|
|
@@ -472,7 +472,17 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
472
472
|
if (failedExit) {
|
|
473
473
|
details.exitCode = exitCode;
|
|
474
474
|
}
|
|
475
|
-
|
|
475
|
+
// Final defense at the tool-result boundary: no bash path (client bridge,
|
|
476
|
+
// head-retention spill, minimizer miss) may emit more than
|
|
477
|
+
// ~DEFAULT_MAX_BYTES inline. No-op for already-bounded output.
|
|
478
|
+
const cappedOutputText = await enforceInlineByteCap(outputText, {
|
|
479
|
+
label: "bash output",
|
|
480
|
+
saveArtifact: full => saveBashOriginalArtifact(this.session, full),
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const resultBuilder = toolResult(details)
|
|
484
|
+
.text(cappedOutputText)
|
|
485
|
+
.truncationFromSummary(result, { direction: "tail" });
|
|
476
486
|
if (failedExit) resultBuilder.error();
|
|
477
487
|
return resultBuilder.done();
|
|
478
488
|
}
|
|
@@ -560,7 +570,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
560
570
|
onMinimizedSave: originalText => saveBashOriginalArtifact(this.session, originalText),
|
|
561
571
|
});
|
|
562
572
|
const wallTimeMs = performance.now() - wallTimeStart;
|
|
563
|
-
const finalResult = this.#buildCompletedResult(result, options.timeoutSec, {
|
|
573
|
+
const finalResult = await this.#buildCompletedResult(result, options.timeoutSec, {
|
|
564
574
|
requestedTimeoutSec: options.requestedTimeoutSec,
|
|
565
575
|
notices: options.notices ?? [],
|
|
566
576
|
wallTimeMs,
|
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
ElementHandle,
|
|
11
11
|
ElementScreenshotOptions,
|
|
12
12
|
HTTPResponse,
|
|
13
|
+
ImageFormat,
|
|
13
14
|
KeyInput,
|
|
14
15
|
Page,
|
|
15
16
|
SerializedAXNode,
|
|
@@ -436,6 +437,19 @@ export function describeScreenshot(opts?: ScreenshotOptions): string {
|
|
|
436
437
|
return "tab.screenshot()";
|
|
437
438
|
}
|
|
438
439
|
|
|
440
|
+
/** Map an explicit save path's extension to a puppeteer capture format (default png). */
|
|
441
|
+
export function imageFormatForPath(filePath: string): ImageFormat {
|
|
442
|
+
switch (path.extname(filePath).toLowerCase()) {
|
|
443
|
+
case ".webp":
|
|
444
|
+
return "webp";
|
|
445
|
+
case ".jpg":
|
|
446
|
+
case ".jpeg":
|
|
447
|
+
return "jpeg";
|
|
448
|
+
default:
|
|
449
|
+
return "png";
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
439
453
|
/** Summarize still-running helpers (oldest first) so a cell timeout names what stalled. */
|
|
440
454
|
export function describeInflight(inflight: Map<number, InflightOp>): string {
|
|
441
455
|
const now = Date.now();
|
|
@@ -931,6 +945,12 @@ export class WorkerCore {
|
|
|
931
945
|
): Promise<ScreenshotResult> {
|
|
932
946
|
const page = this.#requirePage();
|
|
933
947
|
const fullPage = opts.selector ? false : (opts.fullPage ?? false);
|
|
948
|
+
// An explicit save path picks the full-res capture format: puppeteer encodes
|
|
949
|
+
// png/jpeg/webp natively, so `save: "shot.webp"` gets real WebP bytes instead
|
|
950
|
+
// of PNG bytes hiding behind a .webp name. Unknown/missing extensions stay PNG.
|
|
951
|
+
const explicitPath = opts.save ? resolveToCwd(opts.save, session.cwd) : undefined;
|
|
952
|
+
const captureType = explicitPath ? imageFormatForPath(explicitPath) : "png";
|
|
953
|
+
const captureMime = `image/${captureType}` as const;
|
|
934
954
|
let buffer: Buffer;
|
|
935
955
|
if (opts.selector) {
|
|
936
956
|
const handle = (await untilAborted(signal, () =>
|
|
@@ -951,24 +971,23 @@ export class WorkerCore {
|
|
|
951
971
|
).catch(() => undefined);
|
|
952
972
|
// scrollIntoView:false skips the same IntersectionObserver check inside screenshot();
|
|
953
973
|
// captureBeyondViewport (puppeteer's default) still renders the clipped region.
|
|
954
|
-
const shotOpts: ElementScreenshotOptions = { type:
|
|
974
|
+
const shotOpts: ElementScreenshotOptions = { type: captureType, scrollIntoView: false };
|
|
955
975
|
buffer = (await untilAborted(signal, () => handle.screenshot(shotOpts))) as Buffer;
|
|
956
976
|
} finally {
|
|
957
977
|
await handle.dispose().catch(() => undefined);
|
|
958
978
|
}
|
|
959
979
|
} else {
|
|
960
|
-
buffer = (await untilAborted(signal, () => page.screenshot({ type:
|
|
980
|
+
buffer = (await untilAborted(signal, () => page.screenshot({ type: captureType, fullPage }))) as Buffer;
|
|
961
981
|
}
|
|
962
982
|
const resized = await resizeImage(
|
|
963
|
-
{ type: "image", data: buffer.toBase64(), mimeType:
|
|
983
|
+
{ type: "image", data: buffer.toBase64(), mimeType: captureMime },
|
|
964
984
|
{ maxWidth: 1024, maxHeight: 1024, maxBytes: 150 * 1024, jpegQuality: 70 },
|
|
965
985
|
);
|
|
966
|
-
const explicitPath = opts.save ? resolveToCwd(opts.save, session.cwd) : undefined;
|
|
967
986
|
const saveFullRes = !!(explicitPath || session.browserScreenshotDir);
|
|
968
987
|
const savedBuffer = saveFullRes ? buffer : resized.buffer;
|
|
969
|
-
const savedMimeType = saveFullRes ?
|
|
970
|
-
//
|
|
971
|
-
//
|
|
988
|
+
const savedMimeType = saveFullRes ? captureMime : resized.mimeType;
|
|
989
|
+
// Names must match the bytes we actually write: full-res follows the capture
|
|
990
|
+
// format, the resized buffer is whichever of PNG/JPEG/WebP encoded smallest.
|
|
972
991
|
const ext = savedMimeType === "image/webp" ? "webp" : savedMimeType === "image/jpeg" ? "jpg" : "png";
|
|
973
992
|
const dest =
|
|
974
993
|
explicitPath ??
|
package/src/tools/browser.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
|
3
3
|
import * as z from "zod/v4";
|
|
4
4
|
import browserDescription from "../prompts/tools/browser.md" with { type: "text" };
|
|
5
5
|
import type { ToolSession } from "../sdk";
|
|
6
|
+
import { enforceInlineByteCap } from "../session/streaming-output";
|
|
6
7
|
import { truncateForPrompt } from "./approval";
|
|
7
8
|
import { acquireBrowser, type BrowserHandle, type BrowserKind, type BrowserKindTag } from "./browser/registry";
|
|
8
9
|
import type { Observation, ScreenshotResult } from "./browser/tab-protocol";
|
|
@@ -271,11 +272,37 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
|
|
|
271
272
|
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
272
273
|
.map(c => c.text)
|
|
273
274
|
.join("\n");
|
|
274
|
-
|
|
275
|
+
// Final defense at the tool-result boundary: a single run can display
|
|
276
|
+
// tens of KB (large JSON returns, dumped observations). Cap the combined
|
|
277
|
+
// text inline; the full text stays recoverable via the artifact footer
|
|
278
|
+
// when allocation succeeds.
|
|
279
|
+
const cappedText = await enforceInlineByteCap(textOnly, {
|
|
280
|
+
label: "browser output",
|
|
281
|
+
saveArtifact: full => saveBrowserOutputArtifact(this.session, full),
|
|
282
|
+
});
|
|
283
|
+
details.result = cappedText;
|
|
284
|
+
if (cappedText !== textOnly) {
|
|
285
|
+
const nonText = content.filter(c => c.type !== "text");
|
|
286
|
+
return toolResult(details)
|
|
287
|
+
.content([...nonText, { type: "text", text: cappedText }])
|
|
288
|
+
.done();
|
|
289
|
+
}
|
|
275
290
|
return toolResult(details).content(content).done();
|
|
276
291
|
}
|
|
277
292
|
}
|
|
278
293
|
|
|
294
|
+
/** Persist over-cap browser run output as a session artifact; mirrors the bash minimizer's save path. */
|
|
295
|
+
async function saveBrowserOutputArtifact(session: ToolSession, fullText: string): Promise<string | undefined> {
|
|
296
|
+
try {
|
|
297
|
+
const alloc = await session.allocateOutputArtifact?.("browser-original");
|
|
298
|
+
if (!alloc?.path || !alloc.id) return undefined;
|
|
299
|
+
await Bun.write(alloc.path, fullText);
|
|
300
|
+
return alloc.id;
|
|
301
|
+
} catch {
|
|
302
|
+
return undefined;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
279
306
|
function describeBrowser(handle: BrowserHandle): string {
|
|
280
307
|
switch (handle.kind.kind) {
|
|
281
308
|
case "headless":
|
package/src/tools/find.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
4
4
|
import * as natives from "@oh-my-pi/pi-natives";
|
|
5
5
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
6
6
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
7
|
-
import { isEnoent, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
7
|
+
import { formatGroupedPaths, isEnoent, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
8
8
|
import * as z from "zod/v4";
|
|
9
9
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
10
10
|
import { InternalUrlRouter } from "../internal-urls";
|
|
@@ -13,7 +13,6 @@ import findDescription from "../prompts/tools/find.md" with { type: "text" };
|
|
|
13
13
|
import { type TruncationResult, truncateHead } from "../session/streaming-output";
|
|
14
14
|
import { Ellipsis, fileHyperlink, renderFileList, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
|
|
15
15
|
import type { ToolSession } from ".";
|
|
16
|
-
import { buildPathTree, walkPathTree } from "./grouped-file-output";
|
|
17
16
|
import { applyListLimit } from "./list-limit";
|
|
18
17
|
import { formatFullOutputReference, type OutputMeta } from "./output-meta";
|
|
19
18
|
import {
|
|
@@ -54,30 +53,6 @@ const DEFAULT_GLOB_TIMEOUT_MS = 5000;
|
|
|
54
53
|
const MIN_GLOB_TIMEOUT_MS = 500;
|
|
55
54
|
const MAX_GLOB_TIMEOUT_MS = 60_000;
|
|
56
55
|
|
|
57
|
-
/**
|
|
58
|
-
* Group find matches into a multi-level directory tree so the model doesn't pay
|
|
59
|
-
* repeated tokens for shared path prefixes. Single-child directory chains fold
|
|
60
|
-
* into one header (`# a/b/c/`), so a common prefix — including an absolute root
|
|
61
|
-
* for out-of-cwd results — collapses to a single line. Each level adds one `#`;
|
|
62
|
-
* files are listed bare under the deepest directory header that owns them.
|
|
63
|
-
*
|
|
64
|
-
* Order follows the input (mtime-desc for native glob): a directory appears when
|
|
65
|
-
* its first member is emitted, and a node's own files precede its subdirectories.
|
|
66
|
-
*/
|
|
67
|
-
export function formatFindGroupedOutput(paths: readonly string[]): string {
|
|
68
|
-
if (paths.length === 0) return "";
|
|
69
|
-
const tree = buildPathTree(paths.map(entry => ({ path: entry, isDir: entry.endsWith("/") })));
|
|
70
|
-
const lines: string[] = [];
|
|
71
|
-
for (const event of walkPathTree(tree)) {
|
|
72
|
-
if (event.kind === "dir") {
|
|
73
|
-
lines.push(`${"#".repeat(event.depth + 1)} ${event.name}/`);
|
|
74
|
-
} else {
|
|
75
|
-
lines.push(event.name);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return lines.join("\n");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
56
|
export interface FindToolDetails {
|
|
82
57
|
truncation?: TruncationResult;
|
|
83
58
|
resultLimitReached?: number;
|
|
@@ -270,7 +245,7 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
270
245
|
const listLimit = applyListLimit(files, { limit: effectiveLimit });
|
|
271
246
|
const limited = listLimit.items;
|
|
272
247
|
const limitMeta = listLimit.meta;
|
|
273
|
-
const baseOutput =
|
|
248
|
+
const baseOutput = formatGroupedPaths(limited);
|
|
274
249
|
const trailingNotes: string[] = [];
|
|
275
250
|
if (notice) trailingNotes.push(notice);
|
|
276
251
|
if (missingPathsNote) trailingNotes.push(missingPathsNote);
|
|
@@ -1,123 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
function isUrlLikePath(filePath: string): boolean {
|
|
6
|
-
return URL_LIKE_PATH_RE.test(filePath);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// =============================================================================
|
|
10
|
-
// Multi-level path tree
|
|
11
|
-
// =============================================================================
|
|
12
|
-
//
|
|
13
|
-
// File listings (grep / ast-grep / ast-edit / lsp diagnostics / find) used to
|
|
14
|
-
// group by the *immediate* parent directory and print the full directory path in
|
|
15
|
-
// every header. For results spread across a deep tree — or rooted outside cwd,
|
|
16
|
-
// where paths stay absolute — that repeated the shared prefix on every line. The
|
|
17
|
-
// tree below folds single-child directory chains (so the common prefix collapses
|
|
18
|
-
// into one header) and nests the rest, charging the model one token per path
|
|
19
|
-
// segment instead of one per file.
|
|
20
|
-
|
|
21
|
-
interface PathTreeNode {
|
|
22
|
-
/** Direct file leaves, in first-seen order. */
|
|
23
|
-
files: Array<{ name: string; key: string }>;
|
|
24
|
-
/** Dedup set for `files` (a glob can surface the same path twice on retry). */
|
|
25
|
-
fileNames: Set<string>;
|
|
26
|
-
/** Child directories, in first-seen order. */
|
|
27
|
-
subdirs: Array<{ name: string; node: PathTreeNode }>;
|
|
28
|
-
/** Dedup index for `subdirs`. */
|
|
29
|
-
dirIndex: Map<string, PathTreeNode>;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface PathTreeInput {
|
|
33
|
-
/** Path string; absolute, cwd-relative, or url-like. Backslashes are normalized. */
|
|
34
|
-
path: string;
|
|
35
|
-
/** Whether the leaf itself is a directory (trailing-slash match from find). */
|
|
36
|
-
isDir: boolean;
|
|
37
|
-
/** Opaque key carried onto file events for section lookup. Defaults to `path`. */
|
|
38
|
-
key?: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** One node emitted while walking the tree: a folded directory or a file leaf. */
|
|
42
|
-
export interface GroupedTreeEvent {
|
|
43
|
-
kind: "dir" | "file";
|
|
44
|
-
/** 0-based nesting depth (root children are depth 0). */
|
|
45
|
-
depth: number;
|
|
46
|
-
/** Folded chain for dirs (e.g. `a/b/c`, no trailing slash); basename for files. */
|
|
47
|
-
name: string;
|
|
48
|
-
/** File key for `kind === "file"`; empty string for directories. */
|
|
49
|
-
key: string;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function createNode(): PathTreeNode {
|
|
53
|
-
return { files: [], fileNames: new Set(), subdirs: [], dirIndex: new Map() };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function addFile(node: PathTreeNode, name: string, key: string): void {
|
|
57
|
-
if (node.fileNames.has(name)) return;
|
|
58
|
-
node.fileNames.add(name);
|
|
59
|
-
node.files.push({ name, key });
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Build a directory tree from a flat list of paths. URL-like entries are kept
|
|
64
|
-
* whole as root-level file leaves (they have no meaningful directory structure).
|
|
65
|
-
* Absolute paths carry a leading empty segment so they share a common `/` root
|
|
66
|
-
* and fold like any other prefix.
|
|
67
|
-
*/
|
|
68
|
-
export function buildPathTree(entries: Iterable<PathTreeInput>): PathTreeNode {
|
|
69
|
-
const root = createNode();
|
|
70
|
-
for (const { path: rawPath, isDir, key } of entries) {
|
|
71
|
-
const normalized = rawPath.replace(/\\/g, "/");
|
|
72
|
-
const fileKey = key ?? rawPath;
|
|
73
|
-
if (isUrlLikePath(normalized)) {
|
|
74
|
-
addFile(root, normalized, fileKey);
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
const trimmed = normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
78
|
-
if (trimmed.length === 0) continue;
|
|
79
|
-
const segments = trimmed.split("/");
|
|
80
|
-
const dirCount = isDir ? segments.length : segments.length - 1;
|
|
81
|
-
let node = root;
|
|
82
|
-
for (let i = 0; i < dirCount; i++) {
|
|
83
|
-
const segment = segments[i]!;
|
|
84
|
-
let child = node.dirIndex.get(segment);
|
|
85
|
-
if (!child) {
|
|
86
|
-
child = createNode();
|
|
87
|
-
node.dirIndex.set(segment, child);
|
|
88
|
-
node.subdirs.push({ name: segment, node: child });
|
|
89
|
-
}
|
|
90
|
-
node = child;
|
|
91
|
-
}
|
|
92
|
-
if (!isDir) {
|
|
93
|
-
addFile(node, segments[segments.length - 1]!, fileKey);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return root;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Depth-first walk yielding directory and file events. Directories collapse their
|
|
101
|
-
* single-child chains (`a` → `a/b` → `a/b/c`) so a shared prefix becomes one
|
|
102
|
-
* header. Each node's direct files are emitted before its subdirectories, keeping
|
|
103
|
-
* a file unambiguously attached to the header above it.
|
|
104
|
-
*/
|
|
105
|
-
export function* walkPathTree(node: PathTreeNode, depth = 0): Generator<GroupedTreeEvent> {
|
|
106
|
-
for (const file of node.files) {
|
|
107
|
-
yield { kind: "file", depth, name: file.name, key: file.key };
|
|
108
|
-
}
|
|
109
|
-
for (const subdir of node.subdirs) {
|
|
110
|
-
let dirNode = subdir.node;
|
|
111
|
-
const parts = [subdir.name];
|
|
112
|
-
while (dirNode.files.length === 0 && dirNode.subdirs.length === 1) {
|
|
113
|
-
const only = dirNode.subdirs[0]!;
|
|
114
|
-
parts.push(only.name);
|
|
115
|
-
dirNode = only.node;
|
|
116
|
-
}
|
|
117
|
-
yield { kind: "dir", depth, name: parts.join("/"), key: "" };
|
|
118
|
-
yield* walkPathTree(dirNode, depth + 1);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
3
|
+
import { buildPathTree, isUrlLikePath, type PathTreeInput, walkPathTree } from "@oh-my-pi/pi-utils";
|
|
121
4
|
|
|
122
5
|
// =============================================================================
|
|
123
6
|
// Grouped file output (grep / ast-grep / ast-edit / lsp diagnostics)
|
package/src/tools/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { LspTool } from "../lsp";
|
|
|
18
18
|
import type { MCPManager } from "../mcp";
|
|
19
19
|
import type { MnemopiSessionState } from "../mnemopi/state";
|
|
20
20
|
import type { PlanModeState } from "../plan-mode/state";
|
|
21
|
-
import {
|
|
21
|
+
import type { AgentRegistry } from "../registry/agent-registry";
|
|
22
22
|
import type { ArtifactManager } from "../session/artifacts";
|
|
23
23
|
import type { ClientBridge } from "../session/client-bridge";
|
|
24
24
|
import type { CustomMessage } from "../session/messages";
|
|
@@ -42,7 +42,7 @@ import { resolveEvalBackends } from "./eval-backends";
|
|
|
42
42
|
import { FindTool } from "./find";
|
|
43
43
|
import { GithubTool } from "./gh";
|
|
44
44
|
import { InspectImageTool } from "./inspect-image";
|
|
45
|
-
import { IrcTool } from "./irc";
|
|
45
|
+
import { IrcTool, isIrcEnabled } from "./irc";
|
|
46
46
|
import { JobTool } from "./job";
|
|
47
47
|
import { MemoryEditTool } from "./memory-edit";
|
|
48
48
|
import { MemoryRecallTool } from "./memory-recall";
|
|
@@ -260,8 +260,6 @@ export interface ToolSession {
|
|
|
260
260
|
recordEvalSubagentUsage?: (output: number) => void;
|
|
261
261
|
/** Bridge to the connected client (e.g. ACP editor host). Tools should route fs/terminal/permission requests through this when available. */
|
|
262
262
|
getClientBridge?: () => ClientBridge | undefined;
|
|
263
|
-
/** Get compact conversation context for subagents (excludes tool results, system prompts) */
|
|
264
|
-
getCompactContext?: () => string;
|
|
265
263
|
/** Get cached todo phases for this session. */
|
|
266
264
|
getTodoPhases?: () => TodoPhase[];
|
|
267
265
|
/** Replace cached todo phases for this session. */
|
|
@@ -420,7 +418,7 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
|
420
418
|
checkpoint: CheckpointTool.createIf,
|
|
421
419
|
rewind: RewindTool.createIf,
|
|
422
420
|
task: s => TaskTool.create(s),
|
|
423
|
-
job: JobTool
|
|
421
|
+
job: s => new JobTool(s),
|
|
424
422
|
irc: IrcTool.createIf,
|
|
425
423
|
todo: s => new TodoTool(s),
|
|
426
424
|
web_search: s => new WebSearchTool(s),
|
|
@@ -539,13 +537,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
539
537
|
if (name === "search_tool_bm25") return discoveryActive;
|
|
540
538
|
if (name === "browser") return session.settings.get("browser.enabled");
|
|
541
539
|
if (name === "checkpoint" || name === "rewind") return session.settings.get("checkpoint.enabled");
|
|
542
|
-
if (name === "irc")
|
|
543
|
-
if (!session.settings.get("irc.enabled")) return false;
|
|
544
|
-
// Main agent only needs `irc` when subagents may run concurrently (async).
|
|
545
|
-
// In sync mode main blocks on `task`, so peer messaging from main is dead weight.
|
|
546
|
-
if (!session.settings.get("async.enabled") && session.getAgentId?.() === MAIN_AGENT_ID) return false;
|
|
547
|
-
return true;
|
|
548
|
-
}
|
|
540
|
+
if (name === "irc") return isIrcEnabled(session.settings, session.taskDepth ?? 0);
|
|
549
541
|
if (name === "retain" || name === "recall" || name === "reflect") {
|
|
550
542
|
return ["hindsight", "mnemopi"].includes(session.settings.get("memory.backend") ?? "");
|
|
551
543
|
}
|