@oh-my-pi/pi-coding-agent 3.21.0 → 3.25.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 +55 -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-commands/bundled/wt/index.ts +3 -0
- 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 +48 -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/complete.ts +131 -0
- 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 +188 -0
- package/src/core/tools/index.ts +106 -236
- package/src/core/tools/jtd-to-json-schema.ts +274 -0
- 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 +130 -31
- 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/artifacts.ts +6 -9
- package/src/core/tools/task/discovery.ts +0 -6
- package/src/core/tools/task/executor.ts +306 -257
- package/src/core/tools/task/index.ts +65 -235
- package/src/core/tools/task/name-generator.ts +247 -0
- package/src/core/tools/task/render.ts +158 -19
- package/src/core/tools/task/types.ts +13 -11
- package/src/core/tools/task/worker-protocol.ts +18 -0
- package/src/core/tools/task/worker.ts +270 -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 +14 -57
- package/src/prompts/tools/output.md +4 -3
- package/src/prompts/tools/task.md +70 -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,12 @@ export interface CreateAgentSessionOptions {
|
|
|
163
147
|
enableMCP?: boolean;
|
|
164
148
|
|
|
165
149
|
/** Tool names explicitly requested (enables disabled-by-default tools) */
|
|
166
|
-
|
|
150
|
+
toolNames?: string[];
|
|
151
|
+
|
|
152
|
+
/** Output schema for structured completion (subagents) */
|
|
153
|
+
outputSchema?: unknown;
|
|
154
|
+
/** Whether to include the complete tool by default */
|
|
155
|
+
requireCompleteTool?: boolean;
|
|
167
156
|
|
|
168
157
|
/** Session manager. Default: SessionManager.create(cwd) */
|
|
169
158
|
sessionManager?: SessionManager;
|
|
@@ -208,21 +197,11 @@ export type { FileSlashCommand } from "./slash-commands";
|
|
|
208
197
|
export type { Tool } from "./tools/index";
|
|
209
198
|
|
|
210
199
|
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,
|
|
200
|
+
// Tool factories
|
|
201
|
+
BUILTIN_TOOLS,
|
|
202
|
+
createTools,
|
|
203
|
+
type ToolSession,
|
|
204
|
+
// Individual tool factories (for custom usage)
|
|
226
205
|
createReadTool,
|
|
227
206
|
createBashTool,
|
|
228
207
|
createEditTool,
|
|
@@ -426,7 +405,6 @@ function customToolToDefinition(tool: CustomTool): ToolDefinition {
|
|
|
426
405
|
label: tool.label,
|
|
427
406
|
description: tool.description,
|
|
428
407
|
parameters: tool.parameters,
|
|
429
|
-
hidden: tool.hidden,
|
|
430
408
|
execute: (toolCallId, params, onUpdate, ctx, signal) =>
|
|
431
409
|
tool.execute(toolCallId, params, onUpdate, createCustomToolContext(ctx), signal),
|
|
432
410
|
onSession: tool.onSession ? (event, ctx) => tool.onSession?.(event, createCustomToolContext(ctx)) : undefined,
|
|
@@ -509,7 +487,7 @@ function createCustomToolsExtension(tools: CustomTool[]): ExtensionFactory {
|
|
|
509
487
|
* model: myModel,
|
|
510
488
|
* getApiKey: async () => process.env.MY_KEY,
|
|
511
489
|
* systemPrompt: 'You are helpful.',
|
|
512
|
-
* tools:
|
|
490
|
+
* tools: codingTools({ cwd: process.cwd() }),
|
|
513
491
|
* skills: [],
|
|
514
492
|
* sessionManager: SessionManager.inMemory(),
|
|
515
493
|
* });
|
|
@@ -630,30 +608,20 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
630
608
|
const contextFiles = options.contextFiles ?? discoverContextFiles(cwd, agentDir);
|
|
631
609
|
time("discoverContextFiles");
|
|
632
610
|
|
|
633
|
-
const
|
|
611
|
+
const toolSession: ToolSession = {
|
|
612
|
+
cwd,
|
|
613
|
+
hasUI: options.hasUI ?? false,
|
|
614
|
+
rulebookRules,
|
|
615
|
+
eventBus,
|
|
616
|
+
outputSchema: options.outputSchema,
|
|
617
|
+
requireCompleteTool: options.requireCompleteTool,
|
|
634
618
|
getSessionFile: () => sessionManager.getSessionFile() ?? null,
|
|
619
|
+
getSessionSpawns: () => options.spawns ?? "*",
|
|
620
|
+
settings: settingsManager,
|
|
635
621
|
};
|
|
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
622
|
|
|
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]);
|
|
623
|
+
const builtinTools = await createTools(toolSession, options.toolNames);
|
|
624
|
+
time("createAllTools");
|
|
657
625
|
|
|
658
626
|
// Discover MCP tools from .mcp.json files
|
|
659
627
|
let mcpManager: MCPManager | undefined;
|
|
@@ -840,61 +808,30 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
840
808
|
hasQueuedMessages: () => session.queuedMessageCount > 0,
|
|
841
809
|
}));
|
|
842
810
|
|
|
811
|
+
// All built-in tools are active (conditional tools like git/ask return null from factory if disabled)
|
|
843
812
|
const toolRegistry = new Map<string, AgentTool>();
|
|
844
|
-
for (const
|
|
845
|
-
toolRegistry.set(name, tool as AgentTool);
|
|
813
|
+
for (const tool of builtinTools) {
|
|
814
|
+
toolRegistry.set(tool.name, tool as AgentTool);
|
|
846
815
|
}
|
|
847
|
-
for (const tool of wrappedExtensionTools
|
|
816
|
+
for (const tool of wrappedExtensionTools) {
|
|
848
817
|
toolRegistry.set(tool.name, tool);
|
|
849
818
|
}
|
|
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
819
|
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);
|
|
820
|
+
for (const tool of toolRegistry.values()) {
|
|
821
|
+
toolRegistry.set(tool.name, wrapToolWithExtensions(tool, extensionRunner));
|
|
878
822
|
}
|
|
879
823
|
}
|
|
824
|
+
time("combineTools");
|
|
880
825
|
|
|
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);
|
|
826
|
+
const rebuildSystemPrompt = (toolNames: string[], tools: Map<string, AgentTool>): string => {
|
|
827
|
+
toolContextStore.setToolNames(toolNames);
|
|
891
828
|
const defaultPrompt = buildSystemPromptInternal({
|
|
892
829
|
cwd,
|
|
893
830
|
skills,
|
|
894
831
|
contextFiles,
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
832
|
+
tools,
|
|
833
|
+
toolNames,
|
|
834
|
+
rules: rulebookRules,
|
|
898
835
|
skillsSettings: settingsManager.getSkillsSettings(),
|
|
899
836
|
});
|
|
900
837
|
|
|
@@ -906,9 +843,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
906
843
|
cwd,
|
|
907
844
|
skills,
|
|
908
845
|
contextFiles,
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
846
|
+
tools,
|
|
847
|
+
toolNames,
|
|
848
|
+
rules: rulebookRules,
|
|
912
849
|
skillsSettings: settingsManager.getSkillsSettings(),
|
|
913
850
|
customPrompt: options.systemPrompt,
|
|
914
851
|
});
|
|
@@ -916,7 +853,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
916
853
|
return options.systemPrompt(defaultPrompt);
|
|
917
854
|
};
|
|
918
855
|
|
|
919
|
-
const systemPrompt = rebuildSystemPrompt(
|
|
856
|
+
const systemPrompt = rebuildSystemPrompt(Array.from(toolRegistry.keys()), toolRegistry);
|
|
920
857
|
time("buildSystemPrompt");
|
|
921
858
|
|
|
922
859
|
const promptTemplates = options.promptTemplates ?? (await discoverPromptTemplates(cwd, agentDir));
|
|
@@ -936,7 +873,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
936
873
|
systemPrompt,
|
|
937
874
|
model,
|
|
938
875
|
thinkingLevel,
|
|
939
|
-
tools:
|
|
876
|
+
tools: Array.from(toolRegistry.values()),
|
|
940
877
|
},
|
|
941
878
|
convertToLlm,
|
|
942
879
|
transformContext: extensionRunner
|
|
@@ -984,7 +921,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
984
921
|
customCommands: customCommandsResult.commands,
|
|
985
922
|
skillsSettings: settingsManager.getSkillsSettings(),
|
|
986
923
|
modelRegistry,
|
|
987
|
-
toolRegistry
|
|
924
|
+
toolRegistry,
|
|
988
925
|
rebuildSystemPrompt,
|
|
989
926
|
ttsrManager,
|
|
990
927
|
});
|
|
@@ -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
|
}
|