mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +00:00
* Add native chat skill and command picker with host-aware discovery Adds a unified, keyboard-first skill and command picker to native chat that: - Uses agent-native invocation syntax (slash for Claude/OpenClaude/Grok, dollar for Codex) - Discovers skills only on the pane's execution host (local, WSL, SSH-unavailable, or runtime) - Groups or separates commands and skills per agent configuration - Deduplicates by canonical path but preserves visibility through all contributing roots - Handles IME composition, loading states, and errors without claiming PTY-level control - Records picker telemetry (open, item accepted, send classification, discovery outcomes) - Extends shared agent profiles to define per-agent skill grammars and source ownership * Remove obsolete reference and design documentation Clean up stale design specs, implementation plans, and investigation notes from docs/reference/. These documents predate the current implementation and are no longer actively maintained or referenced by the codebase. * Extract shared skill discovery utilities and add skill invocation envelo - Move skill comparison and source classification to shared module for native/WSL reuse - Extract display text sanitization to prevent control/zero-width character spoofing - Add native-chat command envelope parser and surfacer for skill invocations - Extend discovery timeout backstop to account for WSL metadata read sequence * Localize skill picker UI for Spanish, Japanese, Korean, Chinese Translate skill picker UI strings including commands, skills, loading states, error messages, and scope labels for the new skill picker feature across four language locales. * Fix skill picker bugs and improve code robustness - Fix i18n plural handling: rename `count` to `sourceCount` to prevent unintended plural-key resolution in localized strings - Fix skill discovery array mutations: copy `root.providers` to prevent bugs during dedup merge - Fix image attachments being silently dropped when message text starts with /skill or agent prefix - Extract `quoteBashString` utility for WSL command code reuse across builders - Add line-separator safety characters (0x2028/0x2029) to skill display filter - Remove stale doc reference links and clarify inline comments * Add reference docs for git compatibility and headless Linux server setup Track previously untracked operational guides in `docs/reference/` that explain Git binary compatibility requirements across host types and how to run `orca serve` on headless Linux. Update AGENTS.md and README.md to link to these references.
64 lines
2.7 KiB
TypeScript
64 lines
2.7 KiB
TypeScript
// Claude-family harnesses record a slash input's user turn as a command
|
|
// envelope (`<command-name>/x</command-name>…`), not as the typed text. The
|
|
// noise filter rightly hides those for catalog commands — Orca shows a local
|
|
// `Ran /x` line instead — but a skill invocation IS the user's chat turn, so
|
|
// dropping its envelope makes the assistant appear to answer an empty
|
|
// conversation. Surface non-catalog envelopes back as plain user text.
|
|
|
|
import { isTextBlock, type NativeChatMessage } from './native-chat-types'
|
|
|
|
const COMMAND_NAME = /<command-name>([\s\S]*?)<\/command-name>/
|
|
const COMMAND_ARGS = /<command-args>([\s\S]*?)<\/command-args>/
|
|
|
|
export type NativeChatCommandEnvelope = { name: string; args: string }
|
|
|
|
/** Parse a user turn recorded as a command envelope. Returns null for any text
|
|
* that does not lead with an envelope tag (ordinary prompts, XML pastes). */
|
|
export function parseNativeChatCommandEnvelope(text: string): NativeChatCommandEnvelope | null {
|
|
const trimmed = text.trimStart()
|
|
if (!trimmed.toLowerCase().startsWith('<command-')) {
|
|
return null
|
|
}
|
|
const name = COMMAND_NAME.exec(trimmed)?.[1]?.trim()
|
|
if (!name) {
|
|
return null
|
|
}
|
|
return { name, args: COMMAND_ARGS.exec(trimmed)?.[1]?.trim() ?? '' }
|
|
}
|
|
|
|
/**
|
|
* Replace skill-invocation envelopes with the token the user sent (`/name
|
|
* args`) so the turn renders as their message and the optimistic echo can
|
|
* reconcile against it. Catalog commands stay untouched — the noise filter
|
|
* hides them and the local `Ran /name` marker is their feedback.
|
|
*/
|
|
export function surfaceSkillInvocationUserTurns(
|
|
messages: readonly NativeChatMessage[],
|
|
catalogCommandNames: ReadonlySet<string>
|
|
): NativeChatMessage[] {
|
|
let changed = false
|
|
const out = messages.map((message) => {
|
|
if (message.role !== 'user' || !message.blocks.every(isTextBlock)) {
|
|
return message
|
|
}
|
|
const envelope = parseNativeChatCommandEnvelope(
|
|
message.blocks.map((block) => block.text).join('\n')
|
|
)
|
|
if (!envelope || catalogCommandNames.has(envelope.name.replace(/^\//, ''))) {
|
|
return message
|
|
}
|
|
// Why: the harness canonicalizes a plugin skill to `/plugin:name`, but the
|
|
// user (and the picker) sent the short frontmatter name. Render the short
|
|
// token so the bubble shows what was typed and the optimistic echo prunes
|
|
// instead of duplicating the turn.
|
|
const shortName = envelope.name.replace(/^\//, '').split(':').at(-1) ?? ''
|
|
const token = `/${shortName}`
|
|
changed = true
|
|
return {
|
|
...message,
|
|
blocks: [{ type: 'text' as const, text: envelope.args ? `${token} ${envelope.args}` : token }]
|
|
}
|
|
})
|
|
return changed ? out : (messages as NativeChatMessage[])
|
|
}
|