/** * Narrow local contracts for the DSH host services and events this plugin * consumes. Keeping these structural copies (instead of importing host source * packages) lets the package build self-contained; a composed DSH profile * supplies the real implementations at runtime. Field shapes mirror * `@deepseek-ai/dsh-agent`, `@deepseek-ai/dsh-session`, and * `@deepseek-ai/dsh-user-approval` as of dsh 0.0.1-rc.2. * @module dsh-lark-channel/host */ import type { Context } from '@deepseek-ai/cordis'; /** The live session a host agent drives; only the identity is read here. */ export interface HostSession { /** The session id shared by the agent registry and session log. */ readonly id: string; /** * The session log, which several host services fold their own state out of * rather than mirroring it. Read-only here: this plugin folds plan mode from * it to know whether a plan review is even meaningful. */ readonly events?: readonly HostSessionEvent[]; } /** Durable metadata for one stored image, from {@link HostAttachments.saveImage}. */ export interface HostImageRef { readonly attachmentId: string; readonly mediaType: string; readonly bytes: number; readonly width: number; readonly height: number; readonly name?: string; } /** One model-facing content block this plugin produces. */ export type HostContentBlock = { readonly type: 'text'; readonly text: string; } | { readonly type: 'image'; readonly attachment: HostImageRef; }; /** A user-role message accepted by {@link HostAgent.followup}. */ export interface HostUserMessage { /** Stable message identity; a fresh UUID per message. */ readonly id: string; readonly role: 'user'; /** Model-facing content blocks: the chat's text, plus any images it carried. */ readonly content: readonly HostContentBlock[]; /** Producer tag: chat input is a direct human prompt. */ readonly source: { readonly kind: 'user'; }; } /** What one image must satisfy to be stored, from the attachment service. */ export interface HostImageLimits { readonly maxImageBytes: number; readonly maxImagesPerMessage: number; readonly maxMessageImageBytes: number; readonly mediaTypes: readonly string[]; } /** * The `attachments` store (subset of the host `AttachmentStore`). Images reach * a model as an opaque reference to bytes this service owns, never as a path * or a URL, so a chat image has to be committed here before it can be sent. */ export interface HostAttachments { readonly imageLimits: HostImageLimits; /** Validate and durably commit one image; the media type is checked against the bytes. */ saveImage(input: { data: Uint8Array; mediaType: string; name?: string; }): Promise; } /** * The deployment's permission presets (subset of the host `permissionPresets` * service). Read-only here: this channel classifies a switch by what the * preset actually does, and leaves the writing to `/permission`. */ export interface HostPermissionPresets { /** Every switchable preset name, in the table's declaration order. */ readonly names: readonly string[]; /** * One preset's knob bundle. * @throws when the name is not in the deployment's table. */ resolve(name: string): { readonly sandbox: string; readonly approval: string; readonly name?: string | undefined; readonly description?: string | undefined; }; } /** * One session as the host's corpus lists it (subset of `SessionRecord`). * `header.cwd` is what decides whether a conversation may continue it, and * `live` is what tells a chat the web UI is already driving it. */ export interface HostSessionRecord { readonly header?: { readonly id?: string; readonly cwd?: string; readonly createdAt?: number; /** * `subagent` for work an agent delegated to itself. Those are not * conversations anyone had: they open with an instruction the agent wrote, * run one turn, and end. */ readonly origin?: string; /** Depth below a driving session; anything above zero was delegated. */ readonly delegationDepth?: number; /** The session this one was forked from, when it was forked at all. */ readonly parentSession?: string; }; /** Whether an agent currently drives this id. */ readonly live?: boolean; } /** One event of a session, as the query engine's lightweight listing gives it. */ export interface HostEventRecord { readonly seq: number; readonly type: string; /** Unix epoch milliseconds. */ readonly time: number; } /** * One raw event as the log holds it. `source` is the field this channel is * after: a session's `user/message` stream carries injected context — system * prompt snapshots, skill catalogs, job notices — beside what a person typed, * and only the source tells them apart. */ export interface HostRawEvent { readonly seq: number; readonly type: string; readonly time?: number; readonly data?: { readonly source?: { readonly kind?: string; }; readonly content?: readonly { readonly type?: string; readonly text?: string; }[]; }; } /** One result of a batch title read; a rejected one carries no value. */ export type HostTitleResult = { readonly sessionId: string; readonly status: 'fulfilled'; readonly value?: { readonly title?: { readonly title?: string; }; }; } | { readonly sessionId: string; readonly status: 'rejected'; readonly reason?: unknown; }; /** * The `sessionQuery` engine (subset). Read-only: this channel lists what a * conversation may continue and folds titles to label them, and leaves every * write to the session store. */ export interface HostSessionQuery { /** The complete logical corpus, newest first. */ listSessions(signal?: AbortSignal): Promise; /** * Lightweight records for one session's events — no text, so counting what a * conversation contains and asking when it last moved costs no payload. */ listEvents?(sessionId: string): Promise; /** * One full event plus a bounded window around it — the only read here that * carries an event's `source`, which is what tells a person's message apart * from the ones a plugin injected into the same stream. */ readEvent?(request: { readonly sessionId: string; readonly seq: number; readonly before?: number; readonly after?: number; }): Promise<{ readonly events?: readonly HostRawEvent[]; }>; /** * Fold the latest title for several sessions at once. Optional because a * deployment may compose a query engine without it; the picker then labels * rows by time alone. */ readTitleSnapshots?(sessionIds: readonly string[], signal?: AbortSignal): Promise; } /** Public live-agent handle (subset of the host `Agent` interface). */ export interface HostAgent { /** The single identity shared with {@link session}. */ readonly id: string; readonly session: HostSession; /** Queue an ordinary follow-up turn and wake the driver. */ followup(message: HostUserMessage): void; /** * Clear queued work and abort the active turn. A no-op when nothing is * active, so a chat may offer it unconditionally. */ cancel(cause: string): void; /** * Resolve once the agent has no driver or maintenance task running. * Optional here because the contract reaches this plugin as an object rather * than as a dependency range: an older host simply has no such method. */ whenIdle?(): Promise; /** * Run one non-turn task from the agent's true idle phase. The task starts * synchronously once that phase is claimed, later waking input waits in the * inbox until it settles, and the claim THROWS SYNCHRONOUSLY when a turn or * another maintenance task already owns the agent. * * This is what makes a channel-issued command safe: every command appends to * the session log, and the log takes one writer. */ runMaintenance?(task: (signal: AbortSignal) => Promise): Promise; } /** An owned agent plus its teardown capability, from `agents.create()`. */ export interface HostAgentHandle { readonly agent: HostAgent; dispose(): Promise; } /** Per-agent provider/model routing accepted by {@link HostAgentRegistry.create}. */ export interface HostAgentOptions { readonly provider?: string | undefined; readonly model?: string | undefined; } /** One persisted session's header, as this plugin's lookup reads it. */ export interface HostSessionHeader { readonly id: string; /** Unix epoch milliseconds; the newest header for a chat is the one to resume. */ readonly createdAt: number; } /** * The `sessionPersistence` store (subset of the host provider). Only headers * are read: enough to find a chat's previous session without loading any log. */ export interface HostSessionPersistence { list(signal?: AbortSignal): Promise; } /** The `agents` registry service (subset of the host `AgentRegistry`). */ export interface HostAgentRegistry { /** Reopen a persisted session as a live agent, replaying its history. */ resume(options: { readonly resumeSessionId: string; readonly agentOptions?: HostAgentOptions; readonly setup?: (agentCtx: Context) => Promise; }): Promise; create(options: { readonly sessionId: string; readonly meta?: { readonly cwd?: string; readonly agentPreset?: string; }; readonly agentOptions?: HostAgentOptions; /** * Creation-time composition of the agent's scoped world, awaited before * the session and agent are published. A rejection rolls the whole * creation back, so a broken composition never yields a half-built session. */ readonly setup?: (agentCtx: Context) => Promise; }): Promise; } /** * The `tools` registry, as this plugin's per-agent composition uses it * (subset of the host `ToolRegistry`). */ export interface HostTools { /** * Register a monotonic execution guard. Registered through an agent's scoped * context it applies to that agent alone; returning a string denies the call * with that reason, and no other guard can force-allow what one denied. */ guard(guard: (execution: { readonly name: string; }) => string | undefined): () => void; /** * Register a tool definition, returning its disposer. Through an agent's * scoped context the registration is that agent's alone, and a name already * present in an outer layer is SHADOWED rather than rejected — the registry * reserves exactly one name from shadowing (`run_code`), which makes * overriding any other an intended capability rather than a trick. * * Optional in this narrow contract so a deployment composing a registry * without it still boots; questions then fall back to being denied. */ register?(definition: object): () => void; /** * One visible tool definition in a viewing scope. The scope is an opaque * `ScopeKey`; omitted views the global layer, which a deployment with a * preset roster leaves empty ({@link HostAgentPresets.standingKeyFor} supplies * the roster's). */ get(name: string, scope?: unknown): HostToolDefinition | undefined; } /** The presentation half of a tool definition (subset of the host `ToolDefinition`). */ export interface HostToolDefinition { /** * Pure projection of one pending call for a UI. Every view variant carries a * `title`: a short, always-visible label describing what THIS call does, * which is what a log line or card header shows. Absent on tools that accept * the generic fallback (title = tool name). */ presentCall?(args: unknown): { readonly title?: string; readonly kind?: string; } | undefined; } /** One command this deployment offers, from {@link HostCommands.list}. */ export interface HostCommandDescriptor { /** Lowercase name without the leading slash. */ readonly name: string; /** Human-readable summary used in discovery surfaces. */ readonly description: string; } /** One settled command execution (subset of the host `CommandExecution`). */ export interface HostCommandExecution { readonly result: { readonly kind: 'success'; readonly text?: string; } | { readonly kind: 'error'; readonly text: string; }; } /** * The `commands` runtime: slash commands dispatched WITHOUT a model turn, which * is why a chat must route them here instead of letting the model read a literal * `/clear` as prose. */ export interface HostCommands { /** Commands available to one agent, for discovery. */ list(agent: HostAgent): readonly HostCommandDescriptor[]; /** * Run one complete slash-command line. Resolves `undefined` when the syntax * or the name does not resolve, which is what distinguishes an unknown * command from one that ran and failed. */ execute(agent: HostAgent, line: string, signal: AbortSignal): Promise; } /** The `systemPrompt` assembler, as this plugin's per-agent composition uses it. */ export interface HostSystemPrompt { /** * Register one ordered prompt section in the calling context's scope layer. * Tool guidance uses orders 100–199; a duplicate name throws. */ section(section: { name: string; order: number; text: string; }): () => void; } /** * The `agentPresets` roster (subset of the host `AgentPresets`). A deployment * that composes one keeps every model-facing row — tools, prompt sections — on * the agent plane, so the tool registry's global layer is EMPTY and an agent * that joins no preset reaches the model with no tools at all. */ export interface HostAgentPresets { /** * Resolve a preset id, or the roster default when absent. * @throws when the roster supplies no such preset. */ resolve(id?: string): Promise<{ readonly id: string; }>; /** * Join one agent's scope to a preset's standing composition. Call from the * agent factory's `setup(agentCtx)`. */ mount(agentCtx: Context, id?: string): Promise; /** * The standing scope key a reader with no agent resolves this preset's * registrations in — the view that holds its tools, since a roster keeps * every model-facing row off the global layer. */ standingKeyFor(id?: string): Promise; } /** One workspace record (subset of the host `Workspace` entity). */ export interface HostWorkspace { readonly id: string; /** The record's canonical (realpath) directory. */ readonly path: string; /** * Account one session under this workspace. Validates the session header's * cwd against {@link path}, so a session created with that exact value * attaches and one created with an uncanonicalized variant is rejected. */ attachSession(id: string): Promise; } /** * The `workspaceRegistry` service (subset of the host registry). Grouping is * accounted, not derived: a session whose cwd merely matches a workspace stays * Ungrouped until something attaches it. */ export interface HostWorkspaceRegistry { /** The record for a canonical path, or undefined when none is registered. */ resolveByPath(path: string): Promise; /** Register a workspace for a directory; at most one record exists per canonical path. */ create(path: string, title?: string): Promise; /** * Every registered workspace. Optional in this narrow contract so a * deployment composing an older registry still boots; `/ws` then lists only * what this channel itself has seen. */ list?(): readonly HostWorkspace[]; /** * Sessions the operator archived: the host's own "hide this from every * grouping surface". Optional for the same reason as {@link list}. */ readonly archivedSessionIds?: readonly string[]; } /** One provider route, as the `llm` registry advertises it. */ export interface HostLlmProvider { readonly id: string; readonly name: string; } /** One advertised model on a provider route. */ export interface HostLlmModel { readonly provider: string; readonly id: string; readonly name: string; } /** * The `llm` adapter registry (subset of the host `LlmRuntime`). Listing is * advisory by the host's own contract — adapters may accept model ids they do * not advertise — so a consumer must never turn absence into rejection. */ export interface HostLlm { listProviders(): HostLlmProvider[]; listModels(provider: string): Promise; } /** The `agentDefaultModel` service (subset of `AgentDefaultModelConfig`). */ export interface HostDefaultModel { /** The deployment's current default provider/model selection. */ currentSelection(): HostAgentOptions; } /** The Cordis loader service; awaited so agents never see a half-composed tree. */ export interface HostLoader { await(): Promise; } /** One registered namespace's owner scope (subset of the host `SettingsScope`). */ export interface HostSettingsScope { /** The resolved value: schema defaults, then composition base, then the user document. */ get(): unknown; /** Deep-merge a patch into the user section and persist it through the provider. */ update(patch: object): Promise; } /** The `settings` user-settings service (subset of `SettingsProvider`). */ export interface HostSettings { /** * Register a namespace schema; the registration is an effect on the calling * fiber. Duplicate namespaces and stored sections the schema rejects fail loud. */ register(ns: string, schema: unknown, options?: { base?: unknown; }): HostSettingsScope; } /** * The `sessionProjections` registry, narrowed to reading one session's cut. * * Every projection the deployment composed folds the same session log: token * usage, context occupancy, step counts. Reading is synchronous and consistent * — one snapshot answers for one log position — so a status report never * mixes two. */ export interface HostSessionProjections { /** * Read every registered projection for one session. * @param session - the session to read. * @returns the cut, keyed by projection, and the log position it answers for. */ snapshot(session: HostSession): { readonly asOfSeq: number; readonly values: Record; }; } /** Whole-session token totals, as the host's `tokenUsage` projection reports them. */ export interface HostTokenUsage { readonly uncachedInputTokens: number; readonly outputTokens: number; readonly cacheReadTokens: number; readonly cacheWriteTokens: number; } /** * How full the model's context is, as the host's `contextPressure` projection * reports it. Every field is optional: a session that has not yet made a * request has no sample, and a provider that reports no window has no * denominator. */ export interface HostContextPressure { /** Prompt-side tokens the last request actually carried. */ readonly pressureTokens?: number; /** What the NEXT request would carry, moved by everything logged since. */ readonly projectedTokens?: number; readonly contextWindow?: number; } /** One immutable entry in the host session log; narrowed via the guards below. */ export interface HostSessionEvent { readonly type: string; readonly data: unknown; } /** The `assistant/message` payload fields this plugin renders. */ export interface AssistantMessageData { readonly turn: number; readonly message: { readonly content: readonly { readonly type: string; readonly text?: string; }[]; }; } /** The `turn/end` payload fields this plugin reports. */ export interface TurnEndData { readonly turn: number; readonly reason: { readonly kind: string; readonly error?: { readonly code?: string; readonly message?: string; }; }; } /** The `step/start` payload fields this plugin uses to warm a card up. */ export interface StepStartData { readonly turn: number; readonly step: number; } /** The `turn/start` payload fields this plugin uses. */ export interface TurnStartData { readonly turn: number; } /** * The `user/message` payload: the message object a turn consumed, carrying the * id its producer stamped on it. One turn may consume SEVERAL queued messages, * so this event — not turn order — is what correlates a turn with the inbound * message(s) it answers. */ export interface UserMessageEventData { readonly id?: string; } /** The `assistant/chunk` payload fields this plugin streams. */ export interface AssistantChunkData { readonly turn: number; /** * One raw stream chunk. Only `text-delta` reaches the chat: `reasoning-delta` * is the model's private thinking and stays off the wire, and tool-call * deltas are raw JSON fragments reported through `tool/call` instead. */ readonly chunk: { readonly type: string; readonly text?: string; }; } /** The `tool/result` payload fields a thinking process reports. */ export interface ToolResultData { readonly turn: number; readonly message: { /** The producing call, so a result pairs with the call that asked. */ readonly source?: { readonly callId?: string; }; readonly content: readonly { readonly type: string; readonly toolCallId?: string; /** Nested model-facing blocks; a tool's text output lives here. */ readonly content?: readonly { readonly type: string; readonly text?: string; }[]; }[]; }; readonly error?: { readonly name: string; readonly code: string; }; } /** The `tool/call` payload fields this plugin surfaces as activity. */ export interface ToolCallData { readonly turn: number; /** Pairs the call with the approval question that decides it. */ readonly callId: string; readonly name: string; /** Raw arguments JSON exactly as the model produced it (unparsed, untrusted). */ readonly arguments: string; } /** * Narrow a session event to the assembled assistant message for one step. * @param event - any session event. * @returns whether `event.data` carries {@link AssistantMessageData}. */ export declare function isAssistantMessageEvent(event: HostSessionEvent): event is HostSessionEvent & { readonly data: AssistantMessageData; }; /** * Narrow a session event to a closed turn boundary. * @param event - any session event. * @returns whether `event.data` carries {@link TurnEndData}. */ export declare function isTurnEndEvent(event: HostSessionEvent): event is HostSessionEvent & { readonly data: TurnEndData; }; /** * Narrow a session event to the opening of one step. * @param event - any session event. * @returns whether `event.data` carries {@link StepStartData}. */ export declare function isStepStartEvent(event: HostSessionEvent): event is HostSessionEvent & { readonly data: StepStartData; }; /** * Narrow a session event to the opening of one turn. * @param event - any session event. * @returns whether `event.data` carries {@link TurnStartData}. */ export declare function isTurnStartEvent(event: HostSessionEvent): event is HostSessionEvent & { readonly data: TurnStartData; }; /** * Narrow a session event to a user message a turn consumed. * @param event - any session event. * @returns whether `event.data` carries {@link UserMessageEventData}. */ export declare function isUserMessageEvent(event: HostSessionEvent): event is HostSessionEvent & { readonly data: UserMessageEventData; }; /** * Narrow a session event to one raw assistant stream chunk. * @param event - any session event. * @returns whether `event.data` carries {@link AssistantChunkData}. */ export declare function isAssistantChunkEvent(event: HostSessionEvent): event is HostSessionEvent & { readonly data: AssistantChunkData; }; /** * Narrow a session event to one completed tool call's result. * @param event - any session event. * @returns whether `event.data` carries {@link ToolResultData}. */ export declare function isToolResultEvent(event: HostSessionEvent): event is HostSessionEvent & { readonly data: ToolResultData; }; /** * The call one result answers, and the text it produced. * @param data - the completed result payload. * @returns the call id and its joined text output. */ export declare function toolResultText(data: ToolResultData): { callId: string | undefined; text: string; }; /** * Narrow a session event to one model-requested tool invocation. * @param event - any session event. * @returns whether `event.data` carries {@link ToolCallData}. */ export declare function isToolCallEvent(event: HostSessionEvent): event is HostSessionEvent & { readonly data: ToolCallData; }; /** * Join the text blocks of a committed assistant message. * @param data - the committed message payload. * @returns the concatenated text, empty when the step produced none. */ export declare function assistantText(data: AssistantMessageData): string; /** * Render a failed turn's reason as one operator-readable line. * @param data - the closed turn payload. * @returns the error detail, empty when the turn did not fail. */ export declare function turnErrorDetail(data: TurnEndData): string; /** Closed outcome of a host approval question; `'allowed-once'` is the only grant. */ export type HostApprovalOutcome = 'allowed-once' | 'rejected' | 'cancelled' | 'unavailable'; /** Readonly same-process permission question (subset of `ApprovalRequest`). */ export interface HostApprovalRequest { /** The agent on whose behalf the question is asked; routes the question. */ readonly agent: HostAgent; /** The tool the question is about (presentation and audit). */ readonly toolName: string; /** The exact tool call being decided, when the asker has one. */ readonly callId?: string; /** The asker's human-readable explanation of WHY it is asking. */ readonly reason?: string; /** Aborting withdraws the question; a late answer is discarded. */ readonly signal?: AbortSignal; } declare module '@deepseek-ai/cordis' { interface Context { /** The host agent registry; required via `inject`. */ agents: HostAgentRegistry; } interface Events { /** Durable session facts broadcast by the host session store. */ 'session/event'(session: HostSession, event: HostSessionEvent): void; /** * One message left an agent's inbox inside its open turn. This is where a * turn decides what it is answering, and it precedes the turn's first * step — which is why a reply is aimed here rather than at the later * `user/message` record. */ 'agent/inbox/claimed'(payload: { readonly agent: HostAgent; readonly message: { readonly id?: string; }; readonly turn?: number; }): void; /** Waterfall permission question; answer only for owned agents, else delegate via `next()`. */ 'approval/request'(request: HostApprovalRequest, next: () => Promise): Promise; } } //# sourceMappingURL=host.d.ts.map