Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/skill-import-prompt-symlink-copy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@inkeep/open-knowledge": patch
---

Clarify the skills import prompt. The one-time banner now states what Import actually does: it moves the editor-dir skills into `.ok/skills` and replaces the `.claude`, `.codex`, etc. copies with symlinks back to it, so the knowledge base is the single place to edit them and every editor stays in sync. It also flags the consequences a user needs before clicking: if those folders are committed to git the change should be reviewed, and symlinks can behave differently on some editors and on Windows.
9 changes: 9 additions & 0 deletions .changeset/skills-tool-builtin-skill-hint.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@inkeep/open-knowledge": patch
---

Stop two ways agents get derailed around the skill surface.

- The `skills` MCP tool now short-circuits OpenKnowledge's own built-in skills instead of 404-ing. An agent told to "load the open-knowledge skill" would call `skills({ name: "open-knowledge" })`, hit a bare `Skill not found.`, and fall back to cat-ing the bundled SKILL.md. The built-ins (`open-knowledge`, `open-knowledge-discovery`, `open-knowledge-write-skill`) are runtime agent skills projected into editor host dirs, never KB content skills, so a READ aimed at one now returns a teaching error explaining it is already in the agent's loaded skill list and is not fetched through this tool. User-authored `open-knowledge-pack-*` skills are unaffected, and the tool description states the boundary up front.

- The project SKILL.md escape hatch now tells agents that their initial tool list is not exhaustive: some clients (notably Codex) defer MCP tools behind a lazy `tool_search` step, so `mcp__open-knowledge__*` is absent until discovered. Absence from the visible list means "not discovered yet," not "not registered" — agents must run tool discovery before invoking the native-tools escape hatch.
6 changes: 4 additions & 2 deletions packages/app/src/components/SkillsSidebarSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -432,8 +432,10 @@ function SkillImportPrompt() {
<div className="mx-2 mb-1 rounded-md border border-border/60 bg-muted/40 p-2 text-xs">
<p className="mb-2 text-muted-foreground">
<Trans>
Open Knowledge can manage {state.importable} editor skill(s) for this project — import
them into the knowledge base?
Open Knowledge can manage {state.importable} editor skill(s) for this project. Import
moves them into .ok/skills and replaces the .claude, .codex, etc. copies with symlinks —
one place to edit, in sync everywhere. If those folders are committed to git, review the
change first; symlinks can behave differently on some editors and on Windows.
</Trans>
</p>
<div className="flex gap-1.5">
Expand Down
10 changes: 5 additions & 5 deletions packages/app/src/locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1676,6 +1676,11 @@
"msOK6I": [
"Repositories you've already cloned may keep syncing through git's own saved credentials, even after you disconnect."
],
"mu1O0q": [
"Open Knowledge can manage ",
["0"],
" editor skill(s) for this project. Import moves them into .ok/skills and replaces the .claude, .codex, etc. copies with symlinks — one place to edit, in sync everywhere. If those folders are committed to git, review the change first; symlinks can behave differently on some editors and on Windows."
],
"mwMF9u": ["Open CodeMirror search in source editor mode."],
"mxY24F": ["Duplicate the focused file-tree item when focus is in the Files sidebar."],
"n2lRue": ["Commit or stash changes to switch:"],
Expand Down Expand Up @@ -2051,11 +2056,6 @@
"y1t8bQ": ["Settings sections"],
"y3aU20": ["Save changes"],
"y46m5r": ["The desktop app isn't responding. Refresh and try again."],
"y48eD2": [
"Open Knowledge can manage ",
["0"],
" editor skill(s) for this project — import them into the knowledge base?"
],
"y5FUYi": ["Suggestion menu open"],
"y8B54M": ["Couldn't send your prompt — please try again."],
"yCX4jp": [" Your selection could not be restored."],
Expand Down
4 changes: 2 additions & 2 deletions packages/app/src/locales/en/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -4310,8 +4310,8 @@ msgstr "Open in current branch"

#. placeholder {0}: state.importable
#: src/components/SkillsSidebarSection.tsx
msgid "Open Knowledge can manage {0} editor skill(s) for this project — import them into the knowledge base?"
msgstr "Open Knowledge can manage {0} editor skill(s) for this project — import them into the knowledge base?"
msgid "Open Knowledge can manage {0} editor skill(s) for this project. Import moves them into .ok/skills and replaces the .claude, .codex, etc. copies with symlinks — one place to edit, in sync everywhere. If those folders are committed to git, review the change first; symlinks can behave differently on some editors and on Windows."
msgstr "Open Knowledge can manage {0} editor skill(s) for this project. Import moves them into .ok/skills and replaces the .claude, .codex, etc. copies with symlinks — one place to edit, in sync everywhere. If those folders are committed to git, review the change first; symlinks can behave differently on some editors and on Windows."

#: src/components/GraphPanel.tsx
msgid "Open link"
Expand Down
10 changes: 5 additions & 5 deletions packages/app/src/locales/pseudo/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1676,6 +1676,11 @@
"msOK6I": [
"Ŕēƥōśĩţōŕĩēś ŷōũ'vē àĺŕēàďŷ ćĺōńēď ḿàŷ ķēēƥ śŷńćĩńĝ ţĥŕōũĝĥ ĝĩţ'ś ōŵń śàvēď ćŕēďēńţĩàĺś, ēvēń àƒţēŕ ŷōũ ďĩśćōńńēćţ."
],
"mu1O0q": [
"Ōƥēń Ķńōŵĺēďĝē ćàń ḿàńàĝē ",
["0"],
" ēďĩţōŕ śķĩĺĺ(ś) ƒōŕ ţĥĩś ƥŕōĴēćţ. Ĩḿƥōŕţ ḿōvēś ţĥēḿ ĩńţō .ōķ/śķĩĺĺś àńď ŕēƥĺàćēś ţĥē .ćĺàũďē, .ćōďēx, ēţć. ćōƥĩēś ŵĩţĥ śŷḿĺĩńķś — ōńē ƥĺàćē ţō ēďĩţ, ĩń śŷńć ēvēŕŷŵĥēŕē. Ĩƒ ţĥōśē ƒōĺďēŕś àŕē ćōḿḿĩţţēď ţō ĝĩţ, ŕēvĩēŵ ţĥē ćĥàńĝē ƒĩŕśţ; śŷḿĺĩńķś ćàń ƀēĥàvē ďĩƒƒēŕēńţĺŷ ōń śōḿē ēďĩţōŕś àńď ōń Ŵĩńďōŵś."
],
"mwMF9u": ["Ōƥēń ĆōďēḾĩŕŕōŕ śēàŕćĥ ĩń śōũŕćē ēďĩţōŕ ḿōďē."],
"mxY24F": ["Ďũƥĺĩćàţē ţĥē ƒōćũśēď ƒĩĺē-ţŕēē ĩţēḿ ŵĥēń ƒōćũś ĩś ĩń ţĥē Ƒĩĺēś śĩďēƀàŕ."],
"n2lRue": ["Ćōḿḿĩţ ōŕ śţàśĥ ćĥàńĝēś ţō śŵĩţćĥ:"],
Expand Down Expand Up @@ -2051,11 +2056,6 @@
"y1t8bQ": ["Śēţţĩńĝś śēćţĩōńś"],
"y3aU20": ["Śàvē ćĥàńĝēś"],
"y46m5r": ["Ţĥē ďēśķţōƥ àƥƥ ĩśń'ţ ŕēśƥōńďĩńĝ. Ŕēƒŕēśĥ àńď ţŕŷ àĝàĩń."],
"y48eD2": [
"Ōƥēń Ķńōŵĺēďĝē ćàń ḿàńàĝē ",
["0"],
" ēďĩţōŕ śķĩĺĺ(ś) ƒōŕ ţĥĩś ƥŕōĴēćţ — ĩḿƥōŕţ ţĥēḿ ĩńţō ţĥē ķńōŵĺēďĝē ƀàśē?"
],
"y5FUYi": ["Śũĝĝēśţĩōń ḿēńũ ōƥēń"],
"y8B54M": ["Ćōũĺďń'ţ śēńď ŷōũŕ ƥŕōḿƥţ — ƥĺēàśē ţŕŷ àĝàĩń."],
"yCX4jp": [" Ŷōũŕ śēĺēćţĩōń ćōũĺď ńōţ ƀē ŕēśţōŕēď."],
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/locales/pseudo/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -4305,7 +4305,7 @@ msgstr ""

#. placeholder {0}: state.importable
#: src/components/SkillsSidebarSection.tsx
msgid "Open Knowledge can manage {0} editor skill(s) for this project — import them into the knowledge base?"
msgid "Open Knowledge can manage {0} editor skill(s) for this project. Import moves them into .ok/skills and replaces the .claude, .codex, etc. copies with symlinks — one place to edit, in sync everywhere. If those folders are committed to git, review the change first; symlinks can behave differently on some editors and on Windows."
msgstr ""

#: src/components/GraphPanel.tsx
Expand Down
4 changes: 3 additions & 1 deletion packages/server/assets/skills/project/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ Tools NOT in OK MCP (your host's): `preview_start`, `preview_screenshot`, `WebFe

**MCP tool visibility — not seeing `exec` is NOT the escape hatch.** MCP wiring varies by client (Claude Code, Cursor, Codex, Windsurf, VS Code). Server labels are user-defined; tools may not appear as top-level symbols named `exec`. If OpenKnowledge is registered as an MCP server here, route markdown reads through its `exec` / `search` via your client's documented MCP invocation (including any generic "call MCP tool" flow). Registration is the test, not top-level-symbol visibility.

**Escape hatch.** Native `Read` / `Grep` / `Glob` on `.md` / `.mdx` is allowed **only** when no OpenKnowledge MCP server is registered for this project, **or** immediately after you tried an MCP call and it failed — then begin a user-visible sentence with `OpenKnowledge MCP unavailable:`. Never use the hatch because you skipped your client's MCP path, didn't see `exec` as a top-level tool, or rationalized the skill wasn't necessary.
**Your initial tool list is NOT exhaustive — run tool discovery before concluding the MCP is missing.** Some clients (notably Codex) defer MCP tools behind a lazy `tool_search` / tool-discovery step, so `mcp__open-knowledge__*` is absent from the upfront tool set and only appears after you search for it. Absence from the visible list means "I have not discovered it yet," NOT "it is not registered." If you don't see the tools, your first move is to run your client's tool-discovery / `tool_search` for `open-knowledge` (or `exec` / `search` / `write`) — do not infer unavailability from the initial list.

**Escape hatch.** Native `Read` / `Grep` / `Glob` on `.md` / `.mdx` is allowed **only** when, after running tool discovery (above), no OpenKnowledge MCP server is registered for this project, **or** immediately after you actually invoked an MCP call and it failed — then begin a user-visible sentence with `OpenKnowledge MCP unavailable:`. "Not registered" is a conclusion you may only reach after tool discovery turned it up empty — never from the initial tool list alone. Never use the hatch because you skipped your client's MCP path, didn't see `exec` as a top-level tool, didn't run tool discovery, or rationalized the skill wasn't necessary.

**Source code and non-markdown files** (`.ts`, `.py`, `package.json`, …): native `Read` / `Grep` / `Glob` always.

Expand Down
38 changes: 36 additions & 2 deletions packages/server/src/mcp/tools/skills.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expect, test } from 'bun:test';
import { type Config, ConfigSchema } from '../../config/schema.ts';
import type { ServerInstance } from './shared.ts';
import { BUNDLE_SKILL_NAME } from '../../skill-bundles.ts';
import { HOCUSPOCUS_NOT_RUNNING_ERROR, type ServerInstance } from './shared.ts';
import { register as registerSkills, type SkillsToolDeps } from './skills.ts';

const BASE_CONFIG: Config = ConfigSchema.parse({});
Expand Down Expand Up @@ -60,6 +61,39 @@ describe('skills read tool — server-required', () => {
const handler = captureSkills(undefined);
const r = await handler({ name: 'trip-log' });
expect(r.isError).toBe(true);
expect(text(r)).toContain('Hocuspocus server is not running');
expect(text(r)).toContain(HOCUSPOCUS_NOT_RUNNING_ERROR);
});
});

describe('skills read tool — built-in OK skills short-circuit before the network', () => {
test('READ open-knowledge teaches instead of looking it up', async () => {
const handler = captureSkills(undefined);
const r = await handler({ name: 'open-knowledge', scope: 'project' });
expect(r.isError).toBe(true);
expect(text(r)).toContain('built-in agent skills');
expect(text(r)).toContain('already provided to you in your loaded skill list');
});

test('every shipped bundle name short-circuits (not just open-knowledge)', async () => {
const handler = captureSkills(undefined);
for (const name of Object.values(BUNDLE_SKILL_NAME)) {
const r = await handler({ name });
expect(r.isError, `isError for "${name}"`).toBe(true);
expect(text(r), `teaching error for "${name}"`).toContain('NOT managed by this tool');
}
});

test('READ-file on a built-in skill is short-circuited too', async () => {
const handler = captureSkills(undefined);
const r = await handler({ name: 'open-knowledge', file: 'references/x.md' });
expect(r.isError).toBe(true);
expect(text(r)).toContain('built-in agent skills');
});

test('a user-authored pack skill is NOT treated as built-in', async () => {
const handler = captureSkills(undefined);
const r = await handler({ name: 'open-knowledge-pack-fishing' });
expect(r.isError).toBe(true);
expect(text(r)).toContain(HOCUSPOCUS_NOT_RUNNING_ERROR);
});
});
17 changes: 17 additions & 0 deletions packages/server/src/mcp/tools/skills.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { z } from 'zod';
import { BUNDLE_SKILL_NAME } from '../../skill-bundles.ts';
import {
type ConfigOrResolver,
HOCUSPOCUS_NOT_RUNNING_ERROR,
Expand All @@ -18,6 +19,16 @@ function bundleFileKind(path: string): 'reference' | 'script' {
return path.replace(/\\/g, '/').startsWith('scripts/') ? 'script' : 'reference';
}

const INTERNAL_BUNDLE_SKILL_NAMES = new Set<string>(Object.values(BUNDLE_SKILL_NAME));

function internalSkillHint(name: string): string {
return [
`"${name}" is one of OpenKnowledge's built-in agent skills — it is NOT managed by this tool and cannot be read or listed here.`,
"It is already provided to you in your loaded skill list (a hidden runtime skill projected into your editor); don't fetch or re-load it — just follow the skill you already have.",
'The `skills` tool only covers skills authored as KB content (`.ok/skills` = project, `~/.ok/skills` = global). Built-in `open-knowledge*` skills never appear in either scope.',
].join(' ');
}

const SCOPE_FIELD_DESCRIBE =
"Which level the skill lives at. `project` = this KB's `.ok/skills/` (shared via git with everyone on the project); `global` = your user-level `~/.ok/skills/` (available in every project on this machine, not shared).";

Expand Down Expand Up @@ -63,6 +74,8 @@ const DESCRIPTION = [
'',
'This is how you find and read skills. Skills are addressed by `name` + `scope`, NOT by path — do NOT `ls`/`cat` `.ok/skills/` or pass raw `.ok/...` paths; `.ok/` is opaque internal state.',
'',
"Covers only skills authored as KB content. OpenKnowledge's own built-in `open-knowledge*` skills (e.g. the `open-knowledge` project skill) are runtime skills already loaded in your skill list — they are NOT here, and you never fetch them through this tool.",
'',
'**Three modes:**',
'- **List** (omit `name`): every skill across BOTH levels — Project (this KB) and Global (user-level). Returns name, scope, description, installed/hosts, and (for starter packs) `updateAvailable`.',
"- **Read skill** (pass `name`): that skill's description + body + a `files` list (`{ path, kind }`, no inline text) of its `references/**`+`scripts/**` bundle files. `scope` optional — omitted, it resolves by name (preferring Project when a name exists at both levels).",
Expand Down Expand Up @@ -108,6 +121,10 @@ export function register(server: ServerInstance, deps: SkillsToolDeps): void {
}),
},
async (args: { name?: string; file?: string; scope?: SkillScope; cwd?: string }) => {
if (args.name !== undefined && INTERNAL_BUNDLE_SKILL_NAMES.has(args.name)) {
return textResult(`Error: ${internalSkillHint(args.name)}`, true);
}

const context = await resolveProjectServerContext(
deps.resolveCwd,
deps.config,
Expand Down