@dsh-plus/subagent-model 0.1.2 → 0.1.4

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/client.js CHANGED
@@ -611,9 +611,78 @@ const name = "dsh-plus-subagent-model";
611
611
  const inject = [
612
612
  "slots",
613
613
  "locale",
614
- "settingsScope",
615
- "connection"
614
+ "connection",
615
+ "remote"
616
616
  ];
617
+ /**
618
+ * api 直连实现的命名空间 scope(官方配置页 tab 同款模式)。
619
+ * 读:describe 取命名空间视图(脱敏 value/revision + 顶层 writable);
620
+ * 刷新:remote `settings/document-updated`(按命名空间过滤)与
621
+ * `connection/reset`;generation 防旧读覆盖新发布。任何页面 origin
622
+ * (loopback 或 tailnet 信任域名)下行为一致。
623
+ */
624
+ function createApiScope(api, ns, c) {
625
+ let state = {
626
+ status: "loading",
627
+ value: void 0,
628
+ revision: void 0,
629
+ writable: false
630
+ };
631
+ const listeners = /* @__PURE__ */ new Set();
632
+ let generation = 0;
633
+ const publish = (next) => {
634
+ state = next;
635
+ for (const listener of [...listeners]) listener();
636
+ };
637
+ const load = async () => {
638
+ const gen = ++generation;
639
+ let response;
640
+ try {
641
+ response = await api.describe({});
642
+ } catch {
643
+ return;
644
+ }
645
+ if (gen !== generation || !response.result.ok) return;
646
+ const writable = response.result.value?.writable ?? false;
647
+ const view = response.result.value?.namespaces.find((candidate) => candidate.ns === ns);
648
+ if (view === void 0) {
649
+ publish({
650
+ status: "unavailable",
651
+ value: void 0,
652
+ revision: void 0,
653
+ writable
654
+ });
655
+ return;
656
+ }
657
+ publish({
658
+ status: "ready",
659
+ value: view.value,
660
+ revision: view.revision,
661
+ writable
662
+ });
663
+ };
664
+ const refresh = (namespace) => {
665
+ if (namespace !== void 0 && namespace !== ns) return;
666
+ load();
667
+ };
668
+ const disposers = [c.get("remote").$on("settings/document-updated", refresh), c.on("connection/reset", () => {
669
+ load();
670
+ })];
671
+ c.effect(() => () => {
672
+ for (const dispose of disposers) dispose();
673
+ }, "subagent-model: settings scope");
674
+ load();
675
+ return {
676
+ getSnapshot: () => state,
677
+ subscribe: (listener) => {
678
+ listeners.add(listener);
679
+ return () => {
680
+ listeners.delete(listener);
681
+ };
682
+ },
683
+ load
684
+ };
685
+ }
617
686
  function apply(ctx) {
618
687
  const c = ctx;
619
688
  const tag = injectStyle();
@@ -624,8 +693,8 @@ function apply(ctx) {
624
693
  zh,
625
694
  en
626
695
  }), "subagent-model: locale");
627
- const scope = c.settingsScope.bind({ namespace: SETTINGS_NS });
628
696
  const api = c.get("connection").api.settings;
697
+ const scope = createApiScope(api, SETTINGS_NS, c);
629
698
  c.slots.inject("settings.plugin.item", () => c.slots.register({
630
699
  name: "settings.plugin.item",
631
700
  key: SETTINGS_NS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-plus/subagent-model",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "dsh-plus service+ui plugin: 为 subagent/subagent_fork 等子代理按提供商配置模型与思考程度(或继承主代理)",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -26,24 +26,24 @@
26
26
  "@deepseek-ai/dsh-client-runtime",
27
27
  "@deepseek-ai/dsh-client-locale",
28
28
  "@deepseek-ai/dsh-client-connection",
29
- "@deepseek-ai/dsh-client-ui-settings"
29
+ "@deepseek-ai/dsh-api-remotes"
30
30
  ],
31
31
  "platform": "web"
32
32
  }
33
33
  },
34
34
  "dependencies": {
35
35
  "@deepseek-ai/cordis": "4.0.1",
36
- "@deepseek-ai/dsh-settings": "0.1.0-rc.7",
36
+ "@deepseek-ai/dsh-settings": "0.1.0-rc.8",
37
37
  "@deepseek-ai/schemastery": "3.18.1"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@deepseek-ai/cordis": "4.0.1",
41
41
  "@deepseek-ai/cosmokit": "1.8.2",
42
- "@deepseek-ai/dsh-agent": "0.1.0-rc.7",
43
- "@deepseek-ai/dsh-host-webserver": "0.1.0-rc.7",
44
- "@deepseek-ai/dsh-llm": "0.1.0-rc.7",
45
- "@deepseek-ai/dsh-session": "0.1.0-rc.7",
46
- "@deepseek-ai/dsh-subagent": "0.1.0-rc.7",
42
+ "@deepseek-ai/dsh-agent": "0.1.0-rc.8",
43
+ "@deepseek-ai/dsh-host-webserver": "0.1.0-rc.8",
44
+ "@deepseek-ai/dsh-llm": "0.1.0-rc.8",
45
+ "@deepseek-ai/dsh-session": "0.1.0-rc.8",
46
+ "@deepseek-ai/dsh-subagent": "0.1.0-rc.8",
47
47
  "@types/react": "~18.3.1",
48
48
  "react": "^18.2.0"
49
49
  },
@@ -3,15 +3,16 @@
3
3
  * 构建产物为 window.__ModuleLoader__.load({id, factory}) 形式的 CJS factory
4
4
  * (包装见 tsdown.config.ts);样式沿用官方 data-plugin-css 约定,HMR 据此卸载。
5
5
  *
6
- * 类型说明:浏览器半只用到 slots/locale/settingsScope/connection 的很窄一面,
6
+ * 类型说明:浏览器半只用到 slots/locale/connection/remote 的很窄一面,
7
7
  * 此处以最小本地接口声明(见 ./scope.ts),避免为构建期类型引入整条官方
8
8
  * client 依赖树;运行时契约以官方 dsh-client-ui-settings-plugins 的
9
- * settings.plugin.item 插槽与 dsh-client-ui-settings 的 settingsScope 服务为准。
9
+ * settings.plugin.item 插槽与 dsh-client-connection 的 settings RPC 面为准。
10
10
  * rc7 起该插槽为 keyed 槽位:key 必须是卡片编辑的 settings 命名空间
11
11
  * (即服务端 settingsNamespace() 注册的同一字面量,见 ../ns.ts),
12
12
  * 官方配置页按此 key 与 Host 已注册命名空间配对分发。
13
- * 配置读写经官方 settingsScope 传输(rc7 起第三方命名空间对 settings RPC
14
- * 全量开放);自定义端点仅剩「模型目录」(api.ts)。
13
+ * 配置读写经官方 settings RPC 直连(rc7 起第三方命名空间全量开放;
14
+ * 不复用 settingsScope 服务——非 loopback 页面下它无数据,见 scope.ts);
15
+ * 自定义端点仅剩「模型目录」(api.ts)。
15
16
  * @module @dsh-plus/subagent-model/client
16
17
  */
17
18
  import type { Context } from '@deepseek-ai/cordis'
@@ -25,7 +26,7 @@ import { injectStyle } from './styles.ts'
25
26
  export const name = 'dsh-plus-subagent-model'
26
27
 
27
28
  /** 浏览器半需要的 cordis 服务 key(loader 据此注入;package.json 的 dsh.client.inject 管包加载顺序)。 */
28
- export const inject = ['slots', 'locale', 'settingsScope', 'connection'] as const
29
+ export const inject = ['slots', 'locale', 'connection', 'remote'] as const
29
30
 
30
31
  interface SlotsLike {
31
32
  inject(key: string, callback: () => unknown): unknown
@@ -37,23 +38,86 @@ interface LocaleLike {
37
38
  bind(ns: string): (key: string) => string
38
39
  }
39
40
 
40
- interface SettingsScopeServiceLike {
41
- bind(spec: { namespace: string }): Scope
41
+ interface RemoteLike {
42
+ $on(event: string, listener: (payload?: unknown) => void): () => void
42
43
  }
43
44
 
44
45
  interface ConnectionLike {
45
- settings: SettingsApi
46
+ api: { settings: SettingsApi }
46
47
  }
47
48
 
48
49
  interface ClientContext {
49
50
  slots: SlotsLike
50
51
  locale: LocaleLike
51
- settingsScope: SettingsScopeServiceLike
52
52
  get(key: 'connection'): ConnectionLike
53
+ get(key: 'remote'): RemoteLike
54
+ on(event: string, listener: () => void): () => void
53
55
  effect(execute: () => () => void, label?: string): unknown
54
56
  }
55
57
 
58
+ /**
59
+ * api 直连实现的命名空间 scope(官方配置页 tab 同款模式)。
60
+ * 读:describe 取命名空间视图(脱敏 value/revision + 顶层 writable);
61
+ * 刷新:remote `settings/document-updated`(按命名空间过滤)与
62
+ * `connection/reset`;generation 防旧读覆盖新发布。任何页面 origin
63
+ * (loopback 或 tailnet 信任域名)下行为一致。
64
+ */
65
+ function createApiScope(api: SettingsApi, ns: string, c: ClientContext): Scope {
66
+ let state: ScopeSnapshot = { status: 'loading', value: undefined, revision: undefined, writable: false }
67
+ const listeners = new Set<() => void>()
68
+ let generation = 0
69
+ const publish = (next: ScopeSnapshot): void => {
70
+ state = next
71
+ for (const listener of [...listeners]) listener()
72
+ }
73
+ const load = async (): Promise<void> => {
74
+ const gen = ++generation
75
+ let response
76
+ try {
77
+ response = await api.describe({})
78
+ } catch {
79
+ return
80
+ }
81
+ if (gen !== generation || !response.result.ok) return
82
+ const writable = response.result.value?.writable ?? false
83
+ const view = response.result.value?.namespaces.find((candidate) => candidate.ns === ns)
84
+ if (view === undefined) {
85
+ publish({ status: 'unavailable', value: undefined, revision: undefined, writable })
86
+ return
87
+ }
88
+ publish({ status: 'ready', value: view.value, revision: view.revision, writable })
89
+ }
90
+ const refresh = (namespace?: unknown): void => {
91
+ if (namespace !== undefined && namespace !== ns) return
92
+ void load()
93
+ }
94
+ const disposers = [
95
+ c.get('remote').$on('settings/document-updated', refresh),
96
+ c.on('connection/reset', () => {
97
+ void load()
98
+ }),
99
+ ]
100
+ c.effect(
101
+ () => () => {
102
+ for (const dispose of disposers) dispose()
103
+ },
104
+ 'subagent-model: settings scope',
105
+ )
106
+ void load()
107
+ return {
108
+ getSnapshot: () => state,
109
+ subscribe: (listener: () => void) => {
110
+ listeners.add(listener)
111
+ return () => {
112
+ listeners.delete(listener)
113
+ }
114
+ },
115
+ load,
116
+ }
117
+ }
118
+
56
119
  export function apply(ctx: Context): void {
120
+
57
121
  const c = ctx as unknown as ClientContext
58
122
  const tag = injectStyle()
59
123
  c.effect(
@@ -66,8 +130,8 @@ export function apply(ctx: Context): void {
66
130
  () => c.locale.register(NS, { zh, en }),
67
131
  'subagent-model: locale',
68
132
  )
69
- const scope = c.settingsScope.bind({ namespace: SETTINGS_NS })
70
133
  const api = c.get('connection').api.settings
134
+ const scope = createApiScope(api, SETTINGS_NS, c)
71
135
  c.slots.inject('settings.plugin.item', () =>
72
136
  c.slots.register(
73
137
  {
@@ -1,10 +1,14 @@
1
1
  /**
2
2
  * 浏览器半 settingsScope/connection 的最小本地接口声明。
3
3
  * 只声明本插件用到的窄面,避免为构建期类型引入整条官方 client 依赖树;
4
- * 运行时契约以官方 dsh-client-ui-settings 的 settingsScope 服务与
5
- * dsh-client-connection 的 settings RPC 面为准(rc7:白名单已移除,
6
- * 任何已注册命名空间均可 describe/update/replace)。
7
- * @module subagent-model/client/scope
4
+ * 运行时契约以官方 dsh-client-connection 的 settings RPC 面为准
5
+ * (rc7:白名单已移除,任何已注册命名空间均可 describe/update/replace)。
6
+ *
7
+ * 实现说明:本插件不复用 settingsScope 服务的 bind()——它在非 loopback
8
+ * 页面(如经 tailnet 域名访问)下走 memory 模式、永远无数据;这里按官方
9
+ * 配置页 tab 的同款模式用 `api.settings` 直连实现 Scope(describe 读 +
10
+ * remote `settings/document-updated` / `connection/reset` 刷新,见 client.ts)。
11
+ * @module {pkg}/client/scope
8
12
  */
9
13
 
10
14
  /** settings 命名空间 scope 的快照(官方 SettingsScopeSnapshot 的最小投影)。 */
@@ -27,7 +31,7 @@ export interface Scope {
27
31
 
28
32
  /** connection api.settings 的 RPC 面(本插件用到的三个方法)。 */
29
33
  export interface SettingsApi {
30
- describe(): Promise<{ result: { ok: boolean; value?: { namespaces: Array<{ ns: string; secrets: Array<{ path: string[]; set: boolean }> }> }; error?: { message?: string } } }>
34
+ describe(payload?: Record<string, never>): Promise<{ result: { ok: boolean; value?: { namespaces: Array<{ ns: string; secrets: Array<{ path: string[]; set: boolean }> }> }; error?: { message?: string } } }>
31
35
  update(request: { ns: string; patch: Record<string, unknown>; expectedRevision?: number }): Promise<{ result: { ok: boolean; error?: { message?: string } } }>
32
36
  replace(request: { ns: string; section: Record<string, unknown>; expectedRevision?: number }): Promise<{ result: { ok: boolean; error?: { message?: string } } }>
33
37
  }