@oh-my-pi/pi-coding-agent 3.21.0 → 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 +40 -1
- package/docs/sdk.md +47 -50
- package/examples/custom-tools/README.md +0 -15
- package/examples/hooks/custom-compaction.ts +1 -3
- package/examples/sdk/README.md +6 -10
- package/package.json +5 -5
- package/src/cli/args.ts +9 -6
- package/src/core/agent-session.ts +3 -3
- package/src/core/custom-tools/wrapper.ts +0 -1
- package/src/core/extensions/index.ts +1 -6
- package/src/core/extensions/wrapper.ts +0 -7
- package/src/core/file-mentions.ts +5 -8
- package/src/core/sdk.ts +41 -111
- package/src/core/session-manager.ts +7 -0
- package/src/core/system-prompt.ts +22 -33
- package/src/core/tools/ask.ts +14 -7
- package/src/core/tools/bash-interceptor.ts +4 -4
- package/src/core/tools/bash.ts +19 -9
- package/src/core/tools/context.ts +7 -0
- package/src/core/tools/edit.ts +8 -15
- package/src/core/tools/exa/render.ts +4 -16
- package/src/core/tools/find.ts +7 -18
- package/src/core/tools/git.ts +13 -3
- package/src/core/tools/grep.ts +7 -18
- package/src/core/tools/index.test.ts +180 -0
- package/src/core/tools/index.ts +94 -237
- package/src/core/tools/ls.ts +4 -9
- package/src/core/tools/lsp/index.ts +32 -29
- package/src/core/tools/lsp/render.ts +7 -28
- package/src/core/tools/notebook.ts +3 -5
- package/src/core/tools/output.ts +5 -17
- package/src/core/tools/read.ts +8 -19
- package/src/core/tools/review.ts +0 -18
- package/src/core/tools/rulebook.ts +8 -2
- 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 +45 -220
- package/src/core/tools/task/render.ts +21 -11
- package/src/core/tools/task/types.ts +6 -11
- package/src/core/tools/task/worker-protocol.ts +17 -0
- package/src/core/tools/task/worker.ts +238 -0
- package/src/core/tools/web-fetch.ts +4 -36
- package/src/core/tools/web-search/index.ts +2 -1
- package/src/core/tools/web-search/render.ts +1 -4
- package/src/core/tools/write.ts +7 -15
- package/src/discovery/helpers.test.ts +1 -1
- package/src/index.ts +5 -16
- package/src/main.ts +4 -4
- package/src/modes/interactive/theme/theme.ts +4 -4
- package/src/prompts/task.md +0 -7
- package/src/prompts/tools/output.md +2 -2
- package/src/prompts/tools/task.md +68 -0
- 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/sdk.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* const session = await createAgentSession({
|
|
19
19
|
* model: myModel,
|
|
20
20
|
* getApiKey: async () => process.env.MY_KEY,
|
|
21
|
-
*
|
|
21
|
+
* toolNames: ["read", "bash", "edit", "write"], // Filter tools
|
|
22
22
|
* extensions: [],
|
|
23
23
|
* skills: [],
|
|
24
24
|
* sessionFile: false,
|
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
loadExtensionFromFactory,
|
|
56
56
|
type ToolDefinition,
|
|
57
57
|
wrapRegisteredTools,
|
|
58
|
-
|
|
58
|
+
wrapToolWithExtensions,
|
|
59
59
|
} from "./extensions/index";
|
|
60
60
|
import { logger } from "./logger";
|
|
61
61
|
import { discoverAndLoadMCPTools, type MCPManager, type MCPToolsLoadResult } from "./mcp/index";
|
|
@@ -75,48 +75,34 @@ import { time } from "./timings";
|
|
|
75
75
|
import { createToolContextStore } from "./tools/context";
|
|
76
76
|
import { getGeminiImageTools } from "./tools/gemini-image";
|
|
77
77
|
import {
|
|
78
|
-
|
|
79
|
-
applyBashInterception,
|
|
80
|
-
baseCodingToolNames,
|
|
81
|
-
bashTool,
|
|
82
|
-
codingTools,
|
|
83
|
-
createAllTools,
|
|
78
|
+
BUILTIN_TOOLS,
|
|
84
79
|
createBashTool,
|
|
85
|
-
createCodingTools,
|
|
86
80
|
createEditTool,
|
|
87
81
|
createFindTool,
|
|
88
82
|
createGitTool,
|
|
89
83
|
createGrepTool,
|
|
90
84
|
createLsTool,
|
|
91
|
-
createReadOnlyTools,
|
|
92
85
|
createReadTool,
|
|
93
|
-
|
|
86
|
+
createTools,
|
|
94
87
|
createWriteTool,
|
|
95
|
-
editTool,
|
|
96
88
|
filterRulebookRules,
|
|
97
|
-
findTool,
|
|
98
89
|
getWebSearchTools,
|
|
99
|
-
gitTool,
|
|
100
|
-
grepTool,
|
|
101
|
-
lsTool,
|
|
102
|
-
readOnlyTools,
|
|
103
|
-
readTool,
|
|
104
90
|
setPreferredImageProvider,
|
|
105
91
|
setPreferredWebSearchProvider,
|
|
106
92
|
type Tool,
|
|
107
|
-
type
|
|
93
|
+
type ToolSession,
|
|
108
94
|
warmupLspServers,
|
|
109
|
-
writeTool,
|
|
110
95
|
} from "./tools/index";
|
|
111
96
|
import { createTtsrManager } from "./ttsr";
|
|
112
97
|
|
|
113
98
|
// Types
|
|
114
|
-
|
|
115
99
|
export interface CreateAgentSessionOptions {
|
|
116
100
|
/** Working directory for project-local discovery. Default: process.cwd() */
|
|
117
101
|
cwd?: string;
|
|
118
102
|
/** Global config directory. Default: ~/.omp/agent */
|
|
119
103
|
agentDir?: string;
|
|
104
|
+
/** Spawns to allow. Default: "*" */
|
|
105
|
+
spawns?: string;
|
|
120
106
|
|
|
121
107
|
/** Auth storage for credentials. Default: discoverAuthStorage(agentDir) */
|
|
122
108
|
authStorage?: AuthStorage;
|
|
@@ -133,8 +119,6 @@ export interface CreateAgentSessionOptions {
|
|
|
133
119
|
/** System prompt. String replaces default, function receives default and returns final. */
|
|
134
120
|
systemPrompt?: string | ((defaultPrompt: string) => string);
|
|
135
121
|
|
|
136
|
-
/** Built-in tools to use. Default: all coding tools (read, bash, edit, write, grep, find, ls, lsp, notebook, output, task, web_fetch, web_search) */
|
|
137
|
-
tools?: Tool[];
|
|
138
122
|
/** Custom tools to register (in addition to built-in tools). Accepts both CustomTool and ToolDefinition. */
|
|
139
123
|
customTools?: (CustomTool | ToolDefinition)[];
|
|
140
124
|
/** Inline extensions (merged with discovery). */
|
|
@@ -163,7 +147,7 @@ export interface CreateAgentSessionOptions {
|
|
|
163
147
|
enableMCP?: boolean;
|
|
164
148
|
|
|
165
149
|
/** Tool names explicitly requested (enables disabled-by-default tools) */
|
|
166
|
-
|
|
150
|
+
toolNames?: string[];
|
|
167
151
|
|
|
168
152
|
/** Session manager. Default: SessionManager.create(cwd) */
|
|
169
153
|
sessionManager?: SessionManager;
|
|
@@ -208,21 +192,11 @@ export type { FileSlashCommand } from "./slash-commands";
|
|
|
208
192
|
export type { Tool } from "./tools/index";
|
|
209
193
|
|
|
210
194
|
export {
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
grepTool,
|
|
217
|
-
findTool,
|
|
218
|
-
gitTool,
|
|
219
|
-
lsTool,
|
|
220
|
-
codingTools,
|
|
221
|
-
readOnlyTools,
|
|
222
|
-
allTools as allBuiltInTools,
|
|
223
|
-
// Tool factories (for custom cwd)
|
|
224
|
-
createCodingTools,
|
|
225
|
-
createReadOnlyTools,
|
|
195
|
+
// Tool factories
|
|
196
|
+
BUILTIN_TOOLS,
|
|
197
|
+
createTools,
|
|
198
|
+
type ToolSession,
|
|
199
|
+
// Individual tool factories (for custom usage)
|
|
226
200
|
createReadTool,
|
|
227
201
|
createBashTool,
|
|
228
202
|
createEditTool,
|
|
@@ -426,7 +400,6 @@ function customToolToDefinition(tool: CustomTool): ToolDefinition {
|
|
|
426
400
|
label: tool.label,
|
|
427
401
|
description: tool.description,
|
|
428
402
|
parameters: tool.parameters,
|
|
429
|
-
hidden: tool.hidden,
|
|
430
403
|
execute: (toolCallId, params, onUpdate, ctx, signal) =>
|
|
431
404
|
tool.execute(toolCallId, params, onUpdate, createCustomToolContext(ctx), signal),
|
|
432
405
|
onSession: tool.onSession ? (event, ctx) => tool.onSession?.(event, createCustomToolContext(ctx)) : undefined,
|
|
@@ -509,7 +482,7 @@ function createCustomToolsExtension(tools: CustomTool[]): ExtensionFactory {
|
|
|
509
482
|
* model: myModel,
|
|
510
483
|
* getApiKey: async () => process.env.MY_KEY,
|
|
511
484
|
* systemPrompt: 'You are helpful.',
|
|
512
|
-
* tools:
|
|
485
|
+
* tools: codingTools({ cwd: process.cwd() }),
|
|
513
486
|
* skills: [],
|
|
514
487
|
* sessionManager: SessionManager.inMemory(),
|
|
515
488
|
* });
|
|
@@ -630,30 +603,18 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
630
603
|
const contextFiles = options.contextFiles ?? discoverContextFiles(cwd, agentDir);
|
|
631
604
|
time("discoverContextFiles");
|
|
632
605
|
|
|
633
|
-
const
|
|
606
|
+
const toolSession: ToolSession = {
|
|
607
|
+
cwd,
|
|
608
|
+
hasUI: options.hasUI ?? false,
|
|
609
|
+
rulebookRules,
|
|
610
|
+
eventBus,
|
|
634
611
|
getSessionFile: () => sessionManager.getSessionFile() ?? null,
|
|
612
|
+
getSessionSpawns: () => options.spawns ?? "*",
|
|
613
|
+
settings: settingsManager,
|
|
635
614
|
};
|
|
636
|
-
const allBuiltInToolsMap = await createAllTools(cwd, sessionContext, {
|
|
637
|
-
lspFormatOnWrite: settingsManager.getLspFormatOnWrite(),
|
|
638
|
-
lspDiagnosticsOnWrite: settingsManager.getLspDiagnosticsOnWrite(),
|
|
639
|
-
lspDiagnosticsOnEdit: settingsManager.getLspDiagnosticsOnEdit(),
|
|
640
|
-
editFuzzyMatch: settingsManager.getEditFuzzyMatch(),
|
|
641
|
-
readAutoResizeImages: settingsManager.getImageAutoResize(),
|
|
642
|
-
});
|
|
643
|
-
time("createAllTools");
|
|
644
615
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
? options.tools.map((t) => t.name).filter((n): n is ToolName => n in allBuiltInToolsMap)
|
|
648
|
-
: [...baseCodingToolNames];
|
|
649
|
-
|
|
650
|
-
// Filter out git tool if disabled in settings
|
|
651
|
-
if (!settingsManager.getGitToolEnabled()) {
|
|
652
|
-
baseToolNames = baseToolNames.filter((name) => name !== "git");
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
const initialActiveToolNames: ToolName[] = baseToolNames;
|
|
656
|
-
const initialActiveBuiltInTools = initialActiveToolNames.map((name) => allBuiltInToolsMap[name]);
|
|
616
|
+
const builtinTools = await createTools(toolSession, options.toolNames);
|
|
617
|
+
time("createAllTools");
|
|
657
618
|
|
|
658
619
|
// Discover MCP tools from .mcp.json files
|
|
659
620
|
let mcpManager: MCPManager | undefined;
|
|
@@ -840,61 +801,30 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
840
801
|
hasQueuedMessages: () => session.queuedMessageCount > 0,
|
|
841
802
|
}));
|
|
842
803
|
|
|
804
|
+
// All built-in tools are active (conditional tools like git/ask return null from factory if disabled)
|
|
843
805
|
const toolRegistry = new Map<string, AgentTool>();
|
|
844
|
-
for (const
|
|
845
|
-
toolRegistry.set(name, tool as AgentTool);
|
|
806
|
+
for (const tool of builtinTools) {
|
|
807
|
+
toolRegistry.set(tool.name, tool as AgentTool);
|
|
846
808
|
}
|
|
847
|
-
for (const tool of wrappedExtensionTools
|
|
809
|
+
for (const tool of wrappedExtensionTools) {
|
|
848
810
|
toolRegistry.set(tool.name, tool);
|
|
849
811
|
}
|
|
850
|
-
|
|
851
|
-
let activeToolsArray: Tool[] = [...initialActiveBuiltInTools, ...wrappedExtensionTools];
|
|
852
|
-
|
|
853
|
-
if (rulebookRules.length > 0) {
|
|
854
|
-
activeToolsArray.push(createRulebookTool(rulebookRules));
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
if (options.explicitTools) {
|
|
858
|
-
const explicitSet = new Set(options.explicitTools);
|
|
859
|
-
activeToolsArray = activeToolsArray.filter((tool) => !tool.hidden || explicitSet.has(tool.name));
|
|
860
|
-
} else {
|
|
861
|
-
activeToolsArray = activeToolsArray.filter((tool) => !tool.hidden);
|
|
862
|
-
}
|
|
863
|
-
time("combineTools");
|
|
864
|
-
|
|
865
|
-
if (settingsManager.getBashInterceptorEnabled()) {
|
|
866
|
-
activeToolsArray = applyBashInterception(activeToolsArray);
|
|
867
|
-
}
|
|
868
|
-
time("applyBashInterception");
|
|
869
|
-
|
|
870
|
-
let wrappedToolRegistry: Map<string, AgentTool> | undefined;
|
|
871
812
|
if (extensionRunner) {
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
const wrappedAllTools = wrapToolsWithExtensions(allRegistryTools, extensionRunner);
|
|
875
|
-
wrappedToolRegistry = new Map<string, AgentTool>();
|
|
876
|
-
for (const tool of wrappedAllTools) {
|
|
877
|
-
wrappedToolRegistry.set(tool.name, tool);
|
|
813
|
+
for (const tool of toolRegistry.values()) {
|
|
814
|
+
toolRegistry.set(tool.name, wrapToolWithExtensions(tool, extensionRunner));
|
|
878
815
|
}
|
|
879
816
|
}
|
|
817
|
+
time("combineTools");
|
|
880
818
|
|
|
881
|
-
const rebuildSystemPrompt = (toolNames: string[]): string => {
|
|
882
|
-
|
|
883
|
-
const extraToolDescriptions = toolNames
|
|
884
|
-
.filter((name) => !(name in allBuiltInToolsMap))
|
|
885
|
-
.map((name) => {
|
|
886
|
-
const tool = toolRegistry.get(name);
|
|
887
|
-
if (!tool) return null;
|
|
888
|
-
return { name, description: tool.description || tool.label || "Custom tool" };
|
|
889
|
-
})
|
|
890
|
-
.filter((tool): tool is { name: string; description: string } => tool !== null);
|
|
819
|
+
const rebuildSystemPrompt = (toolNames: string[], tools: Map<string, AgentTool>): string => {
|
|
820
|
+
toolContextStore.setToolNames(toolNames);
|
|
891
821
|
const defaultPrompt = buildSystemPromptInternal({
|
|
892
822
|
cwd,
|
|
893
823
|
skills,
|
|
894
824
|
contextFiles,
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
825
|
+
tools,
|
|
826
|
+
toolNames,
|
|
827
|
+
rules: rulebookRules,
|
|
898
828
|
skillsSettings: settingsManager.getSkillsSettings(),
|
|
899
829
|
});
|
|
900
830
|
|
|
@@ -906,9 +836,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
906
836
|
cwd,
|
|
907
837
|
skills,
|
|
908
838
|
contextFiles,
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
839
|
+
tools,
|
|
840
|
+
toolNames,
|
|
841
|
+
rules: rulebookRules,
|
|
912
842
|
skillsSettings: settingsManager.getSkillsSettings(),
|
|
913
843
|
customPrompt: options.systemPrompt,
|
|
914
844
|
});
|
|
@@ -916,7 +846,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
916
846
|
return options.systemPrompt(defaultPrompt);
|
|
917
847
|
};
|
|
918
848
|
|
|
919
|
-
const systemPrompt = rebuildSystemPrompt(
|
|
849
|
+
const systemPrompt = rebuildSystemPrompt(Array.from(toolRegistry.keys()), toolRegistry);
|
|
920
850
|
time("buildSystemPrompt");
|
|
921
851
|
|
|
922
852
|
const promptTemplates = options.promptTemplates ?? (await discoverPromptTemplates(cwd, agentDir));
|
|
@@ -936,7 +866,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
936
866
|
systemPrompt,
|
|
937
867
|
model,
|
|
938
868
|
thinkingLevel,
|
|
939
|
-
tools:
|
|
869
|
+
tools: Array.from(toolRegistry.values()),
|
|
940
870
|
},
|
|
941
871
|
convertToLlm,
|
|
942
872
|
transformContext: extensionRunner
|
|
@@ -984,7 +914,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
984
914
|
customCommands: customCommandsResult.commands,
|
|
985
915
|
skillsSettings: settingsManager.getSkillsSettings(),
|
|
986
916
|
modelRegistry,
|
|
987
|
-
toolRegistry
|
|
917
|
+
toolRegistry,
|
|
988
918
|
rebuildSystemPrompt,
|
|
989
919
|
ttsrManager,
|
|
990
920
|
});
|
|
@@ -698,6 +698,13 @@ async function truncateForPersistence<T>(obj: T, key?: string): Promise<T> {
|
|
|
698
698
|
let changed = false;
|
|
699
699
|
const result: Record<string, unknown> = {};
|
|
700
700
|
for (const [k, v] of Object.entries(obj as Record<string, unknown>)) {
|
|
701
|
+
// Strip transient/redundant properties that shouldn't be persisted
|
|
702
|
+
// - partialJson: streaming accumulator for tool call JSON parsing
|
|
703
|
+
// - jsonlEvents: raw subprocess streaming events (already saved to artifact files)
|
|
704
|
+
if (k === "partialJson" || k === "jsonlEvents") {
|
|
705
|
+
changed = true;
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
701
708
|
const newV = await truncateForPersistence(v, k);
|
|
702
709
|
result[k] = newV;
|
|
703
710
|
if (newV !== v) changed = true;
|
|
@@ -256,10 +256,10 @@ export function loadSystemPromptFiles(options: LoadContextFilesOptions = {}): st
|
|
|
256
256
|
export interface BuildSystemPromptOptions {
|
|
257
257
|
/** Custom system prompt (replaces default). */
|
|
258
258
|
customPrompt?: string;
|
|
259
|
-
/** Tools to include in prompt.
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
|
|
259
|
+
/** Tools to include in prompt. */
|
|
260
|
+
tools?: Map<string, { description: string; label: string }>;
|
|
261
|
+
/** Tool names to include in prompt. */
|
|
262
|
+
toolNames?: string[];
|
|
263
263
|
/** Text to append to system prompt. */
|
|
264
264
|
appendSystemPrompt?: string;
|
|
265
265
|
/** Skills settings for discovery. */
|
|
@@ -271,21 +271,21 @@ export interface BuildSystemPromptOptions {
|
|
|
271
271
|
/** Pre-loaded skills (skips discovery if provided). */
|
|
272
272
|
skills?: Skill[];
|
|
273
273
|
/** Pre-loaded rulebook rules (rules with descriptions, excluding TTSR and always-apply). */
|
|
274
|
-
|
|
274
|
+
rules?: Rule[];
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
/** Build the system prompt with tools, guidelines, and context */
|
|
278
278
|
export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): string {
|
|
279
279
|
const {
|
|
280
280
|
customPrompt,
|
|
281
|
-
|
|
282
|
-
extraToolDescriptions = [],
|
|
281
|
+
tools,
|
|
283
282
|
appendSystemPrompt,
|
|
284
283
|
skillsSettings,
|
|
284
|
+
toolNames,
|
|
285
285
|
cwd,
|
|
286
286
|
contextFiles: providedContextFiles,
|
|
287
287
|
skills: providedSkills,
|
|
288
|
-
rulebookRules,
|
|
288
|
+
rules: rulebookRules,
|
|
289
289
|
} = options;
|
|
290
290
|
const resolvedCwd = cwd ?? process.cwd();
|
|
291
291
|
const resolvedCustomPrompt = resolvePromptInput(customPrompt, "system prompt");
|
|
@@ -311,6 +311,9 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
|
|
|
311
311
|
// Resolve context files: use provided or discover
|
|
312
312
|
const contextFiles = providedContextFiles ?? loadProjectContextFiles({ cwd: resolvedCwd });
|
|
313
313
|
|
|
314
|
+
// Build tools list based on selected tools
|
|
315
|
+
const toolsList = toolNames?.map((name) => `- ${name}: ${toolDescriptions[name as ToolName]}`).join("\n") ?? "";
|
|
316
|
+
|
|
314
317
|
// Resolve skills: use provided or discover
|
|
315
318
|
const skills =
|
|
316
319
|
providedSkills ??
|
|
@@ -335,9 +338,11 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
|
|
|
335
338
|
}
|
|
336
339
|
|
|
337
340
|
// Append custom tool descriptions if provided
|
|
338
|
-
if (
|
|
339
|
-
prompt += "\n\n#
|
|
340
|
-
prompt +=
|
|
341
|
+
if (tools && tools.size > 0) {
|
|
342
|
+
prompt += "\n\n# Tools\n\n";
|
|
343
|
+
prompt += Array.from(tools.entries())
|
|
344
|
+
.map(([name, { description }]) => `- ${name}: ${description}`)
|
|
345
|
+
.join("\n");
|
|
341
346
|
}
|
|
342
347
|
|
|
343
348
|
// Append git context if in a git repo
|
|
@@ -347,8 +352,7 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
|
|
|
347
352
|
}
|
|
348
353
|
|
|
349
354
|
// Append skills section (only if read tool is available)
|
|
350
|
-
|
|
351
|
-
if (customPromptHasRead && skills.length > 0) {
|
|
355
|
+
if (tools?.has("read") && skills.length > 0) {
|
|
352
356
|
prompt += formatSkillsForPrompt(skills);
|
|
353
357
|
}
|
|
354
358
|
|
|
@@ -369,25 +373,16 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
|
|
|
369
373
|
const docsPath = getDocsPath();
|
|
370
374
|
const examplesPath = getExamplesPath();
|
|
371
375
|
|
|
372
|
-
// Build tools list based on selected tools
|
|
373
|
-
const tools = selectedTools || (["read", "bash", "edit", "write"] as ToolName[]);
|
|
374
|
-
const builtInToolsList = tools.map((t) => `- ${t}: ${toolDescriptions[t]}`).join("\n");
|
|
375
|
-
const extraToolsList =
|
|
376
|
-
extraToolDescriptions.length > 0
|
|
377
|
-
? extraToolDescriptions.map((tool) => `- ${tool.name}: ${tool.description}`).join("\n")
|
|
378
|
-
: "";
|
|
379
|
-
const toolsList = [builtInToolsList, extraToolsList].filter(Boolean).join("\n");
|
|
380
|
-
|
|
381
376
|
// Generate anti-bash rules (returns null if not applicable)
|
|
382
|
-
const antiBashSection = generateAntiBashRules(tools);
|
|
377
|
+
const antiBashSection = generateAntiBashRules(Array.from(tools?.keys() ?? []));
|
|
383
378
|
|
|
384
379
|
// Build guidelines based on which tools are actually available
|
|
385
380
|
const guidelinesList: string[] = [];
|
|
386
381
|
|
|
387
|
-
const hasBash = tools
|
|
388
|
-
const hasEdit = tools
|
|
389
|
-
const hasWrite = tools
|
|
390
|
-
const hasRead = tools
|
|
382
|
+
const hasBash = tools?.has("bash");
|
|
383
|
+
const hasEdit = tools?.has("edit");
|
|
384
|
+
const hasWrite = tools?.has("write");
|
|
385
|
+
const hasRead = tools?.has("read");
|
|
391
386
|
|
|
392
387
|
// Read-only mode notice (no bash, edit, or write)
|
|
393
388
|
if (!hasBash && !hasEdit && !hasWrite) {
|
|
@@ -454,12 +449,6 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
|
|
|
454
449
|
}
|
|
455
450
|
}
|
|
456
451
|
|
|
457
|
-
// Append custom tool descriptions if provided
|
|
458
|
-
if (extraToolDescriptions.length > 0) {
|
|
459
|
-
prompt += "\n\n# Additional Tools\n\n";
|
|
460
|
-
prompt += extraToolDescriptions.map((tool) => `- ${tool.name}: ${tool.description}`).join("\n");
|
|
461
|
-
}
|
|
462
|
-
|
|
463
452
|
// Append git context if in a git repo
|
|
464
453
|
const gitContext = loadGitContext(resolvedCwd);
|
|
465
454
|
if (gitContext) {
|
package/src/core/tools/ask.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { Type } from "@sinclair/typebox";
|
|
|
22
22
|
import { type Theme, theme } from "../../modes/interactive/theme/theme";
|
|
23
23
|
import askDescription from "../../prompts/tools/ask.md" with { type: "text" };
|
|
24
24
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
25
|
+
import type { ToolSession } from "./index";
|
|
25
26
|
import { formatErrorMessage, formatMeta } from "./render-utils";
|
|
26
27
|
|
|
27
28
|
// =============================================================================
|
|
@@ -67,7 +68,10 @@ function getDoneOptionLabel(): string {
|
|
|
67
68
|
// Tool Implementation
|
|
68
69
|
// =============================================================================
|
|
69
70
|
|
|
70
|
-
export function createAskTool(
|
|
71
|
+
export function createAskTool(session: ToolSession): null | AgentTool<typeof askSchema, AskToolDetails> {
|
|
72
|
+
if (!session.hasUI) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
71
75
|
return {
|
|
72
76
|
name: "ask",
|
|
73
77
|
label: "Ask",
|
|
@@ -193,8 +197,14 @@ export function createAskTool(_cwd: string): AgentTool<typeof askSchema, AskTool
|
|
|
193
197
|
};
|
|
194
198
|
}
|
|
195
199
|
|
|
196
|
-
/** Default ask tool
|
|
197
|
-
export const askTool = createAskTool(
|
|
200
|
+
/** Default ask tool - returns null when no UI */
|
|
201
|
+
export const askTool = createAskTool({
|
|
202
|
+
cwd: process.cwd(),
|
|
203
|
+
hasUI: false,
|
|
204
|
+
rulebookRules: [],
|
|
205
|
+
getSessionFile: () => null,
|
|
206
|
+
getSessionSpawns: () => "*",
|
|
207
|
+
});
|
|
198
208
|
|
|
199
209
|
// =============================================================================
|
|
200
210
|
// TUI Renderer
|
|
@@ -225,10 +235,7 @@ export const askToolRenderer = {
|
|
|
225
235
|
const opt = args.options[i];
|
|
226
236
|
const isLast = i === args.options.length - 1;
|
|
227
237
|
const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
228
|
-
text += `\n ${uiTheme.fg("dim", branch)} ${uiTheme.fg(
|
|
229
|
-
"dim",
|
|
230
|
-
uiTheme.checkbox.unchecked,
|
|
231
|
-
)} ${uiTheme.fg("muted", opt.label)}`;
|
|
238
|
+
text += `\n ${uiTheme.fg("dim", branch)} ${uiTheme.fg("dim", uiTheme.checkbox.unchecked)} ${uiTheme.fg("muted", opt.label)}`;
|
|
232
239
|
}
|
|
233
240
|
}
|
|
234
241
|
|
|
@@ -80,13 +80,13 @@ const forbiddenPatterns: Array<{
|
|
|
80
80
|
* @param availableTools Set of tool names that are available
|
|
81
81
|
* @returns InterceptionResult indicating if the command should be blocked
|
|
82
82
|
*/
|
|
83
|
-
export function checkBashInterception(command: string, availableTools:
|
|
83
|
+
export function checkBashInterception(command: string, availableTools: string[]): InterceptionResult {
|
|
84
84
|
// Normalize command for pattern matching
|
|
85
85
|
const normalizedCommand = command.trim();
|
|
86
86
|
|
|
87
87
|
for (const { pattern, tool, message } of forbiddenPatterns) {
|
|
88
88
|
// Only block if the suggested tool is actually available
|
|
89
|
-
if (!availableTools.
|
|
89
|
+
if (!availableTools.includes(tool)) {
|
|
90
90
|
continue;
|
|
91
91
|
}
|
|
92
92
|
|
|
@@ -106,8 +106,8 @@ export function checkBashInterception(command: string, availableTools: Set<strin
|
|
|
106
106
|
* Check if a command is a simple directory listing that should use `ls` tool.
|
|
107
107
|
* Only applies to bare `ls` without complex flags.
|
|
108
108
|
*/
|
|
109
|
-
export function checkSimpleLsInterception(command: string, availableTools:
|
|
110
|
-
if (!availableTools.
|
|
109
|
+
export function checkSimpleLsInterception(command: string, availableTools: string[]): InterceptionResult {
|
|
110
|
+
if (!availableTools.includes("ls")) {
|
|
111
111
|
return { block: false };
|
|
112
112
|
}
|
|
113
113
|
|
package/src/core/tools/bash.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
1
|
+
import type { AgentTool, AgentToolContext } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { Type } from "@sinclair/typebox";
|
|
@@ -6,6 +6,8 @@ import type { Theme } from "../../modes/interactive/theme/theme";
|
|
|
6
6
|
import bashDescription from "../../prompts/tools/bash.md" with { type: "text" };
|
|
7
7
|
import { executeBash } from "../bash-executor";
|
|
8
8
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
9
|
+
import { checkBashInterception, checkSimpleLsInterception } from "./bash-interceptor";
|
|
10
|
+
import type { ToolSession } from "./index";
|
|
9
11
|
import { formatBytes, wrapBrackets } from "./render-utils";
|
|
10
12
|
import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateTail } from "./truncate";
|
|
11
13
|
|
|
@@ -19,7 +21,7 @@ export interface BashToolDetails {
|
|
|
19
21
|
fullOutputPath?: string;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
export function createBashTool(
|
|
24
|
+
export function createBashTool(session: ToolSession): AgentTool<typeof bashSchema> {
|
|
23
25
|
return {
|
|
24
26
|
name: "bash",
|
|
25
27
|
label: "Bash",
|
|
@@ -30,12 +32,25 @@ export function createBashTool(cwd: string): AgentTool<typeof bashSchema> {
|
|
|
30
32
|
{ command, timeout }: { command: string; timeout?: number },
|
|
31
33
|
signal?: AbortSignal,
|
|
32
34
|
onUpdate?,
|
|
35
|
+
ctx?: AgentToolContext,
|
|
33
36
|
) => {
|
|
37
|
+
// Check interception if enabled and available tools are known
|
|
38
|
+
if (session.settings?.getBashInterceptorEnabled()) {
|
|
39
|
+
const interception = checkBashInterception(command, ctx?.toolNames ?? []);
|
|
40
|
+
if (interception.block) {
|
|
41
|
+
throw new Error(interception.message);
|
|
42
|
+
}
|
|
43
|
+
const lsInterception = checkSimpleLsInterception(command, ctx?.toolNames ?? []);
|
|
44
|
+
if (lsInterception.block) {
|
|
45
|
+
throw new Error(lsInterception.message);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
34
49
|
// Track output for streaming updates
|
|
35
50
|
let currentOutput = "";
|
|
36
51
|
|
|
37
52
|
const result = await executeBash(command, {
|
|
38
|
-
cwd,
|
|
53
|
+
cwd: session.cwd,
|
|
39
54
|
timeout: timeout ? timeout * 1000 : undefined, // Convert to milliseconds
|
|
40
55
|
signal,
|
|
41
56
|
onChunk: (chunk) => {
|
|
@@ -92,9 +107,6 @@ export function createBashTool(cwd: string): AgentTool<typeof bashSchema> {
|
|
|
92
107
|
};
|
|
93
108
|
}
|
|
94
109
|
|
|
95
|
-
/** Default bash tool using process.cwd() - for backwards compatibility */
|
|
96
|
-
export const bashTool = createBashTool(process.cwd());
|
|
97
|
-
|
|
98
110
|
// =============================================================================
|
|
99
111
|
// TUI Renderer
|
|
100
112
|
// =============================================================================
|
|
@@ -183,9 +195,7 @@ export const bashToolRenderer = {
|
|
|
183
195
|
warnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);
|
|
184
196
|
} else {
|
|
185
197
|
warnings.push(
|
|
186
|
-
`Truncated: ${truncation.outputLines} lines shown (${formatBytes(
|
|
187
|
-
truncation.maxBytes ?? DEFAULT_MAX_BYTES,
|
|
188
|
-
)} limit)`,
|
|
198
|
+
`Truncated: ${truncation.outputLines} lines shown (${formatBytes(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)`,
|
|
189
199
|
);
|
|
190
200
|
}
|
|
191
201
|
}
|
|
@@ -6,27 +6,34 @@ declare module "@oh-my-pi/pi-agent-core" {
|
|
|
6
6
|
interface AgentToolContext extends CustomToolContext {
|
|
7
7
|
ui?: ExtensionUIContext;
|
|
8
8
|
hasUI?: boolean;
|
|
9
|
+
toolNames?: string[];
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export interface ToolContextStore {
|
|
13
14
|
getContext(): AgentToolContext;
|
|
14
15
|
setUIContext(uiContext: ExtensionUIContext, hasUI: boolean): void;
|
|
16
|
+
setToolNames(names: string[]): void;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
export function createToolContextStore(getBaseContext: () => CustomToolContext): ToolContextStore {
|
|
18
20
|
let uiContext: ExtensionUIContext | undefined;
|
|
19
21
|
let hasUI = false;
|
|
22
|
+
let toolNames: string[] = [];
|
|
20
23
|
|
|
21
24
|
return {
|
|
22
25
|
getContext: () => ({
|
|
23
26
|
...getBaseContext(),
|
|
24
27
|
ui: uiContext,
|
|
25
28
|
hasUI,
|
|
29
|
+
toolNames,
|
|
26
30
|
}),
|
|
27
31
|
setUIContext: (context, uiAvailable) => {
|
|
28
32
|
uiContext = context;
|
|
29
33
|
hasUI = uiAvailable;
|
|
30
34
|
},
|
|
35
|
+
setToolNames: (names) => {
|
|
36
|
+
toolNames = names;
|
|
37
|
+
},
|
|
31
38
|
};
|
|
32
39
|
}
|
package/src/core/tools/edit.ts
CHANGED
|
@@ -17,7 +17,8 @@ import {
|
|
|
17
17
|
restoreLineEndings,
|
|
18
18
|
stripBom,
|
|
19
19
|
} from "./edit-diff";
|
|
20
|
-
import
|
|
20
|
+
import type { ToolSession } from "./index";
|
|
21
|
+
import { createLspWritethrough, type FileDiagnosticsResult } from "./lsp/index";
|
|
21
22
|
import { resolveToCwd } from "./path-utils";
|
|
22
23
|
import {
|
|
23
24
|
formatDiagnostics,
|
|
@@ -46,16 +47,11 @@ export interface EditToolDetails {
|
|
|
46
47
|
diagnostics?: FileDiagnosticsResult;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
export
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
writethrough
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function createEditTool(cwd: string, options: EditToolOptions = {}): AgentTool<typeof editSchema> {
|
|
57
|
-
const allowFuzzy = options.fuzzyMatch ?? true;
|
|
58
|
-
const writethrough = options.writethrough ?? writethroughNoop;
|
|
50
|
+
export function createEditTool(session: ToolSession): AgentTool<typeof editSchema> {
|
|
51
|
+
const allowFuzzy = session.settings?.getEditFuzzyMatch() ?? true;
|
|
52
|
+
const enableDiagnostics = session.settings?.getLspDiagnosticsOnEdit() ?? false;
|
|
53
|
+
const enableFormat = session.settings?.getLspFormatOnWrite() ?? true;
|
|
54
|
+
const writethrough = createLspWritethrough(session.cwd, { enableFormat, enableDiagnostics });
|
|
59
55
|
return {
|
|
60
56
|
name: "edit",
|
|
61
57
|
label: "Edit",
|
|
@@ -71,7 +67,7 @@ export function createEditTool(cwd: string, options: EditToolOptions = {}): Agen
|
|
|
71
67
|
throw new Error("Cannot edit Jupyter notebooks with the Edit tool. Use the NotebookEdit tool instead.");
|
|
72
68
|
}
|
|
73
69
|
|
|
74
|
-
const absolutePath = resolveToCwd(path, cwd);
|
|
70
|
+
const absolutePath = resolveToCwd(path, session.cwd);
|
|
75
71
|
|
|
76
72
|
const file = Bun.file(absolutePath);
|
|
77
73
|
if (!(await file.exists())) {
|
|
@@ -203,9 +199,6 @@ export function createEditTool(cwd: string, options: EditToolOptions = {}): Agen
|
|
|
203
199
|
};
|
|
204
200
|
}
|
|
205
201
|
|
|
206
|
-
/** Default edit tool using process.cwd() - for backwards compatibility */
|
|
207
|
-
export const editTool = createEditTool(process.cwd());
|
|
208
|
-
|
|
209
202
|
// =============================================================================
|
|
210
203
|
// TUI Renderer
|
|
211
204
|
// =============================================================================
|