refactor(commands): move the command service to Remote

`CommandService.list` and `execute` carry the wire contract directly through
`@Remote`, and the Client assembly mounts the generated commands
contribution. The legacy API Proxy route, its schemas, the map rows, the
generated client methods and the fixture's command domain are removed, so the
catalog and the admission call have one owner again.

`Session.command()` keeps a result-shaped public face for parity with the
prompt, cancel and attachment neighbours it sits beside, and reads the
generated namespace through one `SessionRemotes` parameter. The Session
cluster declares that face against the owning business package rather than the
generated contribution: the Host compiler aggregate builds this package, and
it runs before any contribution is emitted.

Migrated calls lose the `title-invalid` class of protocol-only error codes and
report `internal`; no production caller branched on them.
This commit is contained in:
imccyu
2026-08-11 19:10:31 +08:00
parent a2981207b0
commit 070a2a7f1e
71 changed files with 648 additions and 1129 deletions

View File

@@ -30,6 +30,14 @@
"types": "./lib/types/brand.d.ts",
"default": "./lib/types/brand.js"
},
"./typert": {
"types": "./lib/typert.host.d.ts",
"default": "./lib/typert.host.js"
},
"./remote": {
"types": "./lib/typert.remote-client.d.ts",
"default": "./lib/typert.remote-client.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
@@ -37,7 +45,12 @@
"lib/index.js",
"lib/invariant.js",
"lib/types/**/*.js",
"lib/types/**/*.d.ts"
"lib/types/**/*.d.ts",
"lib/typert.host.js",
"lib/typert.host.d.ts",
"lib/typert.remote-client.js",
"lib/typert.remote-client.d.ts",
"lib/typert.remote-client.d.ts.map"
],
"license": "BSD-3-Clause",
"peerDependencies": {
@@ -46,6 +59,7 @@
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-scope": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-type-meta": "workspace:^",
"@deepseek-ai/cordis": "workspace:^"
},
"devDependencies": {
@@ -54,6 +68,7 @@
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-scope": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-type-meta": "workspace:^",
"@deepseek-ai/cordis": "workspace:^"
}
}

View File

@@ -3,26 +3,27 @@
* @module @deepseek-ai/dsh-commands
*/
import { Context, Service } from '@deepseek-ai/cordis'
import { Context } from '@deepseek-ai/cordis'
import type { Agent } from '@deepseek-ai/dsh-agent'
import { NamedEntries, ScopedLayers } 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 { GatewayService, Remote } from '@deepseek-ai/dsh-type-meta'
import { CommandId } from './brand.ts'
import type {
CommandDescriptor,
CommandExecution,
CommandInputDescriptor,
CommandResult,
} from './types.ts'
export { CommandId } from './brand.ts'
export type { CommandSource, CommandSourceMap } from './types.ts'
export type * from './types.ts'
export const name = 'commands'
const COMMAND_NAME = /^[a-z][a-z0-9_-]*$/u
/** Immutable metadata for a command's optional unstructured input. */
export interface CommandInputDescriptor {
/** Placeholder shown before the user supplies free-form input. */
readonly hint: string
}
/** Invocation passed to one registered command handler. */
export interface CommandInvocation {
/** Pairing id already written to this invocation's `command/run` event. */
@@ -35,29 +36,6 @@ export interface CommandInvocation {
readonly signal: AbortSignal
}
/** Expected command outcome rendered directly by the dispatching UI. */
export type CommandResult =
| {
readonly kind: 'success'
readonly text?: string
/** Earlier authoritative domain event that owns a richer presentation. */
readonly sourceEventSeq?: number
}
| { readonly kind: 'error'; readonly text: string }
/**
* One settled command execution: the handler's normalized result plus the
* lifecycle pairing id minted for its `command/run`/`command/done` records,
* so a dispatching surface can correlate the RPC-level acknowledgment with
* the flow node those events produce.
*/
export interface CommandExecution {
/** Pairing id carried by this execution's lifecycle events. */
readonly commandId: CommandId
/** The handler's normalized outcome. */
readonly result: CommandResult
}
/** Plugin-owned command registration. */
export interface CommandDefinition {
/** Lowercase command name without the leading slash. */
@@ -76,16 +54,6 @@ export interface CommandDefinition {
readonly handler: (invocation: CommandInvocation) => CommandResult | Promise<CommandResult>
}
/** Handler-free immutable command view returned to UI adapters. */
export interface CommandDescriptor {
/** Lowercase command name without the leading slash. */
readonly name: string
/** Human-readable summary used in discovery UI. */
readonly description: string
/** Optional free-form input hint advertised to capable clients. */
readonly input?: CommandInputDescriptor
}
/** Syntactically valid slash command before registry resolution. */
export interface ParsedCommand {
/** Lowercase command name without the leading slash. */
@@ -254,7 +222,7 @@ function normalizeResult(command: string, value: unknown): CommandResult {
* registered through a command-injected child of an agent context shadow
* globals for that agent.
*/
export class CommandService extends Service {
export class CommandService extends GatewayService {
private readonly layers = new ScopedLayers(
scope => new CommandLayer(scope),
() => { this.notifyChange() },
@@ -288,6 +256,7 @@ export class CommandService extends Service {
* @param agent - exact receiving agent and scoped-layer key.
* @returns name-sorted descriptors after scoped shadowing.
*/
@Remote
list(agent: Agent): readonly CommandDescriptor[] {
return Object.freeze([...this.view(agent).values()]
.map(command => command.descriptor)
@@ -324,6 +293,7 @@ export class CommandService extends Service {
* @returns the settled execution (result + lifecycle pairing id), or
* `undefined` when syntax or name does not resolve.
*/
@Remote
async execute(
agent: Agent,
line: string,

View File

@@ -9,6 +9,45 @@
import type { CommandId } from './brand.ts'
/** Immutable metadata for a command's optional unstructured input. */
export interface CommandInputDescriptor {
/** Placeholder shown before the user supplies free-form input. */
readonly hint: string
}
/** Expected command outcome rendered directly by the dispatching UI. */
export type CommandResult =
| {
readonly kind: 'success'
readonly text?: string
/** Earlier authoritative domain event that owns a richer presentation. */
readonly sourceEventSeq?: number
}
| { readonly kind: 'error'; readonly text: string }
/**
* One settled command execution: the handler's normalized result plus the
* lifecycle pairing id minted for its `command/run`/`command/done` records,
* so a dispatching surface can correlate the Remote acknowledgment with the
* flow node those events produce.
*/
export interface CommandExecution {
/** Pairing id carried by this execution's lifecycle events. */
readonly commandId: CommandId
/** The handler's normalized outcome. */
readonly result: CommandResult
}
/** Handler-free immutable command view returned to UI adapters. */
export interface CommandDescriptor {
/** Lowercase command name without the leading slash. */
readonly name: string
/** Human-readable summary used in discovery UI. */
readonly description: string
/** Optional free-form input hint advertised to capable clients. */
readonly input?: CommandInputDescriptor
}
/**
* Producer record for one command invocation (the `command/run` event's
* source slot). Merge-extensible sum type mirroring `MessageSourceMap`'s

View File

@@ -28,6 +28,9 @@
},
{
"path": "../../support/invariants"
},
{
"path": "../../typert/type-meta"
}
]
}