@dsh-plus/secret-env 0.1.16 → 0.1.17

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
@@ -983,7 +983,7 @@ ${extra}
983
983
  timer.current = setTimeout(() => setCopied(false), 1500);
984
984
  });
985
985
  },
986
- children: copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, { size: 14 })
986
+ children: copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutlineRegular, { size: 14 })
987
987
  });
988
988
  }
989
989
  /** 会话内屏蔽开关(眼睛语义:可见=注入,划掉=本会话屏蔽)。 */
@@ -1031,7 +1031,7 @@ ${extra}
1031
1031
  onClick: () => {
1032
1032
  unsetSession(props.sessionId, entry.name).then(() => props.onChanged()).catch((error) => props.onError(errorText(t, error)));
1033
1033
  },
1034
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
1034
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutlineRegular, { size: 14 })
1035
1035
  })]
1036
1036
  })]
1037
1037
  });
@@ -1146,7 +1146,7 @@ ${extra}
1146
1146
  title: t("refresh"),
1147
1147
  "aria-label": t("refresh"),
1148
1148
  onClick: load,
1149
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, { size: 14 })
1149
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineRegular, { size: 14 })
1150
1150
  }),
1151
1151
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1152
1152
  type: "button",
@@ -1154,7 +1154,7 @@ ${extra}
1154
1154
  title: t("close"),
1155
1155
  "aria-label": t("close"),
1156
1156
  onClick: props.onClose,
1157
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
1157
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineMedium, { size: 14 })
1158
1158
  })
1159
1159
  ]
1160
1160
  }) : null,
@@ -1377,7 +1377,7 @@ ${extra}
1377
1377
  timer.current = setTimeout(() => setCopied(false), 1500);
1378
1378
  });
1379
1379
  },
1380
- children: copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, { size: 14 })
1380
+ children: copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutlineRegular, { size: 14 })
1381
1381
  });
1382
1382
  }
1383
1383
  /** 全局变量行:完整变量名(复制)+ 描述 + 状态徽标 + 两步删除。 */
@@ -1432,7 +1432,7 @@ ${extra}
1432
1432
  title: t("delete"),
1433
1433
  "aria-label": t("delete"),
1434
1434
  onClick: () => setConfirming(true),
1435
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
1435
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutlineRegular, { size: 14 })
1436
1436
  })
1437
1437
  ]
1438
1438
  })]
package/lib/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import z from "@deepseek-ai/schemastery";
2
2
  import { Context, Service } from "@deepseek-ai/cordis";
3
+ import { UnwrapVolatile } from "@dsh-plus/shared";
3
4
  //#region src/config.d.ts
4
5
  /** 一条全局密钥的元数据(值不在此处)。 */
5
6
  interface SecretMeta {
@@ -13,14 +14,17 @@ interface SecretMetaInput {
13
14
  description?: string;
14
15
  createdAt?: string;
15
16
  }
16
- declare const Config: z<Schemastery.ObjectS<{
17
- secrets: z<SecretMetaInput[], SecretMeta[]>;
18
- masked: z<string[], string[]>;
19
- }>, Schemastery.ObjectT<{
20
- secrets: z<SecretMetaInput[], SecretMeta[]>;
21
- masked: z<string[], string[]>;
22
- }>>;
23
- type SecretEnvConfig = Schemastery.TypeT<typeof Config>;
17
+ declare const Config: z<Schemastery.ObjectS<NoInfer<{
18
+ secrets: z<NoInfer<SecretMetaInput[]>, NoInfer<SecretMeta[]>, "volatile-defined">;
19
+ masked: z<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
20
+ }>>, Schemastery.ObjectT<NoInfer<{
21
+ secrets: z<NoInfer<SecretMetaInput[]>, NoInfer<SecretMeta[]>, "volatile-defined">;
22
+ masked: z<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
23
+ }>>, "plain">;
24
+ /** 活动字段形态(0.1.7 loader 解析产物:volatile 字段为活动引用)。 */
25
+ type SecretEnvConfigFields = Schemastery.TypeT<typeof Config>;
26
+ /** 平面配置形态(消费面的读取形态,由活动引用解包得到)。 */
27
+ type SecretEnvConfig = UnwrapVolatile<SecretEnvConfigFields>;
24
28
  //#endregion
25
29
  //#region src/inventory.d.ts
26
30
  /** 列表端点的全局条目(describe 视图,绝无值)。 */
@@ -66,17 +70,17 @@ declare class SecretEnvService extends Service {
66
70
  private readonly sessionMasks;
67
71
  /** contributor 登记簿(受管/继承两条线,一键一主)。 */
68
72
  private readonly book;
69
- /** 行级 config(settings 缺席时的索引来源)。 */
73
+ /** 行级 config 解包基线(settings 缺席时的索引来源)。 */
70
74
  private readonly base;
71
- /** installSection 给的实时 getter(scope.get 的活视图,读即最新,无时序竞态)。 */
75
+ /** 活动引用现取 getter(loader 原位提交,读即最新;行级 config 缺席时为 undefined)。 */
72
76
  private readIndex;
73
77
  /** 上次调和时的索引快照(onChange 差量用)。 */
74
78
  private lastMeta;
75
79
  private settingsRef;
76
80
  /** 启动镜像建立完成(测试与需要确定性的调用方可等待)。 */
77
81
  readonly ready: Promise<void>;
78
- constructor(ctx: Context, config: SecretEnvConfig | undefined);
79
- /** 当前生效索引(settings 活视图优先,缺席回落行级 config)。 */
82
+ constructor(ctx: Context, config: SecretEnvConfig | SecretEnvConfigFields | undefined);
83
+ /** 当前生效索引(活动引用现取优先,行级 config 缺席回落空索引)。 */
80
84
  private currentMeta;
81
85
  /** 当前全局屏蔽名单(与索引同源,活读取)。 */
82
86
  private currentMasked;
package/lib/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import z from "@deepseek-ai/schemastery";
2
2
  import { Service } from "@deepseek-ai/cordis";
3
3
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
4
+ import { unwrapVolatile } from "@dsh-plus/shared";
4
5
  //#region src/ns.ts
5
6
  /**
6
7
  * settings 命名空间字面量(纯常量,零依赖,浏览器半可安全引入)。
@@ -23,8 +24,8 @@ const SecretMetaSchema = z.object({
23
24
  createdAt: z.string().description("创建时间 ISO").default("")
24
25
  });
25
26
  const Config = z.object({
26
- secrets: z.array(SecretMetaSchema).description("全局密钥元数据索引(值存凭据库)").default([]),
27
- masked: z.array(z.string()).description("全局屏蔽的变量名后缀(继承变量等;注入时跳过)").default([])
27
+ secrets: z.array(SecretMetaSchema).description("全局密钥元数据索引(值存凭据库)").default([]).volatile(),
28
+ masked: z.array(z.string()).description("全局屏蔽的变量名后缀(继承变量等;注入时跳过)").default([]).volatile()
28
29
  });
29
30
  //#endregion
30
31
  //#region src/errors.ts
@@ -320,9 +321,9 @@ var SecretEnvService = class extends Service {
320
321
  sessionMasks = /* @__PURE__ */ new Map();
321
322
  /** contributor 登记簿(受管/继承两条线,一键一主)。 */
322
323
  book;
323
- /** 行级 config(settings 缺席时的索引来源)。 */
324
+ /** 行级 config 解包基线(settings 缺席时的索引来源)。 */
324
325
  base;
325
- /** installSection 给的实时 getter(scope.get 的活视图,读即最新,无时序竞态)。 */
326
+ /** 活动引用现取 getter(loader 原位提交,读即最新;行级 config 缺席时为 undefined)。 */
326
327
  readIndex;
327
328
  /** 上次调和时的索引快照(onChange 差量用)。 */
328
329
  lastMeta = [];
@@ -331,10 +332,11 @@ var SecretEnvService = class extends Service {
331
332
  ready;
332
333
  constructor(ctx, config) {
333
334
  super(ctx, "secretEnv");
334
- this.base = config ?? {
335
+ this.base = config !== void 0 ? unwrapVolatile(config) : {
335
336
  secrets: [],
336
337
  masked: []
337
338
  };
339
+ this.readIndex = config !== void 0 ? () => unwrapVolatile(config) : void 0;
338
340
  this.book = new ContributorBook({
339
341
  shellEnv: ctx.shellEnv,
340
342
  describeManaged: (suffix) => {
@@ -351,18 +353,10 @@ var SecretEnvService = class extends Service {
351
353
  }
352
354
  });
353
355
  ctx.inject(["settings"], (settingsCtx) => {
354
- const settings = settingsCtx.settings;
355
- settings.installSection(ctx, SETTINGS_NS, Config, this.base, {
356
- setSource: (source) => {
357
- this.readIndex = source;
358
- },
359
- onChange: () => {
360
- this.reconcile();
361
- }
362
- });
363
- this.settingsRef = settings;
356
+ this.settingsRef = settingsCtx.settings;
364
357
  this.reconcile();
365
358
  });
359
+ ctx.events.on("loader/volatile-update", () => void this.reconcile());
366
360
  ctx.root.on("session/disposed", (session) => {
367
361
  this.sessionMasks.delete(session.id);
368
362
  this.dropBucket(session.id);
@@ -377,7 +371,7 @@ var SecretEnvService = class extends Service {
377
371
  for (const suffix of this.inheritedSuffixes()) this.book.syncInherited(suffix);
378
372
  this.ready = this.reconcile();
379
373
  }
380
- /** 当前生效索引(settings 活视图优先,缺席回落行级 config)。 */
374
+ /** 当前生效索引(活动引用现取优先,行级 config 缺席回落空索引)。 */
381
375
  currentMeta() {
382
376
  return asMeta(this.readIndex !== void 0 ? this.readIndex() : this.base);
383
377
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-plus/secret-env",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "dsh-plus service+ui plugin: 密钥环境变量——以 $DSH_VAR_* 变量名向 agent 暴露密钥(全局持久/会话级/一次性),执行期经 dsh-shell-env 原生注入,值不进消息流,不影响缓存率",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -19,27 +19,27 @@
19
19
  "src"
20
20
  ],
21
21
  "dependencies": {
22
- "@dsh-plus/shared": "0.1.21"
22
+ "@dsh-plus/shared": "0.1.22"
23
23
  },
24
24
  "peerDependencies": {
25
- "@deepseek-ai/cordis": "^4.0.2",
26
- "@deepseek-ai/dsh-credentials": "^0.1.6-alpha.2",
27
- "@deepseek-ai/dsh-host-webserver": "^0.1.6-alpha.2",
28
- "@deepseek-ai/dsh-settings": "^0.1.6-alpha.2",
29
- "@deepseek-ai/dsh-shell-env": "^0.1.6-alpha.2",
30
- "@deepseek-ai/schemastery": "^3.18.2"
25
+ "@deepseek-ai/cordis": "^4.0.4",
26
+ "@deepseek-ai/dsh-credentials": "^0.1.7-alpha.2",
27
+ "@deepseek-ai/dsh-host-webserver": "^0.1.7-alpha.2",
28
+ "@deepseek-ai/dsh-settings": "^0.1.7-alpha.2",
29
+ "@deepseek-ai/dsh-shell-env": "^0.1.7-alpha.2",
30
+ "@deepseek-ai/schemastery": "^3.18.4"
31
31
  },
32
32
  "devDependencies": {
33
- "@deepseek-ai/cordis": "4.0.2",
34
- "@deepseek-ai/dsh-client-ui-commands": "0.1.6-alpha.2",
35
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.6-alpha.2",
36
- "@deepseek-ai/dsh-credentials": "0.1.6-alpha.2",
37
- "@deepseek-ai/dsh-host-webserver": "0.1.6-alpha.2",
38
- "@deepseek-ai/dsh-session": "0.1.6-alpha.2",
39
- "@deepseek-ai/dsh-settings": "0.1.6-alpha.2",
40
- "@deepseek-ai/dsh-shell-env": "0.1.6-alpha.2",
41
- "@deepseek-ai/dsh-tools": "0.1.6-alpha.2",
42
- "@deepseek-ai/schemastery": "3.18.2",
33
+ "@deepseek-ai/cordis": "4.0.4",
34
+ "@deepseek-ai/dsh-client-ui-commands": "0.1.7-alpha.2",
35
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-alpha.2",
36
+ "@deepseek-ai/dsh-credentials": "0.1.7-alpha.2",
37
+ "@deepseek-ai/dsh-host-webserver": "0.1.7-alpha.2",
38
+ "@deepseek-ai/dsh-session": "0.1.7-alpha.2",
39
+ "@deepseek-ai/dsh-settings": "0.1.7-alpha.2",
40
+ "@deepseek-ai/dsh-shell-env": "0.1.7-alpha.2",
41
+ "@deepseek-ai/dsh-tools": "0.1.7-alpha.2",
42
+ "@deepseek-ai/schemastery": "3.18.4",
43
43
  "@types/node": "26.4.0",
44
44
  "@types/react": "~18.3.31",
45
45
  "react": "^18.2.0"
@@ -7,13 +7,13 @@
7
7
  */
8
8
 
9
9
  import {
10
- IconCheckOutline16,
11
- IconCloseOutline16,
12
- IconCopyOutline16,
10
+ IconCheckOutlineRegular,
11
+ IconCloseOutlineMedium,
12
+ IconCopyOutlineRegular,
13
13
  IconEye,
14
14
  IconEyeOff,
15
- IconRefreshOutline16,
16
- IconTrashOutline16,
15
+ IconRefreshOutlineRegular,
16
+ IconTrashOutlineRegular,
17
17
  } from '@dsh-plus/shared/client'
18
18
  import { type ReactElement, useEffect, useRef, useState } from 'react'
19
19
 
@@ -72,7 +72,7 @@ function CopyButton(props: { t: Translate; text: string }): ReactElement {
72
72
  })
73
73
  }}
74
74
  >
75
- {copied ? <IconCheckOutline16 size={14} /> : <IconCopyOutline16 size={14} />}
75
+ {copied ? <IconCheckOutlineRegular size={14} /> : <IconCopyOutlineRegular size={14} />}
76
76
  </button>
77
77
  )
78
78
  }
@@ -124,7 +124,7 @@ function SessionRow(props: {
124
124
  .catch((error: unknown) => props.onError(errorText(t, error)))
125
125
  }}
126
126
  >
127
- <IconTrashOutline16 size={14} />
127
+ <IconTrashOutlineRegular size={14} />
128
128
  </button>
129
129
  </div>
130
130
  </div>
@@ -253,7 +253,7 @@ export function SessionSecretsPanel(props: SessionSecretsPanelProps): ReactEleme
253
253
  aria-label={t('refresh')}
254
254
  onClick={load}
255
255
  >
256
- <IconRefreshOutline16 size={14} />
256
+ <IconRefreshOutlineRegular size={14} />
257
257
  </button>
258
258
  <button
259
259
  type="button"
@@ -262,7 +262,7 @@ export function SessionSecretsPanel(props: SessionSecretsPanelProps): ReactEleme
262
262
  aria-label={t('close')}
263
263
  onClick={props.onClose}
264
264
  >
265
- <IconCloseOutline16 size={14} />
265
+ <IconCloseOutlineMedium size={14} />
266
266
  </button>
267
267
  </div>
268
268
  ) : null}
@@ -8,11 +8,11 @@
8
8
  */
9
9
 
10
10
  import {
11
- IconCheckOutline16,
12
- IconCopyOutline16,
11
+ IconCheckOutlineRegular,
12
+ IconCopyOutlineRegular,
13
13
  IconEye,
14
14
  IconEyeOff,
15
- IconTrashOutline16,
15
+ IconTrashOutlineRegular,
16
16
  } from '@dsh-plus/shared/client'
17
17
  import { type ReactElement, useEffect, useRef, useState, useSyncExternalStore } from 'react'
18
18
 
@@ -78,7 +78,7 @@ function CopyButton(props: { t: Translate; text: string }): ReactElement {
78
78
  })
79
79
  }}
80
80
  >
81
- {copied ? <IconCheckOutline16 size={14} /> : <IconCopyOutline16 size={14} />}
81
+ {copied ? <IconCheckOutlineRegular size={14} /> : <IconCopyOutlineRegular size={14} />}
82
82
  </button>
83
83
  )
84
84
  }
@@ -138,7 +138,7 @@ function GlobalRow(props: {
138
138
  aria-label={t('delete')}
139
139
  onClick={() => setConfirming(true)}
140
140
  >
141
- <IconTrashOutline16 size={14} />
141
+ <IconTrashOutlineRegular size={14} />
142
142
  </button>
143
143
  )}
144
144
  </div>
package/src/config.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  * @module secret-env/config
7
7
  */
8
8
  import z from '@deepseek-ai/schemastery'
9
+ import type { UnwrapVolatile } from '@dsh-plus/shared'
9
10
 
10
11
  import { SETTINGS_NS as NS_LITERAL } from './ns.ts'
11
12
 
@@ -35,12 +36,22 @@ const SecretMetaSchema: z<SecretMetaInput, SecretMeta> = z.object({
35
36
  createdAt: z.string().description('创建时间 ISO').default(''),
36
37
  })
37
38
 
39
+ // 0.1.7:全字段 `.volatile()`——条目进入 settings describe 视图(卡片可读写),
40
+ // loader 原位提交活动引用,索引读取现取即热。
38
41
  export const Config = z.object({
39
- secrets: z.array(SecretMetaSchema).description('全局密钥元数据索引(值存凭据库)').default([]),
42
+ secrets: z
43
+ .array(SecretMetaSchema)
44
+ .description('全局密钥元数据索引(值存凭据库)')
45
+ .default([])
46
+ .volatile(),
40
47
  masked: z
41
48
  .array(z.string())
42
49
  .description('全局屏蔽的变量名后缀(继承变量等;注入时跳过)')
43
- .default([]),
50
+ .default([])
51
+ .volatile(),
44
52
  })
45
53
 
46
- export type SecretEnvConfig = Schemastery.TypeT<typeof Config>
54
+ /** 活动字段形态(0.1.7 loader 解析产物:volatile 字段为活动引用)。 */
55
+ export type SecretEnvConfigFields = Schemastery.TypeT<typeof Config>
56
+ /** 平面配置形态(消费面的读取形态,由活动引用解包得到)。 */
57
+ export type SecretEnvConfig = UnwrapVolatile<SecretEnvConfigFields>
package/src/service.ts CHANGED
@@ -20,9 +20,10 @@ import type {} from '@deepseek-ai/dsh-session'
20
20
  import type {} from '@deepseek-ai/dsh-settings'
21
21
  import type {} from '@deepseek-ai/dsh-shell-env'
22
22
  import type { ToolExecution } from '@deepseek-ai/dsh-tools'
23
+ import { unwrapVolatile } from '@dsh-plus/shared'
23
24
 
24
25
  import { registerSecretEnvApi } from './api.ts'
25
- import { Config, type SecretEnvConfig, type SecretMeta } from './config.ts'
26
+ import type { SecretEnvConfig, SecretEnvConfigFields, SecretMeta } from './config.ts'
26
27
  import { ContributorBook } from './contributors.ts'
27
28
  import { SecretEnvError } from './errors.ts'
28
29
  import {
@@ -38,16 +39,9 @@ import {
38
39
  import { envNameOf, suffixOf } from './names.ts'
39
40
  import { SETTINGS_NS } from './ns.ts'
40
41
 
42
+ /** settings 服务窄面(0.1.7 只需 update 写入用户层;describe 读由 loader 并入 config)。 */
41
43
  interface SettingsLike {
42
- get(ns: string): unknown
43
44
  update(ns: string, patch: Record<string, unknown>): Promise<void>
44
- installSection(
45
- ownerCtx: Context,
46
- ns: string,
47
- schema: unknown,
48
- base: unknown,
49
- hooks: { setSource(source: () => unknown): void; onChange(): void },
50
- ): void
51
45
  }
52
46
 
53
47
  export class SecretEnvService extends Service {
@@ -63,19 +57,22 @@ export class SecretEnvService extends Service {
63
57
  private readonly sessionMasks = new Map<string, Set<string>>()
64
58
  /** contributor 登记簿(受管/继承两条线,一键一主)。 */
65
59
  private readonly book: ContributorBook
66
- /** 行级 config(settings 缺席时的索引来源)。 */
60
+ /** 行级 config 解包基线(settings 缺席时的索引来源)。 */
67
61
  private readonly base: SecretEnvConfig
68
- /** installSection 给的实时 getter(scope.get 的活视图,读即最新,无时序竞态)。 */
69
- private readIndex: (() => unknown) | undefined
62
+ /** 活动引用现取 getter(loader 原位提交,读即最新;行级 config 缺席时为 undefined)。 */
63
+ private readIndex: (() => SecretEnvConfig) | undefined
70
64
  /** 上次调和时的索引快照(onChange 差量用)。 */
71
65
  private lastMeta: SecretMeta[] = []
72
66
  private settingsRef: SettingsLike | undefined
73
67
  /** 启动镜像建立完成(测试与需要确定性的调用方可等待)。 */
74
68
  readonly ready: Promise<void>
75
69
 
76
- constructor(ctx: Context, config: SecretEnvConfig | undefined) {
70
+ constructor(ctx: Context, config: SecretEnvConfig | SecretEnvConfigFields | undefined) {
77
71
  super(ctx, 'secretEnv')
78
- this.base = config ?? { secrets: [], masked: [] }
72
+ // 0.1.7 替代 installSection/setSource:活动引用原位提交——索引读取经
73
+ // readIndex 现取平面快照,用户层写入走 loader 落盘 + 原位提交热生效。
74
+ this.base = config !== undefined ? unwrapVolatile(config) : { secrets: [], masked: [] }
75
+ this.readIndex = config !== undefined ? () => unwrapVolatile(config) : undefined
79
76
  this.book = new ContributorBook({
80
77
  shellEnv: ctx.shellEnv,
81
78
  describeManaged: (suffix) => {
@@ -95,22 +92,14 @@ export class SecretEnvService extends Service {
95
92
  return process.env[envNameOf(suffix)]
96
93
  },
97
94
  })
98
- // 官方 installSection 范式(同 usage-panel):setSource 收到的是
99
- // () => scope.get() 活视图——索引读取永远走 currentMeta() 现取,
100
- // onChange 仅触发差量调和,因此用户层加载早晚都不会被写覆盖。
95
+ // settings 在时捕获写入引用并立即差量调和(用户层索引并入镜像,
96
+ // 用户层加载早晚都不会被写覆盖);volatile 提交事件驱动重调和
97
+ // (替代原 installSection onChange)。
101
98
  ctx.inject(['settings'], (settingsCtx) => {
102
- const settings = settingsCtx.settings as unknown as SettingsLike
103
- settings.installSection(ctx, SETTINGS_NS, Config, this.base, {
104
- setSource: (source) => {
105
- this.readIndex = source
106
- },
107
- onChange: () => {
108
- void this.reconcile()
109
- },
110
- })
111
- this.settingsRef = settings
99
+ this.settingsRef = settingsCtx.settings as unknown as SettingsLike
112
100
  void this.reconcile()
113
101
  })
102
+ ctx.events.on('loader/volatile-update', () => void this.reconcile())
114
103
  ctx.root.on('session/disposed', (session: { id: string }) => {
115
104
  this.sessionMasks.delete(session.id)
116
105
  this.dropBucket(session.id)
@@ -130,7 +119,7 @@ export class SecretEnvService extends Service {
130
119
  this.ready = this.reconcile()
131
120
  }
132
121
 
133
- /** 当前生效索引(settings 活视图优先,缺席回落行级 config)。 */
122
+ /** 当前生效索引(活动引用现取优先,行级 config 缺席回落空索引)。 */
134
123
  private currentMeta(): SecretMeta[] {
135
124
  return asMeta(this.readIndex !== undefined ? this.readIndex() : this.base)
136
125
  }