@modesto_ma/dsh-channels 0.5.0-beta.7 → 0.6.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/src/service.ts CHANGED
@@ -57,8 +57,7 @@ import type {
57
57
  } from './domain.ts'
58
58
  import { RouteStore } from './session/routes.ts'
59
59
  import { AnalyzeLauncher } from './analyze/launcher.ts'
60
- import { startAnalyzeServer } from './analyze/server.ts'
61
- import type { AnalyzeServer } from './analyze/server.ts'
60
+ import type { AnalyzeHandler } from './analyze/server.ts'
62
61
  import { InflightRepair } from './session/repair.ts'
63
62
  import { SessionManager } from './session/manager.ts'
64
63
  import type { LiveAgent, RenewCommit } from './session/manager.ts'
@@ -313,10 +312,6 @@ export class FeishuService {
313
312
  private readonly inbound: InboundRouter
314
313
  /** Log-platform analyze API business side (requests → shared analysis topics). */
315
314
  private readonly analyzeLauncher: AnalyzeLauncher
316
- /** Bound analyze listener while connected with `analyzeListen` set. */
317
- private analyzeServer: AnalyzeServer | undefined
318
- /** Why the analyze listener could not bind at the last connect. */
319
- private analyzeListenError: string | undefined
320
315
  /** Agent/session lifecycle: live registry, resume, presets, renew. */
321
316
  private readonly sessions: SessionManager
322
317
  /**
@@ -980,6 +975,8 @@ export class FeishuService {
980
975
  }
981
976
  }
982
977
 
978
+ /** The analyze API's business side, for the Web route. */
979
+ analyzeHandler(): AnalyzeHandler { return this.analyzeLauncher }
983
980
  isRunning(): boolean { return this.started }
984
981
  isRestarting(): boolean { return this.restarting }
985
982
  /** True while the panel's explicit connect action is in flight. */
@@ -1660,9 +1657,9 @@ export class FeishuService {
1660
1657
  // Background: the results only feed the panel and the log.
1661
1658
  void this.runScopeCheck(channel, appId, ownerLookup)
1662
1659
  if (this.config.slashCommandPanel) void this.runSlashCommandSync(channel)
1663
- // Before the listener opens, so every `running` record left is a previous instance's.
1660
+ // Before admission opens, so every `running` record left is a previous instance's.
1664
1661
  await this.analyzeLauncher.markInterrupted()
1665
- await this.openAnalyzeListener()
1662
+ this.analyzeLauncher.open()
1666
1663
  // Repair cards a previous instance left mid-flight (best-effort, background).
1667
1664
  void this.repair.repairAll().catch((error: unknown) => {
1668
1665
  this.ctx.logger?.warn?.('dsh-channels: inflight repair failed: %s', String(error))
@@ -1720,37 +1717,13 @@ export class FeishuService {
1720
1717
  )
1721
1718
  }
1722
1719
 
1723
- /**
1724
- * Bind the analyze listener when `analyzeListen` is set. A bind failure is
1725
- * logged and folded into the health snapshot (panel reasons, `/health`); the Feishu connection stays up.
1726
- */
1727
- private async openAnalyzeListener(): Promise<void> {
1728
- this.analyzeListenError = undefined
1729
- const listen = this.config.analyzeListen
1730
- if (listen === undefined) return
1731
- try {
1732
- this.analyzeServer = await startAnalyzeServer(
1733
- listen,
1734
- this.analyzeLauncher,
1735
- (message, ...args) => this.ctx.logger?.warn?.(message, ...args),
1736
- )
1737
- console.info('[dsh-channels] 日志分析接口已监听', listen)
1738
- } catch (error) {
1739
- this.analyzeListenError = messageOf(error)
1740
- this.ctx.logger?.error?.('dsh-channels: analyze listener on %s failed: %s', listen, this.analyzeListenError)
1741
- console.error('[dsh-channels] 日志分析接口监听失败:', listen, error)
1742
- }
1743
- }
1744
-
1745
1720
  /** Tear down: close the connection and dispose all live agents. */
1746
1721
  async shutdown(): Promise<void> {
1747
1722
  // Close admission first. In-flight Host commands receive cancellation
1748
1723
  // before their Agent/channel/storage dependencies are disposed, while
1749
1724
  // already queued route operations observe `started === false` and retire.
1750
1725
  this.started = false
1751
- const analyzeServer = this.analyzeServer
1752
- this.analyzeServer = undefined
1753
- await analyzeServer?.close()
1726
+ this.analyzeLauncher.close()
1754
1727
  await this.analyzeLauncher.drain()
1755
1728
  for (const controller of this.commandControllers) controller.abort()
1756
1729
  await Promise.allSettled([...this.commandOperations])
@@ -2488,8 +2461,15 @@ export class FeishuService {
2488
2461
  modelAdmission: ModelAdmission | undefined
2489
2462
  }): Promise<void> {
2490
2463
  const { spec, msg, current, live, result, compactBefore, renewAdmission, effortAdmission, modelAdmission } = input
2491
- if (spec.name === 'effort') effortAdmission?.controller.sync(live.agent.session.snapshotEvents())
2492
- if (spec.name === 'model') modelAdmission?.controller.sync(live.agent.session.snapshotEvents())
2464
+ // A settled switch (not a bare view) reconciles the Agent's selection even
2465
+ // when the folded policy looks unchanged, e.g. a reset after restart.
2466
+ const reconcile = !input.bare && result.kind === 'success' ? spec.name : undefined
2467
+ if (spec.name === 'effort') {
2468
+ effortAdmission?.controller.sync(live.agent.session.snapshotEvents(), live.agent, reconcile === 'effort' ? 'effort' : undefined)
2469
+ }
2470
+ if (spec.name === 'model') {
2471
+ modelAdmission?.controller.sync(live.agent.session.snapshotEvents(), live.agent, reconcile === 'model' ? 'model' : undefined)
2472
+ }
2493
2473
  if (input.bare && result.kind === 'success') {
2494
2474
  const picker = spec.name === 'model' && modelAdmission !== undefined
2495
2475
  ? await this.modelPicker(current, live, modelAdmission)
@@ -2710,7 +2690,6 @@ export class FeishuService {
2710
2690
  routeCount: this.routes.size(),
2711
2691
  liveAgentCount: this.sessions.liveCount(),
2712
2692
  inflightTurnCount,
2713
- ...(this.analyzeListenError === undefined ? {} : { analyzeListenError: this.analyzeListenError }),
2714
2693
  })
2715
2694
  }
2716
2695
 
@@ -3102,11 +3081,12 @@ function currentModelRoute(
3102
3081
  controller: RequestPolicyController,
3103
3082
  ): { provider: string | undefined; model: string | undefined } {
3104
3083
  const state = controller.state
3084
+ const selected = live.agent.model?.current()
3105
3085
  const header = live.agent.session.requestHeader?.()?.config
3106
3086
  return {
3107
- provider: state.effectiveModel?.provider
3087
+ provider: state.effectiveModel?.provider ?? selected?.provider
3108
3088
  ?? (typeof header?.provider === 'string' && header.provider !== '' ? header.provider : undefined),
3109
- model: state.effectiveModel?.model
3089
+ model: state.effectiveModel?.model ?? selected?.model
3110
3090
  ?? (typeof header?.model === 'string' && header.model !== '' ? header.model : undefined),
3111
3091
  }
3112
3092
  }
@@ -207,7 +207,7 @@ export class SessionManager {
207
207
 
208
208
  /** Borrow one foreign live agent and mount this channel's scoped policy. */
209
209
  private async borrow(sessionId: string, agent: Agent): Promise<LiveAgent> {
210
- const borrowed = borrowLiveAgent(agent, this.mountRequestPolicy(agent.ctx, agent.session))
210
+ const borrowed = borrowLiveAgent(agent, this.mountRequestPolicy(agent.ctx, agent))
211
211
  this.live.set(sessionId, borrowed)
212
212
  // Best-effort tool backfill (D7): the foreign owner may have composed the
213
213
  // session without this plugin's setup (web UI resolver, previous plugin
@@ -253,13 +253,13 @@ export class SessionManager {
253
253
  }
254
254
 
255
255
  /** Mount one mutable Session-aware request policy outside every other wrapper. */
256
- mountRequestPolicy(agentCtx: AgentContext, session?: Session): () => void {
256
+ mountRequestPolicy(agentCtx: AgentContext, agent?: Agent): () => void {
257
257
  const controller = installRequestPolicy(agentCtx, {
258
258
  effort: this.deps.config.reasoningEffort,
259
259
  provider: this.deps.config.provider,
260
260
  model: this.deps.config.model,
261
261
  })
262
- if (session !== undefined) controller.sync(session.snapshotEvents())
262
+ if (agent !== undefined) controller.sync(agent.session.snapshotEvents(), agent)
263
263
  this.policies.set(agentCtx, controller)
264
264
  return () => {
265
265
  controller.dispose()
@@ -273,7 +273,7 @@ export class SessionManager {
273
273
  if (controller === undefined) {
274
274
  throw new Error('dsh-channels: request policy is not mounted for this Agent')
275
275
  }
276
- controller.sync(agent.session.snapshotEvents())
276
+ controller.sync(agent.session.snapshotEvents(), agent)
277
277
  return controller
278
278
  }
279
279
 
@@ -7,6 +7,12 @@
7
7
  * rewrite the request after `next()` resolves. The outer channel wrapper
8
8
  * therefore writes last.
9
9
  *
10
+ * DSH 0.2.2 moved model choice onto the Agent (`agent.model`) and fails a
11
+ * step whose `agent/request` listener changes provider, model, or reasoning
12
+ * effort. On such hosts the waterfall passes the request through and `sync`
13
+ * records the effective policy with `agent.model.select()` instead; DSH 0.2.1
14
+ * hosts (no `agent.model`) keep the waterfall override.
15
+ *
10
16
  * Session overrides are recovered from successful command lifecycles
11
17
  * (`/effort`, `/model`). Reusing Harness' durable `command/run` /
12
18
  * `command/done` pair avoids inventing an out-of-repo Session event that
@@ -15,7 +21,7 @@
15
21
  * @module dsh-channels/session/request-policy
16
22
  */
17
23
 
18
- import type { AgentContext, CommandId, SessionEvent } from '../dsh-types.ts'
24
+ import type { Agent, AgentContext, AgentModel, CommandId, SessionEvent } from '../dsh-types.ts'
19
25
 
20
26
  /** A model selection override: a bare model keeps the composed provider. */
21
27
  export interface ModelOverride {
@@ -51,8 +57,16 @@ export interface RequestPolicyState {
51
57
 
52
58
  export interface RequestPolicyController {
53
59
  readonly state: RequestPolicyState
54
- /** Re-fold the durable log after resume or a settled command lifecycle. */
55
- sync(events: readonly SessionEvent[]): void
60
+ /**
61
+ * Re-fold the durable log after resume or a settled command lifecycle.
62
+ * With an `agent` whose host owns model selection, a changed policy is
63
+ * recorded through `agent.model.select()` for the next step. `reconcile`
64
+ * names a just-settled `/effort` or `/model` switch: the policy is recorded
65
+ * even when this controller saw no change, so a reset also clears a
66
+ * selection persisted before the controller existed (earlier plugin
67
+ * instance or since-removed config default).
68
+ */
69
+ sync(events: readonly SessionEvent[], agent?: Agent, reconcile?: 'effort' | 'model'): void
56
70
  /** Remove only this controller's Agent-scoped request wrapper. */
57
71
  dispose(): void
58
72
  }
@@ -134,6 +148,39 @@ export function foldModelOverride(events: readonly SessionEvent[]): ModelOverrid
134
148
  return foldRequestPolicy(events).model
135
149
  }
136
150
 
151
+ /** Policy values last recorded through `agent.model`, compared to skip no-op selections. */
152
+ interface AppliedPolicy {
153
+ readonly effort: string | undefined
154
+ readonly model: ModelOverride | undefined
155
+ }
156
+
157
+ function sameModel(left: ModelOverride | undefined, right: ModelOverride | undefined): boolean {
158
+ return left?.provider === right?.provider && left?.model === right?.model
159
+ }
160
+
161
+ /**
162
+ * Record one effective policy as the Agent's next-step selection. A policy
163
+ * without a model keeps the current route, except right after a model
164
+ * override is cleared, when the Agent's live options (deployment default)
165
+ * supply it again. Without a resolvable provider and model nothing is recorded.
166
+ */
167
+ function selectPolicy(
168
+ model: AgentModel,
169
+ agent: Agent,
170
+ effort: string | undefined,
171
+ target: ModelOverride | undefined,
172
+ modelCleared: boolean,
173
+ ): void {
174
+ const current = model.current()
175
+ const base = modelCleared && agent.options.provider !== undefined && agent.options.model !== undefined
176
+ ? { provider: agent.options.provider, model: agent.options.model }
177
+ : current
178
+ const provider = target?.provider ?? base?.provider
179
+ const modelId = target?.model ?? base?.model
180
+ if (provider === undefined || provider === '' || modelId === undefined || modelId === '') return
181
+ model.select({ provider, model: modelId, ...(effort === undefined ? {} : { reasoningEffort: effort }) })
182
+ }
183
+
137
184
  /** Install the mutable request policy outside every other request wrapper. */
138
185
  export function installRequestPolicy(
139
186
  agentCtx: AgentContext,
@@ -142,12 +189,16 @@ export function installRequestPolicy(
142
189
  let effortOverride: string | undefined
143
190
  let modelOverride: ModelOverride | undefined
144
191
  let disposed = false
192
+ /** Last policy recorded through `agent.model`; undefined until the first selecting sync. */
193
+ let applied: AppliedPolicy | undefined
145
194
  const configuredModel: ModelOverride | undefined = config.provider !== undefined && config.model !== undefined
146
195
  ? { provider: config.provider, model: config.model }
147
196
  : undefined
148
197
 
149
- const disposeListener = agentCtx.on('agent/request', async (_payload, next) => {
198
+ const disposeListener = agentCtx.on('agent/request', async (payload, next) => {
150
199
  const request = await next()
200
+ // Hosts that own model selection reject a changed route or effort here.
201
+ if (payload.agent?.model !== undefined) return request
151
202
  const effort = effortOverride ?? config.effort
152
203
  const model = modelOverride ?? configuredModel
153
204
  if (effort === undefined && model === undefined) return request
@@ -178,10 +229,18 @@ export function installRequestPolicy(
178
229
  : configuredModel !== undefined ? 'config' : 'deployment-default',
179
230
  }
180
231
  },
181
- sync(events) {
232
+ sync(events, agent, reconcile) {
182
233
  const folded = foldRequestPolicy(events)
183
234
  effortOverride = folded.effort
184
235
  modelOverride = folded.model
236
+ const model = agent?.model
237
+ if (agent === undefined || model === undefined || disposed) return
238
+ const next: AppliedPolicy = { effort: effortOverride ?? config.effort, model: modelOverride ?? configuredModel }
239
+ const previous = applied ?? { effort: undefined, model: undefined }
240
+ applied = next
241
+ if (reconcile === undefined && previous.effort === next.effort && sameModel(previous.model, next.model)) return
242
+ const modelCleared = next.model === undefined && (reconcile === 'model' || previous.model !== undefined)
243
+ selectPolicy(model, agent, next.effort, next.model, modelCleared)
185
244
  },
186
245
  dispose() {
187
246
  if (disposed) return
@@ -7,29 +7,45 @@
7
7
  * @module dsh-channels/settings/contract
8
8
  */
9
9
 
10
- /** Same-origin routes the settings panel talks to. */
11
- export const STATUS_ROUTE = '/dsh-channels/status'
12
- export const PANEL_ROUTE = '/dsh-channels/panel'
13
- export const CONFIG_ROUTE = '/dsh-channels/config'
14
- export const CREDENTIALS_ROUTE = '/dsh-channels/credentials'
10
+ /**
11
+ * Settings panel routes: exact DSH `/api` Fetch routes named
12
+ * `/api/dsh-channels.<method>`. Reads are GET; every write is POST, because
13
+ * DSH exact `/api` routes accept only GET, HEAD, and POST.
14
+ */
15
+ export const STATUS_ROUTE = '/api/dsh-channels.status'
16
+ export const PANEL_ROUTE = '/api/dsh-channels.panel'
17
+ /** Persist a partial config edit (POST, body `{ values }`). */
18
+ export const SAVE_CONFIG_ROUTE = '/api/dsh-channels.saveConfig'
19
+ /** Drop every Web override (POST, no body). */
20
+ export const CLEAR_CONFIG_ROUTE = '/api/dsh-channels.clearConfig'
21
+ /** Store credential values (POST, body `SaveCredentialsRequest`). */
22
+ export const SAVE_CREDENTIALS_ROUTE = '/api/dsh-channels.saveCredentials'
23
+ /** Remove one stored credential (POST, body `{ field }`). */
24
+ export const CLEAR_CREDENTIAL_ROUTE = '/api/dsh-channels.clearCredential'
15
25
  /**
16
26
  * Connection lifecycle action (POST). While the channel runs it restarts the
17
27
  * long connection; while it is stopped it opens one — so the panel has exactly
18
28
  * one "connect / reconnect" button. Credentials are resolved again either way.
19
29
  */
20
- export const RECONNECT_ROUTE = '/dsh-channels/reconnect'
30
+ export const RECONNECT_ROUTE = '/api/dsh-channels.reconnect'
21
31
  /** 模型/推理档位/预设选择器的宿主目录(provider → model → effort 级联刷新)。 */
22
- export const CATALOG_ROUTE = '/dsh-channels/catalog'
23
- /** 访问与授权只读快照(GET/HEAD):准入模式、管理员、群/用户/知识库白名单、待处理申请。 */
24
- export const ACCESS_ROUTE = '/dsh-channels/access'
25
- /** 群白名单增删(POST/DELETE,body `{ chatId, name? }` / `{ chatId }`)。 */
26
- export const ACCESS_GROUPS_ROUTE = '/dsh-channels/access/groups'
27
- /** 用户白名单增删(POST/DELETE,body `{ userId, name? }` / `{ userId }`)。 */
28
- export const ACCESS_USERS_ROUTE = '/dsh-channels/access/users'
29
- /** 知识库白名单增删(POST/DELETE,body `{ userId, name? }` / `{ userId }`)。 */
30
- export const ACCESS_WIKI_USERS_ROUTE = '/dsh-channels/access/wiki-users'
32
+ export const CATALOG_ROUTE = '/api/dsh-channels.catalog'
33
+ /** 访问与授权只读快照(GET):准入模式、管理员、群/用户/知识库白名单、待处理申请。 */
34
+ export const ACCESS_ROUTE = '/api/dsh-channels.access'
35
+ /** 授权一个群(POST,body `{ chatId, name? }`)。 */
36
+ export const ADD_ACCESS_GROUP_ROUTE = '/api/dsh-channels.addAccessGroup'
37
+ /** 撤销一个群(POST,body `{ chatId }`)。 */
38
+ export const REMOVE_ACCESS_GROUP_ROUTE = '/api/dsh-channels.removeAccessGroup'
39
+ /** 用户白名单添加(POST,body `{ userId, name? }`)。 */
40
+ export const ADD_ACCESS_USER_ROUTE = '/api/dsh-channels.addAccessUser'
41
+ /** 用户白名单移除(POST,body `{ userId }`)。 */
42
+ export const REMOVE_ACCESS_USER_ROUTE = '/api/dsh-channels.removeAccessUser'
43
+ /** 知识库白名单添加(POST,body `{ userId, name? }`)。 */
44
+ export const ADD_WIKI_USER_ROUTE = '/api/dsh-channels.addWikiUser'
45
+ /** 知识库白名单移除(POST,body `{ userId }`)。 */
46
+ export const REMOVE_WIKI_USER_ROUTE = '/api/dsh-channels.removeWikiUser'
31
47
  /** 结算一条待处理授权申请(POST,body `{ token, decision }`)。 */
32
- export const ACCESS_REQUESTS_ROUTE = '/dsh-channels/access/requests'
48
+ export const RESOLVE_ACCESS_REQUEST_ROUTE = '/api/dsh-channels.resolveAccessRequest'
33
49
  /**
34
50
  * 打开宿主机的系统目录选择框(POST,无请求体),返回绝对路径。
35
51
  *
@@ -37,9 +53,9 @@ export const ACCESS_REQUESTS_ROUTE = '/dsh-channels/access/requests'
37
53
  * 会注册实际选择逻辑;远程/无头会话的 browse 后端不可用,面板不下发
38
54
  * [`EditableFieldWire.browse`],保留手写输入。
39
55
  */
40
- export const PICK_DIRECTORY_ROUTE = '/dsh-channels/pick-directory'
41
- /** 会话的飞书来源(GET/HEAD,`?sessionId=`):对话右侧栏「飞书来源」标签与会话头部入口使用。 */
42
- export const SESSION_ORIGIN_ROUTE = '/dsh-channels/session-origin'
56
+ export const PICK_DIRECTORY_ROUTE = '/api/dsh-channels.pickDirectory'
57
+ /** 会话的飞书来源(GET,`?sessionId=`):对话右侧栏「飞书来源」标签与会话头部入口使用。 */
58
+ export const SESSION_ORIGIN_ROUTE = '/api/dsh-channels.sessionOrigin'
43
59
 
44
60
  /** Where one Feishu-routed Session came from; names are resolved live and may be absent. */
45
61
  export interface SessionOriginWire {
@@ -76,7 +92,7 @@ export interface SessionOriginWire {
76
92
  * names and avatars for the users and chats the panel shows. At most
77
93
  * {@link PROFILES_MAX_IDS} ids per list.
78
94
  */
79
- export const PROFILES_ROUTE = '/dsh-channels/profiles'
95
+ export const PROFILES_ROUTE = '/api/dsh-channels.profiles'
80
96
 
81
97
  /** Most `user_id`s, and separately most `chat_id`s, one profiles request may name. */
82
98
  export const PROFILES_MAX_IDS = 200
@@ -111,7 +127,7 @@ export interface SessionOriginResponse {
111
127
  * `cordis.patch.yml`(见 README「Web 设置面板」)。
112
128
  *
113
129
  * 刻意不包含 `appId` / `appSecret`:它们是**凭据引用名**,面板改为直接
114
- * 编辑引用背后的值(`PUT /dsh-channels/credentials`,经 DSH 凭据服务写入),
130
+ * 编辑引用背后的值(`POST /api/dsh-channels.saveCredentials`,经 DSH 凭据服务写入),
115
131
  * 因此不会落进覆盖层文件。
116
132
  *
117
133
  * `baseDir`(工作区根目录 = 模型/工具产生文件的落点)可覆盖,但面板只接受
@@ -172,7 +188,7 @@ export const EDITABLE_KEYS = [
172
188
  'feishuTools.wikiWrite',
173
189
  'wikiRoots',
174
190
  // 日志平台分析接口(访问令牌是凭据引用 analyzeToken,值写在 DSH 凭据里)
175
- 'analyzeListen',
191
+ 'analyzeEnabled',
176
192
  // 日志
177
193
  'loggerLevel',
178
194
  ] as const
@@ -187,7 +203,6 @@ export const RESTART_KEYS = [
187
203
  'dmMode',
188
204
  'requireMention',
189
205
  'slashCommandPanel',
190
- 'analyzeListen',
191
206
  'refreshMs',
192
207
  'statusRefreshMs',
193
208
  'loggerLevel',
@@ -305,7 +320,7 @@ export interface SelectCatalogWire {
305
320
  help?: string
306
321
  }
307
322
 
308
- /** `GET /dsh-channels/catalog` payload: the model-facing selector catalog. */
323
+ /** `GET /api/dsh-channels.catalog` payload: the model-facing selector catalog. */
309
324
  export interface CatalogWire {
310
325
  provider: SelectCatalogWire
311
326
  model: SelectCatalogWire
@@ -499,7 +514,7 @@ export interface AccessRequestWire {
499
514
  }
500
515
 
501
516
  /**
502
- * `GET /dsh-channels/access` payload: admission mode, the administrator
517
+ * `GET /api/dsh-channels.access` payload: admission mode, the administrator
503
518
  * reference, the group, user, and wiki whitelists, and the still-pending
504
519
  * authorization requests.
505
520
  */
@@ -528,12 +543,12 @@ export interface AccessWire {
528
543
  requests: AccessRequestWire[]
529
544
  }
530
545
 
531
- /** Write responses for every `/dsh-channels/access*` mutation. */
546
+ /** Write responses for every access mutation route. */
532
547
  export type AccessMutationResponse =
533
548
  | { ok: true; access: AccessWire }
534
549
  | { ok: false; error: string }
535
550
 
536
- /** `GET /dsh-channels/panel` payload. */
551
+ /** `GET /api/dsh-channels.panel` payload. */
537
552
  export interface PanelWire {
538
553
  status: ChannelStatusWire
539
554
  config: PanelConfigWire
@@ -542,7 +557,7 @@ export interface PanelWire {
542
557
  }
543
558
 
544
559
  /**
545
- * `POST /dsh-channels/pick-directory` response. `canceled` is a normal
560
+ * `POST /api/dsh-channels.pickDirectory` response. `canceled` is a normal
546
561
  * outcome (the operator dismissed the OS dialog), not an error; `path` is an
547
562
  * absolute directory the Host's native chooser returned.
548
563
  */
@@ -551,7 +566,7 @@ export type PickDirectoryResponse =
551
566
  | { ok: true; canceled: true }
552
567
  | { ok: false; error: string }
553
568
 
554
- /** `PUT /dsh-channels/config` body. */
569
+ /** `POST /api/dsh-channels.saveConfig` body. */
555
570
  export interface SaveConfigRequest {
556
571
  values: EditableValues
557
572
  }
@@ -567,30 +582,30 @@ export interface FieldIssueWire {
567
582
  index?: number
568
583
  }
569
584
 
570
- /** `PUT /dsh-channels/config` response. */
585
+ /** `POST /api/dsh-channels.saveConfig` response. */
571
586
  export type SaveConfigResponse =
572
587
  | { ok: true; panel: PanelWire; clearedKeys: EditableKey[] }
573
588
  | { ok: false; error: string; issues?: FieldIssueWire[] }
574
589
 
575
- /** `POST /dsh-channels/reconnect` response. */
590
+ /** `POST /api/dsh-channels.reconnect` response. */
576
591
  export type ReconnectResponse =
577
592
  | { ok: true; panel: PanelWire }
578
593
  /** `unconfigured` tells the panel to route the user to the credentials form. */
579
594
  | { ok: false; error: string; unconfigured?: boolean }
580
595
 
581
- /** `DELETE /dsh-channels/config` response. */
596
+ /** `POST /api/dsh-channels.clearConfig` response. */
582
597
  export type ClearConfigResponse =
583
598
  | { ok: true; panel: PanelWire }
584
599
  | { ok: false; error: string }
585
600
 
586
- /** `PUT /dsh-channels/credentials` body: only provided, non-empty values are written. */
601
+ /** `POST /api/dsh-channels.saveCredentials` body: only provided, non-empty values are written. */
587
602
  export interface SaveCredentialsRequest {
588
603
  appId?: string
589
604
  appSecret?: string
590
605
  analyzeToken?: string
591
606
  }
592
607
 
593
- /** `DELETE /dsh-channels/credentials` body. */
608
+ /** `POST /api/dsh-channels.clearCredential` body. */
594
609
  export interface ClearCredentialRequest {
595
610
  field: CredentialField
596
611
  }