@oh-my-pi/pi-coding-agent 3.20.1 → 3.24.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 +107 -8
- package/docs/custom-tools.md +3 -3
- package/docs/extensions.md +226 -220
- package/docs/hooks.md +2 -2
- package/docs/sdk.md +50 -53
- package/examples/custom-tools/README.md +2 -17
- package/examples/extensions/README.md +76 -74
- package/examples/extensions/todo.ts +2 -5
- package/examples/hooks/custom-compaction.ts +2 -4
- package/examples/hooks/handoff.ts +1 -1
- package/examples/hooks/qna.ts +1 -1
- package/examples/sdk/02-custom-model.ts +1 -1
- package/examples/sdk/README.md +7 -11
- package/package.json +6 -6
- package/src/cli/args.ts +9 -6
- package/src/cli/file-processor.ts +1 -1
- package/src/cli/list-models.ts +1 -1
- package/src/core/agent-session.ts +16 -5
- package/src/core/auth-storage.ts +1 -1
- package/src/core/compaction/branch-summarization.ts +2 -2
- package/src/core/compaction/compaction.ts +2 -2
- package/src/core/compaction/utils.ts +1 -1
- package/src/core/custom-tools/types.ts +1 -1
- package/src/core/custom-tools/wrapper.ts +0 -1
- package/src/core/extensions/index.ts +1 -6
- package/src/core/extensions/runner.ts +1 -1
- package/src/core/extensions/types.ts +1 -1
- package/src/core/extensions/wrapper.ts +1 -8
- package/src/core/file-mentions.ts +5 -8
- package/src/core/hooks/runner.ts +2 -2
- package/src/core/hooks/types.ts +1 -1
- package/src/core/messages.ts +1 -1
- package/src/core/model-registry.ts +1 -1
- package/src/core/model-resolver.ts +1 -1
- package/src/core/sdk.ts +64 -105
- package/src/core/session-manager.ts +18 -22
- package/src/core/settings-manager.ts +66 -1
- package/src/core/slash-commands.ts +12 -5
- package/src/core/system-prompt.ts +49 -36
- package/src/core/title-generator.ts +2 -2
- package/src/core/tools/ask.ts +98 -4
- package/src/core/tools/bash-interceptor.ts +11 -4
- package/src/core/tools/bash.ts +121 -5
- package/src/core/tools/context.ts +7 -0
- package/src/core/tools/edit-diff.ts +73 -24
- package/src/core/tools/edit.ts +221 -34
- package/src/core/tools/exa/render.ts +4 -16
- package/src/core/tools/find.ts +149 -5
- package/src/core/tools/gemini-image.ts +279 -56
- package/src/core/tools/git.ts +17 -3
- package/src/core/tools/grep.ts +185 -5
- package/src/core/tools/index.test.ts +180 -0
- package/src/core/tools/index.ts +96 -242
- package/src/core/tools/ls.ts +133 -5
- package/src/core/tools/lsp/index.ts +32 -29
- package/src/core/tools/lsp/render.ts +21 -22
- package/src/core/tools/notebook.ts +112 -4
- package/src/core/tools/output.ts +175 -15
- package/src/core/tools/read.ts +127 -25
- package/src/core/tools/render-utils.ts +241 -0
- package/src/core/tools/renderers.ts +40 -828
- package/src/core/tools/review.ts +26 -25
- package/src/core/tools/rulebook.ts +11 -3
- package/src/core/tools/task/agents.ts +28 -7
- package/src/core/tools/task/discovery.ts +0 -6
- package/src/core/tools/task/executor.ts +264 -254
- package/src/core/tools/task/index.ts +48 -208
- package/src/core/tools/task/render.ts +26 -11
- package/src/core/tools/task/types.ts +7 -12
- package/src/core/tools/task/worker-protocol.ts +17 -0
- package/src/core/tools/task/worker.ts +238 -0
- package/src/core/tools/truncate.ts +27 -1
- package/src/core/tools/web-fetch.ts +25 -49
- package/src/core/tools/web-search/index.ts +132 -46
- package/src/core/tools/web-search/providers/anthropic.ts +7 -2
- package/src/core/tools/web-search/providers/exa.ts +2 -1
- package/src/core/tools/web-search/providers/perplexity.ts +6 -1
- package/src/core/tools/web-search/render.ts +6 -4
- package/src/core/tools/web-search/types.ts +13 -0
- package/src/core/tools/write.ts +96 -14
- package/src/core/voice.ts +1 -1
- package/src/discovery/helpers.test.ts +1 -1
- package/src/index.ts +5 -16
- package/src/main.ts +5 -5
- package/src/modes/interactive/components/assistant-message.ts +1 -1
- package/src/modes/interactive/components/custom-message.ts +1 -1
- package/src/modes/interactive/components/extensions/inspector-panel.ts +25 -22
- package/src/modes/interactive/components/extensions/state-manager.ts +12 -0
- package/src/modes/interactive/components/footer.ts +1 -1
- package/src/modes/interactive/components/hook-message.ts +1 -1
- package/src/modes/interactive/components/model-selector.ts +1 -1
- package/src/modes/interactive/components/oauth-selector.ts +1 -1
- package/src/modes/interactive/components/settings-defs.ts +49 -0
- package/src/modes/interactive/components/status-line.ts +1 -1
- package/src/modes/interactive/components/tool-execution.ts +93 -538
- package/src/modes/interactive/interactive-mode.ts +19 -7
- package/src/modes/interactive/theme/theme.ts +4 -4
- package/src/modes/print-mode.ts +1 -1
- package/src/modes/rpc/rpc-client.ts +1 -1
- package/src/modes/rpc/rpc-types.ts +1 -1
- package/src/prompts/system-prompt.md +4 -0
- package/src/prompts/task.md +0 -7
- package/src/prompts/tools/gemini-image.md +5 -1
- package/src/prompts/tools/output.md +6 -2
- package/src/prompts/tools/task.md +68 -0
- package/src/prompts/tools/web-fetch.md +1 -0
- package/src/prompts/tools/web-search.md +2 -0
- package/src/utils/image-convert.ts +8 -2
- package/src/utils/image-magick.ts +247 -0
- package/src/utils/image-resize.ts +53 -13
- package/examples/custom-tools/question/index.ts +0 -84
- package/examples/custom-tools/subagent/README.md +0 -172
- package/examples/custom-tools/subagent/agents/planner.md +0 -37
- package/examples/custom-tools/subagent/agents/scout.md +0 -50
- package/examples/custom-tools/subagent/agents/worker.md +0 -24
- package/examples/custom-tools/subagent/agents.ts +0 -156
- package/examples/custom-tools/subagent/commands/implement-and-review.md +0 -10
- package/examples/custom-tools/subagent/commands/implement.md +0 -10
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +0 -9
- package/examples/custom-tools/subagent/index.ts +0 -1002
- package/examples/sdk/05-tools.ts +0 -94
- package/examples/sdk/12-full-control.ts +0 -95
- package/src/prompts/browser.md +0 -71
package/src/core/tools/grep.ts
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
import { readFileSync, type Stats, statSync } from "node:fs";
|
|
2
2
|
import nodePath from "node:path";
|
|
3
3
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
4
|
+
import type { Component } from "@oh-my-pi/pi-tui";
|
|
5
|
+
import { Text } from "@oh-my-pi/pi-tui";
|
|
4
6
|
import { Type } from "@sinclair/typebox";
|
|
5
7
|
import type { Subprocess } from "bun";
|
|
8
|
+
import { getLanguageFromPath, type Theme } from "../../modes/interactive/theme/theme";
|
|
6
9
|
import grepDescription from "../../prompts/tools/grep.md" with { type: "text" };
|
|
7
10
|
import { ensureTool } from "../../utils/tools-manager";
|
|
11
|
+
import type { RenderResultOptions } from "../custom-tools/types";
|
|
12
|
+
import type { ToolSession } from "./index";
|
|
8
13
|
import { resolveToCwd } from "./path-utils";
|
|
14
|
+
import {
|
|
15
|
+
formatCount,
|
|
16
|
+
formatEmptyMessage,
|
|
17
|
+
formatErrorMessage,
|
|
18
|
+
formatExpandHint,
|
|
19
|
+
formatMeta,
|
|
20
|
+
formatMoreItems,
|
|
21
|
+
formatScope,
|
|
22
|
+
formatTruncationSuffix,
|
|
23
|
+
PREVIEW_LIMITS,
|
|
24
|
+
} from "./render-utils";
|
|
9
25
|
import {
|
|
10
26
|
DEFAULT_MAX_BYTES,
|
|
11
27
|
formatSize,
|
|
@@ -64,7 +80,7 @@ export interface GrepToolDetails {
|
|
|
64
80
|
error?: string;
|
|
65
81
|
}
|
|
66
82
|
|
|
67
|
-
export function createGrepTool(
|
|
83
|
+
export function createGrepTool(session: ToolSession): AgentTool<typeof grepSchema> {
|
|
68
84
|
return {
|
|
69
85
|
name: "grep",
|
|
70
86
|
label: "Grep",
|
|
@@ -112,9 +128,9 @@ export function createGrepTool(cwd: string): AgentTool<typeof grepSchema> {
|
|
|
112
128
|
throw new Error("ripgrep (rg) is not available and could not be downloaded");
|
|
113
129
|
}
|
|
114
130
|
|
|
115
|
-
const searchPath = resolveToCwd(searchDir || ".", cwd);
|
|
131
|
+
const searchPath = resolveToCwd(searchDir || ".", session.cwd);
|
|
116
132
|
const scopePath = (() => {
|
|
117
|
-
const relative = nodePath.relative(cwd, searchPath).replace(/\\/g, "/");
|
|
133
|
+
const relative = nodePath.relative(session.cwd, searchPath).replace(/\\/g, "/");
|
|
118
134
|
return relative.length === 0 ? "." : relative;
|
|
119
135
|
})();
|
|
120
136
|
let searchStat: Stats;
|
|
@@ -580,5 +596,169 @@ export function createGrepTool(cwd: string): AgentTool<typeof grepSchema> {
|
|
|
580
596
|
};
|
|
581
597
|
}
|
|
582
598
|
|
|
583
|
-
|
|
584
|
-
|
|
599
|
+
// =============================================================================
|
|
600
|
+
// TUI Renderer
|
|
601
|
+
// =============================================================================
|
|
602
|
+
|
|
603
|
+
interface GrepRenderArgs {
|
|
604
|
+
pattern: string;
|
|
605
|
+
path?: string;
|
|
606
|
+
glob?: string;
|
|
607
|
+
type?: string;
|
|
608
|
+
ignoreCase?: boolean;
|
|
609
|
+
caseSensitive?: boolean;
|
|
610
|
+
literal?: boolean;
|
|
611
|
+
multiline?: boolean;
|
|
612
|
+
context?: number;
|
|
613
|
+
limit?: number;
|
|
614
|
+
outputMode?: string;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const COLLAPSED_LIST_LIMIT = PREVIEW_LIMITS.COLLAPSED_ITEMS;
|
|
618
|
+
const COLLAPSED_TEXT_LIMIT = PREVIEW_LIMITS.COLLAPSED_LINES * 2;
|
|
619
|
+
|
|
620
|
+
export const grepToolRenderer = {
|
|
621
|
+
renderCall(args: GrepRenderArgs, uiTheme: Theme): Component {
|
|
622
|
+
const label = uiTheme.fg("toolTitle", uiTheme.bold("Grep"));
|
|
623
|
+
let text = `${label} ${uiTheme.fg("accent", args.pattern || "?")}`;
|
|
624
|
+
|
|
625
|
+
const meta: string[] = [];
|
|
626
|
+
if (args.path) meta.push(`in ${args.path}`);
|
|
627
|
+
if (args.glob) meta.push(`glob:${args.glob}`);
|
|
628
|
+
if (args.type) meta.push(`type:${args.type}`);
|
|
629
|
+
if (args.outputMode && args.outputMode !== "files_with_matches") meta.push(`mode:${args.outputMode}`);
|
|
630
|
+
if (args.caseSensitive) {
|
|
631
|
+
meta.push("case:sensitive");
|
|
632
|
+
} else if (args.ignoreCase) {
|
|
633
|
+
meta.push("case:insensitive");
|
|
634
|
+
}
|
|
635
|
+
if (args.literal) meta.push("literal");
|
|
636
|
+
if (args.multiline) meta.push("multiline");
|
|
637
|
+
if (args.context !== undefined) meta.push(`context:${args.context}`);
|
|
638
|
+
if (args.limit !== undefined) meta.push(`limit:${args.limit}`);
|
|
639
|
+
|
|
640
|
+
text += formatMeta(meta, uiTheme);
|
|
641
|
+
|
|
642
|
+
return new Text(text, 0, 0);
|
|
643
|
+
},
|
|
644
|
+
|
|
645
|
+
renderResult(
|
|
646
|
+
result: { content: Array<{ type: string; text?: string }>; details?: GrepToolDetails },
|
|
647
|
+
{ expanded }: RenderResultOptions,
|
|
648
|
+
uiTheme: Theme,
|
|
649
|
+
): Component {
|
|
650
|
+
const details = result.details;
|
|
651
|
+
|
|
652
|
+
if (details?.error) {
|
|
653
|
+
return new Text(formatErrorMessage(details.error, uiTheme), 0, 0);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const hasDetailedData = details?.matchCount !== undefined || details?.fileCount !== undefined;
|
|
657
|
+
|
|
658
|
+
if (!hasDetailedData) {
|
|
659
|
+
const textContent = result.content?.find((c) => c.type === "text")?.text;
|
|
660
|
+
if (!textContent || textContent === "No matches found") {
|
|
661
|
+
return new Text(formatEmptyMessage("No matches found", uiTheme), 0, 0);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const lines = textContent.split("\n").filter((line) => line.trim() !== "");
|
|
665
|
+
const maxLines = expanded ? lines.length : Math.min(lines.length, COLLAPSED_TEXT_LIMIT);
|
|
666
|
+
const displayLines = lines.slice(0, maxLines);
|
|
667
|
+
const remaining = lines.length - maxLines;
|
|
668
|
+
const hasMore = remaining > 0;
|
|
669
|
+
|
|
670
|
+
const icon = uiTheme.styledSymbol("status.success", "success");
|
|
671
|
+
const summary = formatCount("item", lines.length);
|
|
672
|
+
const expandHint = formatExpandHint(expanded, hasMore, uiTheme);
|
|
673
|
+
let text = `${icon} ${uiTheme.fg("dim", summary)}${expandHint}`;
|
|
674
|
+
|
|
675
|
+
for (let i = 0; i < displayLines.length; i++) {
|
|
676
|
+
const isLast = i === displayLines.length - 1 && remaining === 0;
|
|
677
|
+
const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
678
|
+
text += `\n ${uiTheme.fg("dim", branch)} ${uiTheme.fg("toolOutput", displayLines[i])}`;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if (remaining > 0) {
|
|
682
|
+
text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg("muted", formatMoreItems(remaining, "item", uiTheme))}`;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
return new Text(text, 0, 0);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const matchCount = details?.matchCount ?? 0;
|
|
689
|
+
const fileCount = details?.fileCount ?? 0;
|
|
690
|
+
const mode = details?.mode ?? "files_with_matches";
|
|
691
|
+
const truncated = details?.truncated ?? details?.truncation?.truncated ?? false;
|
|
692
|
+
const files = details?.files ?? [];
|
|
693
|
+
|
|
694
|
+
if (matchCount === 0) {
|
|
695
|
+
return new Text(formatEmptyMessage("No matches found", uiTheme), 0, 0);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const icon = uiTheme.styledSymbol("status.success", "success");
|
|
699
|
+
const summaryParts =
|
|
700
|
+
mode === "files_with_matches"
|
|
701
|
+
? [formatCount("file", fileCount)]
|
|
702
|
+
: [formatCount("match", matchCount), formatCount("file", fileCount)];
|
|
703
|
+
const summaryText = summaryParts.join(uiTheme.sep.dot);
|
|
704
|
+
const scopeLabel = formatScope(details?.scopePath, uiTheme);
|
|
705
|
+
|
|
706
|
+
const fileEntries: Array<{ path: string; count?: number }> = details?.fileMatches?.length
|
|
707
|
+
? details.fileMatches.map((entry) => ({ path: entry.path, count: entry.count }))
|
|
708
|
+
: files.map((path) => ({ path }));
|
|
709
|
+
const maxFiles = expanded ? fileEntries.length : Math.min(fileEntries.length, COLLAPSED_LIST_LIMIT);
|
|
710
|
+
const hasMoreFiles = fileEntries.length > maxFiles;
|
|
711
|
+
const expandHint = formatExpandHint(expanded, hasMoreFiles, uiTheme);
|
|
712
|
+
|
|
713
|
+
let text = `${icon} ${uiTheme.fg("dim", summaryText)}${formatTruncationSuffix(truncated, uiTheme)}${scopeLabel}${expandHint}`;
|
|
714
|
+
|
|
715
|
+
const truncationReasons: string[] = [];
|
|
716
|
+
if (details?.matchLimitReached) {
|
|
717
|
+
truncationReasons.push(`limit ${details.matchLimitReached} matches`);
|
|
718
|
+
}
|
|
719
|
+
if (details?.headLimitReached) {
|
|
720
|
+
truncationReasons.push(`head limit ${details.headLimitReached}`);
|
|
721
|
+
}
|
|
722
|
+
if (details?.truncation?.truncated) {
|
|
723
|
+
truncationReasons.push("size limit");
|
|
724
|
+
}
|
|
725
|
+
if (details?.linesTruncated) {
|
|
726
|
+
truncationReasons.push("line length");
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const hasTruncation = truncationReasons.length > 0;
|
|
730
|
+
|
|
731
|
+
if (fileEntries.length > 0) {
|
|
732
|
+
for (let i = 0; i < maxFiles; i++) {
|
|
733
|
+
const entry = fileEntries[i];
|
|
734
|
+
const isLast = i === maxFiles - 1 && !hasMoreFiles && !hasTruncation;
|
|
735
|
+
const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
736
|
+
const isDir = entry.path.endsWith("/");
|
|
737
|
+
const entryPath = isDir ? entry.path.slice(0, -1) : entry.path;
|
|
738
|
+
const lang = isDir ? undefined : getLanguageFromPath(entryPath);
|
|
739
|
+
const entryIcon = isDir
|
|
740
|
+
? uiTheme.fg("accent", uiTheme.icon.folder)
|
|
741
|
+
: uiTheme.fg("muted", uiTheme.getLangIcon(lang));
|
|
742
|
+
const countLabel =
|
|
743
|
+
entry.count !== undefined
|
|
744
|
+
? ` ${uiTheme.fg("dim", `(${entry.count} match${entry.count !== 1 ? "es" : ""})`)}`
|
|
745
|
+
: "";
|
|
746
|
+
text += `\n ${uiTheme.fg("dim", branch)} ${entryIcon} ${uiTheme.fg("accent", entry.path)}${countLabel}`;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
if (hasMoreFiles) {
|
|
750
|
+
const moreFilesBranch = hasTruncation ? uiTheme.tree.branch : uiTheme.tree.last;
|
|
751
|
+
text += `\n ${uiTheme.fg("dim", moreFilesBranch)} ${uiTheme.fg(
|
|
752
|
+
"muted",
|
|
753
|
+
formatMoreItems(fileEntries.length - maxFiles, "file", uiTheme),
|
|
754
|
+
)}`;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
if (hasTruncation) {
|
|
759
|
+
text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg("warning", `truncated: ${truncationReasons.join(", ")}`)}`;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
return new Text(text, 0, 0);
|
|
763
|
+
},
|
|
764
|
+
};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { BUILTIN_TOOLS, createTools, HIDDEN_TOOLS, type ToolSession } from "./index";
|
|
3
|
+
|
|
4
|
+
function createTestSession(overrides: Partial<ToolSession> = {}): ToolSession {
|
|
5
|
+
return {
|
|
6
|
+
cwd: "/tmp/test",
|
|
7
|
+
hasUI: false,
|
|
8
|
+
rulebookRules: [],
|
|
9
|
+
getSessionFile: () => null,
|
|
10
|
+
getSessionSpawns: () => "*",
|
|
11
|
+
...overrides,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe("createTools", () => {
|
|
16
|
+
it("creates all builtin tools by default", async () => {
|
|
17
|
+
const session = createTestSession();
|
|
18
|
+
const tools = await createTools(session);
|
|
19
|
+
const names = tools.map((t) => t.name);
|
|
20
|
+
|
|
21
|
+
// Core tools should always be present
|
|
22
|
+
expect(names).toContain("bash");
|
|
23
|
+
expect(names).toContain("read");
|
|
24
|
+
expect(names).toContain("edit");
|
|
25
|
+
expect(names).toContain("write");
|
|
26
|
+
expect(names).toContain("grep");
|
|
27
|
+
expect(names).toContain("find");
|
|
28
|
+
expect(names).toContain("ls");
|
|
29
|
+
expect(names).toContain("lsp");
|
|
30
|
+
expect(names).toContain("notebook");
|
|
31
|
+
expect(names).toContain("task");
|
|
32
|
+
expect(names).toContain("output");
|
|
33
|
+
expect(names).toContain("web_fetch");
|
|
34
|
+
expect(names).toContain("web_search");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("respects requested tool subset", async () => {
|
|
38
|
+
const session = createTestSession();
|
|
39
|
+
const tools = await createTools(session, ["read", "write"]);
|
|
40
|
+
const names = tools.map((t) => t.name);
|
|
41
|
+
|
|
42
|
+
expect(names).toEqual(["read", "write"]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("includes hidden tools when explicitly requested", async () => {
|
|
46
|
+
const session = createTestSession();
|
|
47
|
+
const tools = await createTools(session, ["report_finding"]);
|
|
48
|
+
const names = tools.map((t) => t.name);
|
|
49
|
+
|
|
50
|
+
expect(names).toEqual(["report_finding"]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("excludes ask tool when hasUI is false", async () => {
|
|
54
|
+
const session = createTestSession({ hasUI: false });
|
|
55
|
+
const tools = await createTools(session);
|
|
56
|
+
const names = tools.map((t) => t.name);
|
|
57
|
+
|
|
58
|
+
expect(names).not.toContain("ask");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("includes ask tool when hasUI is true", async () => {
|
|
62
|
+
const session = createTestSession({ hasUI: true });
|
|
63
|
+
const tools = await createTools(session);
|
|
64
|
+
const names = tools.map((t) => t.name);
|
|
65
|
+
|
|
66
|
+
expect(names).toContain("ask");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("excludes rulebook tool when no rules provided", async () => {
|
|
70
|
+
const session = createTestSession({ rulebookRules: [] });
|
|
71
|
+
const tools = await createTools(session);
|
|
72
|
+
const names = tools.map((t) => t.name);
|
|
73
|
+
|
|
74
|
+
expect(names).not.toContain("rulebook");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("includes rulebook tool when rules provided", async () => {
|
|
78
|
+
const session = createTestSession({
|
|
79
|
+
rulebookRules: [
|
|
80
|
+
{
|
|
81
|
+
path: "/test/rule.md",
|
|
82
|
+
name: "Test Rule",
|
|
83
|
+
content: "Test content",
|
|
84
|
+
description: "A test rule",
|
|
85
|
+
_source: { provider: "test", providerName: "Test", path: "/test", level: "project" },
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
const tools = await createTools(session);
|
|
90
|
+
const names = tools.map((t) => t.name);
|
|
91
|
+
|
|
92
|
+
expect(names).toContain("rulebook");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("excludes git tool when disabled in settings", async () => {
|
|
96
|
+
const session = createTestSession({
|
|
97
|
+
settings: {
|
|
98
|
+
getImageAutoResize: () => true,
|
|
99
|
+
getLspFormatOnWrite: () => true,
|
|
100
|
+
getLspDiagnosticsOnWrite: () => true,
|
|
101
|
+
getLspDiagnosticsOnEdit: () => false,
|
|
102
|
+
getEditFuzzyMatch: () => true,
|
|
103
|
+
getGitToolEnabled: () => false,
|
|
104
|
+
getBashInterceptorEnabled: () => true,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
const tools = await createTools(session);
|
|
108
|
+
const names = tools.map((t) => t.name);
|
|
109
|
+
|
|
110
|
+
expect(names).not.toContain("git");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("includes git tool when enabled in settings", async () => {
|
|
114
|
+
const session = createTestSession({
|
|
115
|
+
settings: {
|
|
116
|
+
getImageAutoResize: () => true,
|
|
117
|
+
getLspFormatOnWrite: () => true,
|
|
118
|
+
getLspDiagnosticsOnWrite: () => true,
|
|
119
|
+
getLspDiagnosticsOnEdit: () => false,
|
|
120
|
+
getEditFuzzyMatch: () => true,
|
|
121
|
+
getGitToolEnabled: () => true,
|
|
122
|
+
getBashInterceptorEnabled: () => true,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
const tools = await createTools(session);
|
|
126
|
+
const names = tools.map((t) => t.name);
|
|
127
|
+
|
|
128
|
+
expect(names).toContain("git");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("includes git tool when no settings provided (default enabled)", async () => {
|
|
132
|
+
const session = createTestSession({ settings: undefined });
|
|
133
|
+
const tools = await createTools(session);
|
|
134
|
+
const names = tools.map((t) => t.name);
|
|
135
|
+
|
|
136
|
+
expect(names).toContain("git");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("always includes output tool when task tool is present", async () => {
|
|
140
|
+
const session = createTestSession();
|
|
141
|
+
const tools = await createTools(session);
|
|
142
|
+
const names = tools.map((t) => t.name);
|
|
143
|
+
|
|
144
|
+
// Both should be present together
|
|
145
|
+
expect(names).toContain("task");
|
|
146
|
+
expect(names).toContain("output");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("BUILTIN_TOOLS contains all expected tools", () => {
|
|
150
|
+
const expectedTools = [
|
|
151
|
+
"ask",
|
|
152
|
+
"bash",
|
|
153
|
+
"edit",
|
|
154
|
+
"find",
|
|
155
|
+
"git",
|
|
156
|
+
"grep",
|
|
157
|
+
"ls",
|
|
158
|
+
"lsp",
|
|
159
|
+
"notebook",
|
|
160
|
+
"output",
|
|
161
|
+
"read",
|
|
162
|
+
"rulebook",
|
|
163
|
+
"task",
|
|
164
|
+
"web_fetch",
|
|
165
|
+
"web_search",
|
|
166
|
+
"write",
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
for (const tool of expectedTools) {
|
|
170
|
+
expect(BUILTIN_TOOLS).toHaveProperty(tool);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Ensure we haven't missed any
|
|
174
|
+
expect(Object.keys(BUILTIN_TOOLS).sort()).toEqual(expectedTools.sort());
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("HIDDEN_TOOLS contains review tools", () => {
|
|
178
|
+
expect(Object.keys(HIDDEN_TOOLS).sort()).toEqual(["report_finding", "submit_review"]);
|
|
179
|
+
});
|
|
180
|
+
});
|