@dsh-plus/secret-env 0.1.16 → 0.1.18
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 +6 -6
- package/lib/index.d.ts +16 -12
- package/lib/index.js +10 -16
- package/package.json +18 -18
- package/src/client/panel.tsx +9 -9
- package/src/client/section.tsx +5 -5
- package/src/config.ts +14 -3
- package/src/service.ts +17 -28
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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[]
|
|
18
|
-
masked: z<string[]
|
|
19
|
-
}
|
|
20
|
-
secrets: z<SecretMetaInput[]
|
|
21
|
-
masked: z<string[]
|
|
22
|
-
}
|
|
23
|
-
|
|
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
|
|
73
|
+
/** 行级 config 解包基线(settings 缺席时的索引来源)。 */
|
|
70
74
|
private readonly base;
|
|
71
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
324
|
+
/** 行级 config 解包基线(settings 缺席时的索引来源)。 */
|
|
324
325
|
base;
|
|
325
|
-
/**
|
|
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
|
-
|
|
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
|
-
/**
|
|
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.
|
|
3
|
+
"version": "0.1.18",
|
|
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.
|
|
22
|
+
"@dsh-plus/shared": "0.1.23"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
26
|
-
"@deepseek-ai/dsh-credentials": "^0.1.
|
|
27
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.
|
|
28
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
29
|
-
"@deepseek-ai/dsh-shell-env": "^0.1.
|
|
30
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
25
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
26
|
+
"@deepseek-ai/dsh-credentials": "^0.1.7-rc.1",
|
|
27
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.1",
|
|
28
|
+
"@deepseek-ai/dsh-settings": "^0.1.7-rc.1",
|
|
29
|
+
"@deepseek-ai/dsh-shell-env": "^0.1.7-rc.1",
|
|
30
|
+
"@deepseek-ai/schemastery": "^3.18.4"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@deepseek-ai/cordis": "4.0.
|
|
34
|
-
"@deepseek-ai/dsh-client-ui-commands": "0.1.
|
|
35
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.1.
|
|
36
|
-
"@deepseek-ai/dsh-credentials": "0.1.
|
|
37
|
-
"@deepseek-ai/dsh-host-webserver": "0.1.
|
|
38
|
-
"@deepseek-ai/dsh-session": "0.1.
|
|
39
|
-
"@deepseek-ai/dsh-settings": "0.1.
|
|
40
|
-
"@deepseek-ai/dsh-shell-env": "0.1.
|
|
41
|
-
"@deepseek-ai/dsh-tools": "0.1.
|
|
42
|
-
"@deepseek-ai/schemastery": "3.18.
|
|
33
|
+
"@deepseek-ai/cordis": "4.0.4",
|
|
34
|
+
"@deepseek-ai/dsh-client-ui-commands": "0.1.7-rc.1",
|
|
35
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.1",
|
|
36
|
+
"@deepseek-ai/dsh-credentials": "0.1.7-rc.1",
|
|
37
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.7-rc.1",
|
|
38
|
+
"@deepseek-ai/dsh-session": "0.1.7-rc.1",
|
|
39
|
+
"@deepseek-ai/dsh-settings": "0.1.7-rc.1",
|
|
40
|
+
"@deepseek-ai/dsh-shell-env": "0.1.7-rc.1",
|
|
41
|
+
"@deepseek-ai/dsh-tools": "0.1.7-rc.1",
|
|
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"
|
package/src/client/panel.tsx
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
IconCheckOutlineRegular,
|
|
11
|
+
IconCloseOutlineMedium,
|
|
12
|
+
IconCopyOutlineRegular,
|
|
13
13
|
IconEye,
|
|
14
14
|
IconEyeOff,
|
|
15
|
-
|
|
16
|
-
|
|
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 ? <
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
265
|
+
<IconCloseOutlineMedium size={14} />
|
|
266
266
|
</button>
|
|
267
267
|
</div>
|
|
268
268
|
) : null}
|
package/src/client/section.tsx
CHANGED
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
IconCheckOutlineRegular,
|
|
12
|
+
IconCopyOutlineRegular,
|
|
13
13
|
IconEye,
|
|
14
14
|
IconEyeOff,
|
|
15
|
-
|
|
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 ? <
|
|
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
|
-
<
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
|
60
|
+
/** 行级 config 解包基线(settings 缺席时的索引来源)。 */
|
|
67
61
|
private readonly base: SecretEnvConfig
|
|
68
|
-
/**
|
|
69
|
-
private readIndex: (() =>
|
|
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
|
-
|
|
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
|
-
//
|
|
99
|
-
//
|
|
100
|
-
// onChange
|
|
95
|
+
// settings 在时捕获写入引用并立即差量调和(用户层索引并入镜像,
|
|
96
|
+
// 用户层加载早晚都不会被写覆盖);volatile 提交事件驱动重调和
|
|
97
|
+
// (替代原 installSection onChange)。
|
|
101
98
|
ctx.inject(['settings'], (settingsCtx) => {
|
|
102
|
-
|
|
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
|
-
/**
|
|
122
|
+
/** 当前生效索引(活动引用现取优先,行级 config 缺席回落空索引)。 */
|
|
134
123
|
private currentMeta(): SecretMeta[] {
|
|
135
124
|
return asMeta(this.readIndex !== undefined ? this.readIndex() : this.base)
|
|
136
125
|
}
|