@hyzyn/dsh-docker 0.7.1 → 0.7.3

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/lib/index.d.ts CHANGED
@@ -2,8 +2,9 @@
2
2
  * @hyzyn/dsh-docker — DSH Web GUI 的 Docker 容器面板(host 半体)。
3
3
  *
4
4
  * 与 dsh-tty 的关系(方案 A:独立插件,tty 零改动):
5
- * - **连接簿**:只读复用 tty 的 settings 命名空间(`ctx.settings.get('tty')`
6
- * 的 `sshHosts`)。tty 未安装时退化为「只支持本机 / 内联 SSH 字段」。
5
+ * - **连接簿**:只读复用 tty 的 entry settings(DSH ≥0.1.7 的
6
+ * `settings.describe()`,经 kit 的 `readSettingsEntry(ctx, 'tty')`)的
7
+ * `sshHosts`。tty 未安装时退化为「只支持本机 / 内联 SSH 字段」。
7
8
  * - **主机指纹**:本插件自持一份 `hostKeys`(TOFU),并优先读取 tty 已记录
8
9
  * 的指纹作为种子,避免同一主机在两处重复确认。
9
10
  * - **执行通道**:自持池化 SSH exec(src/ssh-exec.ts),与 tty 的 PTY 会话
@@ -16,6 +17,7 @@
16
17
  * 显式打开。agent 工具同样受这两个开关约束(未开启时连工具都不注册)。
17
18
  */
18
19
  import type { Context } from '@deepseek-ai/cordis';
20
+ import z from '@deepseek-ai/schemastery';
19
21
  import { DockerApi, assertBin, assertImageRef, parseImageHistoryJson, parseImageHistoryText, parseImageInspectJson, parseInspectJson, parsePsJson, parseStatsJson } from './docker.js';
20
22
  import type { DockerTarget, ResolvedTarget } from './docker.js';
21
23
  import type { HostKeyRecord, SshSpec } from './ssh-exec.js';
@@ -45,6 +47,15 @@ export interface Config {
45
47
  /** SSH 主机指纹记录(TOFU,随 settings 落盘)。 */
46
48
  hostKeys?: HostKeyRecord[];
47
49
  }
50
+ /**
51
+ * 运行时 Config schema——DSH ≥0.1.7 起同时就是本插件的 settings 存储。
52
+ *
53
+ * 全部字段都标 `.volatile()`:它们都是卡片可改项(见 KNOWN_CONFIG_KEYS),而
54
+ * `settings.update(entryId, patch)` 只接受 volatile 路径;loader 对 volatile-only
55
+ * 变更原地更新引用并发 `loader/volatile-update`,不重挂插件——插件订阅后走
56
+ * `applySection` 热应用(见 kit 的 settingsEntryScope)。
57
+ */
58
+ export declare const Config: z;
48
59
  /** 解析后的运行期配置(settings 与 composition 两条来源统一到这一形状)。 */
49
60
  interface LiveConfig {
50
61
  enabled: boolean;
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import z from '@deepseek-ai/schemastery';
2
- import { definePlugin } from '@hyzyn/dsh-kit';
2
+ import { definePlugin, plainConfig, readSettingsEntry, settingsEntryScope, suppressAutoSettingsPage } from '@hyzyn/dsh-kit';
3
3
  import { defineTool } from '@deepseek-ai/dsh-tools';
4
4
  import * as dns from 'node:dns';
5
5
  import { DockerApi, assertBin, assertImageRef, assertName, assertRef, assertSince, createRunner, parseImageHistoryJson, parseImageHistoryText, parseContainerEvent, parseEventsJson, parseImageInspectJson, parseInspectJson, parsePsJson, parseStatsJson, } from './docker.js';
@@ -27,18 +27,26 @@ const HOST_KEY_SCHEMA = z.object({
27
27
  /** 旧版单指纹字段:仅作迁移输入(sanitizeHostKeys 会并进 fingerprints)。 */
28
28
  fingerprint: z.string().default(''),
29
29
  });
30
- const DOCKER_SETTINGS_SCHEMA = z.object({
31
- enabled: z.boolean().default(true),
32
- announceToAgent: z.boolean().default(true),
33
- dockerBin: z.string().default('docker'),
34
- allowMutations: z.boolean().default(false),
35
- allowExec: z.boolean().default(false),
36
- execTimeoutSec: z.natural().max(120).default(30),
37
- pollIntervalSec: z.natural().max(60).default(5),
38
- logTailDefault: z.natural().max(5000).default(200),
39
- maxOutputKb: z.natural().max(8192).default(512),
40
- targets: z.array(TARGET_SCHEMA).default([]),
41
- hostKeys: z.array(HOST_KEY_SCHEMA).default([]),
30
+ /**
31
+ * 运行时 Config schema——DSH ≥0.1.7 起同时就是本插件的 settings 存储。
32
+ *
33
+ * 全部字段都标 `.volatile()`:它们都是卡片可改项(见 KNOWN_CONFIG_KEYS),而
34
+ * `settings.update(entryId, patch)` 只接受 volatile 路径;loader 对 volatile-only
35
+ * 变更原地更新引用并发 `loader/volatile-update`,不重挂插件——插件订阅后走
36
+ * `applySection` 热应用(见 kit 的 settingsEntryScope)。
37
+ */
38
+ export const Config = z.object({
39
+ enabled: z.boolean().default(true).volatile(),
40
+ announceToAgent: z.boolean().default(true).volatile(),
41
+ dockerBin: z.string().default('docker').volatile(),
42
+ allowMutations: z.boolean().default(false).volatile(),
43
+ allowExec: z.boolean().default(false).volatile(),
44
+ execTimeoutSec: z.natural().max(120).default(30).volatile(),
45
+ pollIntervalSec: z.natural().max(60).default(5).volatile(),
46
+ logTailDefault: z.natural().max(5000).default(200).volatile(),
47
+ maxOutputKb: z.natural().max(8192).default(512).volatile(),
48
+ targets: z.array(TARGET_SCHEMA).default([]).volatile(),
49
+ hostKeys: z.array(HOST_KEY_SCHEMA).default([]).volatile(),
42
50
  });
43
51
  /** 可经 HTTP POST 写入的配置键(白名单)。 */
44
52
  const KNOWN_CONFIG_KEYS = new Set([
@@ -432,7 +440,7 @@ export function formatBytes(value) {
432
440
  const text = unit === 0 ? String(Math.round(size)) : size.toFixed(size >= 100 ? 0 : 1);
433
441
  return `${text} ${units[unit] ?? 'B'}`;
434
442
  }
435
- /** 从 tty 的 settings 命名空间读取连接簿(只读;tty 未安装时为空表)。 */
443
+ /** 从 tty 的 entry settings 读取连接簿(只读;tty 未安装时为空表)。 */
436
444
  function readTtyBooks(settings) {
437
445
  const out = new Map();
438
446
  if (settings === undefined)
@@ -494,7 +502,13 @@ export function resolveTarget(target, books) {
494
502
  if (target.book !== undefined && target.book !== '') {
495
503
  const spec = books.get(target.book);
496
504
  if (spec === undefined) {
497
- return { error: `目标「${target.name}」引用的连接簿条目不存在:${target.book}(请在 tty 终端面板的设置卡片里添加,或改为内联 host/username)` };
505
+ /*
506
+ * 指引必须指向**用户真能做的动作**。旧文案说"请在 tty 终端面板的设置卡片里添加"——
507
+ * 但那张卡片管的是 tty 自己的连接簿条目,改不了 docker 目标引用的名字:用户照着找
508
+ * 只会扑空(实测:目标引用 HS-248、连接簿里只有 HS_248_ADMIN,进 tty 卡片什么也改不了)。
509
+ * 真正要改的字段是**本卡片这条目标的「连接簿」下拉**,所以先把这里说清楚,再说备选。
510
+ */
511
+ return { error: `目标「${target.name}」引用的连接簿条目不存在:${target.book}(在本卡片这条目标的「连接簿」下拉里改选一个已有条目;或把 tty 终端面板的连接簿补一个同名条目;也可清空下拉改为手填 host/username)` };
498
512
  }
499
513
  return { resolved: { name: target.name, kind: 'ssh', spec } };
500
514
  }
@@ -520,8 +534,10 @@ export function resolveTarget(target, books) {
520
534
  * ------------------------------------------------------------------ */
521
535
  const plugin = definePlugin({
522
536
  name: 'docker',
523
- apply(ctx, config) {
524
- let live = normalizeConfig((config ?? {}));
537
+ apply(ctx, rawConfig) {
538
+ // volatile 字段解析后是 `{ get() }` 引用,先还原成纯数据(见 @hyzyn/dsh-kit 的 plainConfig)。
539
+ const config = plainConfig((rawConfig ?? {}));
540
+ let live = normalizeConfig(config);
525
541
  if (!live.enabled)
526
542
  return;
527
543
  const logger = {
@@ -530,6 +546,11 @@ const plugin = definePlugin({
530
546
  };
531
547
  /* ---------- 主机指纹(TOFU,本插件自持;tty 记录作种子) ---------- */
532
548
  let settingsScope;
549
+ /**
550
+ * 只读他人 entry(如 tty 的连接簿):旧 `settings.get(ns)` 在新宿主已不存在,
551
+ * 改走 `settings.describe()` 的 entry 查找(kit 的 readSettingsEntry)。settings
552
+ * 子上下文就绪前保持 undefined,调用方当空表处理——与旧行为一致。
553
+ */
533
554
  let settingsApi;
534
555
  const ttySeed = () => {
535
556
  const map = new Map();
@@ -583,7 +604,7 @@ const plugin = definePlugin({
583
604
  const remote = new RemoteExec(logger, hostKeyStore);
584
605
  /**
585
606
  * 当前生效的 targets:**以 settings 解析值为准**。
586
- * 为什么不能只读内存里的 live:settings 命名空间注册是异步的,挂载后存在一个
607
+ * 为什么不能只读内存里的 live:settings 解析是异步的(服务就绪后才读一次),存在一个
587
608
  * 窗口期内 live 仍是 composition 配置(targets 为空)。若此时 GET /config 被
588
609
  * 调用,卡片会拿到空目标列表,用户随后保存就把空数组写回 → 目标被清空。
589
610
  */
@@ -2633,7 +2654,7 @@ const plugin = definePlugin({
2633
2654
  writeJson(res, 400, { error: error instanceof Error ? error.message : String(error) });
2634
2655
  return;
2635
2656
  }
2636
- // settings/updated 会触发 applySection;无事件时也应用一次(幂等)
2657
+ // volatile 更新事件会触发 applySection;无事件时也应用一次(幂等)
2637
2658
  }
2638
2659
  // applySection 也可能抛(tools.register / systemPrompt.section,D36):
2639
2660
  // 此时配置已落盘,必须把原因带回给用户,而不是一个空 400/500。
@@ -2958,14 +2979,17 @@ const plugin = definePlugin({
2958
2979
  return () => dispose();
2959
2980
  }, 'dsh-docker: web routes');
2960
2981
  });
2961
- /* ---------- settings 命名空间 ---------- */
2982
+ /* ---------- settings ---------- */
2962
2983
  ctx.inject(['settings'], (settingsCtx) => {
2963
2984
  settingsCtx.effect(() => {
2964
- const settings = settingsCtx.settings;
2965
- const scope = settings.register('docker', DOCKER_SETTINGS_SCHEMA, { base: live });
2966
- settingsApi = settings;
2985
+ const scope = settingsEntryScope(settingsCtx, 'docker');
2986
+ if (scope === undefined)
2987
+ return () => { };
2988
+ // 卡片是自定义页(plugins.row.config),别再让 DSH 为本 entry 自动生成一份。
2989
+ const offAutoPage = suppressAutoSettingsPage(settingsCtx, ctx);
2967
2990
  settingsScope = scope;
2968
- // 注册后立即读一次解析值(schema 默认值 ← composition base ← 用户层)
2991
+ settingsApi = { get: (ns) => readSettingsEntry(settingsCtx, ns) };
2992
+ // 立刻读一次 resolved 值(schema 默认值 ← composition base ← 用户层)
2969
2993
  const resolved = scope.get();
2970
2994
  if (typeof resolved === 'object' && resolved !== null)
2971
2995
  live = normalizeConfig(resolved);
@@ -2973,14 +2997,10 @@ const plugin = definePlugin({
2973
2997
  console.log(`[dsh-docker] settings resolved (keys=${Object.keys(diag).join('|')}, targets=${Array.isArray(diag.targets) ? String(diag.targets.length) : 'not-array'}, hostKeys=${Array.isArray(diag.hostKeys) ? String(diag.hostKeys.length) : 'not-array'})`);
2974
2998
  refreshTools();
2975
2999
  refreshAnnouncement();
2976
- const events = settingsCtx;
2977
- const off = events.events.on('settings/updated', (ns, next) => {
2978
- if (ns !== 'docker' || typeof next !== 'object' || next === null)
2979
- return;
2980
- applySection(next);
2981
- });
3000
+ const off = scope.onChanged(() => applySection(scope.get()));
2982
3001
  return () => {
2983
3002
  off();
3003
+ offAutoPage();
2984
3004
  settingsScope = undefined;
2985
3005
  settingsApi = undefined;
2986
3006
  };