@modesto_ma/dsh-channels 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +302 -2
- package/cordis.patch.yml +3 -0
- package/docs/access-control-design.md +550 -0
- package/docs/assets/web-settings-panel.png +0 -0
- package/docs/assets/web-settings-restart-pending.png +0 -0
- package/docs/feasibility-and-design.md +213 -0
- package/docs/feishu-onboarding.md +268 -0
- package/docs/persistence.md +199 -0
- package/docs/refactor-plan.md +56 -0
- package/docs/review-dsh-0.2.0.md +389 -0
- package/docs/slash-commands-implementation-plan.md +410 -0
- package/docs/web-display-only-surface-roadmap.md +363 -0
- package/lib/client.js +2226 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +10217 -0
- package/lib/index.js.map +1 -0
- package/package.json +98 -4
- package/src/access/cards.ts +188 -0
- package/src/access/controller.ts +201 -0
- package/src/access/index.ts +13 -0
- package/src/access/policy.ts +103 -0
- package/src/access/requests.ts +512 -0
- package/src/access/store.ts +220 -0
- package/src/approvals.ts +8 -0
- package/src/cards/approval.ts +114 -0
- package/src/cards/index.ts +11 -0
- package/src/cards/primitives.ts +82 -0
- package/src/cards/question.ts +141 -0
- package/src/cards/streaming.ts +563 -0
- package/src/cards.ts +8 -0
- package/src/cleanup.ts +398 -0
- package/src/client/api.ts +262 -0
- package/src/client/catalog.ts +92 -0
- package/src/client/index.ts +61 -0
- package/src/client/panel.tsx +1475 -0
- package/src/client/styles.ts +98 -0
- package/src/commands/registry.ts +155 -0
- package/src/commands.ts +335 -0
- package/src/config.ts +206 -0
- package/src/domain.ts +428 -0
- package/src/dsh-types.ts +11 -0
- package/src/feishu/agent-context.ts +62 -0
- package/src/feishu/delivery.ts +188 -0
- package/src/feishu/inbound-attachments.ts +436 -0
- package/src/feishu-channel.ts +339 -0
- package/src/health.ts +233 -0
- package/src/host/agent.ts +181 -0
- package/src/host/commands.ts +63 -0
- package/src/host/core.ts +345 -0
- package/src/host/index.ts +12 -0
- package/src/host/interaction.ts +101 -0
- package/src/host/services.ts +320 -0
- package/src/index.ts +122 -0
- package/src/interactions/approvals.ts +154 -0
- package/src/interactions/pending.ts +162 -0
- package/src/interactions/questions.ts +274 -0
- package/src/questions.ts +8 -0
- package/src/routing/inbound.ts +524 -0
- package/src/service.ts +2138 -0
- package/src/session/manager.ts +352 -0
- package/src/session/repair.ts +157 -0
- package/src/session/request-policy.ts +192 -0
- package/src/session/routes.ts +165 -0
- package/src/session-commands.ts +310 -0
- package/src/session-export.ts +112 -0
- package/src/settings/catalog.ts +309 -0
- package/src/settings/contract.ts +508 -0
- package/src/settings/overlay.ts +164 -0
- package/src/settings/routes.ts +409 -0
- package/src/settings/surface.ts +773 -0
- package/src/stats.ts +485 -0
- package/src/streamer.ts +1108 -0
- package/src/workspaces.ts +199 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host bridge, agent surface: creation/resume options, the live Agent and its
|
|
3
|
+
* scoped context, the agent registry, and the agent-preset roster.
|
|
4
|
+
*
|
|
5
|
+
* @module dsh-channels/host/agent
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { CommandRuntime } from './commands.ts'
|
|
9
|
+
import type { Session, SessionId, UserMessage } from './core.ts'
|
|
10
|
+
|
|
11
|
+
// ---- Agent surface ------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
/** Cancellation causes accepted by `agent.cancel()`. */
|
|
14
|
+
export type AgentCancelCause =
|
|
15
|
+
| { readonly kind: 'user' }
|
|
16
|
+
| { readonly kind: 'parent' }
|
|
17
|
+
| { readonly kind: 'hook'; readonly reason: string }
|
|
18
|
+
| { readonly kind: 'disposed' }
|
|
19
|
+
|
|
20
|
+
export interface AgentOptions {
|
|
21
|
+
provider?: string
|
|
22
|
+
model?: string
|
|
23
|
+
maxTokens?: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Exact model-call configuration exposed by the Agent-scoped request waterfall. */
|
|
27
|
+
export interface AgentRequestConfig {
|
|
28
|
+
provider: string
|
|
29
|
+
model: string
|
|
30
|
+
reasoningEffort?: string
|
|
31
|
+
maxTokens?: number
|
|
32
|
+
[key: string]: unknown
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Agent-scoped request override installed before the first prompt assembly. */
|
|
36
|
+
export type AgentRequestListener = (
|
|
37
|
+
payload: { turn: number; step: number; signal: AbortSignal },
|
|
38
|
+
next: () => Promise<AgentRequestConfig>,
|
|
39
|
+
) => Promise<AgentRequestConfig>
|
|
40
|
+
|
|
41
|
+
/** Cordis listener placement used to make an explicit channel override outermost. */
|
|
42
|
+
export interface AgentRequestListenerOptions {
|
|
43
|
+
prepend?: boolean
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
/** One ordered system-prompt section the plugin contributes to an Agent scope. */
|
|
48
|
+
export interface PromptSection {
|
|
49
|
+
readonly name: string
|
|
50
|
+
readonly order: number
|
|
51
|
+
readonly text: string
|
|
52
|
+
readonly interpolate?: boolean
|
|
53
|
+
readonly complete?: boolean
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The Agent-scoped slice of the host `systemPrompt` registry. */
|
|
57
|
+
export interface SystemPromptRuntime {
|
|
58
|
+
section(section: PromptSection): () => void
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface AgentContext {
|
|
62
|
+
commands: CommandRuntime
|
|
63
|
+
/** Present inside a setup scope that injected `systemPrompt`. */
|
|
64
|
+
systemPrompt?: SystemPromptRuntime
|
|
65
|
+
/** Register an Agent-scoped model-call override. */
|
|
66
|
+
on(
|
|
67
|
+
event: 'agent/request',
|
|
68
|
+
listener: AgentRequestListener,
|
|
69
|
+
options?: boolean | AgentRequestListenerOptions,
|
|
70
|
+
): () => boolean
|
|
71
|
+
/** Mount a child plugin only after its declared Host services are available. */
|
|
72
|
+
inject(
|
|
73
|
+
dependencies: readonly string[],
|
|
74
|
+
callback: (ctx: AgentContext) => void,
|
|
75
|
+
): PromiseLike<unknown>
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface Agent {
|
|
79
|
+
readonly id: SessionId
|
|
80
|
+
readonly options: AgentOptions
|
|
81
|
+
readonly session: Session
|
|
82
|
+
readonly status: 'idle' | 'running'
|
|
83
|
+
readonly inbox: {
|
|
84
|
+
readonly nextTurn: readonly unknown[]
|
|
85
|
+
readonly nextStep: readonly unknown[]
|
|
86
|
+
}
|
|
87
|
+
readonly ctx: AgentContext
|
|
88
|
+
followup(message: UserMessage): void
|
|
89
|
+
/** Abort the active turn (and pending input unless `keepInbox`). */
|
|
90
|
+
cancel?(cause: AgentCancelCause, options?: { keepInbox?: boolean }): void
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface AgentHandle {
|
|
94
|
+
agent: Agent
|
|
95
|
+
dispose(): Promise<void>
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Compose one unpublished agent's scoped world before publication. The host
|
|
100
|
+
* factory awaits it after minting the agent context and before announcing the
|
|
101
|
+
* session/agent, so everything registered there (preset tools, prompt
|
|
102
|
+
* sections, listeners) exists before the first prompt assembly; a rejection
|
|
103
|
+
* rolls the creation back. The plugin only returns `void` — the synchronous
|
|
104
|
+
* `AgentSetupCommit` flavor is never produced here.
|
|
105
|
+
*/
|
|
106
|
+
export type AgentSetup = (agentCtx: AgentContext) => Promise<void> | void
|
|
107
|
+
|
|
108
|
+
export interface CreateAgentOptions {
|
|
109
|
+
readonly sessionId: SessionId
|
|
110
|
+
readonly meta?: { readonly cwd?: string; readonly agentPreset?: string }
|
|
111
|
+
readonly agentOptions?: AgentOptions
|
|
112
|
+
readonly setup?: AgentSetup
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface ResumeAgentOptions {
|
|
116
|
+
readonly resumeSessionId: SessionId
|
|
117
|
+
readonly agentOptions?: AgentOptions
|
|
118
|
+
readonly setup?: AgentSetup
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface AgentRegistry {
|
|
122
|
+
create(options: CreateAgentOptions): Promise<AgentHandle>
|
|
123
|
+
resume(options: ResumeAgentOptions): Promise<AgentHandle>
|
|
124
|
+
/**
|
|
125
|
+
* Synchronous live-registry lookup (host parity with `ctx.agents.get`):
|
|
126
|
+
* the published agent for a session id when one is live, regardless of
|
|
127
|
+
* which owner published it. The plugin uses this to BORROW a live agent
|
|
128
|
+
* instead of cold-resuming a session another owner already holds (the
|
|
129
|
+
* persistence layer rejects `resume` on a live session).
|
|
130
|
+
*/
|
|
131
|
+
get(id: SessionId): Agent | undefined
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
// ---- Agent-preset surface ------------------------------------------------
|
|
136
|
+
|
|
137
|
+
/** One resolvable preset identity on the host's preset roster. */
|
|
138
|
+
export interface AgentPreset {
|
|
139
|
+
readonly id: string
|
|
140
|
+
readonly path?: string
|
|
141
|
+
/** Display metadata published by a user-authored preset declaration. */
|
|
142
|
+
readonly name?: string
|
|
143
|
+
readonly description?: string
|
|
144
|
+
readonly order?: number
|
|
145
|
+
/** Why this preset cannot compose a session; absent when it can. */
|
|
146
|
+
readonly broken?: string
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The host agent-preset roster (`@deepseek-ai/dsh-agent-presets`). In
|
|
151
|
+
* web-profile deployments the model-facing tools live BEHIND agent presets
|
|
152
|
+
* (the host disables the tool rows at the host layer) and `agents.create()`
|
|
153
|
+
* applies none on its own — the caller composes the agent's world through
|
|
154
|
+
* `setup`, which is where a preset is mounted. The plugin reads the roster
|
|
155
|
+
* through `ctx.get('agentPresets')` because deployments without one compose
|
|
156
|
+
* their tools at the host layer instead, and direct property access on a
|
|
157
|
+
* cordis context throws for services that were not injected.
|
|
158
|
+
*/
|
|
159
|
+
export interface AgentPresets {
|
|
160
|
+
/**
|
|
161
|
+
* Default preset id for a session that names none. Absent on roster
|
|
162
|
+
* implementations that do not publish their default.
|
|
163
|
+
*/
|
|
164
|
+
readonly defaultId?: string
|
|
165
|
+
/**
|
|
166
|
+
* Resolve one preset by id; `undefined` resolves the roster default.
|
|
167
|
+
* Resolves BEFORE the session exists so the header meta can record it.
|
|
168
|
+
* @throws when no configured root supplies that id.
|
|
169
|
+
*/
|
|
170
|
+
resolve(id?: string): Promise<AgentPreset>
|
|
171
|
+
/**
|
|
172
|
+
* Compose one agent from a preset (standing mount + scope parent bind).
|
|
173
|
+
* Call from the agent factory's `setup`; a rejection there rolls the
|
|
174
|
+
* agent creation back. `id` defaults to the roster default.
|
|
175
|
+
* @returns the composed preset.
|
|
176
|
+
*/
|
|
177
|
+
mount(agentCtx: AgentContext, id?: string): Promise<AgentPreset>
|
|
178
|
+
/** Every declared preset, including activation diagnostics, when exposed. */
|
|
179
|
+
list?(): Promise<readonly AgentPreset[]>
|
|
180
|
+
}
|
|
181
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host bridge, human-command surface (`ctx.commands`).
|
|
3
|
+
*
|
|
4
|
+
* @module dsh-channels/host/commands
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Agent } from './agent.ts'
|
|
8
|
+
import type { CommandId } from './core.ts'
|
|
9
|
+
|
|
10
|
+
// ---- Human-command surface ---------------------------------------------
|
|
11
|
+
|
|
12
|
+
export interface CommandInputDescriptor {
|
|
13
|
+
readonly hint: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type CommandResult =
|
|
17
|
+
| { readonly kind: 'success'; readonly text?: string; readonly sourceEventSeq?: number }
|
|
18
|
+
| { readonly kind: 'error'; readonly text: string }
|
|
19
|
+
|
|
20
|
+
export interface CommandExecution {
|
|
21
|
+
readonly commandId: CommandId
|
|
22
|
+
readonly result: CommandResult
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface CommandDescriptor {
|
|
26
|
+
readonly name: string
|
|
27
|
+
readonly description: string
|
|
28
|
+
readonly input?: CommandInputDescriptor
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CommandInvocation {
|
|
32
|
+
readonly commandId: CommandId
|
|
33
|
+
readonly agent: Agent
|
|
34
|
+
readonly rawInput: string
|
|
35
|
+
readonly signal: AbortSignal
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface CommandDefinition extends CommandDescriptor {
|
|
39
|
+
readonly recordInput?: boolean
|
|
40
|
+
readonly handler: (invocation: CommandInvocation) => CommandResult | Promise<CommandResult>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Exact wire image input accepted by the Host command runtime. */
|
|
44
|
+
export interface EncodedImageAttachment {
|
|
45
|
+
readonly mediaType: 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif'
|
|
46
|
+
readonly data: string
|
|
47
|
+
readonly name?: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Minimal structural bridge to `@deepseek-ai/dsh-commands`' runtime. */
|
|
51
|
+
export interface CommandRuntime {
|
|
52
|
+
register(definition: CommandDefinition): () => void
|
|
53
|
+
list(agent: Agent): readonly CommandDescriptor[]
|
|
54
|
+
find(agent: Agent, name: string): CommandDefinition | undefined
|
|
55
|
+
execute(
|
|
56
|
+
agent: Agent,
|
|
57
|
+
line: string,
|
|
58
|
+
images: readonly EncodedImageAttachment[],
|
|
59
|
+
signal: AbortSignal,
|
|
60
|
+
): Promise<CommandExecution | undefined>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Agent-scoped Cordis context slice used to register scoped commands. */
|
package/src/host/core.ts
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host bridge, core surface: identities, LLM message/stream vocabulary, the
|
|
3
|
+
* durable Session log, and the session-event union this plugin folds.
|
|
4
|
+
*
|
|
5
|
+
* Split from the former single `dsh-types.ts`; `src/dsh-types.ts` remains the
|
|
6
|
+
* public barrel so consumers keep one import specifier.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-channels/host/core
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Agent } from './agent.ts'
|
|
12
|
+
|
|
13
|
+
/** Branded session identity (runtime value is a plain string). */
|
|
14
|
+
export type SessionId = string & { readonly __sessionId: never }
|
|
15
|
+
/** Branded message identity (runtime value is a plain string). */
|
|
16
|
+
export type MessageId = string & { readonly __messageId: never }
|
|
17
|
+
/** Branded command-execution identity (runtime value is a plain string). */
|
|
18
|
+
export type CommandId = string & { readonly __commandId: never }
|
|
19
|
+
|
|
20
|
+
// ---- LLM surface --------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
export interface TextBlock {
|
|
23
|
+
type: 'text'
|
|
24
|
+
text: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ReasoningBlock {
|
|
28
|
+
type: 'reasoning'
|
|
29
|
+
text: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Per-message model-facing blocks (plugin only ever produces plain text). */
|
|
33
|
+
export type ContentBlock =
|
|
34
|
+
| TextBlock
|
|
35
|
+
| ReasoningBlock
|
|
36
|
+
| { type: string; [key: string]: unknown }
|
|
37
|
+
|
|
38
|
+
/** Provider usage buckets; input/output/cache fields are disjoint. */
|
|
39
|
+
export interface TokenUsage {
|
|
40
|
+
inputTokens: number
|
|
41
|
+
outputTokens: number
|
|
42
|
+
reasoningTokens?: number
|
|
43
|
+
cacheReadTokens?: number
|
|
44
|
+
cacheWriteTokens?: number
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface UserMessage {
|
|
48
|
+
id: MessageId
|
|
49
|
+
role: 'user'
|
|
50
|
+
content: ContentBlock[]
|
|
51
|
+
source: { kind: string }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The chunk vocabulary consumed from live assistant-stream frames. */
|
|
55
|
+
export type StreamChunk =
|
|
56
|
+
| { type: 'block-start'; index: number; blockType: string }
|
|
57
|
+
| { type: 'text-delta'; index: number; text: string }
|
|
58
|
+
| { type: 'reasoning-delta'; index: number; text: string }
|
|
59
|
+
| { type: 'tool-call-delta'; index: number; argumentsDelta: string; id?: string; name?: string }
|
|
60
|
+
| { type: 'block-end'; index: number; block: { type: string; text?: string } }
|
|
61
|
+
| { type: 'usage'; usage: TokenUsage }
|
|
62
|
+
| { type: 'finish'; [key: string]: unknown }
|
|
63
|
+
| { type: string; [key: string]: unknown }
|
|
64
|
+
|
|
65
|
+
/** Compacted durable stream record embedded in `assistant/message.stream`. */
|
|
66
|
+
export type AssistantStreamRecord = { readonly type: string; readonly [key: string]: unknown }
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* One process-local live assistant streaming publication
|
|
70
|
+
* (`agent/assistant-stream`, DSH `packages/core/agent/src/runtime-types.ts`).
|
|
71
|
+
*
|
|
72
|
+
* DSH stopped emitting the durable `assistant/chunk` session event in commit
|
|
73
|
+
* `f99b06eaed` (2026-09-02, "feat(session)!: embed assistant streams in format
|
|
74
|
+
* v2"), so this transient event is the only live delta source; the durable
|
|
75
|
+
* `assistant/message.stream` records are the replay-safe counterpart.
|
|
76
|
+
*/
|
|
77
|
+
export type AssistantStreamFrame =
|
|
78
|
+
| {
|
|
79
|
+
readonly type: 'start'
|
|
80
|
+
readonly attemptId: string
|
|
81
|
+
readonly revision: number
|
|
82
|
+
readonly turn: number
|
|
83
|
+
readonly step: number
|
|
84
|
+
}
|
|
85
|
+
| {
|
|
86
|
+
readonly type: 'chunk'
|
|
87
|
+
readonly attemptId: string
|
|
88
|
+
readonly revision: number
|
|
89
|
+
readonly index: number
|
|
90
|
+
readonly time: number
|
|
91
|
+
readonly chunk: StreamChunk
|
|
92
|
+
}
|
|
93
|
+
| {
|
|
94
|
+
readonly type: 'end'
|
|
95
|
+
readonly attemptId: string
|
|
96
|
+
readonly revision: number
|
|
97
|
+
readonly index: number
|
|
98
|
+
readonly outcome:
|
|
99
|
+
| { readonly kind: 'committed'; readonly eventType: 'assistant/message' | 'assistant/attempt'; readonly seq: number }
|
|
100
|
+
| { readonly kind: 'abandoned' }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface AssistantStreamPayload {
|
|
104
|
+
readonly agent: Agent
|
|
105
|
+
readonly frame: AssistantStreamFrame
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ---- Session surface ----------------------------------------------------
|
|
109
|
+
|
|
110
|
+
export interface SessionHeader {
|
|
111
|
+
readonly version: number
|
|
112
|
+
readonly id: SessionId
|
|
113
|
+
readonly createdAt: number
|
|
114
|
+
readonly cwd?: string
|
|
115
|
+
/**
|
|
116
|
+
* The session this one was forked/derived from — for a subagent child this
|
|
117
|
+
* is the calling parent session's id (the link the streamer uses to fold
|
|
118
|
+
* child progress into the parent's card).
|
|
119
|
+
*/
|
|
120
|
+
readonly parentSession?: SessionId
|
|
121
|
+
/** Coarse product classification (`'subagent'` for subagent children). */
|
|
122
|
+
readonly origin?: string
|
|
123
|
+
/** Delegation depth: 1 for a direct subagent child. */
|
|
124
|
+
readonly delegationDepth?: number
|
|
125
|
+
/** Preset used to compose this Session's agent, when the host uses presets. */
|
|
126
|
+
readonly agentPreset?: string
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The live session object (durable event log + header).
|
|
131
|
+
*
|
|
132
|
+
* DSH removed the synchronous whole-log `events` getter (commit 5660f44d29,
|
|
133
|
+
* 2026-08-28) in favor of `eventAt()` / `snapshotEvents()` / `ownEvents()`;
|
|
134
|
+
* all three are `@deprecated` as of 2026-09-09 in favor of session
|
|
135
|
+
* projections plus explicit paged reads. This bridge keeps only the range
|
|
136
|
+
* reader (the narrowest replacement that preserves existing folds) and the
|
|
137
|
+
* request-header fold; new state should come from `ctx.sessionProjections`
|
|
138
|
+
* or `ctx.sessionQuery` instead of a fresh whole-log read.
|
|
139
|
+
*/
|
|
140
|
+
export interface Session {
|
|
141
|
+
readonly id: SessionId
|
|
142
|
+
readonly header: SessionHeader
|
|
143
|
+
readonly firstLiveSeq: number
|
|
144
|
+
/** Immutable snapshot of a half-open event range (defaults to the whole log). */
|
|
145
|
+
snapshotEvents(fromSeq?: number, toSeqExclusive?: number): readonly SessionEvent[]
|
|
146
|
+
/** Latest folded request header, when the session has produced one. */
|
|
147
|
+
requestHeader?(): { config?: { provider?: string; model?: string; reasoningEffort?: string } } | undefined
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface SessionEventEnvelope {
|
|
151
|
+
readonly seq: number
|
|
152
|
+
/** Unix epoch milliseconds recorded by the Session append boundary. */
|
|
153
|
+
readonly time?: number
|
|
154
|
+
readonly ignorable?: boolean
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface AssistantMessageEvent extends SessionEventEnvelope {
|
|
158
|
+
type: 'assistant/message'
|
|
159
|
+
data: {
|
|
160
|
+
turn: number
|
|
161
|
+
step: number
|
|
162
|
+
message: unknown
|
|
163
|
+
/** Compacted durable stream records (v2 format); replays the exact deltas. */
|
|
164
|
+
stream?: readonly AssistantStreamRecord[]
|
|
165
|
+
usage?: TokenUsage
|
|
166
|
+
/** Set when a cancelled turn finalized its delivered prefix as this message. */
|
|
167
|
+
interrupted?: true
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface StepStartEvent extends SessionEventEnvelope {
|
|
172
|
+
type: 'step/start'
|
|
173
|
+
data: { turn: number; step: number }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface StepEndEvent extends SessionEventEnvelope {
|
|
177
|
+
type: 'step/end'
|
|
178
|
+
data: { turn: number; step: number }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Serializable model failure carried by a failed Harness turn. */
|
|
182
|
+
export interface TurnFailure {
|
|
183
|
+
message: string
|
|
184
|
+
code: string
|
|
185
|
+
[key: string]: unknown
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Why a Harness turn ended. String reasons remain accepted for compatibility
|
|
190
|
+
* with older persisted fixtures; current Harness emits the structured union.
|
|
191
|
+
*/
|
|
192
|
+
export type TurnEndReason =
|
|
193
|
+
| string
|
|
194
|
+
| { kind: 'completed' }
|
|
195
|
+
| { kind: 'aborted'; reason: unknown }
|
|
196
|
+
| { kind: 'blocked' }
|
|
197
|
+
| { kind: 'error'; error: TurnFailure }
|
|
198
|
+
| { kind: 'max-tokens' }
|
|
199
|
+
| { kind: 'interrupted' }
|
|
200
|
+
|
|
201
|
+
export interface TurnEndEvent extends SessionEventEnvelope {
|
|
202
|
+
type: 'turn/end'
|
|
203
|
+
data: { turn: number; reason: TurnEndReason }
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export interface RequestHeaderEvent extends SessionEventEnvelope {
|
|
207
|
+
type: 'request/header'
|
|
208
|
+
data: {
|
|
209
|
+
header: {
|
|
210
|
+
config: {
|
|
211
|
+
provider?: string
|
|
212
|
+
model?: string
|
|
213
|
+
reasoningEffort?: string
|
|
214
|
+
[key: string]: unknown
|
|
215
|
+
}
|
|
216
|
+
[key: string]: unknown
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export interface ToolCallEvent extends SessionEventEnvelope {
|
|
222
|
+
type: 'tool/call'
|
|
223
|
+
data: {
|
|
224
|
+
turn: number
|
|
225
|
+
step: number
|
|
226
|
+
callId: string
|
|
227
|
+
/** Model-facing tool name (e.g. `bash`, `subagent`, `workflow`). */
|
|
228
|
+
name: string
|
|
229
|
+
/** Raw arguments JSON exactly as the model produced it (unparsed). */
|
|
230
|
+
arguments: string
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface ToolResultEvent extends SessionEventEnvelope {
|
|
235
|
+
type: 'tool/result'
|
|
236
|
+
data: {
|
|
237
|
+
turn: number
|
|
238
|
+
step: number
|
|
239
|
+
message: {
|
|
240
|
+
/** Message-level source is the authoritative call correlation. */
|
|
241
|
+
source?: { kind?: string; callId?: string; [key: string]: unknown }
|
|
242
|
+
/** Message-level failure flag; authoritative when no `error` object is attached. */
|
|
243
|
+
isError?: boolean
|
|
244
|
+
content?: ReadonlyArray<{
|
|
245
|
+
type: string
|
|
246
|
+
toolCallId?: string
|
|
247
|
+
isError?: boolean
|
|
248
|
+
[key: string]: unknown
|
|
249
|
+
}>
|
|
250
|
+
[key: string]: unknown
|
|
251
|
+
}
|
|
252
|
+
error?: { name: string; code: string }
|
|
253
|
+
meta?: unknown
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export interface CommandRunEvent extends SessionEventEnvelope {
|
|
258
|
+
type: 'command/run'
|
|
259
|
+
data: {
|
|
260
|
+
commandId: CommandId
|
|
261
|
+
name: string
|
|
262
|
+
args?: string
|
|
263
|
+
source: { kind: string }
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export interface CommandDoneEvent extends SessionEventEnvelope {
|
|
268
|
+
type: 'command/done'
|
|
269
|
+
data: {
|
|
270
|
+
commandId: CommandId
|
|
271
|
+
kind: 'success' | 'error'
|
|
272
|
+
text?: string
|
|
273
|
+
sourceEventSeq?: number
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Durable workflow-run progress record projected into the calling session's log. */
|
|
278
|
+
export interface ToolWorkflowRunStartEvent extends SessionEventEnvelope {
|
|
279
|
+
type: 'tool-workflow/run-start'
|
|
280
|
+
data: { runId: string; name: string }
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export interface ToolWorkflowAgentStartEvent extends SessionEventEnvelope {
|
|
284
|
+
type: 'tool-workflow/agent-start'
|
|
285
|
+
data: { runId: string; seq: number; label: string; phase?: string; childId: string }
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export interface ToolWorkflowAgentEndEvent extends SessionEventEnvelope {
|
|
289
|
+
type: 'tool-workflow/agent-end'
|
|
290
|
+
data: { runId: string; seq: number; outcome: 'completed' | 'failed' | 'cancelled' }
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export interface ToolWorkflowRunEndEvent extends SessionEventEnvelope {
|
|
294
|
+
type: 'tool-workflow/run-end'
|
|
295
|
+
data: { runId: string; stopReason: 'completed' | 'cancelled' | 'error' }
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Durable subagent identity appended to the CHILD session's own log at
|
|
300
|
+
* creation (in-process drivers). The `label` is the delegating model's short
|
|
301
|
+
* description — what the parent card displays while the child runs.
|
|
302
|
+
*/
|
|
303
|
+
export interface SubagentDescriptorEvent extends SessionEventEnvelope {
|
|
304
|
+
type: 'subagent/descriptor'
|
|
305
|
+
data: {
|
|
306
|
+
version: number
|
|
307
|
+
mode: 'one-shot' | 'continuable'
|
|
308
|
+
provider: string
|
|
309
|
+
label?: string
|
|
310
|
+
[key: string]: unknown
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** Runtime preset switches are durable and define the currently composed preset. */
|
|
315
|
+
export interface AgentPresetSelectedEvent extends SessionEventEnvelope {
|
|
316
|
+
type: 'agent-preset/selected'
|
|
317
|
+
data: { agentPreset: string }
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export interface OtherSessionEvent extends SessionEventEnvelope {
|
|
321
|
+
/** Unknown/extension event types are ignored by the folds. */
|
|
322
|
+
type: Exclude<string, 'assistant/message' | 'step/start' | 'turn/end' | 'request/header'
|
|
323
|
+
| 'tool/call' | 'tool/result' | 'subagent/descriptor'
|
|
324
|
+
| 'command/run' | 'command/done'
|
|
325
|
+
| 'tool-workflow/run-start' | 'tool-workflow/agent-start' | 'tool-workflow/agent-end' | 'tool-workflow/run-end'>
|
|
326
|
+
data: unknown
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export type SessionEvent =
|
|
330
|
+
| AssistantMessageEvent
|
|
331
|
+
| StepStartEvent
|
|
332
|
+
| StepEndEvent
|
|
333
|
+
| TurnEndEvent
|
|
334
|
+
| RequestHeaderEvent
|
|
335
|
+
| ToolCallEvent
|
|
336
|
+
| ToolResultEvent
|
|
337
|
+
| CommandRunEvent
|
|
338
|
+
| CommandDoneEvent
|
|
339
|
+
| AgentPresetSelectedEvent
|
|
340
|
+
| SubagentDescriptorEvent
|
|
341
|
+
| ToolWorkflowRunStartEvent
|
|
342
|
+
| ToolWorkflowAgentStartEvent
|
|
343
|
+
| ToolWorkflowAgentEndEvent
|
|
344
|
+
| ToolWorkflowRunEndEvent
|
|
345
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host bridge barrel: the exact DSH surface this plugin consumes, grouped by
|
|
3
|
+
* domain. Everything is type-only (no runtime dependency on DSH internals).
|
|
4
|
+
*
|
|
5
|
+
* @module dsh-channels/host
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export * from './core.ts'
|
|
9
|
+
export * from './agent.ts'
|
|
10
|
+
export * from './commands.ts'
|
|
11
|
+
export * from './interaction.ts'
|
|
12
|
+
export * from './services.ts'
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host bridge, human-interaction surface: the `approval/request` waterfall
|
|
3
|
+
* (HITL approvals) and the `user-questions/request` waterfall
|
|
4
|
+
* (`ask_user_question`).
|
|
5
|
+
*
|
|
6
|
+
* @module dsh-channels/host/interaction
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Agent } from './agent.ts'
|
|
10
|
+
|
|
11
|
+
// ---- Approval (HITL) surface --------------------------------------------
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Closed approval outcomes, mirrored from
|
|
15
|
+
* `@deepseek-ai/dsh-user-approval/types`: a one-shot grant, explicit
|
|
16
|
+
* rejection, withdrawn request, or unavailable answerer. Callers fail closed
|
|
17
|
+
* on anything but `allowed-once`; a missing answerer yields `unavailable`.
|
|
18
|
+
*/
|
|
19
|
+
export type ApprovalOutcome = 'allowed-once' | 'rejected' | 'cancelled' | 'unavailable'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Readonly same-process permission question (the payload of the host's
|
|
23
|
+
* `approval/request` waterfall event). `callId` links to an already-presented
|
|
24
|
+
* tool call; the plugin only renders `toolName` + `reason` on its decision
|
|
25
|
+
* card and does not duplicate tool arguments.
|
|
26
|
+
*/
|
|
27
|
+
export interface ApprovalRequest {
|
|
28
|
+
/** The agent on whose behalf the question is asked (routes + audit). */
|
|
29
|
+
readonly agent: Agent
|
|
30
|
+
/** The tool the question is about (model-facing name, e.g. `bash`). */
|
|
31
|
+
readonly toolName: string
|
|
32
|
+
/** The exact tool call being decided, when the asker has one. */
|
|
33
|
+
readonly callId?: string
|
|
34
|
+
/** The asker's human-readable explanation of WHY it is asking. */
|
|
35
|
+
readonly reason?: string
|
|
36
|
+
/**
|
|
37
|
+
* Localized presentation-only text (DSH 0.2.0). Preferred over `reason`
|
|
38
|
+
* when rendering; never part of the durable audit trail.
|
|
39
|
+
*/
|
|
40
|
+
readonly displayReason?: { readonly en: string; readonly [locale: string]: string }
|
|
41
|
+
/**
|
|
42
|
+
* Aborting withdraws the question: the request settles `'cancelled'` and a
|
|
43
|
+
* late answer is discarded.
|
|
44
|
+
*/
|
|
45
|
+
readonly signal?: AbortSignal
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* One `approval/request` waterfall listener: return an outcome to claim the
|
|
50
|
+
* request, or delegate by returning `next()`. The first claim occupies the
|
|
51
|
+
* single decision slot.
|
|
52
|
+
*/
|
|
53
|
+
export type ApprovalAnswererListener = (
|
|
54
|
+
req: ApprovalRequest,
|
|
55
|
+
next: () => Promise<ApprovalOutcome>,
|
|
56
|
+
) => Promise<ApprovalOutcome>
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
// ---- Structured user questions -------------------------------------------
|
|
60
|
+
|
|
61
|
+
/** One selectable answer offered by `ask_user_question`. */
|
|
62
|
+
export interface AskUserQuestionOption {
|
|
63
|
+
label: string
|
|
64
|
+
description?: string
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** One question in a `user-questions/request` waterfall payload. */
|
|
68
|
+
export interface AskUserQuestionItem {
|
|
69
|
+
id: string
|
|
70
|
+
question: string
|
|
71
|
+
detail?: string
|
|
72
|
+
header?: string
|
|
73
|
+
options?: AskUserQuestionOption[]
|
|
74
|
+
multiSelect?: boolean
|
|
75
|
+
intent?: { kind: 'plan-review'; approve: string; callId?: string }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface AskUserQuestionRequestEvent {
|
|
79
|
+
questions: AskUserQuestionItem[]
|
|
80
|
+
/** Owner agent; present for every agent-originated ask. */
|
|
81
|
+
agent?: Agent
|
|
82
|
+
signal?: AbortSignal
|
|
83
|
+
wait?: { callId: string; timed?: boolean }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface AskUserQuestionAnswerItem {
|
|
87
|
+
id: string
|
|
88
|
+
selected: string[]
|
|
89
|
+
custom?: string
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface AskUserQuestionAnswer {
|
|
93
|
+
answers: AskUserQuestionAnswerItem[]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export type UserQuestionAnswererListener = (
|
|
97
|
+
request: AskUserQuestionRequestEvent,
|
|
98
|
+
next: () => Promise<AskUserQuestionAnswer>,
|
|
99
|
+
) => Promise<AskUserQuestionAnswer>
|
|
100
|
+
|
|
101
|
+
|