refactor: apply repository naming contract
Apply the accepted pre-release package, service, type, directory, and role renames as one repository-wide change.
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/ui-skill/README.md
|
||||
README.md: f6818ae9f44a7d0a484302fe8e294ee2159890ec
|
||||
README.zh.md: 2def01566d6ab388f3501a4f8e24075f073d4e22
|
||||
README.md: f2e2ec8a3061f2cd8617123013d63fad63abf237
|
||||
README.zh.md: 0d450b224a7cddcbaef008c7639075cb9f60ece9
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
Skill invocation source, browser half: registers the `/`-trigger `skill` source into `ctx.slash`. Ordinary-session candidates come from the `skill.list` RPC addressed by the per-call `ClientSessionContext` projection's `{sessionId}`, with the host resolving `cwd` from the session header. The host serves every user-invocable skill; a `modelInvocable: false` entry (a `disable-model-invocation` skill, whose only entry point is this path) wears the user-only marker as a description prefix in the active language. Catalog-addressed continuable children resolve no skill candidates locally because the existing skill RPC requires an attached session; viewing their persisted history must not activate them. Catalogs cache per ordinary session with a single-flight fetch; the scope-birth `warm` hook prewarms the session's entry, the forwarded `agent-preset/selected` owner event drops that one session's entry (the catalog belongs to the preset, and a blank session may switch after the warm), and `connection/reset` clears everything. Results filter by `startsWith(query)`.
|
||||
Skill invocation source, browser half: registers the `/`-trigger `skill` source into `ctx.inputTriggers`. Ordinary-session candidates come from the `skill.list` RPC addressed by the per-call `ClientSessionContext` projection's `{sessionId}`, with the host resolving `cwd` from the session header. The host serves every user-invocable skill; a `modelInvocable: false` entry (a `disable-model-invocation` skill, whose only entry point is this path) wears the user-only marker as a description prefix in the active language. Catalog-addressed continuable children resolve no skill candidates locally because the existing skill RPC requires an attached session; viewing their persisted history must not activate them. Catalogs cache per ordinary session with a single-flight fetch; the scope-birth `warm` hook prewarms the session's entry, the forwarded `agent-preset/selected` owner event drops that one session's entry (the catalog belongs to the preset, and a blank session may switch after the warm), and `connection/reset` clears everything. Results filter by `startsWith(query)`.
|
||||
|
||||
A pick lands the literal `/name ` text and the prompt ships the same literal ([slash-pipeline Agent Note](../../../.agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md)) — this source implements no adjudication hooks and no reference codec. Determinism lives host-side: the pre-step gesture boundary (`dsh-tool-skill`) recognizes whitespace-bounded `/name` tokens naming user-invocable skills anywhere in a user message and injects the rendered `<skill_content>` for every entry point, so a menu pick, a hand-typed token, and a TUI/ACP prompt all load the skill the same way. A name shared with a host command still resolves to the command: adjudication claims the line client-side before it ever becomes a prompt — deliberate precedence, matching peer products. The list RPC rides the plugin's root-context connection captured at registration — the source never reads services off a per-call argument; draft chip visuals derive from the `lexicon` scan.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
skill(技能)调用 source 的浏览器端:把 `/` 触发的 `skill` source 注册进 `ctx.slash`。普通会话的候选来自 `skill.list` RPC,以每次调用的 `ClientSessionContext` 投影中的 `{sessionId}` 寻址,host 从会话 header 解析 `cwd`。宿主提供每一个用户可调用的 skill;`modelInvocable: false` 的条目(即 `disable-model-invocation` skill,此路径是其唯一入口)会以当前语言把仅限用户标记作为描述前缀带上。由目录寻址的可继续 subagent 在客户端解析为没有 skill 候选,因为现有 skill RPC 要求会话已挂载;查看其持久化历史不得激活它。目录按普通会话缓存,拉取走 single-flight;scope 创建时的 `warm` 钩子预热该会话的缓存项,转发的 owner 事件 `agent-preset/selected` 丢弃该会话这一项(目录属于 preset,而空会话可能在预热之后才切换),`connection/reset` 清空全部缓存。结果按 `startsWith(query)` 过滤。
|
||||
skill(技能)调用 source 的浏览器端:把 `/` 触发的 `skill` source 注册进 `ctx.inputTriggers`。普通会话的候选来自 `skill.list` RPC,以每次调用的 `ClientSessionContext` 投影中的 `{sessionId}` 寻址,host 从会话 header 解析 `cwd`。宿主提供每一个用户可调用的 skill;`modelInvocable: false` 的条目(即 `disable-model-invocation` skill,此路径是其唯一入口)会以当前语言把仅限用户标记作为描述前缀带上。由目录寻址的可继续 subagent 在客户端解析为没有 skill 候选,因为现有 skill RPC 要求会话已挂载;查看其持久化历史不得激活它。目录按普通会话缓存,拉取走 single-flight;scope 创建时的 `warm` 钩子预热该会话的缓存项,转发的 owner 事件 `agent-preset/selected` 丢弃该会话这一项(目录属于 preset,而空会话可能在预热之后才切换),`connection/reset` 清空全部缓存。结果按 `startsWith(query)` 过滤。
|
||||
|
||||
pick 会落下字面文本 `/name `,发出的提示词中也是同一段字面文本([slash 流水线 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md))——本 source 不实现任何裁决钩子,也没有引用 codec。确定性在宿主侧:pre-step 手势边界(`dsh-tool-skill`)识别用户消息中任意位置、以空白为界、指名用户可调用 skill 的 `/name` token,并为每个入口注入渲染后的 `<skill_content>`,因此菜单 pick、手动键入的 token 与 TUI/ACP(Agent Client Protocol)提示词都以同一种方式加载 skill。与宿主命令同名的名称仍解析为命令:裁决在客户端把该行认领走,它根本不会成为提示词——这是有意的优先级,与同类产品一致。列表 RPC 使用插件注册时捕获的根上下文连接——source 绝不从每次调用的参数上读取服务;草稿 chip 视觉由 `lexicon` 扫描派生。
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"@deepseek-ai/dsh-client-runtime",
|
||||
"@deepseek-ai/dsh-client-locale",
|
||||
"@deepseek-ai/dsh-client-ui-tool",
|
||||
"@deepseek-ai/dsh-client-ui-slash",
|
||||
"@deepseek-ai/dsh-client-ui-input-trigger",
|
||||
"@deepseek-ai/dsh-api-remotes"
|
||||
],
|
||||
"platform": "web"
|
||||
@@ -52,7 +52,7 @@
|
||||
"@deepseek-ai/dsh-client-locale": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-slash": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-input-trigger": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-tool": "workspace:^",
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
@@ -66,7 +66,7 @@
|
||||
"@deepseek-ai/dsh-client-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-test-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-slash": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-input-trigger": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-tool": "workspace:^",
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* services off a per-call argument. Draft chip visuals derive from
|
||||
* the lexicon scan; this source implements no reference codec.
|
||||
*
|
||||
* Catalog fetches are cached per session (the small twin of the ui-command
|
||||
* Catalog fetches are cached per session (the small twin of the ui-commands
|
||||
* directory): the per-keystroke candidates re-poll filters a settled
|
||||
* snapshot locally, so one session costs one RPC. The scope-birth warm hook
|
||||
* prewarms the session's key; a preset switch drops that one key (the
|
||||
@@ -32,7 +32,7 @@
|
||||
// Type-only: the carrier types, the forwarded Host-event face and the ctx.remote merge.
|
||||
import type { ConnectionHandle, SessionId, SkillEntry } from '@deepseek-ai/dsh-api-remotes/client'
|
||||
import type { ClientContext, ISessions } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type { SlashServiceContract, SlashSource } from '@deepseek-ai/dsh-client-ui-slash/client'
|
||||
import type { InputTriggerServiceContract, InputTriggerSource } from '@deepseek-ai/dsh-client-ui-input-trigger/client'
|
||||
// Type-only: pulls the locale plugin's Context merge (ctx.locale).
|
||||
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
||||
import { SkillRow } from './SkillRow.tsx'
|
||||
@@ -54,7 +54,7 @@ interface CatalogFetch {
|
||||
}
|
||||
|
||||
/** Required services: reference source faces plus the tool-row and locale registries. */
|
||||
export const inject = ['slash', 'connection', 'sessions', 'slots', 'locale', 'remote']
|
||||
export const inject = ['inputTriggers', 'connection', 'sessions', 'slots', 'locale', 'remote']
|
||||
|
||||
/**
|
||||
* Client plugin body: register the '/' source, dictionaries, and keyed tool row.
|
||||
@@ -130,7 +130,7 @@ export function apply(ctx: ClientContext): void {
|
||||
// locale service's own fallback ladder; candidate-time reads stay plain text.
|
||||
const t = ctx.locale.bind(NS)
|
||||
|
||||
const source: SlashSource = {
|
||||
const source: InputTriggerSource = {
|
||||
trigger: '/',
|
||||
name: 'skill',
|
||||
order: 2,
|
||||
@@ -176,13 +176,13 @@ export function apply(ctx: ClientContext): void {
|
||||
return { text: `/${candidate.name} ` }
|
||||
},
|
||||
}
|
||||
const slash = ctx.get('slash') as SlashServiceContract
|
||||
const inputTriggers = ctx.get('inputTriggers') as InputTriggerServiceContract
|
||||
// A preset decides which skill providers an agent reads, so a switched
|
||||
// session's cached catalog belongs to the composition it no longer runs.
|
||||
ctx.remote.$on('agent-preset/selected', invalidate)
|
||||
ctx.on('connection/reset', clearAll)
|
||||
ctx.effect(() => {
|
||||
const unregister = slash.registerSource(source)
|
||||
const unregister = inputTriggers.registerSource(source)
|
||||
return () => {
|
||||
unregister()
|
||||
clearAll()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ui-skill browser half: source and keyed toolview registration +
|
||||
* locale dictionaries + source duplicate-name proof +
|
||||
* fiber-teardown removal (HMR safety) against the real SlashService, then
|
||||
* fiber-teardown removal (HMR safety) against the real InputTriggerService, then
|
||||
* the source behavior contract driven directly on the captured source with
|
||||
* real ClientSessionContext projections — sessionId addressing, the
|
||||
* session-keyed catalog cache (single-flight per key, scope-birth warm
|
||||
@@ -16,10 +16,10 @@
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { SlotsService } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { SlashService } from '@deepseek-ai/dsh-client-ui-slash/client'
|
||||
import { SlotRegistry } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { InputTriggerService } from '@deepseek-ai/dsh-client-ui-input-trigger/client'
|
||||
import { TestRemote } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import type { ClientSessionContext, SlashSource } from '@deepseek-ai/dsh-client-ui-slash/client'
|
||||
import type { ClientSessionContext, InputTriggerSource } from '@deepseek-ai/dsh-client-ui-input-trigger/client'
|
||||
import { apply, inject } from '../src/client/index.ts'
|
||||
import { SkillRow as SkillToolRow } from '../src/client/SkillRow.tsx'
|
||||
|
||||
@@ -34,14 +34,14 @@ type InvokeResult =
|
||||
type InvokeFn = (payload: object) => Promise<{ result: InvokeResult }>
|
||||
|
||||
interface PresentationCapture {
|
||||
slots: SlotsService
|
||||
slots: SlotRegistry
|
||||
dictionaries: Array<{ namespace: string; dictionaries: unknown }>
|
||||
localeDisposed: boolean
|
||||
}
|
||||
|
||||
/** Provide the presentation registries and capture the plugin's registrations. */
|
||||
function providePresentation(ctx: Context): PresentationCapture {
|
||||
const slots = new SlotsService(ctx)
|
||||
const slots = new SlotRegistry(ctx)
|
||||
slots.register({
|
||||
name: 'root',
|
||||
children: { 'tool.call.toolview': { kind: 'keyed', scope: 'session' } },
|
||||
@@ -65,8 +65,8 @@ function providePresentation(ctx: Context): PresentationCapture {
|
||||
/** Boot the plugin over fake slash/connection faces; returns the captured source and its ctx. */
|
||||
async function bench(list: ListFn, addressed?: SessionId, invoke?: InvokeFn) {
|
||||
const ctx = new Context()
|
||||
let captured: SlashSource | undefined
|
||||
ctx.provide('slash', { registerSource: (src: SlashSource) => { captured = src; return () => {} } })
|
||||
let captured: InputTriggerSource | undefined
|
||||
ctx.provide('inputTriggers', { registerSource: (src: InputTriggerSource) => { captured = src; return () => {} } })
|
||||
const defaultInvoke: InvokeFn = () => Promise.resolve({ result: { ok: true as const, value: { accepted: true as const } } })
|
||||
ctx.provide('connection', { api: { skills: { list, invoke: invoke ?? defaultInvoke } } })
|
||||
ctx.provide('sessions', {
|
||||
@@ -107,12 +107,12 @@ const req = (query: string, signal?: AbortSignal) =>
|
||||
|
||||
describe('apply', () => {
|
||||
it('declares the services it binds', () => {
|
||||
expect(inject).toEqual(['slash', 'connection', 'sessions', 'slots', 'locale', 'remote'])
|
||||
expect(inject).toEqual(['inputTriggers', 'connection', 'sessions', 'slots', 'locale', 'remote'])
|
||||
})
|
||||
|
||||
it('registers the dedicated skill row and its locale dictionaries', async () => {
|
||||
const ctx = new Context()
|
||||
ctx.provide('slash', { registerSource: () => () => {} })
|
||||
ctx.provide('inputTriggers', { registerSource: () => () => {} })
|
||||
ctx.provide('connection', { api: { skills: { list: listOk(CATALOG) } } })
|
||||
ctx.provide('sessions', { subagentAddress: () => undefined })
|
||||
new TestRemote(ctx)
|
||||
@@ -144,15 +144,15 @@ describe('apply', () => {
|
||||
|
||||
it('registers the "/" skill source; disposal frees the name (HMR safety)', async () => {
|
||||
const ctx = new Context()
|
||||
// SlashService itself injects 'sessions'; the stub unblocks its fiber.
|
||||
// InputTriggerService itself injects 'sessions'; the stub unblocks its fiber.
|
||||
ctx.provide('sessions', {})
|
||||
await ctx.plugin(SlashService).await()
|
||||
await ctx.plugin(InputTriggerService).await()
|
||||
ctx.provide('connection', { api: { skills: { list: listOk(CATALOG) } } })
|
||||
new TestRemote(ctx)
|
||||
const presentation = providePresentation(ctx)
|
||||
const fiber = ctx.plugin({ inject: [...inject], apply })
|
||||
await fiber.await()
|
||||
const slash = ctx.get('slash') as SlashService
|
||||
const inputTriggers = ctx.get('inputTriggers') as InputTriggerService
|
||||
const rival = {
|
||||
trigger: '/' as const,
|
||||
name: 'skill',
|
||||
@@ -160,10 +160,10 @@ describe('apply', () => {
|
||||
onPick: () => undefined,
|
||||
}
|
||||
// Live registration holds the (trigger, name) seat…
|
||||
expect(() => slash.registerSource(rival)).toThrow(/already registered/)
|
||||
expect(() => inputTriggers.registerSource(rival)).toThrow(/already registered/)
|
||||
// …and fiber teardown releases it.
|
||||
await fiber.dispose()
|
||||
expect(() => slash.registerSource(rival)).not.toThrow()
|
||||
expect(() => inputTriggers.registerSource(rival)).not.toThrow()
|
||||
expect(presentation.slots.entries('tool.call.toolview')).toHaveLength(0)
|
||||
expect(presentation.localeDisposed).toBe(true)
|
||||
})
|
||||
|
||||
@@ -27,13 +27,13 @@
|
||||
"path": "../ui-primitives"
|
||||
},
|
||||
{
|
||||
"path": "../ui-slash"
|
||||
"path": "../ui-input-trigger"
|
||||
},
|
||||
{
|
||||
"path": "../ui-slots"
|
||||
},
|
||||
{
|
||||
"path": "../../support/invariants"
|
||||
"path": "../../runtime-diagnostics/invariants"
|
||||
},
|
||||
{
|
||||
"path": "../../api/remotes/tsconfig.client.json"
|
||||
|
||||
Reference in New Issue
Block a user