@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.
Files changed (73) hide show
  1. package/README.md +302 -2
  2. package/cordis.patch.yml +3 -0
  3. package/docs/access-control-design.md +550 -0
  4. package/docs/assets/web-settings-panel.png +0 -0
  5. package/docs/assets/web-settings-restart-pending.png +0 -0
  6. package/docs/feasibility-and-design.md +213 -0
  7. package/docs/feishu-onboarding.md +268 -0
  8. package/docs/persistence.md +199 -0
  9. package/docs/refactor-plan.md +56 -0
  10. package/docs/review-dsh-0.2.0.md +389 -0
  11. package/docs/slash-commands-implementation-plan.md +410 -0
  12. package/docs/web-display-only-surface-roadmap.md +363 -0
  13. package/lib/client.js +2226 -0
  14. package/lib/client.js.map +1 -0
  15. package/lib/index.js +10217 -0
  16. package/lib/index.js.map +1 -0
  17. package/package.json +98 -4
  18. package/src/access/cards.ts +188 -0
  19. package/src/access/controller.ts +201 -0
  20. package/src/access/index.ts +13 -0
  21. package/src/access/policy.ts +103 -0
  22. package/src/access/requests.ts +512 -0
  23. package/src/access/store.ts +220 -0
  24. package/src/approvals.ts +8 -0
  25. package/src/cards/approval.ts +114 -0
  26. package/src/cards/index.ts +11 -0
  27. package/src/cards/primitives.ts +82 -0
  28. package/src/cards/question.ts +141 -0
  29. package/src/cards/streaming.ts +563 -0
  30. package/src/cards.ts +8 -0
  31. package/src/cleanup.ts +398 -0
  32. package/src/client/api.ts +262 -0
  33. package/src/client/catalog.ts +92 -0
  34. package/src/client/index.ts +61 -0
  35. package/src/client/panel.tsx +1475 -0
  36. package/src/client/styles.ts +98 -0
  37. package/src/commands/registry.ts +155 -0
  38. package/src/commands.ts +335 -0
  39. package/src/config.ts +206 -0
  40. package/src/domain.ts +428 -0
  41. package/src/dsh-types.ts +11 -0
  42. package/src/feishu/agent-context.ts +62 -0
  43. package/src/feishu/delivery.ts +188 -0
  44. package/src/feishu/inbound-attachments.ts +436 -0
  45. package/src/feishu-channel.ts +339 -0
  46. package/src/health.ts +233 -0
  47. package/src/host/agent.ts +181 -0
  48. package/src/host/commands.ts +63 -0
  49. package/src/host/core.ts +345 -0
  50. package/src/host/index.ts +12 -0
  51. package/src/host/interaction.ts +101 -0
  52. package/src/host/services.ts +320 -0
  53. package/src/index.ts +122 -0
  54. package/src/interactions/approvals.ts +154 -0
  55. package/src/interactions/pending.ts +162 -0
  56. package/src/interactions/questions.ts +274 -0
  57. package/src/questions.ts +8 -0
  58. package/src/routing/inbound.ts +524 -0
  59. package/src/service.ts +2138 -0
  60. package/src/session/manager.ts +352 -0
  61. package/src/session/repair.ts +157 -0
  62. package/src/session/request-policy.ts +192 -0
  63. package/src/session/routes.ts +165 -0
  64. package/src/session-commands.ts +310 -0
  65. package/src/session-export.ts +112 -0
  66. package/src/settings/catalog.ts +309 -0
  67. package/src/settings/contract.ts +508 -0
  68. package/src/settings/overlay.ts +164 -0
  69. package/src/settings/routes.ts +409 -0
  70. package/src/settings/surface.ts +773 -0
  71. package/src/stats.ts +485 -0
  72. package/src/streamer.ts +1108 -0
  73. 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. */
@@ -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
+