mini-coder 0.5.14 → 0.6.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/README.md +26 -109
- package/bin/mc.ts +8 -11
- package/bun.lock +79 -269
- package/nono-mini-coder.json +42 -0
- package/package.json +17 -22
- package/src/agent.ts +243 -1403
- package/src/args.ts +289 -0
- package/src/headless.ts +41 -359
- package/src/index.ts +29 -1016
- package/src/oauth.ts +117 -0
- package/src/prompt.ts +219 -284
- package/src/session.ts +55 -1306
- package/src/shared.ts +117 -38
- package/src/tool-bash.ts +110 -0
- package/src/tool-edit.ts +133 -0
- package/src/tool-read.ts +80 -293
- package/src/tui-components.ts +150 -0
- package/src/tui-conversation.ts +271 -0
- package/src/tui-editor.ts +29 -0
- package/src/tui-overlay.ts +403 -0
- package/src/tui.ts +228 -0
- package/src/types.ts +164 -0
- package/tsconfig.json +17 -0
- package/BENCHMARK.md +0 -107
- package/LICENSE +0 -9
- package/PROGRESS.md +0 -5
- package/assets/icon-1-minimal.svg +0 -31
- package/assets/icon-2-dark-terminal.svg +0 -48
- package/assets/icon-3-gradient-modern.svg +0 -45
- package/assets/icon-4-filled-bold.svg +0 -54
- package/assets/icon-5-community-badge.svg +0 -63
- package/assets/mc-claude-smart.png +0 -0
- package/assets/mc-gpt-smart.png +0 -0
- package/assets/preview-0-5-0.png +0 -0
- package/assets/preview.gif +0 -0
- package/benchmark-baseline.sh +0 -15
- package/benchmark-loop.sh +0 -19
- package/skills-lock.json +0 -15
- package/src/assistant-output.ts +0 -73
- package/src/cli.ts +0 -134
- package/src/delegation.ts +0 -238
- package/src/errors.ts +0 -15
- package/src/git.ts +0 -247
- package/src/input.ts +0 -168
- package/src/mcp.ts +0 -609
- package/src/paths.ts +0 -37
- package/src/session-message.ts +0 -385
- package/src/settings.ts +0 -449
- package/src/skills.ts +0 -271
- package/src/submit.ts +0 -376
- package/src/text.ts +0 -71
- package/src/theme.ts +0 -330
- package/src/tool-common.ts +0 -93
- package/src/tool-delegate.ts +0 -125
- package/src/tool-grep.ts +0 -606
- package/src/tool-shell.ts +0 -1051
- package/src/tools.ts +0 -1179
- package/src/ui/agent.ts +0 -320
- package/src/ui/commands.test.ts +0 -957
- package/src/ui/commands.ts +0 -848
- package/src/ui/conversation.test.ts +0 -585
- package/src/ui/conversation.ts +0 -1836
- package/src/ui/help.ts +0 -158
- package/src/ui/input.test.ts +0 -64
- package/src/ui/input.ts +0 -138
- package/src/ui/overlay.ts +0 -59
- package/src/ui/runtime.ts +0 -69
- package/src/ui/status.ts +0 -220
- package/src/ui.ts +0 -1190
- package/src/version.ts +0 -48
package/src/oauth.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import readline from "node:readline";
|
|
2
|
+
|
|
3
|
+
import { getEnvApiKey, getProviders } from "@mariozechner/pi-ai";
|
|
4
|
+
import {
|
|
5
|
+
getOAuthApiKey,
|
|
6
|
+
getOAuthProvider,
|
|
7
|
+
getOAuthProviders,
|
|
8
|
+
type OAuthProviderId,
|
|
9
|
+
} from "@mariozechner/pi-ai/oauth";
|
|
10
|
+
import { AUTH_PATH as AUTH_FILE } from "./shared";
|
|
11
|
+
import type { CliOptions, SavedOAuthCreds } from "./types";
|
|
12
|
+
|
|
13
|
+
export function isOAuthProvider(provider: string): boolean {
|
|
14
|
+
return getOAuthProviders().some(
|
|
15
|
+
(oauthProvider) => oauthProvider.id === provider,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function getAvailableProviders(): Promise<string[]> {
|
|
20
|
+
const auth = await readCreds();
|
|
21
|
+
const loggedInOAuthProviders = getOAuthProviders()
|
|
22
|
+
.map((provider) => provider.id)
|
|
23
|
+
.filter((provider) => auth[provider]);
|
|
24
|
+
const envKeyProviders = getProviders().filter(
|
|
25
|
+
(provider) => !!getEnvApiKey(provider),
|
|
26
|
+
);
|
|
27
|
+
const providers: string[] = [];
|
|
28
|
+
const providerIds = new Set<string>();
|
|
29
|
+
|
|
30
|
+
for (const provider of [...loggedInOAuthProviders, ...envKeyProviders]) {
|
|
31
|
+
if (providerIds.has(provider)) continue;
|
|
32
|
+
|
|
33
|
+
providers.push(provider);
|
|
34
|
+
providerIds.add(provider);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return providers;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function loginOAuth(provider: OAuthProviderId) {
|
|
41
|
+
const oauthProvider = getOAuthProvider(provider);
|
|
42
|
+
if (!oauthProvider) throw new Error(`Unknown OAuth provider: ${provider}`);
|
|
43
|
+
|
|
44
|
+
const rl = readline.createInterface({
|
|
45
|
+
input: process.stdin,
|
|
46
|
+
output: process.stdout,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const creds = await oauthProvider.login({
|
|
51
|
+
onAuth: ({ url, instructions }) => {
|
|
52
|
+
console.log(`Open: ${url}`);
|
|
53
|
+
if (instructions) console.log(instructions);
|
|
54
|
+
},
|
|
55
|
+
onPrompt: async (prompt) => {
|
|
56
|
+
let answer: string = "";
|
|
57
|
+
await rl.question(prompt.message, (a) => (answer = a));
|
|
58
|
+
return answer;
|
|
59
|
+
},
|
|
60
|
+
onProgress: (message) => console.log(message),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await writeCreds({ [provider]: { type: "oauth", ...creds } });
|
|
64
|
+
} finally {
|
|
65
|
+
rl.close();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return await readCreds();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function getApiKey(options: CliOptions) {
|
|
72
|
+
const provider = options.model.provider;
|
|
73
|
+
const auth = await readCreds();
|
|
74
|
+
|
|
75
|
+
if (isOAuthProvider(provider)) {
|
|
76
|
+
const result = await getOAuthApiKey(provider, auth);
|
|
77
|
+
if (result) {
|
|
78
|
+
auth[provider] = { type: "oauth", ...result.newCredentials };
|
|
79
|
+
await writeCreds(auth);
|
|
80
|
+
|
|
81
|
+
return result.apiKey;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const envApiKey = getEnvApiKey(provider);
|
|
86
|
+
if (envApiKey) return envApiKey;
|
|
87
|
+
|
|
88
|
+
const knownProviders = getProviders() as string[];
|
|
89
|
+
if (!knownProviders.includes(provider)) {
|
|
90
|
+
if (options.model.api === "openai-completions") {
|
|
91
|
+
// pi-ai requires a truthy apiKey for OpenAI-compatible local providers like Ollama.
|
|
92
|
+
return "dummy";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
throw new Error("Not logged in");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function readCreds(): Promise<SavedOAuthCreds> {
|
|
102
|
+
const file = Bun.file(AUTH_FILE);
|
|
103
|
+
if (await file.exists()) {
|
|
104
|
+
return JSON.parse(await file.text());
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function writeCreds(creds: SavedOAuthCreds) {
|
|
111
|
+
await Bun.write(AUTH_FILE, JSON.stringify(await mergeCreds(creds)));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function mergeCreds(newCreds: SavedOAuthCreds) {
|
|
115
|
+
const oldCreds = await readCreds();
|
|
116
|
+
return { ...oldCreds, ...newCreds };
|
|
117
|
+
}
|
package/src/prompt.ts
CHANGED
|
@@ -1,322 +1,257 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
1
|
+
import { promises } from "node:fs";
|
|
2
|
+
import { readdir } from "node:fs/promises";
|
|
3
|
+
import { homedir, platform } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import type { Message, ToolCall, ToolResultMessage } from "@mariozechner/pi-ai";
|
|
6
|
+
import simpleGit, { type StatusResult } from "simple-git";
|
|
7
|
+
import { parseSkillFrontmatter } from "./shared";
|
|
8
|
+
|
|
9
|
+
export const MAIN_PROMPT = `# You are "mini-coder", a coding agent.
|
|
10
|
+
|
|
11
|
+
IMPORTANT: Be defensive with existing changes and destructive commands.
|
|
12
|
+
IMPORTANT: Do not overstate what changed or what was verified. Summaries must match the diff.
|
|
13
|
+
|
|
14
|
+
## Role
|
|
15
|
+
You help users by reading files, executing commands, editing code, and writing new files. Prioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without unnecessary superlatives, praise, or emotional validation.
|
|
16
|
+
|
|
17
|
+
<example>
|
|
18
|
+
When referencing specific functions or pieces of code, include the pattern \`file_path:line_number\`.
|
|
19
|
+
For example: "Clients are handled in the \`connectToServer\` function in src/services/process.ts:712."
|
|
20
|
+
</example>
|
|
21
|
+
|
|
22
|
+
User messages and Tool results may include <system-reminder> tags. These contain system-generated reminders and bear no direct relation to the specific tool result in which they appear.
|
|
23
|
+
|
|
24
|
+
## Tools
|
|
25
|
+
- You have access to bash, read and edit tools. Prefer using read and edit for file operations, use bash for finding read candidates or to run development commands.
|
|
26
|
+
|
|
27
|
+
<example>
|
|
28
|
+
> User: please read the README.md and add rich code examples.
|
|
29
|
+
|
|
30
|
+
- Use the bash tool to find the path for README.md, prefer "ls" or "fd/find", and "rg/grep".
|
|
31
|
+
- Then read the file with the read tool to find the replacement areas and mathcing patterns
|
|
32
|
+
- Edit the file using the edit tool. Review the output diff, use the read tool again to verify if needed.
|
|
33
|
+
- Reply to the user that the edit was done.
|
|
34
|
+
</example>
|
|
35
|
+
|
|
36
|
+
## Workflow
|
|
37
|
+
- Stay rooted on the user's request. Don't wander into tangents or explore out of curiosity.
|
|
38
|
+
- Gather only the information needed to fulfill the request, then stop exploring and complete it.
|
|
39
|
+
- Narrate your edits with brief commentary during long tasks so the user can follow progress.
|
|
40
|
+
- Verify your changes via compilation, tests, or manual checks whenever possible.
|
|
41
|
+
|
|
42
|
+
## Tone
|
|
43
|
+
- Be concise. Use a professional colleague tone: direct, never condescending, and never rude.
|
|
44
|
+
|
|
45
|
+
## Error Handling
|
|
46
|
+
- If a tool call fails or is denied, do NOT re-attempt the exact same call. Analyze why it failed and adjust your approach.
|
|
47
|
+
|
|
48
|
+
## Safety rules
|
|
49
|
+
- Answer all user requests without guessing, or assuming. Verify your answers and claims before making them.
|
|
50
|
+
- Use recent online information, the current environment, and your training data combined for a complete answer.
|
|
51
|
+
- Ensure that you fulfill the user's expectation, requirements and contract **exactly**.
|
|
52
|
+
- Be defensive with existing changes and destructive commands, they could harm your user's changes.
|
|
53
|
+
- Use temp directory for temp files, scripts, plan files, or anything that doesn't match the requested output.
|
|
54
|
+
- Do not over-scope your work, or add more scope during implementation.
|
|
55
|
+
- Avoid over-enginnering, hacks or creative solutions. The boring, simple and repliable is always preferred.
|
|
56
|
+
- Do not overstate what changed or what was verified. Summaries must match the diff.
|
|
57
|
+
|
|
58
|
+
IMPORTANT: Never guess or assume. Verify claims before making them.
|
|
59
|
+
IMPORTANT: Do not over-scope work or add scope during implementation.
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
async function getDir() {
|
|
63
|
+
const ignoreFile = Bun.file(".gitignore");
|
|
64
|
+
let ignoreContent = "";
|
|
65
|
+
if (await ignoreFile.exists()) {
|
|
66
|
+
ignoreContent = await ignoreFile.text();
|
|
67
|
+
}
|
|
68
|
+
const ignored = ignoreContent.split("\n");
|
|
69
|
+
const dir = [];
|
|
70
|
+
const glob = promises.glob(["*", "*/*"], { exclude: ignored });
|
|
71
|
+
for await (const file of glob) {
|
|
72
|
+
dir.push(file);
|
|
73
|
+
}
|
|
74
|
+
return dir;
|
|
47
75
|
}
|
|
48
76
|
|
|
49
|
-
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
/** Resolve the AGENTS.md scan root from git/home/env inputs. */
|
|
57
|
-
export function resolveAgentsScanRoot(
|
|
58
|
-
_cwd: string,
|
|
59
|
-
gitRoot: string | null,
|
|
60
|
-
homeDir: string,
|
|
61
|
-
agentsRootEnv = process.env.MC_AGENTS_ROOT,
|
|
62
|
-
): string {
|
|
63
|
-
if (gitRoot) {
|
|
64
|
-
return canonicalizePath(gitRoot);
|
|
77
|
+
async function getEnvPrompt() {
|
|
78
|
+
// TODO: What else do the agents always check before answering every time?
|
|
79
|
+
let gitStatus: StatusResult | { nogit: string };
|
|
80
|
+
try {
|
|
81
|
+
gitStatus = await simpleGit().status();
|
|
82
|
+
} catch (_) {
|
|
83
|
+
gitStatus = { nogit: "No git repo in this folder." };
|
|
65
84
|
}
|
|
66
|
-
|
|
67
|
-
|
|
85
|
+
const envKeys = ["PATH", "USER", "LANG", "HOME", "SHELL", "BUN_INSTALL"];
|
|
86
|
+
const env: Record<string, string> = {};
|
|
87
|
+
for (const key of envKeys) {
|
|
88
|
+
const v = Bun.env[key];
|
|
89
|
+
|
|
90
|
+
if (v !== undefined) {
|
|
91
|
+
env[key] = v;
|
|
92
|
+
}
|
|
68
93
|
}
|
|
69
|
-
return canonicalizePath(homeDir);
|
|
70
|
-
}
|
|
71
94
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
95
|
+
const envStatus = JSON.stringify(
|
|
96
|
+
{
|
|
97
|
+
os: platform(),
|
|
98
|
+
env,
|
|
99
|
+
cwd: process.cwd(),
|
|
100
|
+
dir: await getDir(),
|
|
101
|
+
git: gitStatus,
|
|
102
|
+
},
|
|
103
|
+
null,
|
|
104
|
+
4,
|
|
77
105
|
);
|
|
78
|
-
}
|
|
79
106
|
|
|
80
|
-
|
|
81
|
-
if (!isWithinScanRoot(start, root)) {
|
|
82
|
-
return [start];
|
|
83
|
-
}
|
|
107
|
+
const text = `### Environment status and information
|
|
84
108
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (current === root) {
|
|
90
|
-
return dirs.reverse();
|
|
91
|
-
}
|
|
109
|
+
\`\`\`json
|
|
110
|
+
${envStatus}
|
|
111
|
+
\`\`\`
|
|
112
|
+
`;
|
|
92
113
|
|
|
93
|
-
|
|
94
|
-
if (parent === current) {
|
|
95
|
-
return dirs.reverse();
|
|
96
|
-
}
|
|
97
|
-
current = parent;
|
|
98
|
-
}
|
|
114
|
+
return text;
|
|
99
115
|
}
|
|
100
116
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
117
|
+
// `AGENTS.md` support: find it in current folder (./AGENTS.md) and a global one. (`.agents/AGENTS.md`)
|
|
118
|
+
export async function getAGENTSFiles() {
|
|
119
|
+
const content: string[] = [];
|
|
120
|
+
|
|
121
|
+
const globalPath = join(homedir(), ".agents/AGENTS.md");
|
|
122
|
+
const globalFile = Bun.file(globalPath);
|
|
123
|
+
|
|
124
|
+
if (await globalFile.exists()) {
|
|
125
|
+
content.push(await globalFile.text());
|
|
105
126
|
}
|
|
106
127
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
} catch {
|
|
113
|
-
return null;
|
|
128
|
+
const localPath = join(process.cwd(), "AGENTS.md");
|
|
129
|
+
const localFile = Bun.file(localPath);
|
|
130
|
+
|
|
131
|
+
if (await localFile.exists()) {
|
|
132
|
+
content.push(await localFile.text());
|
|
114
133
|
}
|
|
134
|
+
|
|
135
|
+
return content.join("\n\n").trim();
|
|
115
136
|
}
|
|
116
137
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
globalAgentsDir?: string,
|
|
133
|
-
): AgentsMdFile[] {
|
|
134
|
-
const root = canonicalizePath(scanRoot);
|
|
135
|
-
const start = canonicalizePath(cwd);
|
|
136
|
-
const files: AgentsMdFile[] = [];
|
|
137
|
-
|
|
138
|
-
if (globalAgentsDir) {
|
|
139
|
-
const globalFile = readAgentsMdFile(globalAgentsDir);
|
|
140
|
-
if (globalFile) {
|
|
141
|
-
files.push(globalFile);
|
|
138
|
+
// `SKILLS.md` discovery from [~|.]/agents/skills/*/SKILL.md
|
|
139
|
+
export async function getSkills(): Promise<string> {
|
|
140
|
+
let skillsBlock: string = "";
|
|
141
|
+
const skillRoots = [
|
|
142
|
+
join(homedir(), ".agents", "skills"),
|
|
143
|
+
join(process.cwd(), ".agents", "skills"),
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
for (const root of skillRoots) {
|
|
147
|
+
let entries: string[];
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
entries = await readdir(root);
|
|
151
|
+
} catch {
|
|
152
|
+
continue;
|
|
142
153
|
}
|
|
143
|
-
}
|
|
144
154
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
155
|
+
for (const entry of entries) {
|
|
156
|
+
const path = join(root, entry, "SKILL.md");
|
|
157
|
+
const file = Bun.file(path);
|
|
158
|
+
|
|
159
|
+
if (!(await file.exists())) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const parsed = parseSkillFrontmatter(await file.text());
|
|
164
|
+
|
|
165
|
+
if (!parsed) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
skillsBlock += `## ${parsed.name}
|
|
170
|
+
|
|
171
|
+
> Absolute file path to read: ${path}
|
|
172
|
+
|
|
173
|
+
${parsed.description}
|
|
174
|
+
|
|
175
|
+
`;
|
|
149
176
|
}
|
|
150
177
|
}
|
|
151
178
|
|
|
152
|
-
return
|
|
153
|
-
}
|
|
179
|
+
if (!skillsBlock.length) return "";
|
|
154
180
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
*
|
|
162
|
-
* Fields are omitted when their values are zero. The git line format:
|
|
163
|
-
* `Git: branch main | 3 staged, 1 modified, 2 untracked | +5 −2 vs origin/main`
|
|
164
|
-
* where the trailing upstream label reflects the repository's actual tracking ref.
|
|
165
|
-
*
|
|
166
|
-
* @param state - The git state to format.
|
|
167
|
-
* @returns Formatted git status line.
|
|
168
|
-
*/
|
|
169
|
-
export function formatGitLine(state: GitState): string {
|
|
170
|
-
const parts: string[] = [`Git: branch ${state.branch}`];
|
|
171
|
-
|
|
172
|
-
// Working tree counts
|
|
173
|
-
const counts: string[] = [];
|
|
174
|
-
if (state.staged > 0) counts.push(`${state.staged} staged`);
|
|
175
|
-
if (state.modified > 0) counts.push(`${state.modified} modified`);
|
|
176
|
-
if (state.untracked > 0) counts.push(`${state.untracked} untracked`);
|
|
177
|
-
if (counts.length > 0) parts.push(counts.join(", "));
|
|
178
|
-
|
|
179
|
-
// Ahead/behind
|
|
180
|
-
if (state.ahead > 0 || state.behind > 0) {
|
|
181
|
-
const ab: string[] = [];
|
|
182
|
-
if (state.ahead > 0) ab.push(`+${state.ahead}`);
|
|
183
|
-
if (state.behind > 0) ab.push(`\u2212${state.behind}`);
|
|
184
|
-
const upstream = state.upstream ? ` vs ${state.upstream}` : "";
|
|
185
|
-
parts.push(`${ab.join(" ")}${upstream}`);
|
|
186
|
-
}
|
|
181
|
+
const skills = `# Skills
|
|
182
|
+
|
|
183
|
+
- The following skills provide specialized instructions for specific tasks.
|
|
184
|
+
- Use the bash tool to read a skill's file when the task matches its description.
|
|
185
|
+
- Use the skill provided absolute file path instead of guessing or constructing one.
|
|
186
|
+
- Skills can be global (in ~/.agents/skills) or local to the directory (./agents/skills)
|
|
187
187
|
|
|
188
|
-
|
|
188
|
+
${skillsBlock}`;
|
|
189
|
+
|
|
190
|
+
return skills.trim();
|
|
189
191
|
}
|
|
190
192
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
function buildCorePrompt(opts: BuildSystemPromptOpts): string {
|
|
196
|
-
const lines = [
|
|
197
|
-
"You are mini-coder, the best software engineering assistant in the world.",
|
|
198
|
-
"",
|
|
199
|
-
"The current environment is:",
|
|
200
|
-
`- LLM in use: ${opts.modelLabel}`,
|
|
201
|
-
`- OS: ${opts.os}`,
|
|
202
|
-
`- Current working directory: ${opts.cwd}`,
|
|
203
|
-
];
|
|
193
|
+
export async function buildSystemPrompt(systemPrompt: string) {
|
|
194
|
+
const agentsContent = await getAGENTSFiles();
|
|
195
|
+
const skillsContent = await getSkills();
|
|
196
|
+
let complete = systemPrompt;
|
|
204
197
|
|
|
205
|
-
if (
|
|
206
|
-
|
|
198
|
+
if (skillsContent) {
|
|
199
|
+
complete += `\n${skillsContent}`;
|
|
207
200
|
}
|
|
208
201
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
"- Read: Read a text file from disk with offset/limit support.",
|
|
212
|
-
"- Grep: Search file contents with ripgrep-style options and structured results.",
|
|
213
|
-
"- Edit: Safe exact-text replacement in a single file.",
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
if (opts.supportsImages) {
|
|
217
|
-
lines.push("- Read Image: Read an image from disk.");
|
|
202
|
+
if (agentsContent) {
|
|
203
|
+
complete += `\n${agentsContent}`;
|
|
218
204
|
}
|
|
219
205
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
"## Core working style:",
|
|
223
|
-
"",
|
|
224
|
-
"- Be concise, direct, and useful.",
|
|
225
|
-
"- Use a casual, solution-oriented technical tone. Avoid fluff and performative apologies.",
|
|
226
|
-
"- When the user gives a clear command, do it without adding extra work they did not ask for.",
|
|
227
|
-
"- Prefer the minimal implementation that satisfies the request exactly.",
|
|
228
|
-
"- Use YAGNI. Avoid speculative abstractions, future-proofing, and unnecessary compatibility shims.",
|
|
229
|
-
"- Preserve working behavior where possible. Prefer targeted fixes over rewrites.",
|
|
230
|
-
"- Be thorough, use fresh eyes and internal analysis before taking action.",
|
|
231
|
-
"- Make informed decisions based on the available information and best practices.",
|
|
232
|
-
"- Always verify the result of your actions.",
|
|
233
|
-
"",
|
|
234
|
-
"### Using the shell tool:",
|
|
235
|
-
"",
|
|
236
|
-
"- Always execute shell commands in non-interactive mode.",
|
|
237
|
-
"- Use the appropriate commands and package managers for the specified operating system.",
|
|
238
|
-
"- Don't assume the environment supports all commands; check before using them.",
|
|
239
|
-
"- Avoid destructive commands that can discard changes or override edits.",
|
|
240
|
-
"",
|
|
241
|
-
"### Choosing tools:",
|
|
242
|
-
"",
|
|
243
|
-
"- Prefer `read` for reading file contents instead of `cat`, `sed`, `head`, or `tail`.",
|
|
244
|
-
"- Prefer `grep` for content search instead of raw `grep` / `rg`.",
|
|
245
|
-
"- Use shell `ls` and `fd` for lightweight exploration when you just need to inspect directories or discover candidate files.",
|
|
246
|
-
"",
|
|
247
|
-
"### Working with code:",
|
|
248
|
-
"",
|
|
249
|
-
"- Describe changes before implementing them",
|
|
250
|
-
"- Prefer boring dependable solutions over clever ones",
|
|
251
|
-
"- Avoid creating extra files, systems or documentation outside of what was asked.",
|
|
252
|
-
"- Check requirements, and plan your changes before editing code.",
|
|
253
|
-
"- Implement the necessary changes, following good practices and proper error handling.",
|
|
254
|
-
"- Prefer the smallest path that leaves the requested end state already true; do not stop at helper scripts, instructions, or half-finished setup when the user asked for the live result itself.",
|
|
255
|
-
"- Always verify your changes using compilation, testing, and manual verification when possible.",
|
|
256
|
-
"- Before you finish, re-check the explicit deliverables and current state. If the user named files, paths, ports, services, commands, or output values, make sure they already exist and work now.",
|
|
257
|
-
"- If the request includes structural constraints on files or outputs (for example allowed commands, required lines, exact formats, or counts), treat those as acceptance criteria too and verify them directly against what you produced, not just through downstream behavior.",
|
|
258
|
-
"- Treat concrete command sequences and expected outputs in the user's request as acceptance criteria for the end state. If you verify that flow during the task, do not roll the environment back afterward unless the user explicitly asked for a reset.",
|
|
259
|
-
"- If a check or tool result contradicts your expectation, trust the evidence and resolve the mismatch before you answer.",
|
|
260
|
-
"- When multiple outputs or end states seem plausible, do not guess or swap in a cleaner alternative after verification. Run the smallest check that distinguishes them, and if you change the state later, verify again.",
|
|
261
|
-
"- When verifying with build or test commands, avoid leaving generated binaries or scratch artifacts in the requested output location; use temporary paths or remove them before finishing.",
|
|
262
|
-
"- Do not leave helpers, tests, or any other form of temporary files; clean up after yourself and leave no trace.",
|
|
263
|
-
"- Ensure you match the requested output exactly. This applies to file names, directory structure, number of files, output formats, and all other details.",
|
|
264
|
-
'- "Polish" is not optional; it counts just as much as solving the task.',
|
|
265
|
-
"",
|
|
266
|
-
"### Task management",
|
|
267
|
-
"",
|
|
268
|
-
"- Use `todoWrite` proactively for multi-step or non-trivial tasks.",
|
|
269
|
-
"- Capture new requirements in the todo list as soon as you understand them.",
|
|
270
|
-
"- Use `todoRead` when you need to inspect the current list before updating it or when the user asks for the current plan/status.",
|
|
271
|
-
"- Keep the todo list up-to-date above all; mark tasks `in_progress` before starting them and `completed` as soon as verification succeeds.",
|
|
272
|
-
"- A todo item is only complete if the requested work is actually finished and verified to the degree the task requires.",
|
|
273
|
-
"- Use `cancelled` to remove tasks that are no longer relevant.",
|
|
274
|
-
"- Skip todo tools for single trivial tasks and purely conversational/informational requests.",
|
|
275
|
-
"- Use the `delegate` tool for bounded subtasks when another focused agent pass would help.",
|
|
276
|
-
"- Prefer `delegate` over shelling out to `mc -p` unless you specifically need to exercise the CLI itself.",
|
|
277
|
-
"- Do not re-delegate the whole task, spin on repeated self-review prompts, or ask a delegated child to delegate again.",
|
|
278
|
-
"- Delegate when you are orchestrating a large to-do/plan execution.",
|
|
279
|
-
"",
|
|
280
|
-
);
|
|
206
|
+
return complete;
|
|
207
|
+
}
|
|
281
208
|
|
|
282
|
-
|
|
209
|
+
export async function injectEnvReminder(): Promise<string> {
|
|
210
|
+
const envStatus = await getEnvPrompt();
|
|
211
|
+
return `<system-reminder>\n${envStatus}\n</system-reminder>`;
|
|
283
212
|
}
|
|
284
213
|
|
|
285
|
-
//
|
|
286
|
-
//
|
|
287
|
-
//
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
agentsSection.push(file.content);
|
|
310
|
-
agentsSection.push("");
|
|
214
|
+
// TODO: Needs to be updated since we are deprecating the task tool
|
|
215
|
+
// for now. Needs to check for similar or identical tool calls, aka
|
|
216
|
+
// Doom looping.
|
|
217
|
+
export function insertToolUsageReminder(
|
|
218
|
+
messages: Message[],
|
|
219
|
+
toolMessage: ToolResultMessage,
|
|
220
|
+
) {
|
|
221
|
+
// check for the last 5 tool call assistant messages
|
|
222
|
+
// if they are non-`task` tool calls insert the reminder
|
|
223
|
+
// as a prefix.
|
|
224
|
+
let output = toolMessage.content
|
|
225
|
+
.filter((b) => b.type === "text")
|
|
226
|
+
.map((b) => b.text)
|
|
227
|
+
.join("\n");
|
|
228
|
+
|
|
229
|
+
const budget = 5;
|
|
230
|
+
const toolCalls: ToolCall[] = [];
|
|
231
|
+
const lastUserMessageIndex = messages.findLastIndex((m) => m.role === "user");
|
|
232
|
+
const messagesSinceLastUser = messages.slice(lastUserMessageIndex + 1);
|
|
233
|
+
|
|
234
|
+
messagesSinceLastUser.forEach((m) => {
|
|
235
|
+
if (m.role === "assistant") {
|
|
236
|
+
const toolCallsBlocks = m.content.filter((b) => b.type === "toolCall");
|
|
237
|
+
toolCalls.push(...toolCallsBlocks);
|
|
311
238
|
}
|
|
312
|
-
|
|
313
|
-
|
|
239
|
+
});
|
|
240
|
+
const recentToolCalls = toolCalls.slice(-budget);
|
|
241
|
+
const taskSeen = recentToolCalls.some((call) => call.name === "task");
|
|
242
|
+
|
|
243
|
+
if (toolCalls.length >= budget && !taskSeen) {
|
|
244
|
+
output = `<system-reminder>
|
|
245
|
+
You are currently making repeated individual tool calls. This fragments context and reduces efficiency.
|
|
314
246
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
247
|
+
- Stop and plan: consolidate remaining steps into a single **task** tool call.
|
|
248
|
+
- If the user request is fully completed, stop calling tools and provide your final answer.
|
|
249
|
+
</system-reminder>
|
|
250
|
+
|
|
251
|
+
${output}`;
|
|
319
252
|
}
|
|
320
253
|
|
|
321
|
-
|
|
254
|
+
toolMessage.content = [{ type: "text", text: output }];
|
|
255
|
+
|
|
256
|
+
return toolMessage;
|
|
322
257
|
}
|