refactor: brand the command lifecycle pairing id as CommandId
commandId crosses three boundaries (session log, wire admission response, client flow pairing), so per the branded-id rule it becomes Branded<'CommandId'>, declared in a new pure @deepseek-ai/dsh-commands/brand outlet (the dsh-llm/brand shape: type + constructor, no Context merges, so wire and client programs can name it without loading the host plugin). The event payloads, CommandExecution, and the executor mint carry the brand; the wire schema gains commandIdSchema as the domain's single brand-cast point (the approvals precedent); CommandNode and the fixture's fabrication cast follow type-only.
This commit is contained in:
@@ -30,6 +30,7 @@
|
|||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@deepseek-ai/dsh-host-apiproxy": "workspace:^",
|
"@deepseek-ai/dsh-host-apiproxy": "workspace:^",
|
||||||
|
"@deepseek-ai/dsh-commands": "workspace:^",
|
||||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||||
"@deepseek-ai/dsh-session": "workspace:^",
|
"@deepseek-ai/dsh-session": "workspace:^",
|
||||||
"@deepseek-ai/dsh-tools": "workspace:^"
|
"@deepseek-ai/dsh-tools": "workspace:^"
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
|
|
||||||
import type { ContentBlock } from '@deepseek-ai/dsh-llm/types'
|
import type { ContentBlock } from '@deepseek-ai/dsh-llm/types'
|
||||||
import type { SessionEvent, SessionId, TodoItem } from '@deepseek-ai/dsh-session/types'
|
import type { SessionEvent, SessionId, TodoItem } from '@deepseek-ai/dsh-session/types'
|
||||||
|
// Type-only: the brand constructor is host-side; the fixture casts at its
|
||||||
|
// wire-fabrication boundary (the schema layer's one-cast-point posture).
|
||||||
|
import type { CommandId } from '@deepseek-ai/dsh-commands/brand'
|
||||||
import type {
|
import type {
|
||||||
ApiProxy, ClientRequest, ClientResponse, HistoryEntry, HostFrame, MuxFrame, RpcReceipt,
|
ApiProxy, ClientRequest, ClientResponse, HistoryEntry, HostFrame, MuxFrame, RpcReceipt,
|
||||||
RpcRequest, RpcResponse, RpcResult, ServerRequest, ServerResponse, SessionSummary,
|
RpcRequest, RpcResponse, RpcResult, ServerRequest, ServerResponse, SessionSummary,
|
||||||
@@ -838,7 +841,7 @@ export function createFixtureApi(options: FixtureOptions = {}): ApiProxy {
|
|||||||
}
|
}
|
||||||
const text = name === undefined ? undefined : outcomes[name]
|
const text = name === undefined ? undefined : outcomes[name]
|
||||||
if (name === undefined || text === undefined) return ok(request, { matched: false as const })
|
if (name === undefined || text === undefined) return ok(request, { matched: false as const })
|
||||||
const commandId = `fx-cmd-${logOf(id).length}`
|
const commandId = `fx-cmd-${logOf(id).length}` as CommandId
|
||||||
append(id, { type: 'command/run', data: { commandId, name, args, source: { kind: 'user' } } })
|
append(id, { type: 'command/run', data: { commandId, name, args, source: { kind: 'user' } } })
|
||||||
append(id, { type: 'command/done', data: { commandId, kind: 'success', ...text === '' ? {} : { text } } })
|
append(id, { type: 'command/done', data: { commandId, kind: 'success', ...text === '' ? {} : { text } } })
|
||||||
return ok(request, { matched: true as const, commandId })
|
return ok(request, { matched: true as const, commandId })
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Test-local programmable IApiClient fake (NOT the fixture: fixture is a demo
|
// Test-local programmable IApiClient fake (NOT the fixture: fixture is a demo
|
||||||
// data source on a real clock; behavior tests need per-case responses and
|
// data source on a real clock; behavior tests need per-case responses and
|
||||||
// deferred-controlled timing). Streams are hand pumps: pushMux/pushHost.
|
// deferred-controlled timing). Streams are hand pumps: pushMux/pushHost.
|
||||||
|
import type { CommandId } from '@deepseek-ai/dsh-commands/brand'
|
||||||
import type {
|
import type {
|
||||||
CommandDescriptor, HostFrame, IApiClient, MuxFrame,
|
CommandDescriptor, HostFrame, IApiClient, MuxFrame,
|
||||||
RpcRequest, RpcResponse, SessionId, SkillEntry,
|
RpcRequest, RpcResponse, SessionId, SkillEntry,
|
||||||
@@ -94,7 +95,7 @@ export class FakeApiClient implements IApiClient {
|
|||||||
// wire shapes so cases can program catalogs and skill lists without casts.
|
// wire shapes so cases can program catalogs and skill lists without casts.
|
||||||
onCommandList: (payload: unknown) => Promise<RpcResponse<{ commands: CommandDescriptor[] }>>
|
onCommandList: (payload: unknown) => Promise<RpcResponse<{ commands: CommandDescriptor[] }>>
|
||||||
= () => Promise.resolve(ok({ commands: [] }))
|
= () => Promise.resolve(ok({ commands: [] }))
|
||||||
onCommandExecute: (payload: unknown) => Promise<RpcResponse<{ matched: boolean; commandId?: string }>>
|
onCommandExecute: (payload: unknown) => Promise<RpcResponse<{ matched: boolean; commandId?: CommandId }>>
|
||||||
= () => Promise.resolve(ok({ matched: false }))
|
= () => Promise.resolve(ok({ matched: false }))
|
||||||
onSkillList: (payload: unknown) => Promise<RpcResponse<{ skills: SkillEntry[] }>>
|
onSkillList: (payload: unknown) => Promise<RpcResponse<{ skills: SkillEntry[] }>>
|
||||||
= () => Promise.resolve(ok({ skills: [] }))
|
= () => Promise.resolve(ok({ skills: [] }))
|
||||||
|
|||||||
@@ -15,6 +15,9 @@
|
|||||||
{
|
{
|
||||||
"path": "../../core/session"
|
"path": "../../core/session"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "../../ui/commands"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "../../util/brand"
|
"path": "../../util/brand"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@deepseek-ai/dsh-client-connection": "workspace:^",
|
"@deepseek-ai/dsh-client-connection": "workspace:^",
|
||||||
|
"@deepseek-ai/dsh-commands": "workspace:^",
|
||||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:^",
|
"@deepseek-ai/dsh-client-ui-slots": "workspace:^",
|
||||||
"@deepseek-ai/dsh-host-apiproxy": "workspace:^",
|
"@deepseek-ai/dsh-host-apiproxy": "workspace:^",
|
||||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// substructures keep their references (the React.memo premise). callId/approvalId stay plain
|
// substructures keep their references (the React.memo premise). callId/approvalId stay plain
|
||||||
// string here (narrow to real brands when convenient).
|
// string here (narrow to real brands when convenient).
|
||||||
|
|
||||||
|
import type { CommandId } from '@deepseek-ai/dsh-commands/brand'
|
||||||
import type { ContentBlock } from '@deepseek-ai/dsh-llm/types'
|
import type { ContentBlock } from '@deepseek-ai/dsh-llm/types'
|
||||||
import type { TodoItem } from '@deepseek-ai/dsh-session/types'
|
import type { TodoItem } from '@deepseek-ai/dsh-session/types'
|
||||||
import type {
|
import type {
|
||||||
@@ -136,7 +137,7 @@ export interface CommandNode {
|
|||||||
/** Unix epoch ms of the anchoring event. */
|
/** Unix epoch ms of the anchoring event. */
|
||||||
time: number
|
time: number
|
||||||
/** Pairing id minted by the host executor. */
|
/** Pairing id minted by the host executor. */
|
||||||
commandId: string
|
commandId: CommandId
|
||||||
/** Command name (run payload's structured field); null when the run fell outside the window. */
|
/** Command name (run payload's structured field); null when the run fell outside the window. */
|
||||||
name: string | null
|
name: string | null
|
||||||
/** Verbatim rawInput after the name, separator whitespace included (run payload); null when the run fell outside the window. */
|
/** Verbatim rawInput after the name, separator whitespace included (run payload); null when the run fell outside the window. */
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type { SessionEvent } from '@deepseek-ai/dsh-session/types'
|
|||||||
// go through it — the package root points at lib/index.js (needs a build) which the vite
|
// go through it — the package root points at lib/index.js (needs a build) which the vite
|
||||||
// browser bundle cannot resolve; surface.ts has no Node dependencies.
|
// browser bundle cannot resolve; surface.ts has no Node dependencies.
|
||||||
import { SurfaceManager, isSurfaceEligibleType } from '@deepseek-ai/dsh-session/surface'
|
import { SurfaceManager, isSurfaceEligibleType } from '@deepseek-ai/dsh-session/surface'
|
||||||
|
import type { CommandId } from '@deepseek-ai/dsh-commands/brand'
|
||||||
import type { ToolCallView, ToolEventView, ToolResultView } from '@deepseek-ai/dsh-client-connection/client'
|
import type { ToolCallView, ToolEventView, ToolResultView } from '@deepseek-ai/dsh-client-connection/client'
|
||||||
import type { CommandNode, ConversationNode } from './conversation.ts'
|
import type { CommandNode, ConversationNode } from './conversation.ts'
|
||||||
import { toAssistantBlocks } from './conversation.ts'
|
import { toAssistantBlocks } from './conversation.ts'
|
||||||
@@ -229,7 +230,7 @@ export class FoldAdapter {
|
|||||||
// enter the client program, so this wire consumer narrows structurally
|
// enter the client program, so this wire consumer narrows structurally
|
||||||
// (the same posture as tool/code-dispatch in session.ts).
|
// (the same posture as tool/code-dispatch in session.ts).
|
||||||
if ((event.type as string) === 'command/run') {
|
if ((event.type as string) === 'command/run') {
|
||||||
const data = event.data as unknown as { commandId: string; name: string; args: string }
|
const data = event.data as unknown as { commandId: CommandId; name: string; args: string }
|
||||||
this.commandIdx.set(data.commandId, {
|
this.commandIdx.set(data.commandId, {
|
||||||
kind: 'command', seq: event.seq, time: event.time,
|
kind: 'command', seq: event.seq, time: event.time,
|
||||||
commandId: data.commandId, name: data.name, args: data.args, outcome: null,
|
commandId: data.commandId, name: data.name, args: data.args, outcome: null,
|
||||||
@@ -237,7 +238,7 @@ export class FoldAdapter {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ((event.type as string) !== 'command/done') return
|
if ((event.type as string) !== 'command/done') return
|
||||||
const data = event.data as unknown as { commandId: string; kind: 'success' | 'error'; text?: string }
|
const data = event.data as unknown as { commandId: CommandId; kind: 'success' | 'error'; text?: string }
|
||||||
const run = this.commandIdx.get(data.commandId)
|
const run = this.commandIdx.get(data.commandId)
|
||||||
const outcome = { kind: data.kind, ...data.text === undefined ? {} : { text: data.text } }
|
const outcome = { kind: data.kind, ...data.text === undefined ? {} : { text: data.text } }
|
||||||
if (run === undefined) {
|
if (run === undefined) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Test-local programmable IApiClient fake (NOT the fixture: fixture is a demo
|
// Test-local programmable IApiClient fake (NOT the fixture: fixture is a demo
|
||||||
// data source on a real clock; behavior tests need per-case responses and
|
// data source on a real clock; behavior tests need per-case responses and
|
||||||
// deferred-controlled timing). Streams are hand pumps: pushMux/pushHost.
|
// deferred-controlled timing). Streams are hand pumps: pushMux/pushHost.
|
||||||
|
import type { CommandId } from '@deepseek-ai/dsh-commands/brand'
|
||||||
import type {
|
import type {
|
||||||
ClientResponse, CommandDescriptor, HostFrame, IApiClient, MuxFrame,
|
ClientResponse, CommandDescriptor, HostFrame, IApiClient, MuxFrame,
|
||||||
RpcError, RpcReceipt, RpcRequest, RpcResponse, SessionId, SkillEntry,
|
RpcError, RpcReceipt, RpcRequest, RpcResponse, SessionId, SkillEntry,
|
||||||
@@ -119,7 +120,7 @@ export class FakeApiClient implements IApiClient {
|
|||||||
// skill lists without casts.
|
// skill lists without casts.
|
||||||
onCommandList: (payload: unknown) => Promise<RpcResponse<{ commands: CommandDescriptor[] }>>
|
onCommandList: (payload: unknown) => Promise<RpcResponse<{ commands: CommandDescriptor[] }>>
|
||||||
= () => Promise.resolve(ok({ commands: [] }))
|
= () => Promise.resolve(ok({ commands: [] }))
|
||||||
onCommandExecute: (payload: unknown) => Promise<RpcResponse<{ matched: boolean; commandId?: string }>>
|
onCommandExecute: (payload: unknown) => Promise<RpcResponse<{ matched: boolean; commandId?: CommandId }>>
|
||||||
= () => Promise.resolve(ok({ matched: false }))
|
= () => Promise.resolve(ok({ matched: false }))
|
||||||
onSkillList: (payload: unknown) => Promise<RpcResponse<{ skills: SkillEntry[] }>>
|
onSkillList: (payload: unknown) => Promise<RpcResponse<{ skills: SkillEntry[] }>>
|
||||||
= () => Promise.resolve(ok({ skills: [] }))
|
= () => Promise.resolve(ok({ skills: [] }))
|
||||||
|
|||||||
@@ -23,6 +23,9 @@
|
|||||||
{
|
{
|
||||||
"path": "../../host/apiproxy"
|
"path": "../../host/apiproxy"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "../../ui/commands"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "../../session-projection/session-projection"
|
"path": "../../session-projection/session-projection"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -366,7 +366,7 @@ describe('ChatView', () => {
|
|||||||
|
|
||||||
it('renders command nodes as durable rows: settled text, error state, executing spinner, run-less soft-fall', () => {
|
it('renders command nodes as durable rows: settled text, error state, executing spinner, run-less soft-fall', () => {
|
||||||
const command = (over: Partial<CommandNode>): CommandNode => ({
|
const command = (over: Partial<CommandNode>): CommandNode => ({
|
||||||
kind: 'command', seq: 5, time: 5_000, commandId: 'cmd-1',
|
kind: 'command', seq: 5, time: 5_000, commandId: 'cmd-1' as CommandNode['commandId'],
|
||||||
name: 'plan', args: '', outcome: { kind: 'success', text: '已进入 plan mode' },
|
name: 'plan', args: '', outcome: { kind: 'success', text: '已进入 plan mode' },
|
||||||
...over,
|
...over,
|
||||||
})
|
})
|
||||||
@@ -378,7 +378,7 @@ describe('ChatView', () => {
|
|||||||
|
|
||||||
// Error outcome flips the row state; a text-less error gets the default copy.
|
// Error outcome flips the row state; a text-less error gets the default copy.
|
||||||
const failed = makeHarness({
|
const failed = makeHarness({
|
||||||
nodes: [command({ seq: 6, commandId: 'cmd-2', outcome: { kind: 'error' } })],
|
nodes: [command({ seq: 6, commandId: 'cmd-2' as CommandNode['commandId'], outcome: { kind: 'error' } })],
|
||||||
})
|
})
|
||||||
const fv = render(<failed.ChatView {...failed.props} />)
|
const fv = render(<failed.ChatView {...failed.props} />)
|
||||||
expect(fv.container.querySelector('[data-state="error"]')).not.toBeNull()
|
expect(fv.container.querySelector('[data-state="error"]')).not.toBeNull()
|
||||||
@@ -386,7 +386,7 @@ describe('ChatView', () => {
|
|||||||
|
|
||||||
// Still executing: running state with the executing copy.
|
// Still executing: running state with the executing copy.
|
||||||
const executing = makeHarness({
|
const executing = makeHarness({
|
||||||
nodes: [command({ seq: 7, commandId: 'cmd-3', outcome: null })],
|
nodes: [command({ seq: 7, commandId: 'cmd-3' as CommandNode['commandId'], outcome: null })],
|
||||||
})
|
})
|
||||||
const xv = render(<executing.ChatView {...executing.props} />)
|
const xv = render(<executing.ChatView {...executing.props} />)
|
||||||
expect(xv.container.querySelector('[data-state="running"]')).not.toBeNull()
|
expect(xv.container.querySelector('[data-state="running"]')).not.toBeNull()
|
||||||
@@ -394,7 +394,7 @@ describe('ChatView', () => {
|
|||||||
|
|
||||||
// Cross-window soft-fall (run page truncated): generic title, outcome preserved.
|
// Cross-window soft-fall (run page truncated): generic title, outcome preserved.
|
||||||
const orphan = makeHarness({
|
const orphan = makeHarness({
|
||||||
nodes: [command({ seq: 8, commandId: 'cmd-4', name: null, args: null, outcome: { kind: 'success' } })],
|
nodes: [command({ seq: 8, commandId: 'cmd-4' as CommandNode['commandId'], name: null, args: null, outcome: { kind: 'success' } })],
|
||||||
})
|
})
|
||||||
const ov = render(<orphan.ChatView {...orphan.props} />)
|
const ov = render(<orphan.ChatView {...orphan.props} />)
|
||||||
expect(ov.getByText('命令')).toBeTruthy()
|
expect(ov.getByText('命令')).toBeTruthy()
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
import type { CommandId } from '@deepseek-ai/dsh-commands/brand'
|
||||||
import type { RequestPayload, ResponseValue } from './rpc-map.ts'
|
import type { RequestPayload, ResponseValue } from './rpc-map.ts'
|
||||||
import type { Wire } from './rpc.schema.ts'
|
import type { Wire } from './rpc.schema.ts'
|
||||||
import { sessionIdSchema } from './sessions.schema.ts'
|
import { sessionIdSchema } from './sessions.schema.ts'
|
||||||
@@ -32,8 +33,11 @@ export const commandExecuteRequestSchema = z.object({
|
|||||||
line: z.string(),
|
line: z.string(),
|
||||||
}) satisfies z.ZodType<Wire<RequestPayload<'command.execute'>>>
|
}) satisfies z.ZodType<Wire<RequestPayload<'command.execute'>>>
|
||||||
|
|
||||||
|
/** CommandId: one brand cast after shape validation (the only cast point in this domain). */
|
||||||
|
export const commandIdSchema = z.string().min(1) as unknown as z.ZodType<CommandId>
|
||||||
|
|
||||||
/** command.execute response value: pure admission — outcomes ride the logged lifecycle events; commandId (present exactly when matched) correlates with them. */
|
/** command.execute response value: pure admission — outcomes ride the logged lifecycle events; commandId (present exactly when matched) correlates with them. */
|
||||||
export const commandExecuteValueSchema = z.object({
|
export const commandExecuteValueSchema = z.object({
|
||||||
matched: z.boolean(),
|
matched: z.boolean(),
|
||||||
commandId: z.string().min(1).optional(),
|
commandId: commandIdSchema.optional(),
|
||||||
}) satisfies z.ZodType<Wire<ResponseValue<'command.execute'>>>
|
}) satisfies z.ZodType<Wire<ResponseValue<'command.execute'>>>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
* together), so there is no agent-less surface on this wire.
|
* together), so there is no agent-less surface on this wire.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { CommandId } from '@deepseek-ai/dsh-commands/brand'
|
||||||
import type { SessionId } from '@deepseek-ai/dsh-session/types'
|
import type { SessionId } from '@deepseek-ai/dsh-session/types'
|
||||||
import type { RpcRequest, RpcResponse } from './rpc.ts'
|
import type { RpcRequest, RpcResponse } from './rpc.ts'
|
||||||
|
|
||||||
@@ -43,5 +44,5 @@ export interface CommandsApi {
|
|||||||
* wire: the fetch carrier's request signal cancels the running handler.
|
* wire: the fetch carrier's request signal cancels the running handler.
|
||||||
*/
|
*/
|
||||||
execute(request: RpcRequest<{ sessionId: SessionId; line: string }>, signal: AbortSignal):
|
execute(request: RpcRequest<{ sessionId: SessionId; line: string }>, signal: AbortSignal):
|
||||||
Promise<RpcResponse<{ matched: boolean; commandId?: string }>>
|
Promise<RpcResponse<{ matched: boolean; commandId?: CommandId }>>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { CommandId } from '@deepseek-ai/dsh-commands/brand'
|
||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
import type { ApiProxy, HostFrame, MuxFrame } from '../src/api/index.ts'
|
import type { ApiProxy, HostFrame, MuxFrame } from '../src/api/index.ts'
|
||||||
import type { ClientResponse, RpcMessage, RpcReceipt, RpcRequest } from '../src/api/rpc.ts'
|
import type { ClientResponse, RpcMessage, RpcReceipt, RpcRequest } from '../src/api/rpc.ts'
|
||||||
@@ -91,7 +92,7 @@ function fakeApi(overrides: Partial<{ muxFrames: MuxFrame[]; hostFrames: HostFra
|
|||||||
return { rpcId: request.rpcId, result: { ok: false, error: { code: 'cancelled', message: 'aborted', details: {} } } }
|
return { rpcId: request.rpcId, result: { ok: false, error: { code: 'cancelled', message: 'aborted', details: {} } } }
|
||||||
}
|
}
|
||||||
if (request.payload.line.startsWith('/plan')) {
|
if (request.payload.line.startsWith('/plan')) {
|
||||||
return { rpcId: request.rpcId, result: { ok: true, value: { matched: true, commandId: 'cmd-x' } } }
|
return { rpcId: request.rpcId, result: { ok: true, value: { matched: true, commandId: CommandId('cmd-x') } } }
|
||||||
}
|
}
|
||||||
return { rpcId: request.rpcId, result: { ok: true, value: { matched: false } } }
|
return { rpcId: request.rpcId, result: { ok: true, value: { matched: false } } }
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,12 +15,17 @@
|
|||||||
"types": "./lib/types/invariant.d.ts",
|
"types": "./lib/types/invariant.d.ts",
|
||||||
"default": "./lib/invariant.js"
|
"default": "./lib/invariant.js"
|
||||||
},
|
},
|
||||||
|
"./brand": {
|
||||||
|
"types": "./lib/types/brand.d.ts",
|
||||||
|
"default": "./lib/types/brand.js"
|
||||||
|
},
|
||||||
"./src/*": "./src/*",
|
"./src/*": "./src/*",
|
||||||
"./package.json": "./package.json"
|
"./package.json": "./package.json"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"lib/index.js",
|
"lib/index.js",
|
||||||
"lib/invariant.js",
|
"lib/invariant.js",
|
||||||
|
"lib/types/**/*.js",
|
||||||
"lib/types/**/*.d.ts",
|
"lib/types/**/*.d.ts",
|
||||||
"lib/types/**/*.d.ts.map",
|
"lib/types/**/*.d.ts.map",
|
||||||
"src"
|
"src"
|
||||||
@@ -28,6 +33,7 @@
|
|||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@deepseek-ai/dsh-agent": "^0.0.1",
|
"@deepseek-ai/dsh-agent": "^0.0.1",
|
||||||
|
"@deepseek-ai/dsh-brand": "^0.0.1",
|
||||||
"@deepseek-ai/dsh-invariants": "^0.0.1",
|
"@deepseek-ai/dsh-invariants": "^0.0.1",
|
||||||
"@deepseek-ai/dsh-scope": "^0.0.1",
|
"@deepseek-ai/dsh-scope": "^0.0.1",
|
||||||
"@deepseek-ai/dsh-session": "^0.0.1",
|
"@deepseek-ai/dsh-session": "^0.0.1",
|
||||||
@@ -35,6 +41,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||||
|
"@deepseek-ai/dsh-brand": "workspace:^",
|
||||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||||
"@deepseek-ai/dsh-scope": "workspace:^",
|
"@deepseek-ai/dsh-scope": "workspace:^",
|
||||||
"@deepseek-ai/dsh-session": "workspace:^",
|
"@deepseek-ai/dsh-session": "workspace:^",
|
||||||
|
|||||||
29
packages/ui/commands/src/brand.ts
Normal file
29
packages/ui/commands/src/brand.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* dsh-commands' owned branded id: command lifecycle pairing across the
|
||||||
|
* session log, the wire admission response, and client-side flow pairing.
|
||||||
|
*
|
||||||
|
* The `Branded<B>` primitive lives in `@deepseek-ai/dsh-brand`; this module
|
||||||
|
* is a pure type/constructor outlet (no cordis imports, no module
|
||||||
|
* augmentation) so wire and client programs can name the brand without
|
||||||
|
* loading the host plugin's Context merges — the `dsh-llm/brand` shape.
|
||||||
|
*
|
||||||
|
* @module @deepseek-ai/dsh-commands/brand
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Branded } from '@deepseek-ai/dsh-brand'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pairs one command execution's `command/run`/`command/done` lifecycle
|
||||||
|
* records with each other and with the `command.execute` admission response.
|
||||||
|
* Minted by the executor, monotonic per service instance.
|
||||||
|
*/
|
||||||
|
export type CommandId = Branded<'CommandId'>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Brand a string as a {@link CommandId}.
|
||||||
|
* @param id - the executor-minted pairing id.
|
||||||
|
* @returns the same string, branded; no validation is performed.
|
||||||
|
*/
|
||||||
|
export function CommandId(id: string): CommandId {
|
||||||
|
return id as CommandId
|
||||||
|
}
|
||||||
@@ -8,6 +8,9 @@ import type { Agent } from '@deepseek-ai/dsh-agent'
|
|||||||
import { NamedEntries, ScopedLayers } from '@deepseek-ai/dsh-scope'
|
import { NamedEntries, ScopedLayers } from '@deepseek-ai/dsh-scope'
|
||||||
import type { ScopeKey, ScopeLayer } from '@deepseek-ai/dsh-scope'
|
import type { ScopeKey, ScopeLayer } from '@deepseek-ai/dsh-scope'
|
||||||
import type { Session, SessionEvent, SessionEventMap } from '@deepseek-ai/dsh-session'
|
import type { Session, SessionEvent, SessionEventMap } from '@deepseek-ai/dsh-session'
|
||||||
|
import { CommandId } from './brand.ts'
|
||||||
|
|
||||||
|
export { CommandId } from './brand.ts'
|
||||||
|
|
||||||
export const name = 'commands'
|
export const name = 'commands'
|
||||||
|
|
||||||
@@ -55,7 +58,7 @@ export type CommandResult =
|
|||||||
*/
|
*/
|
||||||
export interface CommandExecution {
|
export interface CommandExecution {
|
||||||
/** Pairing id carried by this execution's lifecycle events. */
|
/** Pairing id carried by this execution's lifecycle events. */
|
||||||
readonly commandId: string
|
readonly commandId: CommandId
|
||||||
/** The handler's normalized outcome. */
|
/** The handler's normalized outcome. */
|
||||||
readonly result: CommandResult
|
readonly result: CommandResult
|
||||||
}
|
}
|
||||||
@@ -131,13 +134,13 @@ declare module '@deepseek-ai/dsh-session' {
|
|||||||
* folding its own command records, a rich command card) never re-parses
|
* folding its own command records, a rich command card) never re-parses
|
||||||
* a line.
|
* a line.
|
||||||
*/
|
*/
|
||||||
'command/run': { commandId: string; name: string; args: string; source: CommandSource }
|
'command/run': { commandId: CommandId; name: string; args: string; source: CommandSource }
|
||||||
/**
|
/**
|
||||||
* The paired command settled. `kind`/`text` carry the handler's verbatim
|
* The paired command settled. `kind`/`text` carry the handler's verbatim
|
||||||
* outcome (a thrown/aborted handler settles as `kind: 'error'` with the
|
* outcome (a thrown/aborted handler settles as `kind: 'error'` with the
|
||||||
* rendered failure); presentation stays client-computed at render time.
|
* rendered failure); presentation stays client-computed at render time.
|
||||||
*/
|
*/
|
||||||
'command/done': { commandId: string; kind: 'success' | 'error'; text?: string }
|
'command/done': { commandId: CommandId; kind: 'success' | 'error'; text?: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OutOfBandSessionEventMap {
|
interface OutOfBandSessionEventMap {
|
||||||
@@ -397,9 +400,9 @@ export class CommandService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Mint the next pairing id (monotonic; instance-token-prefixed so a resumed log never repeats one). */
|
/** Mint the next pairing id (monotonic; instance-token-prefixed so a resumed log never repeats one). */
|
||||||
private mintCommandId(): string {
|
private mintCommandId(): CommandId {
|
||||||
this.commandSeq += 1
|
this.commandSeq += 1
|
||||||
return `cmd-${this.instanceToken}-${this.commandSeq}`
|
return CommandId(`cmd-${this.instanceToken}-${this.commandSeq}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -23,6 +23,9 @@
|
|||||||
{
|
{
|
||||||
"path": "../../core/session"
|
"path": "../../core/session"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "../../util/brand"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "../../support/invariants"
|
"path": "../../support/invariants"
|
||||||
}
|
}
|
||||||
|
|||||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -776,6 +776,9 @@ importers:
|
|||||||
|
|
||||||
packages/client/connection:
|
packages/client/connection:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@deepseek-ai/dsh-commands':
|
||||||
|
specifier: workspace:^
|
||||||
|
version: link:../../ui/commands
|
||||||
'@deepseek-ai/dsh-host-apiproxy':
|
'@deepseek-ai/dsh-host-apiproxy':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../../host/apiproxy
|
version: link:../../host/apiproxy
|
||||||
@@ -868,6 +871,9 @@ importers:
|
|||||||
'@deepseek-ai/dsh-client-ui-slots':
|
'@deepseek-ai/dsh-client-ui-slots':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../ui-slots
|
version: link:../ui-slots
|
||||||
|
'@deepseek-ai/dsh-commands':
|
||||||
|
specifier: workspace:^
|
||||||
|
version: link:../../ui/commands
|
||||||
'@deepseek-ai/dsh-host-apiproxy':
|
'@deepseek-ai/dsh-host-apiproxy':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../../host/apiproxy
|
version: link:../../host/apiproxy
|
||||||
@@ -4365,6 +4371,9 @@ importers:
|
|||||||
'@deepseek-ai/dsh-agent':
|
'@deepseek-ai/dsh-agent':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../../core/agent
|
version: link:../../core/agent
|
||||||
|
'@deepseek-ai/dsh-brand':
|
||||||
|
specifier: workspace:^
|
||||||
|
version: link:../../util/brand
|
||||||
'@deepseek-ai/dsh-invariants':
|
'@deepseek-ai/dsh-invariants':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../../support/invariants
|
version: link:../../support/invariants
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"@deepseek-ai/dsh-session-title/client": ["./packages/session-title/session-title/src/client.ts"],
|
"@deepseek-ai/dsh-session-title/client": ["./packages/session-title/session-title/src/client.ts"],
|
||||||
"@deepseek-ai/dsh-llm/types": ["./packages/llm/llm/src/types.ts"],
|
"@deepseek-ai/dsh-llm/types": ["./packages/llm/llm/src/types.ts"],
|
||||||
"@deepseek-ai/dsh-llm/brand": ["./packages/llm/llm/src/brand.ts"],
|
"@deepseek-ai/dsh-llm/brand": ["./packages/llm/llm/src/brand.ts"],
|
||||||
|
"@deepseek-ai/dsh-commands/brand": ["./packages/ui/commands/src/brand.ts"],
|
||||||
"@deepseek-ai/dsh-tools/presentation": ["./packages/core/tools/src/presentation.ts"],
|
"@deepseek-ai/dsh-tools/presentation": ["./packages/core/tools/src/presentation.ts"],
|
||||||
"@deepseek-ai/dsh-user-approval/types": ["./packages/ui/user-approval/src/types.ts"],
|
"@deepseek-ai/dsh-user-approval/types": ["./packages/ui/user-approval/src/types.ts"],
|
||||||
"@deepseek-ai/dsh-user-interaction/types": ["./packages/ui/user-interaction/src/types.ts"],
|
"@deepseek-ai/dsh-user-interaction/types": ["./packages/ui/user-interaction/src/types.ts"],
|
||||||
|
|||||||
Reference in New Issue
Block a user