@matterailab/orbcode 0.2.2 → 0.2.3
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 +351 -8
- package/dist/commands/mcp.js +266 -0
- package/dist/config/settings.js +27 -0
- package/dist/core/agent.js +28 -7
- package/dist/headless.js +18 -0
- package/dist/index.js +9 -0
- package/dist/mcp/auth.js +289 -0
- package/dist/mcp/client.js +132 -0
- package/dist/mcp/config.js +270 -0
- package/dist/mcp/manager.js +277 -0
- package/dist/mcp/types.js +8 -0
- package/dist/memory/loader.js +167 -0
- package/dist/memory/types.js +1 -0
- package/dist/prompts/system.js +26 -7
- package/dist/skills/loader.js +132 -0
- package/dist/skills/types.js +1 -0
- package/dist/tools/executors/skills.js +28 -0
- package/dist/tools/index.js +22 -3
- package/dist/tools/schemas/index.js +6 -3
- package/dist/tools/schemas/use_skill.js +1 -1
- package/dist/ui/App.js +205 -50
- package/dist/ui/components/McpApprovalPrompt.js +61 -0
- package/dist/ui/components/McpAuthScreen.js +55 -0
- package/dist/ui/components/McpPicker.js +262 -0
- package/dist/ui/components/StatusBar.js +28 -9
- package/dist/utils/clipboard.js +45 -0
- package/package.json +2 -1
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { getConfigDir } from "../config/settings.js";
|
|
5
|
+
/**
|
|
6
|
+
* AGENTS.md memory loader.
|
|
7
|
+
*
|
|
8
|
+
* OrbCode's memory system mirrors Claude Code's CLAUDE.md discovery, but uses
|
|
9
|
+
* the open `AGENTS.md` filename (the cross-tool standard) instead of a
|
|
10
|
+
* vendor-specific name. Files are loaded in this order (lowest precedence
|
|
11
|
+
* first), with closer-to-cwd and higher-precedence types winning:
|
|
12
|
+
*
|
|
13
|
+
* 1. User memory: ~/.orbcode/AGENTS.md
|
|
14
|
+
* 2. Project memory: AGENTS.md and .orbcode/AGENTS.md in cwd and every
|
|
15
|
+
* parent directory (closer-to-cwd wins)
|
|
16
|
+
* 3. Local memory: AGENTS.local.md in cwd and parents (highest precedence)
|
|
17
|
+
*
|
|
18
|
+
* Memory files support `@path` include directives (relative, `~/`, or
|
|
19
|
+
* absolute) to pull in other files. Includes are resolved recursively up to a
|
|
20
|
+
* small depth limit, with cycle detection.
|
|
21
|
+
*/
|
|
22
|
+
const MAX_INCLUDE_DEPTH = 5;
|
|
23
|
+
const MAX_TOTAL_CHARS = 200_000;
|
|
24
|
+
/** Read a file as UTF-8, returning undefined if it's missing or unreadable. */
|
|
25
|
+
function readText(filePath) {
|
|
26
|
+
try {
|
|
27
|
+
return fs.readFileSync(filePath, "utf8");
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Resolve an @include path relative to the including file's directory. */
|
|
34
|
+
function resolveInclude(ref, baseDir) {
|
|
35
|
+
if (ref.startsWith("~/"))
|
|
36
|
+
return path.join(os.homedir(), ref.slice(2));
|
|
37
|
+
if (path.isAbsolute(ref))
|
|
38
|
+
return ref;
|
|
39
|
+
return path.resolve(baseDir, ref);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Extract `@path` references from leaf text (not inside code spans/blocks).
|
|
43
|
+
* A simple, conservative parser: skips fenced code blocks and inline code.
|
|
44
|
+
*/
|
|
45
|
+
function extractIncludes(content, baseDir) {
|
|
46
|
+
const refs = [];
|
|
47
|
+
let inFence = false;
|
|
48
|
+
for (const line of content.split(/\r?\n/)) {
|
|
49
|
+
if (/^```/.test(line.trim())) {
|
|
50
|
+
inFence = !inFence;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (inFence)
|
|
54
|
+
continue;
|
|
55
|
+
// Strip inline code spans before scanning for @refs.
|
|
56
|
+
const stripped = line.replace(/`[^`]*`/g, "");
|
|
57
|
+
const regex = /(?:^|\s)@((?:\.\/|~\/|\/)?[^\s]+)/g;
|
|
58
|
+
let match;
|
|
59
|
+
while ((match = regex.exec(stripped)) !== null) {
|
|
60
|
+
const ref = match[1].replace(/\\ /g, " ");
|
|
61
|
+
// Avoid matching emails, @mentions, etc.
|
|
62
|
+
if (!/^[A-Za-z0-9._~/-]/.test(ref))
|
|
63
|
+
continue;
|
|
64
|
+
refs.push(resolveInclude(ref, baseDir));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return refs;
|
|
68
|
+
}
|
|
69
|
+
/** Recursively load a memory file and all its @includes. */
|
|
70
|
+
function loadWithIncludes(filePath, type, processed, depth) {
|
|
71
|
+
const real = safeRealpath(filePath);
|
|
72
|
+
if (processed.has(real) || depth >= MAX_INCLUDE_DEPTH)
|
|
73
|
+
return [];
|
|
74
|
+
const raw = readText(filePath);
|
|
75
|
+
if (raw === undefined)
|
|
76
|
+
return [];
|
|
77
|
+
processed.add(real);
|
|
78
|
+
const baseDir = path.dirname(filePath);
|
|
79
|
+
const includePaths = extractIncludes(raw, baseDir);
|
|
80
|
+
const results = [];
|
|
81
|
+
// Includes come first (so they appear before the including file's content).
|
|
82
|
+
for (const includePath of includePaths) {
|
|
83
|
+
results.push(...loadWithIncludes(includePath, type, processed, depth + 1));
|
|
84
|
+
}
|
|
85
|
+
results.push({ path: filePath, content: raw, type });
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
/** realpathSync that never throws (falls back to the input path). */
|
|
89
|
+
function safeRealpath(p) {
|
|
90
|
+
try {
|
|
91
|
+
return fs.realpathSync(p);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return path.resolve(p);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** Walk from cwd up to root, collecting directories (root last). */
|
|
98
|
+
function ancestorDirs(cwd) {
|
|
99
|
+
const dirs = [];
|
|
100
|
+
let current = path.resolve(cwd);
|
|
101
|
+
const root = path.parse(current).root;
|
|
102
|
+
while (current !== root) {
|
|
103
|
+
dirs.push(current);
|
|
104
|
+
const parent = path.dirname(current);
|
|
105
|
+
if (parent === current)
|
|
106
|
+
break;
|
|
107
|
+
current = parent;
|
|
108
|
+
}
|
|
109
|
+
return dirs;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Load all AGENTS.md memory files, in precedence order (lowest first). The
|
|
113
|
+
* caller concatenates them in order so higher-precedence files appear last
|
|
114
|
+
* (and thus get more weight from the model).
|
|
115
|
+
*/
|
|
116
|
+
export function loadMemoryFiles(cwd = process.cwd()) {
|
|
117
|
+
const processed = new Set();
|
|
118
|
+
const files = [];
|
|
119
|
+
// 1. User memory (~/.orbcode/AGENTS.md)
|
|
120
|
+
files.push(...loadWithIncludes(path.join(getConfigDir(), "AGENTS.md"), "user", processed, 0));
|
|
121
|
+
// 2. Project memory (AGENTS.md + .orbcode/AGENTS.md), root -> cwd
|
|
122
|
+
for (const dir of ancestorDirs(cwd).reverse()) {
|
|
123
|
+
files.push(...loadWithIncludes(path.join(dir, "AGENTS.md"), "project", processed, 0));
|
|
124
|
+
files.push(...loadWithIncludes(path.join(dir, ".orbcode", "AGENTS.md"), "project", processed, 0));
|
|
125
|
+
}
|
|
126
|
+
// 3. Local memory (AGENTS.local.md), root -> cwd — highest precedence
|
|
127
|
+
for (const dir of ancestorDirs(cwd).reverse()) {
|
|
128
|
+
files.push(...loadWithIncludes(path.join(dir, "AGENTS.local.md"), "local", processed, 0));
|
|
129
|
+
}
|
|
130
|
+
return files;
|
|
131
|
+
}
|
|
132
|
+
/** Render the memory files into a single system-prompt section. */
|
|
133
|
+
export function renderMemorySection(files) {
|
|
134
|
+
const valid = files.filter((f) => f.content.trim());
|
|
135
|
+
if (valid.length === 0)
|
|
136
|
+
return "";
|
|
137
|
+
const parts = [
|
|
138
|
+
"# Project & User Instructions (AGENTS.md)",
|
|
139
|
+
"",
|
|
140
|
+
"Instructions from AGENTS.md files are shown below. Adhere to these instructions; they override default behavior. Treat them as authoritative guidance from the user about this codebase.",
|
|
141
|
+
"",
|
|
142
|
+
];
|
|
143
|
+
let total = 0;
|
|
144
|
+
for (const file of valid) {
|
|
145
|
+
if (total >= MAX_TOTAL_CHARS) {
|
|
146
|
+
parts.push("… (remaining memory files truncated to stay within context budget)");
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
const label = labelFor(file);
|
|
150
|
+
parts.push(`## ${label}: \`${file.path}\``);
|
|
151
|
+
parts.push("");
|
|
152
|
+
parts.push(file.content.trim());
|
|
153
|
+
parts.push("");
|
|
154
|
+
total += file.content.length;
|
|
155
|
+
}
|
|
156
|
+
return parts.join("\n");
|
|
157
|
+
}
|
|
158
|
+
function labelFor(file) {
|
|
159
|
+
switch (file.type) {
|
|
160
|
+
case "user":
|
|
161
|
+
return "User memory";
|
|
162
|
+
case "project":
|
|
163
|
+
return "Project memory";
|
|
164
|
+
case "local":
|
|
165
|
+
return "Local memory";
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/prompts/system.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as os from "node:os";
|
|
2
2
|
import { getShell } from "../utils/shell.js";
|
|
3
|
+
import { renderMemorySection } from "../memory/loader.js";
|
|
4
|
+
import { renderSkillCatalog } from "../skills/loader.js";
|
|
3
5
|
// Role definition and tool guide ported verbatim from the Orbital extension
|
|
4
6
|
// (agent mode roleDefinition + applyDiffToolDescription). Only the system
|
|
5
7
|
// information section is adapted from the IDE to the CLI environment.
|
|
@@ -51,6 +53,18 @@ Bias towards not asking the user for help if you can find the answer yourself.
|
|
|
51
53
|
|
|
52
54
|
Code chunks that you receive (via tool calls or from user) may include inline line numbers in the form LINE_NUMBER|LINE_CONTENT. Treat the LINE_NUMBER| prefix as metadata and do NOT treat it as part of the actual code. LINE_NUMBER is right-aligned number padded with spaces to 6 characters.
|
|
53
55
|
|
|
56
|
+
# Project & User Instructions (AGENTS.md)
|
|
57
|
+
|
|
58
|
+
Your system prompt may include an "Project & User Instructions (AGENTS.md)" section. These are instructions from AGENTS.md files in the user's home directory, the project root, and parent directories. They contain project-specific guidance: build commands, code style, architecture notes, conventions. Treat them as authoritative instructions from the user about this codebase and follow them exactly. They override default behavior.
|
|
59
|
+
|
|
60
|
+
# Skills
|
|
61
|
+
|
|
62
|
+
Your system prompt may include an "Available Skills" section listing skills by name with a description and when-to-use hint. Skills are reusable instruction sets stored in ~/.orbcode/skills/ and .orbcode/skills/. When a task matches a skill's when-to-use condition, invoke the \`use_skill\` tool with the skill's name to load its full instructions, then follow them for the current task.
|
|
63
|
+
|
|
64
|
+
# MCP Tools
|
|
65
|
+
|
|
66
|
+
Tools whose names start with \`mcp__\` are provided by external MCP servers the user has configured. They work exactly like native tools — call them with the standard tool call format when the task requires their capabilities. Their descriptions and parameter schemas come from the MCP servers.
|
|
67
|
+
|
|
54
68
|
CRITICAL: For any task, small or big, you will always and always use the update_todo_list tool to create the TODO list, always keep is upto date with updates to the status and updating/editing the list as needed.`;
|
|
55
69
|
const toolGuide = `
|
|
56
70
|
Common tool calls and explanations
|
|
@@ -373,11 +387,16 @@ function getSystemInfoSection(cwd) {
|
|
|
373
387
|
|
|
374
388
|
The Current Workspace Directory is the directory the user launched OrbCode CLI from, and is therefore the default directory for all tool operations. Commands run in the current workspace directory unless a different cwd is passed; changing directories inside a command does not modify the workspace directory. When the user initially gives you a task, a listing of filepaths in the current workspace directory will be included in the Environment Details section. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current workspace directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.`;
|
|
375
389
|
}
|
|
376
|
-
export function buildSystemPrompt(cwd) {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
390
|
+
export function buildSystemPrompt(cwd, options = {}) {
|
|
391
|
+
const memorySection = options.memoryFiles ? renderMemorySection(options.memoryFiles) : "";
|
|
392
|
+
const skillSection = options.skills ? renderSkillCatalog(options.skills) : "";
|
|
393
|
+
return [
|
|
394
|
+
roleDefinition,
|
|
395
|
+
toolGuide,
|
|
396
|
+
getSystemInfoSection(cwd),
|
|
397
|
+
memorySection,
|
|
398
|
+
skillSection,
|
|
399
|
+
]
|
|
400
|
+
.filter(Boolean)
|
|
401
|
+
.join("\n\n");
|
|
383
402
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { getConfigDir } from "../config/settings.js";
|
|
4
|
+
/**
|
|
5
|
+
* Skill loader.
|
|
6
|
+
*
|
|
7
|
+
* Skills are markdown files that inject specialized instructions when the model
|
|
8
|
+
* invokes the `use_skill` tool. Discovery mirrors Claude Code's layout:
|
|
9
|
+
*
|
|
10
|
+
* - User skills: ~/.orbcode/skills/<name>/SKILL.md
|
|
11
|
+
* - Project skills: .orbcode/skills/<name>/SKILL.md (in cwd and parents)
|
|
12
|
+
*
|
|
13
|
+
* Only the directory format (`<name>/SKILL.md`) is supported, matching Claude
|
|
14
|
+
* Code's current skills/ convention. Project skills override user skills on
|
|
15
|
+
* name collisions (closer-to-cwd wins).
|
|
16
|
+
*/
|
|
17
|
+
/** Parse a leading `---\n...---\n` YAML frontmatter block. */
|
|
18
|
+
export function parseFrontmatter(raw) {
|
|
19
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(raw);
|
|
20
|
+
if (!match)
|
|
21
|
+
return { frontmatter: {}, content: raw };
|
|
22
|
+
const [, yamlBlock, body] = match;
|
|
23
|
+
const frontmatter = {};
|
|
24
|
+
for (const line of yamlBlock.split(/\r?\n/)) {
|
|
25
|
+
const idx = line.indexOf(":");
|
|
26
|
+
if (idx === -1)
|
|
27
|
+
continue;
|
|
28
|
+
const key = line.slice(0, idx).trim();
|
|
29
|
+
const value = line.slice(idx + 1).trim().replace(/^["']|["']$/g, "");
|
|
30
|
+
if (key)
|
|
31
|
+
frontmatter[key] = value;
|
|
32
|
+
}
|
|
33
|
+
return { frontmatter, content: body.trimStart() };
|
|
34
|
+
}
|
|
35
|
+
/** Extract a description from the first markdown paragraph if frontmatter lacks one. */
|
|
36
|
+
function descriptionFromBody(content) {
|
|
37
|
+
const text = content
|
|
38
|
+
.replace(/^#+\s.*$/gm, "")
|
|
39
|
+
.replace(/```[\s\S]*?```/g, "")
|
|
40
|
+
.trim();
|
|
41
|
+
const firstPara = text.split(/\n\s*\n/)[0] ?? "";
|
|
42
|
+
return firstPara.replace(/\s+/g, " ").trim().slice(0, 160);
|
|
43
|
+
}
|
|
44
|
+
/** Load a single skill from a `<dir>/SKILL.md` path. */
|
|
45
|
+
function loadSkill(skillDir, source) {
|
|
46
|
+
const skillFile = path.join(skillDir, "SKILL.md");
|
|
47
|
+
let raw;
|
|
48
|
+
try {
|
|
49
|
+
raw = fs.readFileSync(skillFile, "utf8");
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
const name = path.basename(skillDir);
|
|
55
|
+
const { frontmatter, content } = parseFrontmatter(raw);
|
|
56
|
+
return {
|
|
57
|
+
name,
|
|
58
|
+
description: frontmatter.description || descriptionFromBody(content),
|
|
59
|
+
whenToUse: frontmatter.when_to_use,
|
|
60
|
+
content,
|
|
61
|
+
source,
|
|
62
|
+
dir: skillDir,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Load all skills from a `skills/` directory (each subdirectory is one skill). */
|
|
66
|
+
function loadSkillsDir(skillsDir, source) {
|
|
67
|
+
let entries;
|
|
68
|
+
try {
|
|
69
|
+
entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
const skills = [];
|
|
75
|
+
for (const entry of entries) {
|
|
76
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
77
|
+
continue;
|
|
78
|
+
const skill = loadSkill(path.join(skillsDir, entry.name), source);
|
|
79
|
+
if (skill)
|
|
80
|
+
skills.push(skill);
|
|
81
|
+
}
|
|
82
|
+
return skills;
|
|
83
|
+
}
|
|
84
|
+
/** Walk from cwd up to root, collecting `.orbcode/skills` dirs (root last). */
|
|
85
|
+
function projectSkillsDirs(cwd) {
|
|
86
|
+
const dirs = [];
|
|
87
|
+
let current = path.resolve(cwd);
|
|
88
|
+
const root = path.parse(current).root;
|
|
89
|
+
while (current !== root) {
|
|
90
|
+
dirs.push(path.join(current, ".orbcode", "skills"));
|
|
91
|
+
const parent = path.dirname(current);
|
|
92
|
+
if (parent === current)
|
|
93
|
+
break;
|
|
94
|
+
current = parent;
|
|
95
|
+
}
|
|
96
|
+
return dirs;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Load all skills. Project skills (closer-to-cwd) override user skills on name
|
|
100
|
+
* collisions. Returns a map keyed by skill name.
|
|
101
|
+
*/
|
|
102
|
+
export function loadSkills(cwd = process.cwd()) {
|
|
103
|
+
const skills = new Map();
|
|
104
|
+
// User skills first (lowest precedence).
|
|
105
|
+
for (const skill of loadSkillsDir(path.join(getConfigDir(), "skills"), "user")) {
|
|
106
|
+
skills.set(skill.name, skill);
|
|
107
|
+
}
|
|
108
|
+
// Project skills, root -> cwd so cwd wins.
|
|
109
|
+
for (const dir of projectSkillsDirs(cwd).reverse()) {
|
|
110
|
+
for (const skill of loadSkillsDir(dir, "project")) {
|
|
111
|
+
skills.set(skill.name, skill);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return skills;
|
|
115
|
+
}
|
|
116
|
+
/** Render the skill catalog for the system prompt (names + when_to_use). */
|
|
117
|
+
export function renderSkillCatalog(skills) {
|
|
118
|
+
if (skills.size === 0)
|
|
119
|
+
return "";
|
|
120
|
+
const lines = ["# Available Skills", ""];
|
|
121
|
+
for (const skill of skills.values()) {
|
|
122
|
+
const when = skill.whenToUse ? ` — use when: ${skill.whenToUse}` : "";
|
|
123
|
+
lines.push(`- \`${skill.name}\`: ${skill.description}${when}`);
|
|
124
|
+
}
|
|
125
|
+
lines.push("");
|
|
126
|
+
lines.push("Use the `use_skill` tool with a `skill_name` to load a skill's full instructions.");
|
|
127
|
+
return lines.join("\n");
|
|
128
|
+
}
|
|
129
|
+
/** Substitute ${SKILL_DIR} in a skill's content with its directory path. */
|
|
130
|
+
export function renderSkillContent(skill) {
|
|
131
|
+
return skill.content.replace(/\$\{SKILL_DIR\}/g, skill.dir);
|
|
132
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { loadSkills, renderSkillContent } from "../../skills/loader.js";
|
|
2
|
+
/**
|
|
3
|
+
* `use_skill` executor: loads a skill's full markdown instructions and returns
|
|
4
|
+
* them to the model so it can follow the skill's guidance for the current task.
|
|
5
|
+
*
|
|
6
|
+
* Skills are discovered from ~/.orbcode/skills/ and .orbcode/skills/ (see the
|
|
7
|
+
* skills loader). The model sees the catalog in the system prompt and invokes
|
|
8
|
+
* this tool with a `skill_name`.
|
|
9
|
+
*/
|
|
10
|
+
export async function useSkill(args, context) {
|
|
11
|
+
const skillName = String(args.skill_name ?? "").trim();
|
|
12
|
+
if (!skillName) {
|
|
13
|
+
return { text: "No skill_name provided.", isError: true };
|
|
14
|
+
}
|
|
15
|
+
const skills = loadSkills(context.cwd);
|
|
16
|
+
const skill = skills.get(skillName);
|
|
17
|
+
if (!skill) {
|
|
18
|
+
const available = [...skills.keys()].join(", ") || "(none)";
|
|
19
|
+
return {
|
|
20
|
+
text: `Skill "${skillName}" not found. Available skills: ${available}`,
|
|
21
|
+
isError: true,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const content = renderSkillContent(skill);
|
|
25
|
+
return {
|
|
26
|
+
text: `# Skill: ${skill.name}\n\n${content}\n\n---\nFollow this skill's instructions for the current task.`,
|
|
27
|
+
};
|
|
28
|
+
}
|
package/dist/tools/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { fileEdit, fileWrite, multiFileEdit, readFile } from "./executors/files.
|
|
|
3
3
|
import { listFiles } from "./executors/listFiles.js";
|
|
4
4
|
import { searchFiles } from "./executors/searchFiles.js";
|
|
5
5
|
import { executeCommand } from "./executors/executeCommand.js";
|
|
6
|
+
import { useSkill } from "./executors/skills.js";
|
|
6
7
|
import { webFetch, webSearch } from "./executors/web.js";
|
|
7
8
|
export { nativeTools };
|
|
8
9
|
/** Tools that modify the user's system and need approval before running. */
|
|
@@ -44,11 +45,18 @@ export function describeToolCall(toolName, args) {
|
|
|
44
45
|
return String(args.url ?? "");
|
|
45
46
|
case "update_todo_list":
|
|
46
47
|
return "updating tasks";
|
|
48
|
+
case "use_skill":
|
|
49
|
+
return String(args.skill_name ?? "");
|
|
47
50
|
case "ask_followup_question":
|
|
48
51
|
return String(args.question ?? "");
|
|
49
52
|
case "attempt_completion":
|
|
50
53
|
return "task complete";
|
|
51
54
|
default:
|
|
55
|
+
// MCP tools (mcp__<server>__<tool>) — show the server + tool name.
|
|
56
|
+
if (/^mcp__/.test(toolName)) {
|
|
57
|
+
const match = /^mcp__([^_]+)__(.+)$/.exec(toolName);
|
|
58
|
+
return match ? `${match[2]} (${match[1]})` : toolName;
|
|
59
|
+
}
|
|
52
60
|
return toolName;
|
|
53
61
|
}
|
|
54
62
|
}
|
|
@@ -62,13 +70,20 @@ const executors = {
|
|
|
62
70
|
execute_command: executeCommand,
|
|
63
71
|
web_search: webSearch,
|
|
64
72
|
web_fetch: webFetch,
|
|
73
|
+
use_skill: useSkill,
|
|
65
74
|
update_todo_list: async (args, context) => {
|
|
66
75
|
context.setTodos(String(args.todos ?? ""));
|
|
67
76
|
return { text: "Todo list updated." };
|
|
68
77
|
},
|
|
69
78
|
// ask_followup_question and attempt_completion are handled by the agent loop.
|
|
79
|
+
// MCP tools (mcp__<server>__<tool>) are routed via the McpManager in executeTool.
|
|
70
80
|
};
|
|
71
|
-
export async function executeTool(toolName, args, context) {
|
|
81
|
+
export async function executeTool(toolName, args, context, mcp) {
|
|
82
|
+
// MCP tools are namespaced as mcp__<server>__<tool> and routed to the manager.
|
|
83
|
+
if (mcp && mcp.hasTool(toolName)) {
|
|
84
|
+
const result = await mcp.callTool(toolName, args);
|
|
85
|
+
return { text: result.text, isError: result.isError };
|
|
86
|
+
}
|
|
72
87
|
const executor = executors[toolName];
|
|
73
88
|
if (!executor) {
|
|
74
89
|
return { text: `Tool "${toolName}" is not available in OrbCode CLI.`, isError: true };
|
|
@@ -80,6 +95,10 @@ export async function executeTool(toolName, args, context) {
|
|
|
80
95
|
return { text: `Tool ${toolName} failed: ${error.message}`, isError: true };
|
|
81
96
|
}
|
|
82
97
|
}
|
|
83
|
-
|
|
84
|
-
|
|
98
|
+
/** Native tools plus any MCP tools from connected servers. */
|
|
99
|
+
export function getActiveTools(mcp) {
|
|
100
|
+
const tools = [...nativeTools];
|
|
101
|
+
if (mcp)
|
|
102
|
+
tools.push(...mcp.getTools());
|
|
103
|
+
return tools;
|
|
85
104
|
}
|
|
@@ -8,11 +8,13 @@ import listFiles from "./list_files.js";
|
|
|
8
8
|
import { read_file_single } from "./read_file.js";
|
|
9
9
|
import searchFiles from "./search_files.js";
|
|
10
10
|
import updateTodoList from "./update_todo_list.js";
|
|
11
|
+
import useSkill from "./use_skill.js";
|
|
11
12
|
import webFetch from "./web_fetch.js";
|
|
12
13
|
import webSearch from "./web_search.js";
|
|
13
|
-
// Native tool schemas ported
|
|
14
|
-
//
|
|
15
|
-
//
|
|
14
|
+
// Native tool schemas ported from the Orbital extension. IDE-only tools
|
|
15
|
+
// (codebase_search, lsp, check_past_chat_memories, browser_action, …) are not
|
|
16
|
+
// active in the CLI. use_skill is now active: skills are loaded from
|
|
17
|
+
// ~/.orbcode/skills/ and .orbcode/skills/ (see src/skills/loader.ts).
|
|
16
18
|
export const nativeTools = [
|
|
17
19
|
fileEdit,
|
|
18
20
|
multiFileEdit,
|
|
@@ -24,6 +26,7 @@ export const nativeTools = [
|
|
|
24
26
|
read_file_single,
|
|
25
27
|
searchFiles,
|
|
26
28
|
updateTodoList,
|
|
29
|
+
useSkill,
|
|
27
30
|
webFetch,
|
|
28
31
|
webSearch,
|
|
29
32
|
];
|
|
@@ -2,7 +2,7 @@ export default {
|
|
|
2
2
|
type: "function",
|
|
3
3
|
function: {
|
|
4
4
|
name: "use_skill",
|
|
5
|
-
description: "Use a specific skill to guide the task execution. This tool
|
|
5
|
+
description: "Use a specific skill to guide the task execution. This tool applies predefined skills stored in ~/.orbcode/skills/ (user) and .orbcode/skills/ (project). Each skill is a directory containing a SKILL.md file with specialized instructions for performing specific tasks or following particular patterns. The available skills are listed in the 'Available Skills' section of your system prompt — invoke this tool with a skill_name from that list when the task matches a skill's when-to-use condition.",
|
|
6
6
|
strict: true,
|
|
7
7
|
parameters: {
|
|
8
8
|
type: "object",
|