mirror of
https://github.com/logancyang/obsidian-copilot.git
synced 2026-07-22 07:50:24 +00:00
* feat(agent-mode): teach backends about @-mention pill syntax
The chat editor serializes @-mention pills to `[[note]]`, `{folder}`, and
`{activeNote}` tokens, but only `[[note]]` was understood — the others
were literal strings to the agent. Add a shared pill-syntax directive
appended to opencode, codex, and claude SDK spawn-time instructions
(mirroring the existing skill-creation directive plumbing), and resolve
`{activeNote}` to the real `[[Note Title]]` at send time since the active
file is a client-side concept the agent can't query.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(agent-mode): prune narration comments from pill-syntax change
Cut multi-line JSDoc and inline rationale that explained WHAT or referenced
callers, per the project's "one short line max, only WHY" rule.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(agent-mode): preserve extension when resolving {activeNote}
Non-markdown active files (.pdf, .canvas) lost their extension when
{activeNote} was rewritten via activeFile.basename, leaving the agent
with an ambiguous [[name]] that didn't match the path the pill envelope
carries. Mirror NotePillNode.getTextContent's extension-aware
serialization so the inline token matches.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
76 lines
4.5 KiB
TypeScript
76 lines
4.5 KiB
TypeScript
/**
|
|
* Copilot Agent Mode system prompts for opencode.
|
|
*
|
|
* Why this exists: opencode picks a provider-default system prompt by
|
|
* substring-matching `model.api.id` (see `opencode/packages/opencode/src/
|
|
* session/system.ts:19-33`). For Copilot Plus models — registered under
|
|
* names like `copilot-plus-flash` — that match falls through every branch
|
|
* to `default.txt`, opencode's terse CLI-coding-agent prompt. The
|
|
* resulting "you are a CLI software engineering tool" framing is wrong
|
|
* for an Obsidian vault assistant and degrades behavior badly on smaller
|
|
* fast models like Gemini 2.5 Flash.
|
|
*
|
|
* The fix: inject our own prompt via `cfg.agent.<id>.prompt` at spawn
|
|
* time so opencode's substring picker is bypassed regardless of model.
|
|
*
|
|
* Content is curated, not invented. Two existing Copilot prompts cover
|
|
* most of what's needed:
|
|
*
|
|
* - `DEFAULT_SYSTEM_PROMPT` (`src/constants.ts`) — the chat-mode
|
|
* identity and formatting rules. Most rules port directly; chat-only
|
|
* hooks (`@vault`, `getCurrentTime`, YouTube auto-transcribe) are
|
|
* dropped because that infrastructure does not exist in opencode.
|
|
* - `AGENT_LOOP_GUIDANCE` (`src/LLMProviders/chainRunner/
|
|
* AutonomousAgentChainRunner.ts`) — the in-process autonomous
|
|
* agent's loop bullets. Ported verbatim — the agent shape is the
|
|
* same.
|
|
*/
|
|
|
|
export const COPILOT_PROMPT_BASE = `You are Obsidian Copilot, an AI assistant that helps users work with their Obsidian vault — markdown notes for knowledge management, writing, and research. You are NOT a software-engineering agent or CLI coding tool. The working directory is the user's Obsidian vault: a collection of markdown notes, not a code repository. Disregard any framing in environment metadata that suggests otherwise.
|
|
|
|
## Grounding
|
|
- The user's vault contains markdown notes. When the user says "note", they mean an Obsidian note in this vault.
|
|
- When the user mentions "tags", they usually mean tags in Obsidian note properties.
|
|
- Never claim you do not have access to something. Rely on the user's provided context and the tools available to you.
|
|
- If you are unsure, say so and ask for more context — don't guess.
|
|
- Always respond in the language of the user's query.
|
|
|
|
## Tool Behavior
|
|
- Prefer evidence from \`read\`, \`grep\`, and \`glob\` over assumption. Don't infer what a note contains from its title — read it.
|
|
- NEVER search for the same or very similar query twice. If results were insufficient, try substantially different terms.
|
|
- After 1-2 searches, synthesize an answer from the results you have. Do not keep searching unless the results are clearly insufficient.
|
|
- If you have enough information to answer, respond directly without calling any more tools.
|
|
|
|
## Response Style
|
|
- Respond at length appropriate to note-taking and knowledge work. Do NOT default to 1-3 line CLI cadence — give the user enough context to understand and act on your answer.
|
|
- Be direct and concrete. Don't pad with preamble or postamble.
|
|
|
|
## Markdown Formatting
|
|
- Use \`$...$\` for LaTeX equations, never \`\\[...\\]\` or \`\\(...\\)\`.
|
|
- For markdown lists, always use \`- \` (hyphen followed by exactly one space) for bullet points. Never use \`*\` for bullets.
|
|
- For tables, use GitHub-flavored markdown.
|
|
- For Obsidian-internal image links, use \`![[link]]\` format. For web image links, use \`\` format.`;
|
|
|
|
/**
|
|
* Pick the Copilot system prompt for a given model.
|
|
*
|
|
* Today returns `COPILOT_PROMPT_BASE` for every model. The function exists
|
|
* so per-provider variants — for prompt-style differences across model
|
|
* families (Anthropic vs Gemini vs GPT, larger vs smaller) — can be added
|
|
* by branching on `modelApiId`, mirroring opencode's own substring picker
|
|
* at `session/system.ts:19-33` but with our own content.
|
|
*
|
|
* Limitation worth knowing for future per-provider work: opencode reads
|
|
* `cfg.agent.<id>.prompt` at spawn time, so the chosen prompt is fixed
|
|
* for the session lifetime. Per-model prompts that follow mid-session
|
|
* model switches would require either spawn-time recycling or an
|
|
* opencode `experimental.chat.system.transform` plugin. For v1 the
|
|
* `modelApiId` argument carries the user's sticky default at spawn.
|
|
*/
|
|
export function selectCopilotPrompt(modelApiId: string | undefined): string {
|
|
// Future per-provider branching goes here, e.g.:
|
|
// if (modelApiId?.includes("gemini")) return COPILOT_PROMPT_GEMINI;
|
|
// if (modelApiId?.includes("claude")) return COPILOT_PROMPT_ANTHROPIC;
|
|
void modelApiId;
|
|
return COPILOT_PROMPT_BASE;
|
|
}
|