From e8e3669991825ce07682ac078dbb8e8b3cf45c50 Mon Sep 17 00:00:00 2001 From: Tioit-Wang Date: Sat, 12 Sep 2026 01:37:04 +0800 Subject: [PATCH 1/3] feat(agent-runtime): ship the Skill tool with the Agent core set `Skill` was registered into the deferred catalog, so the first provider request of an Agent turn had no `Skill` in its tool list. Two decisions ask the model to call it immediately: the `# Skills` section tells it to load a matching skill before answering, and the composer answers a typed `/skill-id` with an instruction to call `Skill { id }` (D393, ADR 0219). Neither works when the tool is missing from the schema. Add `SKILL_TOOL_NAME` to `AGENT_CORE_TOOL_NAMES` so the schema ships with the first request, and drop the now unreachable catalog description. The registration gate is unchanged: the tool exists only while the skill catalog is non-empty, Plan and Goal still omit it, and every other on-demand capability plus `ToolSearch` keep their lazy behavior. Closes #204. Co-authored-by: CommandCodeBot --- ...kill-ships-with-the-agent-core-tool-set.md | 63 +++++++++++++++++++ docs/spec/03-runtime/02-agent-runtime.md | 5 +- .../03-runtime/03-tools-and-permissions.md | 8 ++- .../zh-CN/spec/03-runtime/02-agent-runtime.md | 4 +- .../03-runtime/03-tools-and-permissions.md | 5 +- packages/agent-runtime/src/runtime.test.ts | 20 +++--- packages/agent-runtime/src/runtime.ts | 7 ++- 7 files changed, 90 insertions(+), 22 deletions(-) create mode 100644 docs/adr/0230-skill-ships-with-the-agent-core-tool-set.md diff --git a/docs/adr/0230-skill-ships-with-the-agent-core-tool-set.md b/docs/adr/0230-skill-ships-with-the-agent-core-tool-set.md new file mode 100644 index 000000000..7ac960c96 --- /dev/null +++ b/docs/adr/0230-skill-ships-with-the-agent-core-tool-set.md @@ -0,0 +1,63 @@ +# ADR 0230: Skill Ships with the Agent Core Tool Set + +- Status: Accepted +- Date: 2026-09-11 +- Deciders: PI-Desktop runtime maintainers +- Amends: D174, ADR 0048, ADR 0219 + +## Context + +ADR 0048 keeps the first Agent request on a small core set and defers every +other capability behind the local `ToolSearch` tool, so a large plugin surface +cannot recreate the original prompt bloat. `Skill` was registered into that +deferred set: it only appears in the provider schema after the model searches +for it. + +Two later decisions assume a `Skill` tool the model can call immediately: + +- D174 makes the skill catalog the model-invoked way to load a document, and + advertises it in the `# Skills` system-prompt section with an instruction to + load a matching skill first. +- ADR 0219 answers a user-typed `/skill-id` by persisting an instruction to + call the local `Skill` tool with the validated id on that turn. + +Neither can be satisfied when the tool is absent from the first request's tool +list. A model that does not search for it either re-searches, calls a tool it +cannot see, or answers from the catalog line alone; a user who explicitly asked +for a skill pays one or two extra round trips before the body is ever loaded. + +## Decision + +1. `Skill` joins `AGENT_CORE_TOOL_NAMES`, so an Agent-mode request carries its + schema from the first turn. Its registration gate is unchanged: the tool + exists only when the skill catalog is non-empty, and Plan and Goal continue + to omit it entirely. +2. The tool stays out of the deferred catalog and therefore never appears under + `# On-demand tools`. Other on-demand capabilities (`BrowserPreview`, plugin + tools, plugin-development helpers, and MCP tools) keep their lazy behavior, + and `ToolSearch` is still registered whenever any of them exist. +3. No protocol, storage, permission, or skill-body change follows from this. + Bodies still load on demand through the same local tool and its existing + host path and permission checks. + +## Consequences + +- A matching task loads its skill on the first turn instead of discovering the + tool first, and a `/skill-id` invocation works as ADR 0219 describes. +- Every Agent-mode request carries one more tool schema. The catalog is already + bounded per session, and the delegation lifecycle was admitted to the core set + for the same reason: a capability the model has to go looking for is one it + will not use. +- Removing the `Skill` row from the deferred catalog also removes the only path + that could route a skill through `ToolSearch`, so the catalog description for + it is deleted rather than left unreachable. + +## Alternatives considered + +- **Keep `Skill` deferred and rely on prompting:** rejected because a tool + absent from the schema cannot be called no matter what the system prompt says, + which is the behavior that produced this issue. +- **Inject skill bodies into the system prompt:** rejected for the cost and + reload semantics D174 and ADR 0039 already settled. +- **Make every on-demand tool core:** rejected because it recreates the prompt + bloat ADR 0048 exists to prevent. Only `Skill` is admitted here. diff --git a/docs/spec/03-runtime/02-agent-runtime.md b/docs/spec/03-runtime/02-agent-runtime.md index aa66f6682..be59e29bd 100644 --- a/docs/spec/03-runtime/02-agent-runtime.md +++ b/docs/spec/03-runtime/02-agent-runtime.md @@ -890,6 +890,9 @@ the mode's core set plus any deferred tools that can be restored from successful activation evidence still present in the effective session context: - Agent: `Read`, `Bash`, `Edit`, and `Write` (matching pi's coding-agent core) +- Agent: `Skill` whenever the skill catalog is non-empty (D404, ADR 0230) — the + `# Skills` section and a user-typed `/skill-id` both ask the model to call + it, and a tool that is missing from the schema cannot be called at all - Agent: `Task`, `TaskWait`, `TaskList`, and `TaskStop` as well, whenever the subagent catalog is non-empty (§5f) — a capability the model has to go looking for is one it will not use, and the delegation lifecycle is worth @@ -897,7 +900,7 @@ activation evidence still present in the effective session context: - Plan: `Read`, `Glob`, `Grep`, `BrowserPreview`, and `Bash` - both modes: `ToolSearch` when at least one deferred capability exists -In Agent mode, `Glob` and `Grep` join `BrowserPreview`, plugin tools, `Skill`, +In Agent mode, `Glob` and `Grep` join `BrowserPreview`, plugin tools, and plugin-development helpers in the deferred set. Both contract modes keep their read/inspection core available, while the kind's submit tool (`SubmitPlan` or `SubmitGoal`) is exposed only during the planning state, and diff --git a/docs/spec/03-runtime/03-tools-and-permissions.md b/docs/spec/03-runtime/03-tools-and-permissions.md index dca7d407b..16e331d7f 100644 --- a/docs/spec/03-runtime/03-tools-and-permissions.md +++ b/docs/spec/03-runtime/03-tools-and-permissions.md @@ -47,14 +47,16 @@ Let the agent get things done, but stay under control by default. Following pi's coding-agent default, the first Agent request activates only `Read`, `Bash`, `Edit`, and `Write`; `Glob` and `Grep` are loaded on demand. -Plan and Goal keep their read/inspection core. The runtime also registers capabilities -without sending their full schemas up front: +Plan and Goal keep their read/inspection core. `Skill` is deliberately not +deferred: a `/skill-id` invocation instructs the model to call it, and a tool +absent from the schema cannot be called at all, so it ships with the first +request whenever the skill catalog is non-empty (D404, ADR 0230). The runtime +also registers capabilities without sending their full schemas up front: - `Glob` and `Grep` in Agent mode - `BrowserPreview` - `PluginCheck`, `PluginScaffold`, and `PluginPack` - plugin-declared agent tools -- `Skill` when an enabled plugin contributes skills These tools appear in a bounded `# On-demand tools` catalog with compact descriptions. The model calls the local `ToolSearch` tool with an exact name or diff --git a/docs/zh-CN/spec/03-runtime/02-agent-runtime.md b/docs/zh-CN/spec/03-runtime/02-agent-runtime.md index f1a3cac6c..461ee8988 100644 --- a/docs/zh-CN/spec/03-runtime/02-agent-runtime.md +++ b/docs/zh-CN/spec/03-runtime/02-agent-runtime.md @@ -731,13 +731,15 @@ sidecar 构建了一个完整的工具注册表,但它不会序列化每个工 该模式的核心集: - Agent:`Read`、`Bash`、`Edit` 和 `Write`(匹配 pi 的编码代理核心) +- Agent:只要技能目录非空,`Skill` 也在核心集中(D404、ADR 0230)——`# Skills` + 段落与用户输入的 `/skill-id` 都要求模型调用它,而模式中缺失的工具根本无法被调用 - Agent:当子代理目录非空时,`Task`、`TaskWait`、`TaskList` 和 `TaskStop` 也是如此 (§5f) — 模型必须寻找的能力是它不会使用的能力, 委托生命周期值得每个请求的额外模式 - Plan:`Read`、`Glob`、`Grep`、`BrowserPreview` 和 `Bash` - 两种模式:`ToolSearch`(当至少存在一种延迟功能时) -在Agent模式下,`Glob`和`Grep`加入`BrowserPreview`、插件工具、`Skill`, +在Agent模式下,`Glob`和`Grep`加入`BrowserPreview`、插件工具, 以及延迟集中的插件开发助手。两种合约模式均保留 他们的 read/inspection 核心可用,而该类的提交工具 (`SubmitPlan` 或 `SubmitGoal`)仅在规划状态期间公开,并且 diff --git a/docs/zh-CN/spec/03-runtime/03-tools-and-permissions.md b/docs/zh-CN/spec/03-runtime/03-tools-and-permissions.md index a21cb31f8..c86a37ea1 100644 --- a/docs/zh-CN/spec/03-runtime/03-tools-and-permissions.md +++ b/docs/zh-CN/spec/03-runtime/03-tools-and-permissions.md @@ -50,14 +50,15 @@ 按照 pi 的编码代理默认值,第一个 Agent 请求仅激活 `Read`、`Bash`、`Edit` 和 `Write`; `Glob` 和 `Grep` 按需加载。 -Plan 和 Goal 保留其 read/inspection 核心。运行时还注册功能 +Plan 和 Goal 保留其 read/inspection 核心。`Skill` 有意不作延迟:`/skill-id` +调用会指示模型调用它,而模式中不存在的工具根本无法被调用,因此只要技能目录非空, +它就会随第一个请求一起发送(D404、ADR 0230)。运行时还注册功能 无需预先发送其完整模式: - Agent 模式下的 `Glob` 和 `Grep` - `BrowserPreview` - `PluginCheck`、`PluginScaffold` 和 `PluginPack` - 插件声明的代理工具 -- `Skill` 当启用的插件贡献技能时 这些工具出现在有界的 `# On-demand tools` 目录中,具有紧凑的结构 描述。该模型使用确切的名称调用本地 `ToolSearch` 工具或 diff --git a/packages/agent-runtime/src/runtime.test.ts b/packages/agent-runtime/src/runtime.test.ts index d03255d8e..5a5e14aee 100644 --- a/packages/agent-runtime/src/runtime.test.ts +++ b/packages/agent-runtime/src/runtime.test.ts @@ -1516,6 +1516,7 @@ describe("DesktopAgentRuntime deferred tool catalog", () => { "Edit", "Write", "asktool", + "Skill", "EnterPlanMode", "EnterGoalMode", "new_context", @@ -1526,12 +1527,14 @@ describe("DesktopAgentRuntime deferred tool catalog", () => { expect(names).not.toContain("BrowserPreview"); expect(names).not.toContain("PluginCheck"); expect(names).not.toContain("plugin_demo_validate"); - expect(names).not.toContain("Skill"); const prompt = (runtime as any).agent.state.systemPrompt as string; expect(prompt).toContain("# On-demand tools"); expect(prompt).toContain("BrowserPreview"); expect(prompt).toContain("plugin_demo_validate"); + // The skill catalog ships with the tool, so `Skill` is never a catalog row. + expect(prompt).toContain("# Skills"); + expect(prompt).not.toMatch(/^- Skill:/m); await runtime.dispose(); }); @@ -5128,7 +5131,7 @@ describe("DesktopAgentRuntime plugin skills (D174)", () => { }, ]; - it("advertises the catalog and loads the Skill tool on demand", async () => { + it("advertises the catalog and ships the Skill tool with the first request (D404)", async () => { const runtime = createRuntime({ pluginSkills, projectInstructions: { @@ -5140,14 +5143,10 @@ describe("DesktopAgentRuntime plugin skills (D174)", () => { expect(prompt).toContain("# Skills"); expect(prompt).toContain("`demo.hello/release-notes`"); + expect(prompt).toContain("Run unit tests."); // Only the catalog line travels up front; the body loads on demand. expect(prompt).not.toContain("Skill: Release notes"); - expect(agent.state.tools.some((tool: any) => tool.name === "Skill")).toBe(false); - const search = agent.state.tools.find( - (tool: any) => tool.name === "ToolSearch", - ); - await search.execute("search-1", { query: "Skill" }); - await (runtime as any).rebuiltAgentContext(); + // The catalog is useless behind a search: the tool is callable on turn one. expect(agent.state.tools.some((tool: any) => tool.name === "Skill")).toBe(true); // The user's own instructions come last, so they keep the final word. expect(prompt.indexOf("# Skills")).toBeLessThan( @@ -5210,11 +5209,6 @@ describe("DesktopAgentRuntime plugin skills (D174)", () => { call: vi.fn().mockResolvedValue({ ok: true, content: "# Skill: Release notes" }), }; const runtime = createRuntime({ pluginSkills, host }); - const search = (runtime as any).agent.state.tools.find( - (entry: any) => entry.name === "ToolSearch", - ); - await search.execute("search-1", { query: "Skill" }); - await (runtime as any).rebuiltAgentContext(); const tool = (runtime as any).agent.state.tools.find( (entry: any) => entry.name === "Skill", ); diff --git a/packages/agent-runtime/src/runtime.ts b/packages/agent-runtime/src/runtime.ts index e9eeccc0b..726281c81 100644 --- a/packages/agent-runtime/src/runtime.ts +++ b/packages/agent-runtime/src/runtime.ts @@ -486,6 +486,11 @@ const AGENT_CORE_TOOL_NAMES = new Set([ "Edit", "Bash", ASK_TOOL_NAME, + // The slash menu answers a user-invoked `/skill-id` with an instruction to + // call `Skill { id }` on the first turn (ADR 0219), and a capability the + // model has to go looking for is one it will not use. Registration keeps its + // own gate: the tool only exists when the catalog is non-empty. + SKILL_TOOL_NAME, ]); const MAX_ON_DEMAND_TOOL_PROMPT_ENTRIES = 64; const MAX_TOOL_SEARCH_RESULT_NAMES = 24; @@ -3023,8 +3028,6 @@ Delegation rules: return "Create a PI-Desktop plugin from a template."; case "PluginPack": return "Validate and package a PI-Desktop plugin."; - case SKILL_TOOL_NAME: - return "Load the full instructions for a listed skill."; default: return this.compactToolDescription(tool.description); } From 445ae07c7082faca063e48f9f2bef3bd392f707b Mon Sep 17 00:00:00 2001 From: Tioit-Wang Date: Sat, 12 Sep 2026 01:37:13 +0800 Subject: [PATCH 2/3] feat(composer): open the slash menu from an ideographic comma MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The composer only opens the command menu on a leading ASCII `/`. A Chinese IME produces `、` (U+3001) for that keystroke, so reaching `/new`, `/compact`, a mode alias, or a Skill meant switching input methods mid-sentence and switching back. Rewrite a `、` committed as the first character of an empty draft to `/` before trigger detection runs, and leave the rest of the grammar alone: a mark anywhere later in the draft stays ordinary punctuation, the `@` file menu never reacts to it, and detection, insertion, filtering, and sending are the existing slash paths. Closes #65. Co-authored-by: CommandCodeBot --- apps/desktop/src/components/Composer.tsx | 21 +++++++- apps/desktop/test/composer-ime.test.mjs | 22 ++++++++ ...-ideographic-comma-opens-the-slash-menu.md | 52 +++++++++++++++++++ docs/spec/04-ux/04-builtin-commands.md | 12 +++++ docs/zh-CN/spec/04-ux/04-builtin-commands.md | 9 ++++ packages/shared/src/composer-trigger.test.ts | 23 ++++++++ packages/shared/src/composer-trigger.ts | 16 ++++++ 7 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 docs/adr/0231-ideographic-comma-opens-the-slash-menu.md diff --git a/apps/desktop/src/components/Composer.tsx b/apps/desktop/src/components/Composer.tsx index 76ab49f00..293ad86e4 100644 --- a/apps/desktop/src/components/Composer.tsx +++ b/apps/desktop/src/components/Composer.tsx @@ -25,6 +25,7 @@ import { normalizeLargePasteThreshold, PERMISSION_MODES, restoreInlineComposerFileReferenceTokens, + rewriteIdeographicCommaTrigger, serializeComposerFileReferences, serializeInlineComposerFileReferences, stripInlineComposerFileReferenceTokens, @@ -2337,10 +2338,26 @@ export function Composer({ }} onInput={(e) => { const el = e.currentTarget; - const nextValue = readEditorValue(el); + const source = readEditorValue(el); const { start } = editorSelectionRange(el); + // A Chinese IME commits "、" where an ASCII "/" is meant, and + // a leading "/" is what makes the slash menu reachable + // without switching input methods (D405). Only the first + // character of a draft that was empty is rewritten, so the + // mark stays ordinary punctuation anywhere else. + const nextValue = + valueRef.current === "" + ? rewriteIdeographicCommaTrigger(source) + : source; invalidatePromptEnhancement(); - editorValueRef.current = nextValue; + if (nextValue === source) { + editorValueRef.current = nextValue; + } else { + // Leave editorValueRef stale so the layout effect repaints + // the editable with the substituted "/" and puts the caret + // back after it. + pendingEditorCaretRef.current = start; + } valueRef.current = nextValue; setValue(nextValue); // `filter` allocates even when it drops nothing, and a new diff --git a/apps/desktop/test/composer-ime.test.mjs b/apps/desktop/test/composer-ime.test.mjs index 3a5a4e09d..c65b9af56 100644 --- a/apps/desktop/test/composer-ime.test.mjs +++ b/apps/desktop/test/composer-ime.test.mjs @@ -56,3 +56,25 @@ test("model menu keydown ignores IME composition keystrokes", () => { "menu navigation must bail out while an IME composition is active", ); }); + +test("an ideographic comma opens the slash menu from an empty draft (D405)", () => { + const handler = composerSource.slice( + composerSource.indexOf("onInput={(e) => {"), + composerSource.indexOf("onCompositionStart={() => setComposing(true)}"), + ); + assert.match( + handler, + /rewriteIdeographicCommaTrigger\(/, + "the editable must route a committed 、 through the shared rewrite", + ); + assert.match( + handler, + /valueRef\.current === ""/, + "only a draft with nothing in it may be rewritten", + ); + assert.match( + handler, + /pendingEditorCaretRef\.current = start/, + "the caret must land after the substituted slash", + ); +}); diff --git a/docs/adr/0231-ideographic-comma-opens-the-slash-menu.md b/docs/adr/0231-ideographic-comma-opens-the-slash-menu.md new file mode 100644 index 000000000..198a4f76e --- /dev/null +++ b/docs/adr/0231-ideographic-comma-opens-the-slash-menu.md @@ -0,0 +1,52 @@ +# ADR 0231: Ideographic Comma Opens the Composer Slash Menu + +- Status: Accepted +- Date: 2026-09-11 +- Deciders: PI-Desktop desktop UI maintainers +- Amends: D123, D139, ADR 0024 + +## Context + +The composer `/` menu mirrors the pi CLI grammar: the trigger is the ASCII `/` +as the very first character of the draft. A Chinese IME produces `、` (U+3001, +ideographic comma) for that keystroke, so a user writing in Chinese has to +switch to ASCII input before they can reach the command menu, then switch back. +The menu already ignores in-flight IME composition (D139), so the missing piece +is only the alias itself. + +Every other trigger character in the grammar is deliberate: `@` opens the file +menu, and a `/` anywhere but the first character is ordinary prose. The alias +must not turn legitimate punctuation into commands. + +## Decision + +1. A `、` committed as the first character of an empty composer draft is + rewritten to `/` before trigger detection runs. Afterwards the draft is an + ordinary slash invocation: the same menu opens, the same filtering applies, + and the same send path handles it. +2. Only that position is rewritten. The composer requires the draft to have been + empty before the keystroke, so a `、` that appears later — including at the + start of a draft that already holds text — stays ordinary punctuation. +3. The rewrite is a pure string function in the shared composer-trigger module, + unit tested next to the trigger grammar it feeds. The `@` file menu is + unaffected. + +## Consequences + +- A Chinese IME user reaches `/new`, `/compact`, the mode aliases, template + commands, plugin commands, and Skills without leaving the input method. +- A message that genuinely starts with `、` in an empty composer is rewritten. + The menu opens with `/` and the user can keep typing prose, which keeps the + substitution visible instead of silently altering text mid-sentence. +- No IPC, storage, or autocomplete-source change is required. + +## Alternatives considered + +- **Accept `、` as an additional trigger character in `detectTrigger`:** rejected + because the draft would keep a character the send path and transcript chip + would then have to understand, and the menu would open on a mark the pi CLI + grammar does not define. +- **Rewrite on every `、`, not just the first character:** rejected because it + would corrupt ordinary prose, where `、` is the standard list separator. +- **A toolbar button for commands:** rejected as a duplicate entry point to a + menu that already exists (see D123). diff --git a/docs/spec/04-ux/04-builtin-commands.md b/docs/spec/04-ux/04-builtin-commands.md index 57c479f22..cb49a86ab 100644 --- a/docs/spec/04-ux/04-builtin-commands.md +++ b/docs/spec/04-ux/04-builtin-commands.md @@ -112,3 +112,15 @@ model to call the existing `Skill` tool with the validated id before answering. Only Skills active for the current project are listed or accepted, so project scope and plugin activation remain enforced at send time. If the Skill is no longer active, the text follows the normal unknown-slash prompt path. + +## 9. Ideographic comma opens the slash menu (D405) + +A Chinese IME produces `、` (U+3001) where the ASCII `/` is meant, so reaching +the menu otherwise means switching input methods mid-sentence. When the composer +is empty, a committed `、` as its first character is rewritten to `/` before +trigger detection runs, and the ordinary slash menu opens with the same +insertion, filtering, and send behavior described above. + +Only the first character of an empty draft is rewritten. A `、` anywhere else in +the draft is ordinary punctuation and is never touched, and the alias has no +effect on the `@` file menu. diff --git a/docs/zh-CN/spec/04-ux/04-builtin-commands.md b/docs/zh-CN/spec/04-ux/04-builtin-commands.md index de11b9b6d..0726076f1 100644 --- a/docs/zh-CN/spec/04-ux/04-builtin-commands.md +++ b/docs/zh-CN/spec/04-ux/04-builtin-commands.md @@ -93,3 +93,12 @@ Skill ID 作为斜杠名称,显示 Skill 名称和描述,并单独归入 ** 会以可见的消息芯片保留,同时要求模型先用经过校验的 ID 调用现有的 `Skill` 工具,再 回答请求。只有当前项目已激活的 Skill 会被列出或接受,因此项目范围和插件激活状态 仍会在发送时强制校验。如果 Skill 已不再激活,文本会按普通未知斜杠提示处理。 + +## 9. 顿号打开斜杠菜单(D405) + +中文输入法会用顿号「、」(U+3001)代替 ASCII 的 `/`,否则用户必须在书写中途切换 +输入法才能唤出菜单。当输入框为空时,第 1 个字符提交的「、」会在触发检测之前被改写 +为 `/`,随后按上文相同的插入、过滤与发送行为打开普通斜杠菜单。 + +只有空草稿的第 1 个字符会被改写。出现在草稿其他位置的「、」属于普通标点,始终不会 +被改动;该别名对 `@` 文件菜单没有影响。 diff --git a/packages/shared/src/composer-trigger.test.ts b/packages/shared/src/composer-trigger.test.ts index 7478bdbd1..bed6d9a01 100644 --- a/packages/shared/src/composer-trigger.test.ts +++ b/packages/shared/src/composer-trigger.test.ts @@ -7,6 +7,7 @@ import { formatFileInsert, normalizeLargePasteThreshold, restoreInlineComposerFileReferenceTokens, + rewriteIdeographicCommaTrigger, serializeComposerFileReferences, serializeInlineComposerFileReferences, stripInlineComposerFileReferenceTokens, @@ -49,6 +50,28 @@ describe("detectTrigger — slash mode", () => { }); }); +describe("rewriteIdeographicCommaTrigger", () => { + it("turns a leading ideographic comma into the slash trigger", () => { + expect(rewriteIdeographicCommaTrigger("、")).toBe("/"); + expect(rewriteIdeographicCommaTrigger("、rev")).toBe("/rev"); + }); + + it("leaves the mark alone anywhere else in the draft", () => { + expect(rewriteIdeographicCommaTrigger("你好、世界")).toBe("你好、世界"); + expect(rewriteIdeographicCommaTrigger("/cmd 、")).toBe("/cmd 、"); + expect(rewriteIdeographicCommaTrigger("")).toBe(""); + }); + + it("opens the menu through the ordinary detector once rewritten", () => { + const draft = rewriteIdeographicCommaTrigger("、rev"); + expect(detectTrigger(draft, draft.length)).toMatchObject({ + mode: "slash", + query: "rev", + tokenStart: 0, + }); + }); +}); + describe("detectTrigger — file mode", () => { it("triggers on a bare @ at start", () => { expect(detectTrigger("@", 1)).toEqual({ diff --git a/packages/shared/src/composer-trigger.ts b/packages/shared/src/composer-trigger.ts index 6550abb7f..913774e5c 100644 --- a/packages/shared/src/composer-trigger.ts +++ b/packages/shared/src/composer-trigger.ts @@ -45,6 +45,22 @@ const WHITESPACE = new Set([" ", "\t", "\n", "\r"]); /** Characters that end the token scan-back, per pi's autocomplete. */ const DELIMITERS = new Set([" ", "\t", "\n", "\r", '"', "'", "="]); +/** U+3001 IDEOGRAPHIC COMMA — the mark a Chinese IME gives for "/" (D405). */ +export const IDEOGRAPHIC_COMMA = "、"; + +/** + * A Chinese IME types "、" where an ASCII "/" is meant, and switching input + * methods to reach the slash menu breaks the flow of writing (issue #65). The + * first character of an otherwise empty draft is rewritten to "/" so the + * ordinary command menu opens; a mark anywhere later in the draft is text and + * is left untouched. + */ +export function rewriteIdeographicCommaTrigger(value: string): string { + return value.startsWith(IDEOGRAPHIC_COMMA) + ? `/${value.slice(1)}` + : value; +} + function isBoundary(value: string, index: number): boolean { if (index <= 0) return true; return DELIMITERS.has(value[index - 1]); From 891fbd23160c254896b4d58b44205af40ee9b9e6 Mon Sep 17 00:00:00 2001 From: Tioit-Wang Date: Sat, 12 Sep 2026 01:37:23 +0800 Subject: [PATCH 3/3] docs(spec): record Skill residency and the IME slash alias Register D404 / ADR 0230 and D405 / ADR 0231 in the ADR index and the decisions log, and add the two E2E scenarios with their traceability rows: E2E-254 covers `Skill` shipping with the first Agent request, the on-demand catalog, Plan mode, and an empty catalog, and E2E-255 covers the ideographic comma alias. Both locales are updated together so the spec pairs keep their structure. Co-authored-by: CommandCodeBot --- docs/adr/README.md | 2 + docs/spec/06-delivery/04-e2e-test-plan.md | 60 +++++++++++++++++++ docs/spec/08-meta/decisions-log.md | 23 +++++++ .../spec/06-delivery/04-e2e-test-plan.md | 35 +++++++++++ docs/zh-CN/spec/08-meta/decisions-log.md | 14 +++++ 5 files changed, 134 insertions(+) diff --git a/docs/adr/README.md b/docs/adr/README.md index 0f5f3bfbf..0c83bf4e1 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -247,3 +247,5 @@ Each ADR includes: | 0227 | Project group manual ordering | Accepted (amended by 0228) | | 0228 | Long-press the project title to reorder | Accepted (amended by 0229) | | 0229 | Press-and-move project title reorder | Accepted (amends 0228) | +| 0230 | Skill ships with the Agent core tool set | Accepted (amends D174 / ADR 0048 / ADR 0219; issue #204) | +| 0231 | Ideographic comma opens the composer slash menu | Accepted (amends D123 / D139 / ADR 0024; issue #65) | diff --git a/docs/spec/06-delivery/04-e2e-test-plan.md b/docs/spec/06-delivery/04-e2e-test-plan.md index 988c1896e..c1ecffecd 100644 --- a/docs/spec/06-delivery/04-e2e-test-plan.md +++ b/docs/spec/06-delivery/04-e2e-test-plan.md @@ -6318,6 +6318,9 @@ Each scenario is documented in this format: | Security | E2E-028, E2E-029, E2E-030, E2E-024J, E2E-024K, E2E-024M, E2E-049, E2E-068, E2E-086, E2E-102c, E2E-102d, E2E-102e, E2E-105, E2E-106, E2E-107, E2E-108, E2E-109, E2E-110, E2E-112, E2E-113, E2E-115, E2E-116, E2E-117, E2E-119, E2E-121, E2E-122, E2E-123, E2E-142, E2E-148, E2E-151, E2E-153, E2E-158, E2E-187, E2E-196c, E2E-196b, E2E-196 | | Quality | E2E-032, E2E-033, E2E-039, E2E-043, E2E-044, E2E-045, E2E-046, E2E-047, E2E-048, E2E-048A, E2E-049, E2E-050, E2E-053, E2E-055, E2E-056, E2E-057, E2E-058, E2E-059, E2E-060, E2E-061, E2E-062, E2E-063, E2E-064, E2E-065, E2E-066, E2E-067, E2E-068, E2E-069, E2E-070, E2E-071, E2E-072, E2E-073, E2E-074, E2E-075, E2E-076, E2E-077, E2E-078, E2E-079, E2E-080, E2E-081, E2E-082, E2E-083, E2E-084, E2E-085, E2E-086, E2E-092, E2E-093, E2E-094, E2E-095, E2E-096, E2E-097, E2E-098, E2E-099, E2E-100, E2E-101, E2E-102, E2E-102a, E2E-102b, E2E-102c, E2E-102d, E2E-102e, E2E-103, E2E-AGENTS-001, E2E-021a, E2E-024N, E2E-059a, E2E-060b, E2E-060c, E2E-061a, E2E-073a, E2E-111, E2E-114, E2E-117, E2E-118, E2E-119, E2E-120, E2E-122, E2E-123, E2E-142, E2E-143, E2E-144, E2E-145, E2E-146, E2E-147, E2E-148, E2E-150, E2E-151, E2E-153, E2E-155, E2E-158, E2E-159, E2E-160, E2E-161, E2E-162, E2E-163, E2E-168, E2E-172, E2E-173, E2E-174, E2E-011g, E2E-176, E2E-177, E2E-178, E2E-179, E2E-180, E2E-181, E2E-182, E2E-183, E2E-186, E2E-187, E2E-194, E2E-195, E2E-196a, E2E-196b, E2E-196c, E2E-198, E2E-199, E2E-200, E2E-196, E2E-201, E2E-204, E2E-202, E2E-203, E2E-205, E2E-206, E2E-207, E2E-208, E2E-209, E2E-210, E2E-218, E2E-219, E2E-250, E2E-252, E2E-102i | | Quality (project ordering) | E2E-253 | +| C — Conversation & stream (IME slash alias) | E2E-255 | +| E — Tools & permissions (Skill residency) | E2E-254 | +| Quality (Skill residency and IME slash alias) | E2E-254, E2E-255 | | Milestone | Scenarios | |---|---| @@ -6327,6 +6330,8 @@ Each scenario is documented in this format: | M4 | E2E-022, E2E-023, E2E-024, E2E-025, E2E-026, E2E-030, E2E-038 | | M5 | E2E-008a, E2E-032, E2E-033, E2E-034, E2E-039, E2E-043, E2E-044, E2E-045, E2E-046, E2E-047, E2E-048, E2E-048A, E2E-049, E2E-050, E2E-051, E2E-052, E2E-053, E2E-054, E2E-055, E2E-056, E2E-057, E2E-058, E2E-059, E2E-060, E2E-061, E2E-062, E2E-063, E2E-064, E2E-065, E2E-066, E2E-067, E2E-068, E2E-069, E2E-070, E2E-071, E2E-072, E2E-073, E2E-074, E2E-075, E2E-076, E2E-077, E2E-078, E2E-079, E2E-080, E2E-081, E2E-082, E2E-083, E2E-084, E2E-085, E2E-086, E2E-092, E2E-093, E2E-096, E2E-097, E2E-098, E2E-099, E2E-100, E2E-101, E2E-102, E2E-102a, E2E-102b, E2E-102c, E2E-102d, E2E-102e, E2E-AGENTS-001, E2E-059a, E2E-060b, E2E-060c, E2E-061a, E2E-073a, E2E-094, E2E-095, E2E-143, E2E-145, E2E-146, E2E-146a, E2E-147, E2E-177, E2E-178, E2E-180, E2E-181, E2E-182, E2E-183, E2E-186, E2E-187, E2E-194, E2E-195, E2E-204, E2E-208, E2E-250, E2E-252, E2E-102i | | M5 (project ordering) | E2E-253 | +| M2 (IME slash alias) | E2E-255 | +| M5 (Skill residency) | E2E-254 | | M6 | E2E-104, E2E-105, E2E-106, E2E-107, E2E-108, E2E-109, E2E-110, E2E-111, E2E-112, E2E-113, E2E-114, E2E-115, E2E-116, E2E-117, E2E-118, E2E-119, E2E-120, E2E-103, E2E-172 | | M6+ | E2E-121, E2E-122, E2E-148, E2E-150, E2E-151, E2E-154, E2E-155, E2E-158, E2E-159, E2E-160, E2E-161, E2E-162, E2E-163, E2E-166, E2E-168, E2E-173, E2E-174, E2E-176, E2E-179, E2E-196a, E2E-196b, E2E-196c, E2E-198, E2E-199, E2E-200, E2E-202, E2E-203, E2E-205, E2E-209, E2E-210, E2E-212, E2E-213, E2E-214, E2E-215, E2E-216, E2E-217, E2E-218, E2E-219 | | Post-MVP | E2E-022A, E2E-022B, E2E-022C, E2E-024I, E2E-024J, E2E-024K, E2E-024L, E2E-024M (plugin roadmap R2/R3/R6) | @@ -10105,3 +10110,58 @@ sample extensions under `apps/desktop/test/fixtures/pi-extensions/`. - **Status**: Source-contract covered (`app-store-sidebar.test.mjs`, `sidebar-preferences.test.mjs`, `sidebar-project-reorder.test.mjs`); rendered desktop journey Draft + +#### E2E-254: A skill loads on the first Agent turn + +- **Preconditions**: At least one Skill is active for the current project, a + provider is configured, and the session runs in Agent mode with another + on-demand capability present (for example `BrowserPreview` or a plugin tool). +- **Steps**: + 1. Open a new Agent conversation and send a prompt that matches the active + Skill's description. + 2. Inspect the first provider request and its tool list. + 3. Confirm the model calls `Skill` with the exact id without calling + `ToolSearch` first, and that the returned document is the skill body. + 4. Send `/` from the composer and inspect the following turn. + 5. Switch the session to Plan mode and inspect the tool list again. + 6. Disable or remove every Skill and start another Agent turn. +- **Expected**: Whenever the skill catalog is non-empty, `Skill` ships with the + first request and never appears under `# On-demand tools`, so both a matching + task and a `/skill-id` invocation load the body without a discovery round + trip. `ToolSearch` still exists for the other on-demand capabilities and + never returns `Skill`. Plan mode omits the tool and the `# Skills` section, + and an empty catalog registers no `Skill` tool at all. +- **Specs linked**: `03-runtime/02-agent-runtime.md` (§7.1), + `03-runtime/03-tools-and-permissions.md` (§2.1), + `04-ux/04-builtin-commands.md` (§8), `08-meta/decisions-log.md` (D404), + ADR 0048, ADR 0219, ADR 0230 +- **Acceptance**: C (conversation & stream), E (tools & permissions), Quality +- **Milestone**: M5 +- **Status**: Unit-covered (`packages/agent-runtime/src/runtime.test.ts`); + rendered desktop journey Draft + (do not run E2E locally unless explicitly requested) + +#### E2E-255: An ideographic comma opens the slash menu + +- **Preconditions**: A Chinese IME is available, the composer draft is empty, + and at least one slash entry exists (builtin alias, template, plugin command, + or Skill). +- **Steps**: + 1. With the draft empty, type `、` and inspect the composer. + 2. Continue typing a command name and accept the highlighted row. + 3. Type a draft that contains `、` between other characters. + 4. Send a draft whose first character was typed as `、` without accepting any + row. +- **Expected**: The committed `、` is rewritten to `/` in place, the ordinary + slash menu opens with the same filtering and keyboard behavior as a typed + `/`, and the caret stays after the substituted character. A `、` anywhere + later in the draft stays untouched text, and the `@` file menu never reacts + to the mark. +- **Specs linked**: `04-ux/04-builtin-commands.md` (§9), + `04-ux/08-component-spec.md` (§11), `08-meta/decisions-log.md` (D405), + ADR 0024, ADR 0231 +- **Acceptance**: C (conversation & stream), Localization, Quality +- **Milestone**: M2 +- **Status**: Unit-covered (`packages/shared/src/composer-trigger.test.ts`, + `apps/desktop/test/composer-ime.test.mjs`); rendered desktop journey Draft + (do not run E2E locally unless explicitly requested) diff --git a/docs/spec/08-meta/decisions-log.md b/docs/spec/08-meta/decisions-log.md index 24b535fb6..2c748372a 100644 --- a/docs/spec/08-meta/decisions-log.md +++ b/docs/spec/08-meta/decisions-log.md @@ -75,6 +75,8 @@ This log freezes previously open questions into concrete decisions. | D400 | Restore deferred tools from effective session context | **Amend D185 / ADR 0048: before each new prompt and after a mode switch, clear the in-memory deferred activation set, then restore names from successful `ToolSearch` results (`addedToolNames`) and successful deferred-tool results in the effective `buildSessionContext` projection. Keep only names still in the current deferred catalog and mode; ignore errors, interrupted or missing-result placeholders, and assistant/user prose. No host permission or workspace boundary changes.** | Clearing activations while retaining their successful transcript markers left the model able to see a capability that was absent from the next provider schema. Reconstructing only from effective successful evidence keeps the provider request coherent without parsing prose or reviving stale or disallowed tools. See ADR 0225 and E2E-008a. | | D402 | Long-press project title to reorder | *(amended by D403)* **Amend D399 / D093 / ADR 0227: retained project groups have no visible reorder grip. A 400ms still press on the project title arms a pointer reorder; movement beyond 8px before that delay cancels it so a click still selects and toggles collapse. ArrowUp/ArrowDown on the focused title is the keyboard path; Escape cancels. Persistence, pin/archive buckets, host workspace identity, session ordering, and on-disk directories are unchanged. See ADR 0228 and E2E-253.** | The dedicated grip consumed a leading column and made reorder a second control beside the title that already selects and collapses the group. | | D403 | Press-and-move project title reorder | **Amend D402 / D093 / ADR 0228: mouse and pen reorder by pressing the project title and moving 8px; a click with no qualifying movement still selects and toggles collapse. Touch does not start a reorder. An accent insertion line shows before/after placement. ArrowUp/ArrowDown and Escape are unchanged. See ADR 0229 and E2E-253.** | A 400ms still press is a mobile long-press pattern and is slower than ChatGPT-style desktop sidebar lists. | +| D404 | Skill ships with the Agent core tool set | **Amend D174 / D185 / ADR 0048 / ADR 0219: `Skill` joins the Agent-mode core tool set, so its schema is present on the first provider request whenever the skill catalog is non-empty. It is removed from the deferred catalog and never appears under `# On-demand tools`; the other on-demand capabilities and `ToolSearch` are unchanged, and Plan and Goal still omit the tool entirely. No protocol, storage, permission, or skill-body change. See ADR 0230 and E2E-254.** | A user-typed `/skill-id` and the `# Skills` section both ask the model to call `Skill`, and a tool that is absent from the schema cannot be called at all: the deferred entry added a discovery round trip before any skill body could load (issue #204). | +| D405 | Ideographic comma opens the slash menu | **Amend D123 / D139 / ADR 0024: a `、` (U+3001) committed as the first character of an empty composer draft is rewritten to `/` before trigger detection, so a Chinese IME reaches the ordinary slash menu without switching input methods. Only that position is rewritten; a `、` anywhere else stays ordinary punctuation, and the `@` file menu is unaffected. Shared grammar and renderer only; no IPC, storage, or autocomplete-source change. See ADR 0231 and E2E-255.** | Reaching `/new`, `/compact`, a mode alias, or a Skill forced a Chinese IME user to switch to ASCII input mid-sentence and then switch back (issue #65). | | D244 | Compact context usage summary | **Amend D103 / D184 / ADR 0047: keep the context inspector's remaining-capacity trigger, used/window counts, turn total, completed-turn speed, exact provider values, aggregate tool types/calls/tokens, and checkpoint summary, but render them as a short summary. Remove the per-tool rows, share bars, source badges, explanatory estimate paragraph, and used-capacity meter from the default panel. No protocol, storage, runtime accounting, or model metadata changes.** *(Amended by D347: the trigger moves to the composer toolbar.)* | The prior diagnostic layout made a routine capacity check tall and visually dense. Keeping the aggregate signal while removing drill-down chrome makes the default status surface scannable without changing the underlying usage data. See ADR 0103 and E2E-060d / US-UI-61. | @@ -4662,3 +4664,24 @@ D193, and D194. - An accent insertion line shows before or after the target group from the pointer's vertical midpoint. ArrowUp/ArrowDown and Escape are unchanged. - Decision D403 amends D402 / D093 / ADR 0228. See ADR 0229 and E2E-253. + +## 2026-09-11 — Skill ships with the Agent core tool set (D404) + +- `Skill` joins the Agent-mode core tool set, so its schema is present on the + first provider request whenever the skill catalog is non-empty. It is no + longer part of the deferred catalog and never appears under `# On-demand + tools`. +- Registration is unchanged: the tool exists only with a non-empty catalog, and + Plan and Goal still omit it entirely. Every other on-demand capability and + `ToolSearch` itself keep their lazy behavior. +- Decision D404 amends D174 / D185 / ADR 0048 / ADR 0219. See ADR 0230 and + E2E-254. + +## 2026-09-11 — Ideographic comma opens the slash menu (D405) + +- A `、` committed as the first character of an empty composer draft is + rewritten to `/` before trigger detection runs, so a Chinese IME reaches the + ordinary slash menu without switching input methods. +- Only that position is rewritten: a `、` anywhere later in the draft stays + ordinary punctuation, and the `@` file menu is unaffected. +- Decision D405 amends D123 / D139 / ADR 0024. See ADR 0231 and E2E-255. diff --git a/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md b/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md index 1f4b66ff0..59c83d3f6 100644 --- a/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md +++ b/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md @@ -4550,6 +4550,9 @@ IPC 请求无法关闭。 | 安全性 | E2E-028、E2E-029、E2E-030、E2E-024J、E2E-024K、E2E-024M、E2E-049、E2E-068、E2E-086、E2E-105、E2E-106、E2E-107、E2E-108、E2E-109、 E2E-110、E2E-112、E2E-113、E2E-115、E2E-116、E2E-117、E2E-119、E2E-121、E2E-122、E2E-123、E2E-142、E2E-148、E2E-151、E2E-153 | | 品质 | E2E-032、E2E-033、E2E-039、E2E-043、E2E-044、E2E-045、E2E-046、E2E-047、E2E-048、E2E-048A、E2E-049、E2E-050、E2E-053、E2E-055、 E2E-056、E2E-057、E2E-058、E2E-059、E2E-060、E2E-061、E2E-062、E2E-063、E2E-064、E2E-065、E2E-066、E2E-067、E2E-068、E2E-069、 E2E-070、E2E-071、E2E-072、E2E-073、E2E-074、E2E-075、E2E-076、E2E-077、E2E-078、E2E-079、E2E-080、E2E-081、E2E-082、E2E-083、 E2E-084、E2E-085、E2E-086、E2E-092、E2E-093、E2E-094、E2E-095、E2E-096、E2E-097、E2E-098、E2E-099、E2E-100、E2E-101、E2E-102、 E2E-102a、E2E-102b、E2E-103、E2E-AGENTS-001、E2E-024N、E2E-024O、E2E-059a、E2E-060b、E2E-060c、E2E-060d、E2E-061a、E2E-073a、E2E-111、 E2E-114、E2E-117、E2E-118、E2E-119、E2E-120、E2E-122、E2E-123、E2E-142、E2E-143、E2E-144、E2E-145、E2E-146、E2E-147、E2E-148、E2E-150、E2E-151、E2E-153、E2E-194、E2E-195、E2E-199、E2E-200、E2E-201、E2E-202、E2E-203、E2E-204、E2E-209、E2E-210、E2E-250 | | 品质(项目排序) | E2E-253 | +| C — 对话和直播(输入法斜杠别名) | E2E-255 | +| E——工具和权限(Skill 常驻) | E2E-254 | +| 品质(Skill 常驻与输入法斜杠别名) | E2E-254、E2E-255 | | 里程碑 | 应用场景 | |---|---| @@ -4559,6 +4562,8 @@ IPC 请求无法关闭。 | M4 | E2E-022、E2E-023、E2E-024、E2E-025、E2E-026、E2E-030、E2E-038 | | M5 | E2E-008a、E2E-032、E2E-033、E2E-034、E2E-039、E2E-043、E2E-044、E2E-045、E2E-046、E2E-047、E2E-048、E2E-048A、E2E-049、E2E-050、 E2E-051、E2E-052、E2E-053、E2E-054、E2E-055、E2E-056、E2E-057、E2E-058、E2E-059、E2E-060、E2E-061、E2E-062、E2E-063、E2E-064、 E2E-065、E2E-066、E2E-067、E2E-068、E2E-069、E2E-070、E2E-071、E2E-072、E2E-073、E2E-074、E2E-075、E2E-076、E2E-077、E2E-078、 E2E-079、E2E-080、E2E-081、E2E-082、E2E-083、E2E-084、E2E-085、E2E-086、E2E-092、E2E-093、E2E-096、E2E-097、E2E-098、E2E-099、 E2E-100、E2E-101、E2E-102、E2E-102a、E2E-102b、E2E-AGENTS-001、E2E-059a、E2E-060b、E2E-060c、E2E-061a、E2E-073a、E2E-094、E2E-095、E2E-143、E2E-145、E2E-146、E2E-147、E2E-194、E2E-195、E2E-204、E2E-250 | | M5(项目排序) | E2E-253 | +| M2(输入法斜杠别名) | E2E-255 | +| M5(Skill 常驻) | E2E-254 | | M6 | E2E-104、E2E-105、E2E-106、E2E-107、E2E-108、E2E-109、E2E-110、E2E-111、E2E-112、E2E-113、E2E-114、E2E-115、E2E-116、E2E-117、 E2E-118、E2E-119、E2E-120、E2E-103 | | M6+ | E2E-121、E2E-122、E2E-123、E2E-142、E2E-148、E2E-150、E2E-151、E2E-168、E2E-199、E2E-200、E2E-202、E2E-203、E2E-209、E2E-211、E2E-212、E2E-213、E2E-214、E2E-215、E2E-216、E2E-217 | | 后MVP | E2E-022A、E2E-022B、E2E-022C、E2E-024I、E2E-024J、E2E-024K、E2E-024L、E2E-024M(插件路线图 R2/R3/R6) | @@ -6504,3 +6509,33 @@ IPC 请求无法关闭。 - **验收**:D(工作区)、F(持久化)、品质 - **里程碑**:M5 - **状态**:源代码契约覆盖(`app-store-sidebar.test.mjs`、`sidebar-preferences.test.mjs`、`sidebar-project-reorder.test.mjs`);渲染桌面旅程为草稿 + +#### E2E-254:技能在第一个 Agent 回合即可加载 + +- **前提条件**:当前项目至少有一个已激活的 Skill;已配置提供商;会话处于 Agent 模式,且存在另一个按需能力(例如 `BrowserPreview` 或某个插件工具)。 +- **步骤**: + 1. 打开新的 Agent 会话,发送一条匹配该 Skill 描述的提示。 + 2. 检查第一个 provider 请求及其工具列表。 + 3. 确认模型直接用精确 id 调用 `Skill`,且没有先调用 `ToolSearch`,返回内容就是技能正文。 + 4. 在输入框发送 `/`,检查随后的回合。 + 5. 把会话切换到 Plan 模式,再次检查工具列表。 + 6. 禁用或移除全部 Skill,再发起一个 Agent 回合。 +- **预期**:只要技能目录非空,`Skill` 就随第一个请求下发,且绝不出现在 `# On-demand tools` 中,因此匹配任务与 `/skill-id` 调用都能直接加载正文,不再多一次发现往返。`ToolSearch` 仍服务于其他按需能力,且永远不会返回 `Skill`。Plan 模式不提供该工具与 `# Skills` 段落;目录为空时不注册任何 `Skill` 工具。 +- **链接规格**:`03-runtime/02-agent-runtime.md`(§7.1)、`03-runtime/03-tools-and-permissions.md`(§2.1)、`04-ux/04-builtin-commands.md`(§8)、`08-meta/decisions-log.md`(D404)、ADR 0048、ADR 0219、ADR 0230 +- **验收**:C(对话与流)、E(工具与权限)、品质 +- **里程碑**:M5 +- **状态**:单元覆盖(`packages/agent-runtime/src/runtime.test.ts`);渲染桌面旅程为草稿(除非明确要求,不本地运行 E2E) + +#### E2E-255:顿号打开斜杠菜单 + +- **前提条件**:可用中文输入法;输入框草稿为空;至少存在一条斜杠条目(内置别名、模板、插件命令或 Skill)。 +- **步骤**: + 1. 保持草稿为空,输入「、」并观察输入框。 + 2. 继续输入命令名并确认高亮行。 + 3. 输入一段在其他字符之间包含「、」的草稿。 + 4. 发送一段首字符为「、」且未确认任何行的草稿。 +- **预期**:「、」被就地改写为 `/`,普通斜杠菜单以与直接输入 `/` 相同的过滤和键盘行为打开,光标停留在替换后的字符之后。出现在草稿其他位置的「、」保持原样,`@` 文件菜单不会对该标点作出反应。 +- **链接规格**:`04-ux/04-builtin-commands.md`(§9)、`04-ux/08-component-spec.md`(§11)、`08-meta/decisions-log.md`(D405)、ADR 0024、ADR 0231 +- **验收**:C(对话与流)、本地化、品质 +- **里程碑**:M2 +- **状态**:单元覆盖(`packages/shared/src/composer-trigger.test.ts`、`apps/desktop/test/composer-ime.test.mjs`);渲染桌面旅程为草稿(除非明确要求,不本地运行 E2E) diff --git a/docs/zh-CN/spec/08-meta/decisions-log.md b/docs/zh-CN/spec/08-meta/decisions-log.md index 2958da124..c4dc22064 100644 --- a/docs/zh-CN/spec/08-meta/decisions-log.md +++ b/docs/zh-CN/spec/08-meta/decisions-log.md @@ -78,6 +78,8 @@ | D400 | 从有效会话上下文恢复延迟工具 | **修订 D185 / ADR 0048:每个新提示和模式切换前,清除内存中的延迟激活集,然后从有效 `buildSessionContext` 投影中的成功 `ToolSearch` 结果(`addedToolNames`)和成功的延迟工具结果恢复名称。仅保留当前延迟目录和模式仍允许的名称;忽略错误、已中断或缺少结果的占位行,以及助手/用户文本。不改变主机权限或工作区边界。** | 清除激活却保留成功的转录标记,会让模型看到能力证据而下一次 provider schema 中没有对应工具。只从有效的成功证据恢复,既保持 provider 请求一致,也不解析文本或复活过时、被禁止的工具。见 ADR 0225 与 E2E-008a。 | | D402 | 长按项目标题重排 | *(由 D403 修订)* **修订 D399 / D093 / ADR 0227:保留的项目组不再显示重排手柄。在项目标题上静止按住 400ms 后开始指针重排;该延迟前移动超过 8px 会取消按住,因此单击仍会选中并折叠/展开。聚焦标题后按 `ArrowUp`/`ArrowDown` 是键盘路径;Escape 取消。持久化、置顶/归档分桶、主机工作区身份、会话顺序和磁盘目录保持不变。见 ADR 0228 与 E2E-253。** | 专用手柄占用一列,并把重排做成标题旁边的第二个控件,而标题本身已经负责选中和折叠。 | | D403 | 按住标题移动即可重排 | **修订 D402 / D093 / ADR 0228:鼠标和触控笔按住项目标题并移动 8px 开始重排;没有足够移动的单击仍会选中并折叠/展开。触摸不会开始重排。强调色插入线标出前/后放置位置。`ArrowUp`/`ArrowDown` 与 Escape 不变。见 ADR 0229 与 E2E-253。** | 静止按住 400ms 是移动端长按模式,比 ChatGPT 一类桌面侧边栏列表更慢。 | +| D404 | Skill 随 Agent 核心工具集下发 | **修订 D174 / D185 / ADR 0048 / ADR 0219:`Skill` 加入 Agent 模式核心工具集,因此只要技能目录非空,第一个 provider 请求就带有它的 schema。它从延迟目录中移除,不再出现在 `# On-demand tools`;其他按需能力与 `ToolSearch` 不变,Plan 与 Goal 仍然完全不提供该工具。不改协议、存储、权限或技能正文。见 ADR 0230 与 E2E-254。** | 用户输入的 `/skill-id` 与 `# Skills` 段落都要求模型调用 `Skill`,而 schema 中不存在的工具根本无法被调用;延迟注册让任何技能正文加载前都多一次发现往返(issue #204)。 | +| D405 | 顿号打开斜杠菜单 | **修订 D123 / D139 / ADR 0024:当输入框为空时,第 1 个字符提交的「、」(U+3001)会在触发检测前改写为 `/`,中文输入法因此无需切换输入方式即可打开普通斜杠菜单。只改写该位置;其他位置的「、」仍是普通标点,`@` 文件菜单不受影响。仅共享语法与渲染器改动;不改 IPC、存储或补全数据源。见 ADR 0231 与 E2E-255。** | 要唤出 `/new`、`/compact`、模式别名或某个 Skill,中文输入法用户必须在书写中途切到 ASCII 输入再切回(issue #65)。 | | D244 | 紧凑的上下文用量摘要 | **修订 D103 / D184 / ADR 0047:保留上下文检查器的剩余容量触发器、已用/窗口计数、回合合计、已完成回合速度、精确的提供商数值、聚合的工具类型/调用数/令牌数以及检查点摘要,但把它们渲染为一段简短摘要。从默认面板中移除逐工具行、占比条、来源徽章、解释性估算段落和已用容量计量条。不改动协议、存储、运行时计费或模型元数据。** *(由 D347 修订:触发器移到输入框工具栏。)* | 之前的诊断式布局让一次例行的容量检查变得又高又密。保留聚合信号、移除下钻装饰,使默认状态界面可以快速浏览,同时不改变底层用量数据。参见 ADR 0103 与 E2E-060d / US-UI-61。 | | D347 | 输入框工具栏中的上下文用量检查器 | **修订 D103 / D184 / D244 / ADR 0047 / ADR 0103:紧凑上下文检查器放在输入框右侧工具栏、模型 × 推理芯片左侧,始终对应当前最新一条已报告用量的助手回合。触发器保留剩余容量圆环和百分比,去掉重复的 Context 文字。弹层标题为剩余 tokens + 百分比;下方行用同一套左标签/右数值节奏,只用留白分隔,不画内部分隔线(D297)。答案下方的助理元只保留模型徽章。仅渲染器改动。** | 挂在最新答案下方的检查器会随记录滚出视野。输入框只保留一个入口作为最新快照的权威位置;标题双线通过去掉多余说明文字解决,而不是加分隔线。参见 ADR 0184 与 E2E-060d / US-UI-61。 | | D355 | 上下文检查器按最后一次请求计算占用 | **修订 D103 / D184 / D244 / D347 / ADR 0047 / ADR 0103 / ADR 0184:剩余容量、已用/窗口计数、本轮合计,以及模型 input/output/cache/reasoning/命中率,都取最新一条已报告用量的助手消息(最后一次模型请求)。占用为该消息的 `input + output + reasoning + cacheRead + cacheWrite`。它们不是视觉工具循环里每一次请求的加总。已完成回合速度和聚合工具行仍描述该视觉回合。仅渲染器改动;宿主回合汇总和 Token Insights 仍做账单累加。** | 把工具循环里的缓存读取加总后,367k 缓存读取会紧挨着 55k 窗口。OpenCode 的上下文组件只用最后一条助手消息。参见 ADR 0193 与 E2E-060d。 | @@ -3860,3 +3862,15 @@ D193 和 D194。 - 鼠标和触控笔按住项目标题并移动 8px 开始重排。没有足够移动的单击仍会选中项目并切换折叠。触摸不会开始重排,以便列表可以滚动。 - 强调色插入线按指针相对目标垂直中点标出前/后放置位置。`ArrowUp`/`ArrowDown` 与 Escape 不变。 - 决策 D403 修订 D402 / D093 / ADR 0228。见 ADR 0229 与 E2E-253。 + +## 2026-09-11 —— Skill 随 Agent 核心工具集下发(D404) + +- `Skill` 加入 Agent 模式核心工具集,因此只要技能目录非空,第一个 provider 请求就带有它的 schema。它不再属于延迟目录,也不会出现在 `# On-demand tools`。 +- 注册门槛不变:只有目录非空时才存在该工具,Plan 与 Goal 仍然完全不提供它。其他所有按需能力与 `ToolSearch` 本身保持原有延迟行为。 +- 决策 D404 修订 D174 / D185 / ADR 0048 / ADR 0219。见 ADR 0230 与 E2E-254。 + +## 2026-09-11 —— 顿号打开斜杠菜单(D405) + +- 当输入框为空时,第 1 个字符提交的「、」会在触发检测之前改写为 `/`,中文输入法因此无需切换输入方式即可打开普通斜杠菜单。 +- 只改写该位置:出现在草稿其他位置的「、」仍是普通标点,`@` 文件菜单不受影响。 +- 决策 D405 修订 D123 / D139 / ADR 0024。见 ADR 0231 与 E2E-255。