@oh-my-pi/pi-coding-agent 4.1.0 → 4.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/README.md +2 -1
- package/docs/sdk.md +0 -3
- package/package.json +6 -5
- package/src/config.ts +9 -0
- package/src/core/agent-session.ts +3 -3
- package/src/core/agent-storage.ts +450 -0
- package/src/core/auth-storage.ts +102 -183
- package/src/core/compaction/branch-summarization.ts +5 -4
- package/src/core/compaction/compaction.ts +7 -6
- package/src/core/compaction/utils.ts +6 -11
- package/src/core/custom-commands/bundled/review/index.ts +22 -94
- package/src/core/custom-share.ts +66 -0
- package/src/core/export-html/index.ts +1 -33
- package/src/core/history-storage.ts +15 -7
- package/src/core/prompt-templates.ts +271 -1
- package/src/core/sdk.ts +14 -3
- package/src/core/settings-manager.ts +100 -34
- package/src/core/slash-commands.ts +4 -1
- package/src/core/storage-migration.ts +215 -0
- package/src/core/system-prompt.ts +130 -290
- package/src/core/title-generator.ts +3 -2
- package/src/core/tools/ask.ts +2 -2
- package/src/core/tools/bash.ts +2 -1
- package/src/core/tools/calculator.ts +2 -1
- package/src/core/tools/complete.ts +5 -2
- package/src/core/tools/edit.ts +2 -1
- package/src/core/tools/find.ts +2 -1
- package/src/core/tools/gemini-image.ts +2 -1
- package/src/core/tools/git.ts +2 -2
- package/src/core/tools/grep.ts +2 -1
- package/src/core/tools/index.test.ts +0 -28
- package/src/core/tools/index.ts +0 -6
- package/src/core/tools/lsp/index.ts +2 -1
- package/src/core/tools/output.ts +2 -1
- package/src/core/tools/read.ts +4 -1
- package/src/core/tools/ssh.ts +4 -2
- package/src/core/tools/task/agents.ts +56 -30
- package/src/core/tools/task/commands.ts +5 -8
- package/src/core/tools/task/index.ts +7 -15
- package/src/core/tools/web-fetch.ts +2 -1
- package/src/core/tools/web-search/auth.ts +106 -16
- package/src/core/tools/web-search/index.ts +3 -2
- package/src/core/tools/web-search/providers/anthropic.ts +44 -6
- package/src/core/tools/write.ts +2 -1
- package/src/core/voice.ts +3 -1
- package/src/discovery/builtin.ts +9 -54
- package/src/discovery/claude.ts +16 -69
- package/src/discovery/codex.ts +11 -36
- package/src/discovery/helpers.ts +52 -1
- package/src/main.ts +1 -1
- package/src/migrations.ts +20 -20
- package/src/modes/interactive/controllers/command-controller.ts +527 -0
- package/src/modes/interactive/controllers/event-controller.ts +340 -0
- package/src/modes/interactive/controllers/extension-ui-controller.ts +600 -0
- package/src/modes/interactive/controllers/input-controller.ts +585 -0
- package/src/modes/interactive/controllers/selector-controller.ts +585 -0
- package/src/modes/interactive/interactive-mode.ts +363 -3139
- package/src/modes/interactive/theme/theme.ts +5 -5
- package/src/modes/interactive/types.ts +189 -0
- package/src/modes/interactive/utils/ui-helpers.ts +449 -0
- package/src/modes/interactive/utils/voice-manager.ts +96 -0
- package/src/prompts/{explore.md → agents/explore.md} +7 -5
- package/src/prompts/agents/frontmatter.md +7 -0
- package/src/prompts/{plan.md → agents/plan.md} +3 -3
- package/src/prompts/agents/planner.md +112 -0
- package/src/prompts/agents/task.md +15 -0
- package/src/prompts/review-request.md +44 -8
- package/src/prompts/system/custom-system-prompt.md +80 -0
- package/src/prompts/system/file-operations.md +12 -0
- package/src/prompts/system/system-prompt.md +237 -0
- package/src/prompts/system/title-system.md +2 -0
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/task.md +34 -22
- package/src/core/tools/rulebook.ts +0 -132
- package/src/prompts/architect-plan.md +0 -10
- package/src/prompts/implement-with-critic.md +0 -11
- package/src/prompts/implement.md +0 -11
- package/src/prompts/system-prompt.md +0 -43
- package/src/prompts/task.md +0 -14
- package/src/prompts/title-system.md +0 -8
- /package/src/prompts/{init.md → agents/init.md} +0 -0
- /package/src/prompts/{reviewer.md → agents/reviewer.md} +0 -0
- /package/src/prompts/{branch-summary-preamble.md → compaction/branch-summary-preamble.md} +0 -0
- /package/src/prompts/{branch-summary.md → compaction/branch-summary.md} +0 -0
- /package/src/prompts/{compaction-summary.md → compaction/compaction-summary.md} +0 -0
- /package/src/prompts/{compaction-turn-prefix.md → compaction/compaction-turn-prefix.md} +0 -0
- /package/src/prompts/{compaction-update-summary.md → compaction/compaction-update-summary.md} +0 -0
- /package/src/prompts/{summarization-system.md → system/summarization-system.md} +0 -0
|
@@ -7,14 +7,14 @@ import { homedir } from "node:os";
|
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import chalk from "chalk";
|
|
9
9
|
import { contextFileCapability } from "../capability/context-file";
|
|
10
|
-
import type { Rule } from "../capability/rule";
|
|
11
10
|
import { systemPromptCapability } from "../capability/system-prompt";
|
|
12
11
|
import { type ContextFile, loadSync, type SystemPrompt as SystemPromptFile } from "../discovery/index";
|
|
13
|
-
import
|
|
12
|
+
import customSystemPromptTemplate from "../prompts/system/custom-system-prompt.md" with { type: "text" };
|
|
13
|
+
import systemPromptTemplate from "../prompts/system/system-prompt.md" with { type: "text" };
|
|
14
|
+
import { renderPromptTemplate } from "./prompt-templates";
|
|
14
15
|
import type { SkillsSettings } from "./settings-manager";
|
|
15
|
-
import {
|
|
16
|
+
import { loadSkills, type Skill } from "./skills";
|
|
16
17
|
import type { ToolName } from "./tools/index";
|
|
17
|
-
import { formatRulesForPrompt } from "./tools/rulebook";
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Execute a git command synchronously and return stdout or null on failure.
|
|
@@ -25,11 +25,19 @@ function execGit(args: string[], cwd: string): string | null {
|
|
|
25
25
|
return result.stdout.toString().trim() || null;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
interface GitContext {
|
|
29
|
+
isRepo: boolean;
|
|
30
|
+
currentBranch: string;
|
|
31
|
+
mainBranch: string;
|
|
32
|
+
status: string;
|
|
33
|
+
commits: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
/**
|
|
29
37
|
* Load git context for the system prompt.
|
|
30
|
-
* Returns
|
|
38
|
+
* Returns structured git data or null if not in a git repo.
|
|
31
39
|
*/
|
|
32
|
-
export function loadGitContext(cwd: string):
|
|
40
|
+
export function loadGitContext(cwd: string): GitContext | null {
|
|
33
41
|
// Check if inside a git repo
|
|
34
42
|
const isGitRepo = execGit(["rev-parse", "--is-inside-work-tree"], cwd);
|
|
35
43
|
if (isGitRepo !== "true") return null;
|
|
@@ -48,22 +56,19 @@ export function loadGitContext(cwd: string): string | null {
|
|
|
48
56
|
|
|
49
57
|
// Get git status (porcelain format for parsing)
|
|
50
58
|
const gitStatus = execGit(["status", "--porcelain"], cwd);
|
|
51
|
-
const
|
|
59
|
+
const status = gitStatus?.trim() || "(clean)";
|
|
52
60
|
|
|
53
61
|
// Get recent commits
|
|
54
62
|
const recentCommits = execGit(["log", "--oneline", "-5"], cwd);
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
return `This is the git status at the start of the conversation. Note that this status is a snapshot in time, and will not update during the conversation.
|
|
58
|
-
Current branch: ${currentBranch}
|
|
63
|
+
const commits = recentCommits?.trim() || "(no commits)";
|
|
59
64
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
return {
|
|
66
|
+
isRepo: true,
|
|
67
|
+
currentBranch,
|
|
68
|
+
mainBranch,
|
|
69
|
+
status,
|
|
70
|
+
commits,
|
|
71
|
+
};
|
|
67
72
|
}
|
|
68
73
|
|
|
69
74
|
/** Tool descriptions for system prompt */
|
|
@@ -88,47 +93,6 @@ const toolDescriptions: Record<ToolName, string> = {
|
|
|
88
93
|
report_finding: "Report a finding during code review",
|
|
89
94
|
};
|
|
90
95
|
|
|
91
|
-
function applyTemplate(template: string, values: Record<string, string>): string {
|
|
92
|
-
let output = template;
|
|
93
|
-
for (const [key, value] of Object.entries(values)) {
|
|
94
|
-
output = output.replaceAll(`{{${key}}}`, value);
|
|
95
|
-
}
|
|
96
|
-
return output;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function appendBlock(prompt: string, block: string | null | undefined, separator = "\n\n"): string {
|
|
100
|
-
if (!block) return prompt;
|
|
101
|
-
if (block.startsWith("\n")) {
|
|
102
|
-
return `${prompt}${block}`;
|
|
103
|
-
}
|
|
104
|
-
return `${prompt}${separator}${block}`;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function appendSection(prompt: string, title: string, content: string | null | undefined): string {
|
|
108
|
-
if (!content) return prompt;
|
|
109
|
-
return `${prompt}\n\n# ${title}\n\n${content}`;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function formatProjectContext(contextFiles: Array<{ path: string; content: string; depth?: number }>): string | null {
|
|
113
|
-
if (contextFiles.length === 0) return null;
|
|
114
|
-
const parts: string[] = ["The following project context files have been loaded:", ""];
|
|
115
|
-
for (const { path: filePath, content } of contextFiles) {
|
|
116
|
-
parts.push(`## ${filePath}`, "", content, "");
|
|
117
|
-
}
|
|
118
|
-
return parts.join("\n").trimEnd();
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function formatToolDescriptions(tools: Map<string, { description: string; label: string }> | undefined): string | null {
|
|
122
|
-
if (!tools || tools.size === 0) return null;
|
|
123
|
-
return Array.from(tools.entries())
|
|
124
|
-
.map(([name, { description }]) => `- ${name}: ${description}`)
|
|
125
|
-
.join("\n");
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function buildPromptFooter(dateTime: string, cwd: string): string {
|
|
129
|
-
return `Current date and time: ${dateTime}\nCurrent working directory: ${cwd}`;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
96
|
function execCommand(args: string[]): string | null {
|
|
133
97
|
const result = Bun.spawnSync(args, { stdin: "ignore", stdout: "pipe", stderr: "pipe" });
|
|
134
98
|
if (result.exitCode !== 0) return null;
|
|
@@ -184,6 +148,45 @@ function stripQuotes(value: string): string {
|
|
|
184
148
|
return value.replace(/^"|"$/g, "");
|
|
185
149
|
}
|
|
186
150
|
|
|
151
|
+
const AGENTS_MD_PATTERN = "**/AGENTS.md";
|
|
152
|
+
const AGENTS_MD_LIMIT = 200;
|
|
153
|
+
|
|
154
|
+
interface AgentsMdSearch {
|
|
155
|
+
scopePath: string;
|
|
156
|
+
limit: number;
|
|
157
|
+
pattern: string;
|
|
158
|
+
files: string[];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function normalizePath(value: string): string {
|
|
162
|
+
return value.replace(/\\/g, "/");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function listAgentsMdFiles(root: string, limit: number): string[] {
|
|
166
|
+
try {
|
|
167
|
+
const entries = Array.from(
|
|
168
|
+
new Bun.Glob(AGENTS_MD_PATTERN).scanSync({ cwd: root, onlyFiles: true, dot: false, absolute: false }),
|
|
169
|
+
);
|
|
170
|
+
const normalized = entries
|
|
171
|
+
.map((entry) => normalizePath(entry))
|
|
172
|
+
.filter((entry) => entry.length > 0 && !entry.includes("node_modules"))
|
|
173
|
+
.sort();
|
|
174
|
+
return normalized.length > limit ? normalized.slice(0, limit) : normalized;
|
|
175
|
+
} catch {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function buildAgentsMdSearch(cwd: string): AgentsMdSearch {
|
|
181
|
+
const files = listAgentsMdFiles(cwd, AGENTS_MD_LIMIT);
|
|
182
|
+
return {
|
|
183
|
+
scopePath: ".",
|
|
184
|
+
limit: AGENTS_MD_LIMIT,
|
|
185
|
+
pattern: AGENTS_MD_PATTERN,
|
|
186
|
+
files,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
187
190
|
function getOsName(): string {
|
|
188
191
|
switch (process.platform) {
|
|
189
192
|
case "win32":
|
|
@@ -504,7 +507,7 @@ function getDiskInfo(): string | null {
|
|
|
504
507
|
}
|
|
505
508
|
}
|
|
506
509
|
|
|
507
|
-
function
|
|
510
|
+
function getEnvironmentInfo(): Array<{ label: string; value: string }> {
|
|
508
511
|
// Load cached system info or collect fresh
|
|
509
512
|
let sysInfo = loadSystemInfoCache();
|
|
510
513
|
if (!sysInfo) {
|
|
@@ -512,127 +515,19 @@ function formatEnvironmentInfo(): string {
|
|
|
512
515
|
saveSystemInfoCache(sysInfo);
|
|
513
516
|
}
|
|
514
517
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
["WM", getWindowManager()],
|
|
518
|
+
return [
|
|
519
|
+
{ label: "OS", value: sysInfo.os },
|
|
520
|
+
{ label: "Distro", value: sysInfo.distro },
|
|
521
|
+
{ label: "Kernel", value: sysInfo.kernel },
|
|
522
|
+
{ label: "Arch", value: sysInfo.arch },
|
|
523
|
+
{ label: "CPU", value: sysInfo.cpu },
|
|
524
|
+
{ label: "GPU", value: sysInfo.gpu },
|
|
525
|
+
{ label: "Disk", value: sysInfo.disk },
|
|
526
|
+
{ label: "Shell", value: getShellName() },
|
|
527
|
+
{ label: "Terminal", value: getTerminalName() },
|
|
528
|
+
{ label: "DE", value: getDesktopEnvironment() },
|
|
529
|
+
{ label: "WM", value: getWindowManager() },
|
|
528
530
|
];
|
|
529
|
-
return items.map(([label, value]) => `- ${label}: ${value}`).join("\n");
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
/**
|
|
533
|
-
* Generate anti-bash rules section if the agent has both bash and specialized tools.
|
|
534
|
-
* Only include rules for tools that are actually available.
|
|
535
|
-
*/
|
|
536
|
-
function generateAntiBashRules(tools: ToolName[]): string | null {
|
|
537
|
-
const hasBash = tools.includes("bash");
|
|
538
|
-
if (!hasBash) return null;
|
|
539
|
-
|
|
540
|
-
const hasRead = tools.includes("read");
|
|
541
|
-
const hasGrep = tools.includes("grep");
|
|
542
|
-
const hasFind = tools.includes("find");
|
|
543
|
-
const hasLs = tools.includes("ls");
|
|
544
|
-
const hasEdit = tools.includes("edit");
|
|
545
|
-
const hasLsp = tools.includes("lsp");
|
|
546
|
-
const hasGit = tools.includes("git");
|
|
547
|
-
|
|
548
|
-
// Only show rules if we have specialized tools that should be preferred
|
|
549
|
-
const hasSpecializedTools = hasRead || hasGrep || hasFind || hasLs || hasEdit || hasGit;
|
|
550
|
-
if (!hasSpecializedTools) return null;
|
|
551
|
-
|
|
552
|
-
const lines: string[] = [];
|
|
553
|
-
lines.push("## Tool Usage Rules — MANDATORY\n");
|
|
554
|
-
lines.push("### Forbidden Bash Patterns");
|
|
555
|
-
lines.push("NEVER use bash for these operations:\n");
|
|
556
|
-
|
|
557
|
-
if (hasRead) lines.push("- **File reading**: Use `read` instead of cat/head/tail/less/more");
|
|
558
|
-
if (hasGrep) lines.push("- **Content search**: Use `grep` instead of grep/rg/ag/ack");
|
|
559
|
-
if (hasFind) lines.push("- **File finding**: Use `find` instead of find/fd/locate");
|
|
560
|
-
if (hasLs) lines.push("- **Directory listing**: Use `ls` instead of bash ls");
|
|
561
|
-
if (hasEdit) lines.push("- **File editing**: Use `edit` instead of sed/awk/perl -pi/echo >/cat <<EOF");
|
|
562
|
-
if (hasGit) lines.push("- **Git operations**: Use `git` tool instead of bash git commands");
|
|
563
|
-
|
|
564
|
-
lines.push("\n### Tool Preference (highest → lowest priority)");
|
|
565
|
-
const ladder: string[] = [];
|
|
566
|
-
if (hasLsp) ladder.push("lsp (go-to-definition, references, type info) — DETERMINISTIC");
|
|
567
|
-
if (hasGrep) ladder.push("grep (text/regex search)");
|
|
568
|
-
if (hasFind) ladder.push("find (locate files by pattern)");
|
|
569
|
-
if (hasRead) ladder.push("read (view file contents)");
|
|
570
|
-
if (hasEdit) ladder.push("edit (precise text replacement)");
|
|
571
|
-
if (hasGit) ladder.push("git (structured git operations with safety guards)");
|
|
572
|
-
ladder.push(`bash (ONLY for ${hasGit ? "" : "git, "}npm, docker, make, cargo, etc.)`);
|
|
573
|
-
lines.push(ladder.map((t, i) => `${i + 1}. ${t}`).join("\n"));
|
|
574
|
-
|
|
575
|
-
// Add LSP guidance if available
|
|
576
|
-
if (hasLsp) {
|
|
577
|
-
lines.push("\n### LSP — Preferred for Semantic Queries");
|
|
578
|
-
lines.push("Use `lsp` instead of grep/bash when you need:");
|
|
579
|
-
lines.push("- **Where is X defined?** → `lsp definition`");
|
|
580
|
-
lines.push("- **What calls X?** → `lsp incoming_calls`");
|
|
581
|
-
lines.push("- **What does X call?** → `lsp outgoing_calls`");
|
|
582
|
-
lines.push("- **What type is X?** → `lsp hover`");
|
|
583
|
-
lines.push("- **What symbols are in this file?** → `lsp symbols`");
|
|
584
|
-
lines.push("- **Find symbol across codebase** → `lsp workspace_symbols`\n");
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
// Add Git guidance if available
|
|
588
|
-
if (hasGit) {
|
|
589
|
-
lines.push("\n### Git Tool — Preferred for Git Operations");
|
|
590
|
-
lines.push("Use `git` instead of bash git when you need:");
|
|
591
|
-
lines.push(
|
|
592
|
-
"- **Status/diff/log**: `git { operation: 'status' }`, `git { operation: 'diff' }`, `git { operation: 'log' }`",
|
|
593
|
-
);
|
|
594
|
-
lines.push(
|
|
595
|
-
"- **Commit workflow**: `git { operation: 'add', paths: [...] }` then `git { operation: 'commit', message: '...' }`",
|
|
596
|
-
);
|
|
597
|
-
lines.push("- **Branching**: `git { operation: 'branch', action: 'create', name: '...' }`");
|
|
598
|
-
lines.push("- **GitHub PRs**: `git { operation: 'pr', action: 'create', title: '...', body: '...' }`");
|
|
599
|
-
lines.push(
|
|
600
|
-
"- **GitHub Issues**: `git { operation: 'issue', action: 'list' }` or `{ operation: 'issue', number: 123 }`",
|
|
601
|
-
);
|
|
602
|
-
lines.push(
|
|
603
|
-
"The git tool provides typed output, safety guards, and a clean API for all git and GitHub operations.\n",
|
|
604
|
-
);
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// Add SSH remote filesystem guidance if available
|
|
608
|
-
const hasSSH = tools.includes("ssh");
|
|
609
|
-
if (hasSSH) {
|
|
610
|
-
lines.push("\n### SSH Command Execution");
|
|
611
|
-
lines.push(
|
|
612
|
-
"**Critical**: Each SSH host runs a specific shell. **You MUST match commands to the host's shell type**.",
|
|
613
|
-
);
|
|
614
|
-
lines.push("Check the host list in the ssh tool description. Shell types:");
|
|
615
|
-
lines.push("- linux/bash, linux/zsh, macos/bash, macos/zsh: ls, cat, grep, find, ps, df, uname");
|
|
616
|
-
lines.push("- windows/bash, windows/sh: ls, cat, grep, find (Windows with WSL/Cygwin — Unix commands)");
|
|
617
|
-
lines.push("- windows/cmd: dir, type, findstr, tasklist, systeminfo");
|
|
618
|
-
lines.push("- windows/powershell: Get-ChildItem, Get-Content, Select-String, Get-Process");
|
|
619
|
-
lines.push("");
|
|
620
|
-
lines.push("### SSH Filesystems");
|
|
621
|
-
lines.push("Mounted at `~/.omp/remote/<hostname>/` — use read/edit/write tools directly.");
|
|
622
|
-
lines.push("Windows paths need colon: `~/.omp/remote/host/C:/Users/...` not `C/Users/...`\n");
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// Add search-first protocol
|
|
626
|
-
if (hasGrep || hasFind) {
|
|
627
|
-
lines.push("\n### Search-First Protocol");
|
|
628
|
-
lines.push("Before reading any file:");
|
|
629
|
-
if (hasFind) lines.push("1. Unknown structure → `find` to see file layout");
|
|
630
|
-
if (hasGrep) lines.push("2. Known location → `grep` for specific symbol/error");
|
|
631
|
-
if (hasRead) lines.push("3. Use `read offset/limit` for line ranges, not entire large files");
|
|
632
|
-
lines.push("4. Never read a large file hoping to find something — search first");
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
return lines.join("\n");
|
|
636
531
|
}
|
|
637
532
|
|
|
638
533
|
/** Resolve input as file path or literal string */
|
|
@@ -732,7 +627,7 @@ export interface BuildSystemPromptOptions {
|
|
|
732
627
|
/** Pre-loaded skills (skips discovery if provided). */
|
|
733
628
|
skills?: Skill[];
|
|
734
629
|
/** Pre-loaded rulebook rules (rules with descriptions, excluding TTSR and always-apply). */
|
|
735
|
-
rules?:
|
|
630
|
+
rules?: Array<{ name: string; description?: string; path: string; globs?: string[] }>;
|
|
736
631
|
}
|
|
737
632
|
|
|
738
633
|
/** Build the system prompt with tools, guidelines, and context */
|
|
@@ -746,7 +641,7 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
|
|
|
746
641
|
cwd,
|
|
747
642
|
contextFiles: providedContextFiles,
|
|
748
643
|
skills: providedSkills,
|
|
749
|
-
rules
|
|
644
|
+
rules,
|
|
750
645
|
} = options;
|
|
751
646
|
const resolvedCwd = cwd ?? process.cwd();
|
|
752
647
|
const resolvedCustomPrompt = resolvePromptInput(customPrompt, "system prompt");
|
|
@@ -769,122 +664,67 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
|
|
|
769
664
|
|
|
770
665
|
// Resolve context files: use provided or discover
|
|
771
666
|
const contextFiles = providedContextFiles ?? loadProjectContextFiles({ cwd: resolvedCwd });
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
667
|
+
const agentsMdSearch = buildAgentsMdSearch(resolvedCwd);
|
|
668
|
+
|
|
669
|
+
// Build tool descriptions array
|
|
670
|
+
// Priority: toolNames (explicit list) > tools (Map) > defaults
|
|
671
|
+
const defaultToolNames: ToolName[] = ["read", "bash", "edit", "write"];
|
|
672
|
+
let toolNamesArray: string[];
|
|
673
|
+
if (toolNames !== undefined) {
|
|
674
|
+
// Explicit toolNames list provided (could be empty)
|
|
675
|
+
toolNamesArray = toolNames;
|
|
676
|
+
} else if (tools !== undefined) {
|
|
677
|
+
// Tools map provided
|
|
678
|
+
toolNamesArray = Array.from(tools.keys());
|
|
679
|
+
} else {
|
|
680
|
+
// Use defaults
|
|
681
|
+
toolNamesArray = defaultToolNames;
|
|
682
|
+
}
|
|
683
|
+
const toolDescriptionsArray = toolNamesArray.map((name) => ({
|
|
684
|
+
name,
|
|
685
|
+
description: toolDescriptions[name as ToolName] ?? "",
|
|
686
|
+
}));
|
|
779
687
|
|
|
780
688
|
// Resolve skills: use provided or discover
|
|
781
689
|
const skills =
|
|
782
690
|
providedSkills ??
|
|
783
691
|
(skillsSettings?.enabled !== false ? loadSkills({ ...skillsSettings, cwd: resolvedCwd }).skills : []);
|
|
784
692
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
? `${systemPromptCustomization}\n\n${resolvedCustomPrompt}`
|
|
788
|
-
: resolvedCustomPrompt;
|
|
789
|
-
|
|
790
|
-
prompt = appendBlock(prompt, resolvedAppendPrompt);
|
|
791
|
-
prompt = appendSection(prompt, "Project Context", formatProjectContext(contextFiles));
|
|
792
|
-
prompt = appendSection(prompt, "Tools", formatToolDescriptions(tools));
|
|
793
|
-
|
|
794
|
-
const gitContext = loadGitContext(resolvedCwd);
|
|
795
|
-
prompt = appendSection(prompt, "Git Status", gitContext);
|
|
693
|
+
// Get git context
|
|
694
|
+
const git = loadGitContext(resolvedCwd);
|
|
796
695
|
|
|
797
|
-
|
|
798
|
-
prompt = appendBlock(prompt, formatSkillsForPrompt(skills));
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
if (rulebookRules && rulebookRules.length > 0) {
|
|
802
|
-
prompt = appendBlock(prompt, formatRulesForPrompt(rulebookRules));
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
prompt = appendBlock(prompt, buildPromptFooter(dateTime, resolvedCwd), "\n");
|
|
806
|
-
|
|
807
|
-
return prompt;
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
// Generate anti-bash rules (returns null if not applicable)
|
|
811
|
-
const antiBashSection = generateAntiBashRules(Array.from(tools?.keys() ?? []));
|
|
812
|
-
const environmentInfo = formatEnvironmentInfo();
|
|
813
|
-
|
|
814
|
-
// Build guidelines based on which tools are actually available
|
|
815
|
-
const guidelinesList: string[] = [];
|
|
816
|
-
|
|
817
|
-
const hasBash = tools?.has("bash");
|
|
818
|
-
const hasEdit = tools?.has("edit");
|
|
819
|
-
const hasWrite = tools?.has("write");
|
|
696
|
+
// Filter skills to only include those with read tool
|
|
820
697
|
const hasRead = tools?.has("read");
|
|
698
|
+
const filteredSkills = hasRead ? skills : [];
|
|
821
699
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
// Always include these
|
|
854
|
-
guidelinesList.push("Be concise in your responses");
|
|
855
|
-
guidelinesList.push("Show file paths clearly when working with files");
|
|
856
|
-
|
|
857
|
-
const guidelines = guidelinesList.map((g) => `- ${g}`).join("\n");
|
|
858
|
-
|
|
859
|
-
// Build the prompt with anti-bash rules prominently placed
|
|
860
|
-
const antiBashBlock = antiBashSection ? `\n${antiBashSection}\n` : "";
|
|
861
|
-
let prompt = applyTemplate(systemPromptTemplate, {
|
|
862
|
-
toolsList,
|
|
863
|
-
antiBashSection: antiBashBlock,
|
|
864
|
-
guidelines,
|
|
865
|
-
environmentInfo,
|
|
700
|
+
if (resolvedCustomPrompt) {
|
|
701
|
+
return renderPromptTemplate(customSystemPromptTemplate, {
|
|
702
|
+
systemPromptCustomization: systemPromptCustomization ?? "",
|
|
703
|
+
customPrompt: resolvedCustomPrompt,
|
|
704
|
+
appendPrompt: resolvedAppendPrompt ?? "",
|
|
705
|
+
contextFiles,
|
|
706
|
+
agentsMdSearch,
|
|
707
|
+
toolDescriptions: toolDescriptionsArray,
|
|
708
|
+
git,
|
|
709
|
+
skills: filteredSkills,
|
|
710
|
+
rules: rules ?? [],
|
|
711
|
+
dateTime,
|
|
712
|
+
cwd: resolvedCwd,
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
return renderPromptTemplate(systemPromptTemplate, {
|
|
717
|
+
tools: toolNamesArray,
|
|
718
|
+
toolDescriptions: toolDescriptionsArray,
|
|
719
|
+
environment: getEnvironmentInfo(),
|
|
720
|
+
systemPromptCustomization: systemPromptCustomization ?? "",
|
|
721
|
+
contextFiles,
|
|
722
|
+
agentsMdSearch,
|
|
723
|
+
git,
|
|
724
|
+
skills: filteredSkills,
|
|
725
|
+
rules: rules ?? [],
|
|
726
|
+
dateTime,
|
|
727
|
+
cwd: resolvedCwd,
|
|
728
|
+
appendSystemPrompt: resolvedAppendPrompt ?? "",
|
|
866
729
|
});
|
|
867
|
-
|
|
868
|
-
prompt = appendBlock(prompt, resolvedAppendPrompt);
|
|
869
|
-
prompt = appendSection(prompt, "Project Context", formatProjectContext(contextFiles));
|
|
870
|
-
|
|
871
|
-
const gitContext = loadGitContext(resolvedCwd);
|
|
872
|
-
prompt = appendSection(prompt, "Git Status", gitContext);
|
|
873
|
-
|
|
874
|
-
if (hasRead && skills.length > 0) {
|
|
875
|
-
prompt = appendBlock(prompt, formatSkillsForPrompt(skills));
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
if (rulebookRules && rulebookRules.length > 0) {
|
|
879
|
-
prompt = appendBlock(prompt, formatRulesForPrompt(rulebookRules));
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
prompt = appendBlock(prompt, buildPromptFooter(dateTime, resolvedCwd), "\n");
|
|
883
|
-
|
|
884
|
-
// Prepend SYSTEM.md customization if present
|
|
885
|
-
if (systemPromptCustomization) {
|
|
886
|
-
prompt = `${systemPromptCustomization}\n\n${prompt}`;
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
return prompt;
|
|
890
730
|
}
|
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
6
6
|
import { completeSimple } from "@oh-my-pi/pi-ai";
|
|
7
|
-
import titleSystemPrompt from "../prompts/title-system.md" with { type: "text" };
|
|
7
|
+
import titleSystemPrompt from "../prompts/system/title-system.md" with { type: "text" };
|
|
8
8
|
import { logger } from "./logger";
|
|
9
9
|
import type { ModelRegistry } from "./model-registry";
|
|
10
10
|
import { parseModelString, SMOL_MODEL_PRIORITY } from "./model-resolver";
|
|
11
|
+
import { renderPromptTemplate } from "./prompt-templates";
|
|
11
12
|
|
|
12
|
-
const TITLE_SYSTEM_PROMPT = titleSystemPrompt;
|
|
13
|
+
const TITLE_SYSTEM_PROMPT = renderPromptTemplate(titleSystemPrompt);
|
|
13
14
|
|
|
14
15
|
const MAX_INPUT_CHARS = 2000;
|
|
15
16
|
|
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 { renderPromptTemplate } from "../prompt-templates";
|
|
25
26
|
import type { ToolSession } from "./index";
|
|
26
27
|
import { createToolUIKit } from "./render-utils";
|
|
27
28
|
|
|
@@ -75,7 +76,7 @@ export function createAskTool(session: ToolSession): null | AgentTool<typeof ask
|
|
|
75
76
|
return {
|
|
76
77
|
name: "ask",
|
|
77
78
|
label: "Ask",
|
|
78
|
-
description: askDescription,
|
|
79
|
+
description: renderPromptTemplate(askDescription),
|
|
79
80
|
parameters: askSchema,
|
|
80
81
|
|
|
81
82
|
async execute(
|
|
@@ -201,7 +202,6 @@ export function createAskTool(session: ToolSession): null | AgentTool<typeof ask
|
|
|
201
202
|
export const askTool = createAskTool({
|
|
202
203
|
cwd: process.cwd(),
|
|
203
204
|
hasUI: false,
|
|
204
|
-
rulebookRules: [],
|
|
205
205
|
getSessionFile: () => null,
|
|
206
206
|
getSessionSpawns: () => "*",
|
|
207
207
|
});
|
package/src/core/tools/bash.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { Theme } from "../../modes/interactive/theme/theme";
|
|
|
8
8
|
import bashDescription from "../../prompts/tools/bash.md" with { type: "text" };
|
|
9
9
|
import { type BashExecutorOptions, executeBash, executeBashWithOperations } from "../bash-executor";
|
|
10
10
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
11
|
+
import { renderPromptTemplate } from "../prompt-templates";
|
|
11
12
|
import { checkBashInterception, checkSimpleLsInterception } from "./bash-interceptor";
|
|
12
13
|
import type { ToolSession } from "./index";
|
|
13
14
|
import { resolveToCwd } from "./path-utils";
|
|
@@ -52,7 +53,7 @@ export function createBashTool(session: ToolSession, options?: BashToolOptions):
|
|
|
52
53
|
return {
|
|
53
54
|
name: "bash",
|
|
54
55
|
label: "Bash",
|
|
55
|
-
description: bashDescription,
|
|
56
|
+
description: renderPromptTemplate(bashDescription),
|
|
56
57
|
parameters: bashSchema,
|
|
57
58
|
execute: async (
|
|
58
59
|
_toolCallId: string,
|
|
@@ -5,6 +5,7 @@ import { Type } from "@sinclair/typebox";
|
|
|
5
5
|
import type { Theme } from "../../modes/interactive/theme/theme";
|
|
6
6
|
import calculatorDescription from "../../prompts/tools/calculator.md" with { type: "text" };
|
|
7
7
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
8
|
+
import { renderPromptTemplate } from "../prompt-templates";
|
|
8
9
|
import { untilAborted } from "../utils";
|
|
9
10
|
import type { ToolSession } from "./index";
|
|
10
11
|
import {
|
|
@@ -393,7 +394,7 @@ export function createCalculatorTool(_session: ToolSession): AgentTool<typeof ca
|
|
|
393
394
|
return {
|
|
394
395
|
name: "calc",
|
|
395
396
|
label: "Calc",
|
|
396
|
-
description: calculatorDescription,
|
|
397
|
+
description: renderPromptTemplate(calculatorDescription),
|
|
397
398
|
parameters: calculatorSchema,
|
|
398
399
|
execute: async (
|
|
399
400
|
_toolCallId: string,
|
|
@@ -79,7 +79,7 @@ export function createCompleteTool(session: ToolSession) {
|
|
|
79
79
|
: Type.Any({ description: "Structured JSON output (no schema specified)" });
|
|
80
80
|
|
|
81
81
|
const completeParams = Type.Object({
|
|
82
|
-
data: dataSchema,
|
|
82
|
+
data: Type.Optional(dataSchema),
|
|
83
83
|
status: Type.Optional(
|
|
84
84
|
Type.Union([Type.Literal("success"), Type.Literal("aborted")], {
|
|
85
85
|
default: "success",
|
|
@@ -99,8 +99,11 @@ export function createCompleteTool(session: ToolSession) {
|
|
|
99
99
|
execute: async (_toolCallId, params) => {
|
|
100
100
|
const status = params.status ?? "success";
|
|
101
101
|
|
|
102
|
-
// Skip
|
|
102
|
+
// Skip validation when aborting - data is optional for aborts
|
|
103
103
|
if (status === "success") {
|
|
104
|
+
if (params.data === undefined) {
|
|
105
|
+
throw new Error("data is required when status is 'success'");
|
|
106
|
+
}
|
|
104
107
|
if (schemaError) {
|
|
105
108
|
throw new Error(`Invalid output schema: ${schemaError}`);
|
|
106
109
|
}
|
package/src/core/tools/edit.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { Type } from "@sinclair/typebox";
|
|
|
5
5
|
import { getLanguageFromPath, type Theme } from "../../modes/interactive/theme/theme";
|
|
6
6
|
import editDescription from "../../prompts/tools/edit.md" with { type: "text" };
|
|
7
7
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
8
|
+
import { renderPromptTemplate } from "../prompt-templates";
|
|
8
9
|
import {
|
|
9
10
|
DEFAULT_FUZZY_THRESHOLD,
|
|
10
11
|
detectLineEnding,
|
|
@@ -48,7 +49,7 @@ export function createEditTool(session: ToolSession): AgentTool<typeof editSchem
|
|
|
48
49
|
return {
|
|
49
50
|
name: "edit",
|
|
50
51
|
label: "Edit",
|
|
51
|
-
description: editDescription,
|
|
52
|
+
description: renderPromptTemplate(editDescription),
|
|
52
53
|
parameters: editSchema,
|
|
53
54
|
execute: async (
|
|
54
55
|
_toolCallId: string,
|
package/src/core/tools/find.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { getLanguageFromPath, type Theme } from "../../modes/interactive/theme/t
|
|
|
7
7
|
import findDescription from "../../prompts/tools/find.md" with { type: "text" };
|
|
8
8
|
import { ensureTool } from "../../utils/tools-manager";
|
|
9
9
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
10
|
+
import { renderPromptTemplate } from "../prompt-templates";
|
|
10
11
|
import { ScopeSignal, untilAborted } from "../utils";
|
|
11
12
|
import type { ToolSession } from "./index";
|
|
12
13
|
import { resolveToCwd } from "./path-utils";
|
|
@@ -113,7 +114,7 @@ export function createFindTool(session: ToolSession, options?: FindToolOptions):
|
|
|
113
114
|
return {
|
|
114
115
|
name: "find",
|
|
115
116
|
label: "Find",
|
|
116
|
-
description: findDescription,
|
|
117
|
+
description: renderPromptTemplate(findDescription),
|
|
117
118
|
parameters: findSchema,
|
|
118
119
|
execute: async (
|
|
119
120
|
_toolCallId: string,
|
|
@@ -5,6 +5,7 @@ import { nanoid } from "nanoid";
|
|
|
5
5
|
import geminiImageDescription from "../../prompts/tools/gemini-image.md" with { type: "text" };
|
|
6
6
|
import { detectSupportedImageMimeTypeFromFile } from "../../utils/mime";
|
|
7
7
|
import type { CustomTool } from "../custom-tools/types";
|
|
8
|
+
import { renderPromptTemplate } from "../prompt-templates";
|
|
8
9
|
import { untilAborted } from "../utils";
|
|
9
10
|
import { resolveReadPath } from "./path-utils";
|
|
10
11
|
import { getEnv } from "./web-search/auth";
|
|
@@ -367,7 +368,7 @@ function createRequestSignal(signal: AbortSignal | undefined, timeoutSeconds: nu
|
|
|
367
368
|
export const geminiImageTool: CustomTool<typeof geminiImageSchema, GeminiImageToolDetails> = {
|
|
368
369
|
name: "generate_image",
|
|
369
370
|
label: "GenerateImage",
|
|
370
|
-
description: geminiImageDescription,
|
|
371
|
+
description: renderPromptTemplate(geminiImageDescription),
|
|
371
372
|
parameters: geminiImageSchema,
|
|
372
373
|
async execute(_toolCallId, params, _onUpdate, ctx, signal) {
|
|
373
374
|
return untilAborted(signal, async () => {
|