@oh-my-pi/pi-coding-agent 1.337.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 +1228 -0
- package/README.md +1041 -0
- package/docs/compaction.md +403 -0
- package/docs/custom-tools.md +541 -0
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +867 -0
- package/docs/rpc.md +1040 -0
- package/docs/sdk.md +994 -0
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +240 -0
- package/docs/skills.md +290 -0
- package/docs/theme.md +637 -0
- package/docs/tree.md +197 -0
- package/docs/tui.md +341 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +124 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/question/index.ts +84 -0
- package/examples/custom-tools/subagent/README.md +172 -0
- package/examples/custom-tools/subagent/agents/planner.md +37 -0
- package/examples/custom-tools/subagent/agents/reviewer.md +35 -0
- package/examples/custom-tools/subagent/agents/scout.md +50 -0
- package/examples/custom-tools/subagent/agents/worker.md +24 -0
- package/examples/custom-tools/subagent/agents.ts +156 -0
- package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
- package/examples/custom-tools/subagent/commands/implement.md +10 -0
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
- package/examples/custom-tools/subagent/index.ts +1002 -0
- package/examples/custom-tools/todo/index.ts +212 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +49 -0
- package/examples/hooks/confirm-destructive.ts +59 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +52 -0
- package/examples/hooks/file-trigger.ts +41 -0
- package/examples/hooks/git-checkpoint.ts +53 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +34 -0
- package/examples/hooks/protected-paths.ts +30 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +44 -0
- package/examples/sdk/04-skills.ts +44 -0
- package/examples/sdk/05-tools.ts +90 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +42 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +95 -0
- package/examples/sdk/README.md +154 -0
- package/package.json +81 -0
- package/src/cli/args.ts +246 -0
- package/src/cli/file-processor.ts +72 -0
- package/src/cli/list-models.ts +104 -0
- package/src/cli/plugin-cli.ts +650 -0
- package/src/cli/session-picker.ts +41 -0
- package/src/cli.ts +10 -0
- package/src/commands/init.md +20 -0
- package/src/config.ts +159 -0
- package/src/core/agent-session.ts +1900 -0
- package/src/core/auth-storage.ts +236 -0
- package/src/core/bash-executor.ts +196 -0
- package/src/core/compaction/branch-summarization.ts +343 -0
- package/src/core/compaction/compaction.ts +742 -0
- package/src/core/compaction/index.ts +7 -0
- package/src/core/compaction/utils.ts +154 -0
- package/src/core/custom-tools/index.ts +21 -0
- package/src/core/custom-tools/loader.ts +248 -0
- package/src/core/custom-tools/types.ts +169 -0
- package/src/core/custom-tools/wrapper.ts +28 -0
- package/src/core/exec.ts +129 -0
- package/src/core/export-html/index.ts +211 -0
- package/src/core/export-html/template.css +781 -0
- package/src/core/export-html/template.html +54 -0
- package/src/core/export-html/template.js +1185 -0
- package/src/core/export-html/vendor/highlight.min.js +1213 -0
- package/src/core/export-html/vendor/marked.min.js +6 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/loader.ts +312 -0
- package/src/core/hooks/runner.ts +434 -0
- package/src/core/hooks/tool-wrapper.ts +99 -0
- package/src/core/hooks/types.ts +773 -0
- package/src/core/index.ts +52 -0
- package/src/core/mcp/client.ts +158 -0
- package/src/core/mcp/config.ts +154 -0
- package/src/core/mcp/index.ts +45 -0
- package/src/core/mcp/loader.ts +68 -0
- package/src/core/mcp/manager.ts +181 -0
- package/src/core/mcp/tool-bridge.ts +148 -0
- package/src/core/mcp/transports/http.ts +316 -0
- package/src/core/mcp/transports/index.ts +6 -0
- package/src/core/mcp/transports/stdio.ts +252 -0
- package/src/core/mcp/types.ts +220 -0
- package/src/core/messages.ts +189 -0
- package/src/core/model-registry.ts +317 -0
- package/src/core/model-resolver.ts +393 -0
- package/src/core/plugins/doctor.ts +59 -0
- package/src/core/plugins/index.ts +38 -0
- package/src/core/plugins/installer.ts +189 -0
- package/src/core/plugins/loader.ts +338 -0
- package/src/core/plugins/manager.ts +672 -0
- package/src/core/plugins/parser.ts +105 -0
- package/src/core/plugins/paths.ts +32 -0
- package/src/core/plugins/types.ts +190 -0
- package/src/core/sdk.ts +760 -0
- package/src/core/session-manager.ts +1128 -0
- package/src/core/settings-manager.ts +443 -0
- package/src/core/skills.ts +437 -0
- package/src/core/slash-commands.ts +248 -0
- package/src/core/system-prompt.ts +439 -0
- package/src/core/timings.ts +25 -0
- package/src/core/tools/ask.ts +211 -0
- package/src/core/tools/bash-interceptor.ts +120 -0
- package/src/core/tools/bash.ts +250 -0
- package/src/core/tools/context.ts +32 -0
- package/src/core/tools/edit-diff.ts +475 -0
- package/src/core/tools/edit.ts +208 -0
- package/src/core/tools/exa/company.ts +59 -0
- package/src/core/tools/exa/index.ts +64 -0
- package/src/core/tools/exa/linkedin.ts +59 -0
- package/src/core/tools/exa/logger.ts +56 -0
- package/src/core/tools/exa/mcp-client.ts +368 -0
- package/src/core/tools/exa/render.ts +196 -0
- package/src/core/tools/exa/researcher.ts +90 -0
- package/src/core/tools/exa/search.ts +337 -0
- package/src/core/tools/exa/types.ts +168 -0
- package/src/core/tools/exa/websets.ts +248 -0
- package/src/core/tools/find.ts +261 -0
- package/src/core/tools/grep.ts +555 -0
- package/src/core/tools/index.ts +202 -0
- package/src/core/tools/ls.ts +140 -0
- package/src/core/tools/lsp/client.ts +605 -0
- package/src/core/tools/lsp/config.ts +147 -0
- package/src/core/tools/lsp/edits.ts +101 -0
- package/src/core/tools/lsp/index.ts +804 -0
- package/src/core/tools/lsp/render.ts +447 -0
- package/src/core/tools/lsp/rust-analyzer.ts +145 -0
- package/src/core/tools/lsp/types.ts +463 -0
- package/src/core/tools/lsp/utils.ts +486 -0
- package/src/core/tools/notebook.ts +229 -0
- package/src/core/tools/path-utils.ts +61 -0
- package/src/core/tools/read.ts +240 -0
- package/src/core/tools/renderers.ts +540 -0
- package/src/core/tools/task/agents.ts +153 -0
- package/src/core/tools/task/artifacts.ts +114 -0
- package/src/core/tools/task/bundled-agents/browser.md +71 -0
- package/src/core/tools/task/bundled-agents/explore.md +82 -0
- package/src/core/tools/task/bundled-agents/plan.md +54 -0
- package/src/core/tools/task/bundled-agents/reviewer.md +59 -0
- package/src/core/tools/task/bundled-agents/task.md +53 -0
- package/src/core/tools/task/bundled-commands/architect-plan.md +10 -0
- package/src/core/tools/task/bundled-commands/implement-with-critic.md +11 -0
- package/src/core/tools/task/bundled-commands/implement.md +11 -0
- package/src/core/tools/task/commands.ts +213 -0
- package/src/core/tools/task/discovery.ts +208 -0
- package/src/core/tools/task/executor.ts +367 -0
- package/src/core/tools/task/index.ts +388 -0
- package/src/core/tools/task/model-resolver.ts +115 -0
- package/src/core/tools/task/parallel.ts +38 -0
- package/src/core/tools/task/render.ts +232 -0
- package/src/core/tools/task/types.ts +99 -0
- package/src/core/tools/truncate.ts +265 -0
- package/src/core/tools/web-fetch.ts +2370 -0
- package/src/core/tools/web-search/auth.ts +193 -0
- package/src/core/tools/web-search/index.ts +537 -0
- package/src/core/tools/web-search/providers/anthropic.ts +198 -0
- package/src/core/tools/web-search/providers/exa.ts +302 -0
- package/src/core/tools/web-search/providers/perplexity.ts +195 -0
- package/src/core/tools/web-search/render.ts +182 -0
- package/src/core/tools/web-search/types.ts +180 -0
- package/src/core/tools/write.ts +99 -0
- package/src/index.ts +176 -0
- package/src/main.ts +464 -0
- package/src/migrations.ts +135 -0
- package/src/modes/index.ts +43 -0
- package/src/modes/interactive/components/armin.ts +382 -0
- package/src/modes/interactive/components/assistant-message.ts +86 -0
- package/src/modes/interactive/components/bash-execution.ts +196 -0
- package/src/modes/interactive/components/bordered-loader.ts +41 -0
- package/src/modes/interactive/components/branch-summary-message.ts +42 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
- package/src/modes/interactive/components/custom-editor.ts +122 -0
- package/src/modes/interactive/components/diff.ts +147 -0
- package/src/modes/interactive/components/dynamic-border.ts +25 -0
- package/src/modes/interactive/components/footer.ts +381 -0
- package/src/modes/interactive/components/hook-editor.ts +117 -0
- package/src/modes/interactive/components/hook-input.ts +64 -0
- package/src/modes/interactive/components/hook-message.ts +96 -0
- package/src/modes/interactive/components/hook-selector.ts +91 -0
- package/src/modes/interactive/components/model-selector.ts +247 -0
- package/src/modes/interactive/components/oauth-selector.ts +120 -0
- package/src/modes/interactive/components/plugin-settings.ts +479 -0
- package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
- package/src/modes/interactive/components/session-selector.ts +204 -0
- package/src/modes/interactive/components/settings-selector.ts +453 -0
- package/src/modes/interactive/components/show-images-selector.ts +45 -0
- package/src/modes/interactive/components/theme-selector.ts +62 -0
- package/src/modes/interactive/components/thinking-selector.ts +64 -0
- package/src/modes/interactive/components/tool-execution.ts +675 -0
- package/src/modes/interactive/components/tree-selector.ts +866 -0
- package/src/modes/interactive/components/user-message-selector.ts +159 -0
- package/src/modes/interactive/components/user-message.ts +18 -0
- package/src/modes/interactive/components/visual-truncate.ts +50 -0
- package/src/modes/interactive/components/welcome.ts +183 -0
- package/src/modes/interactive/interactive-mode.ts +2516 -0
- package/src/modes/interactive/theme/dark.json +101 -0
- package/src/modes/interactive/theme/light.json +98 -0
- package/src/modes/interactive/theme/theme-schema.json +308 -0
- package/src/modes/interactive/theme/theme.ts +998 -0
- package/src/modes/print-mode.ts +128 -0
- package/src/modes/rpc/rpc-client.ts +527 -0
- package/src/modes/rpc/rpc-mode.ts +483 -0
- package/src/modes/rpc/rpc-types.ts +203 -0
- package/src/utils/changelog.ts +99 -0
- package/src/utils/clipboard.ts +265 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/shell.ts +276 -0
- package/src/utils/tools-manager.ts +274 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI renderers for built-in tools.
|
|
3
|
+
*
|
|
4
|
+
* These provide rich visualization for tool calls and results in the TUI.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Component } from "@oh-my-pi/pi-tui";
|
|
8
|
+
import { Text } from "@oh-my-pi/pi-tui";
|
|
9
|
+
import type { Theme } from "../../modes/interactive/theme/theme.js";
|
|
10
|
+
import type { RenderResultOptions } from "../custom-tools/types.js";
|
|
11
|
+
import type { AskToolDetails } from "./ask.js";
|
|
12
|
+
import type { FindToolDetails } from "./find.js";
|
|
13
|
+
import type { GrepToolDetails } from "./grep.js";
|
|
14
|
+
import type { LsToolDetails } from "./ls.js";
|
|
15
|
+
import { renderCall as renderLspCall, renderResult as renderLspResult } from "./lsp/render.js";
|
|
16
|
+
import type { LspToolDetails } from "./lsp/types.js";
|
|
17
|
+
import type { NotebookToolDetails } from "./notebook.js";
|
|
18
|
+
import { renderCall as renderTaskCall, renderResult as renderTaskResult } from "./task/render.js";
|
|
19
|
+
import type { TaskToolDetails } from "./task/types.js";
|
|
20
|
+
import { renderWebFetchCall, renderWebFetchResult, type WebFetchToolDetails } from "./web-fetch.js";
|
|
21
|
+
import { renderWebSearchCall, renderWebSearchResult, type WebSearchRenderDetails } from "./web-search/render.js";
|
|
22
|
+
|
|
23
|
+
// Tree drawing characters
|
|
24
|
+
const TREE_MID = "├─";
|
|
25
|
+
const TREE_END = "└─";
|
|
26
|
+
|
|
27
|
+
// Icons
|
|
28
|
+
const ICON_SUCCESS = "●";
|
|
29
|
+
const ICON_WARNING = "●";
|
|
30
|
+
const ICON_ERROR = "●";
|
|
31
|
+
|
|
32
|
+
interface ToolRenderer<TArgs = any, TDetails = any> {
|
|
33
|
+
renderCall(args: TArgs, theme: Theme): Component;
|
|
34
|
+
renderResult(
|
|
35
|
+
result: { content: Array<{ type: string; text?: string }>; details?: TDetails },
|
|
36
|
+
options: RenderResultOptions,
|
|
37
|
+
theme: Theme,
|
|
38
|
+
): Component;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Grep Renderer
|
|
43
|
+
// ============================================================================
|
|
44
|
+
|
|
45
|
+
interface GrepArgs {
|
|
46
|
+
pattern: string;
|
|
47
|
+
path?: string;
|
|
48
|
+
glob?: string;
|
|
49
|
+
type?: string;
|
|
50
|
+
ignoreCase?: boolean;
|
|
51
|
+
caseSensitive?: boolean;
|
|
52
|
+
literal?: boolean;
|
|
53
|
+
multiline?: boolean;
|
|
54
|
+
context?: number;
|
|
55
|
+
limit?: number;
|
|
56
|
+
outputMode?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const grepRenderer: ToolRenderer<GrepArgs, GrepToolDetails> = {
|
|
60
|
+
renderCall(args, theme) {
|
|
61
|
+
let text = theme.fg("toolTitle", theme.bold("grep "));
|
|
62
|
+
text += theme.fg("accent", args.pattern || "?");
|
|
63
|
+
|
|
64
|
+
const meta: string[] = [];
|
|
65
|
+
if (args.path) meta.push(args.path);
|
|
66
|
+
if (args.glob) meta.push(`glob:${args.glob}`);
|
|
67
|
+
if (args.type) meta.push(`type:${args.type}`);
|
|
68
|
+
if (args.outputMode && args.outputMode !== "files_with_matches") meta.push(args.outputMode);
|
|
69
|
+
if (args.caseSensitive) {
|
|
70
|
+
meta.push("--case-sensitive");
|
|
71
|
+
} else if (args.ignoreCase) {
|
|
72
|
+
meta.push("-i");
|
|
73
|
+
}
|
|
74
|
+
if (args.multiline) meta.push("multiline");
|
|
75
|
+
|
|
76
|
+
if (meta.length > 0) {
|
|
77
|
+
text += ` ${theme.fg("muted", meta.join(" "))}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return new Text(text, 0, 0);
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
renderResult(result, { expanded }, theme) {
|
|
84
|
+
const details = result.details;
|
|
85
|
+
|
|
86
|
+
// Error case
|
|
87
|
+
if (details?.error) {
|
|
88
|
+
return new Text(`${theme.fg("error", ICON_ERROR)} ${theme.fg("error", details.error)}`, 0, 0);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Check for detailed rendering data - fall back to raw output if not available
|
|
92
|
+
const hasDetailedData = details?.matchCount !== undefined || details?.fileCount !== undefined;
|
|
93
|
+
|
|
94
|
+
if (!hasDetailedData) {
|
|
95
|
+
// Fall back to showing raw text content
|
|
96
|
+
const textContent = result.content?.find((c) => c.type === "text")?.text;
|
|
97
|
+
if (!textContent || textContent === "No matches found") {
|
|
98
|
+
return new Text(`${theme.fg("warning", ICON_WARNING)} ${theme.fg("muted", "No matches found")}`, 0, 0);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Show abbreviated output
|
|
102
|
+
const lines = textContent.split("\n");
|
|
103
|
+
const maxLines = expanded ? lines.length : 10;
|
|
104
|
+
const displayLines = lines.slice(0, maxLines);
|
|
105
|
+
const remaining = lines.length - maxLines;
|
|
106
|
+
|
|
107
|
+
let text = `${theme.fg("success", ICON_SUCCESS)} ${theme.fg("toolTitle", "grep")}`;
|
|
108
|
+
text += `\n${displayLines.map((l) => theme.fg("toolOutput", l)).join("\n")}`;
|
|
109
|
+
if (remaining > 0) {
|
|
110
|
+
text += `\n${theme.fg("muted", `... ${remaining} more lines`)}`;
|
|
111
|
+
}
|
|
112
|
+
return new Text(text, 0, 0);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const matchCount = details?.matchCount ?? 0;
|
|
116
|
+
const fileCount = details?.fileCount ?? 0;
|
|
117
|
+
const mode = details?.mode ?? "files_with_matches";
|
|
118
|
+
const truncated = details?.truncated ?? details?.truncation?.truncated ?? false;
|
|
119
|
+
const files = details?.files ?? [];
|
|
120
|
+
|
|
121
|
+
// No matches
|
|
122
|
+
if (matchCount === 0) {
|
|
123
|
+
return new Text(`${theme.fg("warning", ICON_WARNING)} ${theme.fg("muted", "No matches found")}`, 0, 0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Build summary
|
|
127
|
+
const icon = theme.fg("success", ICON_SUCCESS);
|
|
128
|
+
let summary: string;
|
|
129
|
+
if (mode === "files_with_matches") {
|
|
130
|
+
summary = `${fileCount} file${fileCount !== 1 ? "s" : ""}`;
|
|
131
|
+
} else if (mode === "count") {
|
|
132
|
+
summary = `${matchCount} match${matchCount !== 1 ? "es" : ""} in ${fileCount} file${fileCount !== 1 ? "s" : ""}`;
|
|
133
|
+
} else {
|
|
134
|
+
summary = `${matchCount} match${matchCount !== 1 ? "es" : ""} in ${fileCount} file${fileCount !== 1 ? "s" : ""}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (truncated) {
|
|
138
|
+
summary += theme.fg("warning", " (truncated)");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const expandHint = expanded ? "" : theme.fg("dim", " (Ctrl+O to expand)");
|
|
142
|
+
let text = `${icon} ${theme.fg("toolTitle", "grep")} ${theme.fg("dim", summary)}${expandHint}`;
|
|
143
|
+
|
|
144
|
+
// Show file tree if we have files
|
|
145
|
+
if (files.length > 0) {
|
|
146
|
+
const maxFiles = expanded ? files.length : Math.min(files.length, 8);
|
|
147
|
+
for (let i = 0; i < maxFiles; i++) {
|
|
148
|
+
const isLast = i === maxFiles - 1 && (expanded || files.length <= 8);
|
|
149
|
+
const branch = isLast ? TREE_END : TREE_MID;
|
|
150
|
+
text += `\n ${theme.fg("dim", branch)} ${theme.fg("accent", files[i])}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!expanded && files.length > 8) {
|
|
154
|
+
text += `\n ${theme.fg("dim", TREE_END)} ${theme.fg("muted", `… ${files.length - 8} more files`)}`;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return new Text(text, 0, 0);
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// ============================================================================
|
|
163
|
+
// Find Renderer
|
|
164
|
+
// ============================================================================
|
|
165
|
+
|
|
166
|
+
interface FindArgs {
|
|
167
|
+
pattern: string;
|
|
168
|
+
path?: string;
|
|
169
|
+
type?: string;
|
|
170
|
+
hidden?: boolean;
|
|
171
|
+
sortByMtime?: boolean;
|
|
172
|
+
limit?: number;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const findRenderer: ToolRenderer<FindArgs, FindToolDetails> = {
|
|
176
|
+
renderCall(args, theme) {
|
|
177
|
+
let text = theme.fg("toolTitle", theme.bold("find "));
|
|
178
|
+
text += theme.fg("accent", args.pattern || "*");
|
|
179
|
+
|
|
180
|
+
const meta: string[] = [];
|
|
181
|
+
if (args.path) meta.push(args.path);
|
|
182
|
+
if (args.type && args.type !== "all") meta.push(`type:${args.type}`);
|
|
183
|
+
if (args.hidden) meta.push("--hidden");
|
|
184
|
+
|
|
185
|
+
if (meta.length > 0) {
|
|
186
|
+
text += ` ${theme.fg("muted", meta.join(" "))}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return new Text(text, 0, 0);
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
renderResult(result, { expanded }, theme) {
|
|
193
|
+
const details = result.details;
|
|
194
|
+
|
|
195
|
+
// Error case
|
|
196
|
+
if (details?.error) {
|
|
197
|
+
return new Text(`${theme.fg("error", ICON_ERROR)} ${theme.fg("error", details.error)}`, 0, 0);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Check for detailed rendering data - fall back to parsing raw output if not available
|
|
201
|
+
const hasDetailedData = details?.fileCount !== undefined;
|
|
202
|
+
|
|
203
|
+
// Get text content for fallback or to extract file list
|
|
204
|
+
const textContent = result.content?.find((c) => c.type === "text")?.text;
|
|
205
|
+
|
|
206
|
+
if (!hasDetailedData) {
|
|
207
|
+
if (!textContent || textContent.includes("No files matching") || textContent.trim() === "") {
|
|
208
|
+
return new Text(`${theme.fg("warning", ICON_WARNING)} ${theme.fg("muted", "No files found")}`, 0, 0);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Parse the raw output as file list
|
|
212
|
+
const lines = textContent.split("\n").filter((l) => l.trim());
|
|
213
|
+
const maxLines = expanded ? lines.length : Math.min(lines.length, 8);
|
|
214
|
+
const displayLines = lines.slice(0, maxLines);
|
|
215
|
+
const remaining = lines.length - maxLines;
|
|
216
|
+
|
|
217
|
+
let text = `${theme.fg("success", ICON_SUCCESS)} ${theme.fg("toolTitle", "find")} ${theme.fg(
|
|
218
|
+
"dim",
|
|
219
|
+
`${lines.length} file${lines.length !== 1 ? "s" : ""}`,
|
|
220
|
+
)}`;
|
|
221
|
+
for (let i = 0; i < displayLines.length; i++) {
|
|
222
|
+
const isLast = i === displayLines.length - 1 && remaining === 0;
|
|
223
|
+
const branch = isLast ? TREE_END : TREE_MID;
|
|
224
|
+
text += `\n ${theme.fg("dim", branch)} ${theme.fg("accent", displayLines[i])}`;
|
|
225
|
+
}
|
|
226
|
+
if (remaining > 0) {
|
|
227
|
+
text += `\n ${theme.fg("dim", TREE_END)} ${theme.fg("muted", `… ${remaining} more files`)}`;
|
|
228
|
+
}
|
|
229
|
+
return new Text(text, 0, 0);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const fileCount = details?.fileCount ?? 0;
|
|
233
|
+
const truncated = details?.truncated ?? details?.truncation?.truncated ?? false;
|
|
234
|
+
const files = details?.files ?? [];
|
|
235
|
+
|
|
236
|
+
// No matches
|
|
237
|
+
if (fileCount === 0) {
|
|
238
|
+
return new Text(`${theme.fg("warning", ICON_WARNING)} ${theme.fg("muted", "No files found")}`, 0, 0);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Build summary
|
|
242
|
+
const icon = theme.fg("success", ICON_SUCCESS);
|
|
243
|
+
let summary = `${fileCount} file${fileCount !== 1 ? "s" : ""}`;
|
|
244
|
+
|
|
245
|
+
if (truncated) {
|
|
246
|
+
summary += theme.fg("warning", " (truncated)");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const expandHint = expanded ? "" : theme.fg("dim", " (Ctrl+O to expand)");
|
|
250
|
+
let text = `${icon} ${theme.fg("toolTitle", "find")} ${theme.fg("dim", summary)}${expandHint}`;
|
|
251
|
+
|
|
252
|
+
// Show file tree if we have files
|
|
253
|
+
if (files.length > 0) {
|
|
254
|
+
const maxFiles = expanded ? files.length : Math.min(files.length, 8);
|
|
255
|
+
for (let i = 0; i < maxFiles; i++) {
|
|
256
|
+
const isLast = i === maxFiles - 1 && (expanded || files.length <= 8);
|
|
257
|
+
const branch = isLast ? TREE_END : TREE_MID;
|
|
258
|
+
text += `\n ${theme.fg("dim", branch)} ${theme.fg("accent", files[i])}`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (!expanded && files.length > 8) {
|
|
262
|
+
text += `\n ${theme.fg("dim", TREE_END)} ${theme.fg("muted", `… ${files.length - 8} more files`)}`;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return new Text(text, 0, 0);
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
// ============================================================================
|
|
271
|
+
// Notebook Renderer
|
|
272
|
+
// ============================================================================
|
|
273
|
+
|
|
274
|
+
interface NotebookArgs {
|
|
275
|
+
action: string;
|
|
276
|
+
notebookPath: string;
|
|
277
|
+
cellNumber?: number;
|
|
278
|
+
cellType?: string;
|
|
279
|
+
content?: string;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const notebookRenderer: ToolRenderer<NotebookArgs, NotebookToolDetails> = {
|
|
283
|
+
renderCall(args, theme) {
|
|
284
|
+
let text = theme.fg("toolTitle", theme.bold("notebook "));
|
|
285
|
+
text += theme.fg("accent", args.action || "?");
|
|
286
|
+
|
|
287
|
+
const meta: string[] = [];
|
|
288
|
+
meta.push(args.notebookPath || "?");
|
|
289
|
+
if (args.cellNumber !== undefined) meta.push(`cell:${args.cellNumber}`);
|
|
290
|
+
if (args.cellType) meta.push(args.cellType);
|
|
291
|
+
|
|
292
|
+
if (meta.length > 0) {
|
|
293
|
+
text += ` ${theme.fg("muted", meta.join(" "))}`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return new Text(text, 0, 0);
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
renderResult(result, _options, theme) {
|
|
300
|
+
const details = result.details;
|
|
301
|
+
|
|
302
|
+
// Error case - check for error in content
|
|
303
|
+
const content = result.content?.[0];
|
|
304
|
+
if (content?.type === "text" && content.text?.startsWith("Error:")) {
|
|
305
|
+
return new Text(`${theme.fg("error", ICON_ERROR)} ${theme.fg("error", content.text)}`, 0, 0);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const action = details?.action ?? "edit";
|
|
309
|
+
const cellIndex = details?.cellIndex;
|
|
310
|
+
const cellType = details?.cellType;
|
|
311
|
+
const totalCells = details?.totalCells;
|
|
312
|
+
|
|
313
|
+
// Build summary
|
|
314
|
+
const icon = theme.fg("success", ICON_SUCCESS);
|
|
315
|
+
let summary: string;
|
|
316
|
+
|
|
317
|
+
switch (action) {
|
|
318
|
+
case "insert":
|
|
319
|
+
summary = `Inserted ${cellType || "cell"} at index ${cellIndex}`;
|
|
320
|
+
break;
|
|
321
|
+
case "delete":
|
|
322
|
+
summary = `Deleted cell at index ${cellIndex}`;
|
|
323
|
+
break;
|
|
324
|
+
default:
|
|
325
|
+
summary = `Edited ${cellType || "cell"} at index ${cellIndex}`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (totalCells !== undefined) {
|
|
329
|
+
summary += ` (${totalCells} total)`;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return new Text(`${icon} ${theme.fg("toolTitle", "notebook")} ${theme.fg("dim", summary)}`, 0, 0);
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// ============================================================================
|
|
337
|
+
// Ask Renderer
|
|
338
|
+
// ============================================================================
|
|
339
|
+
|
|
340
|
+
interface AskArgs {
|
|
341
|
+
question: string;
|
|
342
|
+
options?: Array<{ label: string }>;
|
|
343
|
+
multi?: boolean;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const askRenderer: ToolRenderer<AskArgs, AskToolDetails> = {
|
|
347
|
+
renderCall(args, theme) {
|
|
348
|
+
if (!args.question) {
|
|
349
|
+
return new Text(theme.fg("error", "ask: no question provided"), 0, 0);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const multiTag = args.multi ? theme.fg("muted", " [multi-select]") : "";
|
|
353
|
+
let text = theme.fg("toolTitle", "? ") + theme.fg("accent", args.question) + multiTag;
|
|
354
|
+
|
|
355
|
+
if (args.options?.length) {
|
|
356
|
+
for (const opt of args.options) {
|
|
357
|
+
text += `\n${theme.fg("dim", " ○ ")}${theme.fg("muted", opt.label)}`;
|
|
358
|
+
}
|
|
359
|
+
text += `\n${theme.fg("dim", " ○ ")}${theme.fg("muted", "Other (custom input)")}`;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return new Text(text, 0, 0);
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
renderResult(result, _opts, theme) {
|
|
366
|
+
const { details } = result;
|
|
367
|
+
if (!details) {
|
|
368
|
+
const txt = result.content[0];
|
|
369
|
+
return new Text(txt?.type === "text" && txt.text ? txt.text : "", 0, 0);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
let text = theme.fg("toolTitle", "? ") + theme.fg("accent", details.question);
|
|
373
|
+
|
|
374
|
+
if (details.customInput) {
|
|
375
|
+
// Custom input provided
|
|
376
|
+
text += `\n${theme.fg("dim", " ⎿ ")}${theme.fg("success", details.customInput)}`;
|
|
377
|
+
} else if (details.selectedOptions.length > 0) {
|
|
378
|
+
// Show only selected options
|
|
379
|
+
const selected = details.selectedOptions;
|
|
380
|
+
if (selected.length === 1) {
|
|
381
|
+
text += `\n${theme.fg("dim", " ⎿ ")}${theme.fg("success", selected[0])}`;
|
|
382
|
+
} else {
|
|
383
|
+
// Multiple selections - tree format
|
|
384
|
+
for (let i = 0; i < selected.length; i++) {
|
|
385
|
+
const isLast = i === selected.length - 1;
|
|
386
|
+
const branch = isLast ? TREE_END : TREE_MID;
|
|
387
|
+
text += `\n${theme.fg("dim", ` ${branch} `)}${theme.fg("success", selected[i])}`;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
} else {
|
|
391
|
+
text += `\n${theme.fg("dim", " ⎿ ")}${theme.fg("warning", "Cancelled")}`;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return new Text(text, 0, 0);
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// ============================================================================
|
|
399
|
+
// Export
|
|
400
|
+
// ============================================================================
|
|
401
|
+
|
|
402
|
+
// ============================================================================
|
|
403
|
+
// LSP Renderer
|
|
404
|
+
// ============================================================================
|
|
405
|
+
|
|
406
|
+
interface LspArgs {
|
|
407
|
+
action: string;
|
|
408
|
+
file?: string;
|
|
409
|
+
files?: string[];
|
|
410
|
+
line?: number;
|
|
411
|
+
column?: number;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const lspRenderer: ToolRenderer<LspArgs, LspToolDetails> = {
|
|
415
|
+
renderCall: renderLspCall,
|
|
416
|
+
renderResult: renderLspResult,
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
// ============================================================================
|
|
420
|
+
// Task Renderer
|
|
421
|
+
// ============================================================================
|
|
422
|
+
|
|
423
|
+
const taskRenderer: ToolRenderer<any, TaskToolDetails> = {
|
|
424
|
+
renderCall: renderTaskCall,
|
|
425
|
+
renderResult: renderTaskResult,
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
// ============================================================================
|
|
429
|
+
// Ls Renderer
|
|
430
|
+
// ============================================================================
|
|
431
|
+
|
|
432
|
+
interface LsArgs {
|
|
433
|
+
path?: string;
|
|
434
|
+
limit?: number;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const lsRenderer: ToolRenderer<LsArgs, LsToolDetails> = {
|
|
438
|
+
renderCall(args, theme) {
|
|
439
|
+
let text = theme.fg("toolTitle", theme.bold("ls "));
|
|
440
|
+
text += theme.fg("accent", args.path || ".");
|
|
441
|
+
if (args.limit !== undefined) {
|
|
442
|
+
text += ` ${theme.fg("muted", `(limit ${args.limit})`)}`;
|
|
443
|
+
}
|
|
444
|
+
return new Text(text, 0, 0);
|
|
445
|
+
},
|
|
446
|
+
|
|
447
|
+
renderResult(result, { expanded }, theme) {
|
|
448
|
+
const details = result.details;
|
|
449
|
+
const textContent = result.content?.find((c: any) => c.type === "text")?.text;
|
|
450
|
+
|
|
451
|
+
if (!textContent || textContent.trim() === "") {
|
|
452
|
+
return new Text(`${theme.fg("warning", ICON_WARNING)} ${theme.fg("muted", "Empty directory")}`, 0, 0);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const entries = textContent.split("\n").filter((l: string) => l.trim());
|
|
456
|
+
const dirs = entries.filter((e: string) => e.endsWith("/"));
|
|
457
|
+
const files = entries.filter((e: string) => !e.endsWith("/"));
|
|
458
|
+
|
|
459
|
+
const truncated = details?.truncation?.truncated || details?.entryLimitReached;
|
|
460
|
+
const icon = truncated ? theme.fg("warning", ICON_WARNING) : theme.fg("success", ICON_SUCCESS);
|
|
461
|
+
|
|
462
|
+
let summary = `${dirs.length} dir${dirs.length !== 1 ? "s" : ""}, ${files.length} file${
|
|
463
|
+
files.length !== 1 ? "s" : ""
|
|
464
|
+
}`;
|
|
465
|
+
if (truncated) {
|
|
466
|
+
summary += theme.fg("warning", " (truncated)");
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const expandHint = expanded ? "" : theme.fg("dim", " (Ctrl+O to expand)");
|
|
470
|
+
let text = `${icon} ${theme.fg("toolTitle", "ls")} ${theme.fg("dim", summary)}${expandHint}`;
|
|
471
|
+
|
|
472
|
+
const maxEntries = expanded ? entries.length : Math.min(entries.length, 12);
|
|
473
|
+
for (let i = 0; i < maxEntries; i++) {
|
|
474
|
+
const entry = entries[i];
|
|
475
|
+
const isLast = i === maxEntries - 1 && (expanded || entries.length <= 12);
|
|
476
|
+
const branch = isLast ? TREE_END : TREE_MID;
|
|
477
|
+
const isDir = entry.endsWith("/");
|
|
478
|
+
const color = isDir ? "accent" : "toolOutput";
|
|
479
|
+
text += `\n ${theme.fg("dim", branch)} ${theme.fg(color, entry)}`;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (!expanded && entries.length > 12) {
|
|
483
|
+
text += `\n ${theme.fg("dim", TREE_END)} ${theme.fg("muted", `… ${entries.length - 12} more entries`)}`;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return new Text(text, 0, 0);
|
|
487
|
+
},
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
// ============================================================================
|
|
491
|
+
// Web Fetch Renderer
|
|
492
|
+
// ============================================================================
|
|
493
|
+
|
|
494
|
+
interface WebFetchArgs {
|
|
495
|
+
url: string;
|
|
496
|
+
timeout?: number;
|
|
497
|
+
raw?: boolean;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const webFetchRenderer: ToolRenderer<WebFetchArgs, WebFetchToolDetails> = {
|
|
501
|
+
renderCall: renderWebFetchCall,
|
|
502
|
+
renderResult: renderWebFetchResult,
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
// ============================================================================
|
|
506
|
+
// Web Search Renderer
|
|
507
|
+
// ============================================================================
|
|
508
|
+
|
|
509
|
+
interface WebSearchArgs {
|
|
510
|
+
query: string;
|
|
511
|
+
provider?: string;
|
|
512
|
+
[key: string]: unknown;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const webSearchRenderer: ToolRenderer<WebSearchArgs, WebSearchRenderDetails> = {
|
|
516
|
+
renderCall: renderWebSearchCall,
|
|
517
|
+
renderResult: renderWebSearchResult,
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
// ============================================================================
|
|
521
|
+
// Export
|
|
522
|
+
// ============================================================================
|
|
523
|
+
|
|
524
|
+
export const toolRenderers: Record<
|
|
525
|
+
string,
|
|
526
|
+
{
|
|
527
|
+
renderCall: (args: any, theme: Theme) => Component;
|
|
528
|
+
renderResult: (result: any, options: RenderResultOptions, theme: Theme) => Component;
|
|
529
|
+
}
|
|
530
|
+
> = {
|
|
531
|
+
ask: askRenderer,
|
|
532
|
+
grep: grepRenderer,
|
|
533
|
+
find: findRenderer,
|
|
534
|
+
notebook: notebookRenderer,
|
|
535
|
+
ls: lsRenderer,
|
|
536
|
+
lsp: lspRenderer,
|
|
537
|
+
task: taskRenderer,
|
|
538
|
+
web_fetch: webFetchRenderer,
|
|
539
|
+
web_search: webSearchRenderer,
|
|
540
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundled agent definitions.
|
|
3
|
+
*
|
|
4
|
+
* Agents are loaded from .md files in the bundled-agents directory.
|
|
5
|
+
* These serve as defaults when no user/project agents are discovered.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import type { AgentDefinition, AgentSource } from "./types.js";
|
|
12
|
+
|
|
13
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const BUNDLED_AGENTS_DIR = path.join(__dirname, "bundled-agents");
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parse YAML frontmatter from markdown content.
|
|
18
|
+
*/
|
|
19
|
+
function parseFrontmatter(content: string): { frontmatter: Record<string, string>; body: string } {
|
|
20
|
+
const frontmatter: Record<string, string> = {};
|
|
21
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
22
|
+
|
|
23
|
+
if (!normalized.startsWith("---")) {
|
|
24
|
+
return { frontmatter, body: normalized };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const endIndex = normalized.indexOf("\n---", 3);
|
|
28
|
+
if (endIndex === -1) {
|
|
29
|
+
return { frontmatter, body: normalized };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const frontmatterBlock = normalized.slice(4, endIndex);
|
|
33
|
+
const body = normalized.slice(endIndex + 4).trim();
|
|
34
|
+
|
|
35
|
+
for (const line of frontmatterBlock.split("\n")) {
|
|
36
|
+
const match = line.match(/^([\w-]+):\s*(.*)$/);
|
|
37
|
+
if (match) {
|
|
38
|
+
let value = match[2].trim();
|
|
39
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
40
|
+
value = value.slice(1, -1);
|
|
41
|
+
}
|
|
42
|
+
frontmatter[match[1]] = value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { frontmatter, body };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Load a single agent from a markdown file.
|
|
51
|
+
*/
|
|
52
|
+
function loadAgentFromFile(filePath: string, source: AgentSource): AgentDefinition | null {
|
|
53
|
+
let content: string;
|
|
54
|
+
try {
|
|
55
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
61
|
+
|
|
62
|
+
if (!frontmatter.name || !frontmatter.description) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const tools = frontmatter.tools
|
|
67
|
+
?.split(",")
|
|
68
|
+
.map((t) => t.trim())
|
|
69
|
+
.filter(Boolean);
|
|
70
|
+
|
|
71
|
+
const recursive =
|
|
72
|
+
frontmatter.recursive === undefined ? false : frontmatter.recursive === "true" || frontmatter.recursive === "1";
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
name: frontmatter.name,
|
|
76
|
+
description: frontmatter.description,
|
|
77
|
+
tools: tools && tools.length > 0 ? tools : undefined,
|
|
78
|
+
model: frontmatter.model,
|
|
79
|
+
recursive,
|
|
80
|
+
systemPrompt: body,
|
|
81
|
+
source,
|
|
82
|
+
filePath,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Cache for bundled agents */
|
|
87
|
+
let bundledAgentsCache: AgentDefinition[] | null = null;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Load all bundled agents from the bundled-agents directory.
|
|
91
|
+
* Results are cached after first load.
|
|
92
|
+
*/
|
|
93
|
+
export function loadBundledAgents(): AgentDefinition[] {
|
|
94
|
+
if (bundledAgentsCache !== null) {
|
|
95
|
+
return bundledAgentsCache;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const agents: AgentDefinition[] = [];
|
|
99
|
+
|
|
100
|
+
if (!fs.existsSync(BUNDLED_AGENTS_DIR)) {
|
|
101
|
+
bundledAgentsCache = agents;
|
|
102
|
+
return agents;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let entries: fs.Dirent[];
|
|
106
|
+
try {
|
|
107
|
+
entries = fs.readdirSync(BUNDLED_AGENTS_DIR, { withFileTypes: true });
|
|
108
|
+
} catch {
|
|
109
|
+
bundledAgentsCache = agents;
|
|
110
|
+
return agents;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const entry of entries) {
|
|
114
|
+
if (!entry.name.endsWith(".md")) continue;
|
|
115
|
+
|
|
116
|
+
const filePath = path.join(BUNDLED_AGENTS_DIR, entry.name);
|
|
117
|
+
const agent = loadAgentFromFile(filePath, "bundled");
|
|
118
|
+
if (agent) {
|
|
119
|
+
agents.push(agent);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
bundledAgentsCache = agents;
|
|
124
|
+
return agents;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get a bundled agent by name.
|
|
129
|
+
*/
|
|
130
|
+
export function getBundledAgent(name: string): AgentDefinition | undefined {
|
|
131
|
+
return loadBundledAgents().find((a) => a.name === name);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get all bundled agents as a map keyed by name.
|
|
136
|
+
*/
|
|
137
|
+
export function getBundledAgentsMap(): Map<string, AgentDefinition> {
|
|
138
|
+
const map = new Map<string, AgentDefinition>();
|
|
139
|
+
for (const agent of loadBundledAgents()) {
|
|
140
|
+
map.set(agent.name, agent);
|
|
141
|
+
}
|
|
142
|
+
return map;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Clear the bundled agents cache (for testing).
|
|
147
|
+
*/
|
|
148
|
+
export function clearBundledAgentsCache(): void {
|
|
149
|
+
bundledAgentsCache = null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Re-export for backward compatibility
|
|
153
|
+
export const BUNDLED_AGENTS = loadBundledAgents;
|