dsh-perm-gate 5.1.0 → 5.1.2-compat.7
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/dsh.plugin.json +1 -1
- package/lib/bridge.d.ts +34 -0
- package/lib/bridge.js +125 -0
- package/lib/cli.js +0 -0
- package/lib/client.js +246 -7
- package/lib/client.js.map +1 -1
- package/lib/debug-generation.d.ts +21 -0
- package/lib/debug-generation.js +44 -0
- package/lib/index.js +104 -7
- package/package.json +31 -20
- package/scripts/patch-permission-glyph.mjs +0 -0
package/dsh.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-perm-gate",
|
|
3
3
|
"name": "dsh-perm-gate",
|
|
4
|
-
"version": "5.1.
|
|
4
|
+
"version": "5.1.2-compat.7",
|
|
5
5
|
"description": {
|
|
6
6
|
"en": "Deterministic-first, fail-closed permission gate for DeepSeek Harness 0.1.2+: a P0 hard-deny -> P1 precise session grant -> P2 static allow/deny rule chain -> P3 optional LLM classifier -> P4 ask pipeline over the tools/pre-execute waterfall, with a standalone CLI dry-run evaluator and an auditable decision log. It only vetoes with deny/ask and always delegates otherwise, and unknown or malformed rules fail loud instead of silently standing the gate down.",
|
|
7
7
|
"zh": "面向 DeepSeek Harness 0.1.2+ 的确定性优先、失败即拒的权限闸门:在 tools/pre-execute 瀑布上串起 P0 硬拒绝 → P1 精确会话授权 → P2 静态 allow/deny 规则链 → P3 可选 LLM 语义分类 → P4 ask,并附带独立 CLI 干跑评估器与可审计的决策日志。只以 deny/ask 否决、其余一律放行,规则未知或非法时直接报错,绝不静默失效。"
|
package/lib/bridge.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* settings bridge — T12(spec webServer 数据桥规范):describe/mutate 两端点。
|
|
3
|
+
*
|
|
4
|
+
* 桥只是 HTTP 皮:读 = svc.describe 取自家 ns 描述符;写 = svc.mutate 转 ops 落
|
|
5
|
+
* 宿主 settings 服务(不落盘不缓存第二份,天然规避双写/锁竞争)。mutate 成功即
|
|
6
|
+
* describe 回填,带 expectedRevision 乐观并发。守卫:POST-only + ns 硬编码自家
|
|
7
|
+
* allowlist + 秘密不回读(redactSecrets)。
|
|
8
|
+
*
|
|
9
|
+
* 数据面消费方 = client BridgeDocHandle(T13b 双轨数据源:0.1.7+ 原生句柄优先,
|
|
10
|
+
* ≤0.1.5 settingsScope 不解析时落到本桥——free-search 同架构)。
|
|
11
|
+
*/
|
|
12
|
+
interface SettingsServiceLike {
|
|
13
|
+
update(ns: string, patch: object): Promise<void>;
|
|
14
|
+
register?(ns: string, schema: unknown, options?: {
|
|
15
|
+
base?: unknown;
|
|
16
|
+
}): unknown;
|
|
17
|
+
installSection?(owner: unknown, ns: string, schema: unknown, entry: unknown, hooks: unknown): unknown;
|
|
18
|
+
describe?(o?: {
|
|
19
|
+
redactSecrets?: boolean;
|
|
20
|
+
}): Array<{
|
|
21
|
+
ns: unknown;
|
|
22
|
+
[k: string]: unknown;
|
|
23
|
+
}>;
|
|
24
|
+
mutate?(ns: string, ops: Array<{
|
|
25
|
+
op: string;
|
|
26
|
+
path: string[];
|
|
27
|
+
value?: unknown;
|
|
28
|
+
}>, expectedRevision?: number): Promise<unknown>;
|
|
29
|
+
writable?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export declare const BRIDGE_DESCRIBE_ROUTE = "/api/dsh-perm-gate/settings/describe";
|
|
32
|
+
export declare const BRIDGE_MUTATE_ROUTE = "/api/dsh-perm-gate/settings/mutate";
|
|
33
|
+
export declare function registerSettingsBridgeRoutes(server: unknown, getSettingsSvc: () => SettingsServiceLike | undefined, ns: string): Array<() => void>;
|
|
34
|
+
export {};
|
package/lib/bridge.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export const BRIDGE_DESCRIBE_ROUTE = '/api/dsh-perm-gate/settings/describe';
|
|
2
|
+
export const BRIDGE_MUTATE_ROUTE = '/api/dsh-perm-gate/settings/mutate';
|
|
3
|
+
function readJsonBody(reqRaw, limit = 256 * 1024) {
|
|
4
|
+
const req = reqRaw;
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
const chunks = [];
|
|
7
|
+
let size = 0;
|
|
8
|
+
req.on('data', (chunk) => {
|
|
9
|
+
const buf = chunk;
|
|
10
|
+
size += buf.length;
|
|
11
|
+
if (size > limit) {
|
|
12
|
+
reject(new Error('bridge body too large'));
|
|
13
|
+
req.resume?.();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
chunks.push(buf);
|
|
17
|
+
});
|
|
18
|
+
req.on('end', () => {
|
|
19
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
20
|
+
if (raw === '') {
|
|
21
|
+
resolve({});
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
resolve(JSON.parse(raw));
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
reject(e instanceof Error ? e : new Error(String(e)));
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
req.on('error', reject);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function sendJson(res, code, body) {
|
|
35
|
+
res.writeHead(code, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-cache' });
|
|
36
|
+
res.end(JSON.stringify(body));
|
|
37
|
+
}
|
|
38
|
+
function isConflict(e) {
|
|
39
|
+
return e instanceof Error && /conflict|revision/i.test(e.message);
|
|
40
|
+
}
|
|
41
|
+
export function registerSettingsBridgeRoutes(server, getSettingsSvc, ns) {
|
|
42
|
+
if (typeof server !== 'object' || server === null)
|
|
43
|
+
return [];
|
|
44
|
+
const ws = typeof server.register === 'function' ? server : undefined;
|
|
45
|
+
if (ws === undefined)
|
|
46
|
+
return [];
|
|
47
|
+
const offs = [];
|
|
48
|
+
offs.push(ws.register({
|
|
49
|
+
kind: 'exact',
|
|
50
|
+
path: BRIDGE_DESCRIBE_ROUTE,
|
|
51
|
+
handler: (rawReq, rawRes) => {
|
|
52
|
+
const req = rawReq;
|
|
53
|
+
const res = rawRes;
|
|
54
|
+
if (req.method !== 'POST') {
|
|
55
|
+
sendJson(res, 405, { ok: false, code: 'method-not-allowed' });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const svc = getSettingsSvc();
|
|
59
|
+
if (svc === undefined || typeof svc.describe !== 'function') {
|
|
60
|
+
sendJson(res, 200, { ok: false, code: 'settings-unavailable', message: 'settings service not resolved yet' });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const descriptor = svc
|
|
65
|
+
.describe({ redactSecrets: true })
|
|
66
|
+
.find((d) => d.ns === ns);
|
|
67
|
+
if (descriptor === undefined) {
|
|
68
|
+
sendJson(res, 200, { ok: false, code: 'ns-not-registered', message: `settings namespace "${ns}" is not registered` });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
sendJson(res, 200, { ok: true, value: { descriptor, writable: svc.writable !== false } });
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
sendJson(res, 200, { ok: false, code: 'internal', message: e instanceof Error ? e.message : String(e) });
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
}));
|
|
78
|
+
offs.push(ws.register({
|
|
79
|
+
kind: 'exact',
|
|
80
|
+
path: BRIDGE_MUTATE_ROUTE,
|
|
81
|
+
handler: (rawReq, rawRes) => {
|
|
82
|
+
const req = rawReq;
|
|
83
|
+
const res = rawRes;
|
|
84
|
+
if (req.method !== 'POST') {
|
|
85
|
+
sendJson(res, 405, { ok: false, code: 'method-not-allowed' });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const svc = getSettingsSvc();
|
|
89
|
+
const mutate = svc === undefined ? undefined : svc.mutate;
|
|
90
|
+
if (typeof mutate !== 'function') {
|
|
91
|
+
sendJson(res, 200, { ok: false, code: 'settings-unavailable', message: 'settings service mutate not available' });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
readJsonBody(req).then((body) => {
|
|
95
|
+
const ops = body.ops;
|
|
96
|
+
if (!Array.isArray(ops) || ops.some((op) => !op || typeof op !== 'object' || !(op.op in { set: 1, unset: 1 }))) {
|
|
97
|
+
sendJson(res, 200, { ok: false, code: 'settings-rejected', message: 'malformed bridge mutate ops' });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const expectedRevision = typeof body.expectedRevision === 'number' ? body.expectedRevision : undefined;
|
|
101
|
+
mutate
|
|
102
|
+
.call(svc, ns, ops, expectedRevision)
|
|
103
|
+
.then(() => {
|
|
104
|
+
// mutate 成功即 describe 回填(规范:写路径仍归口宿主服务,无第二份状态)
|
|
105
|
+
const descriptor = svc
|
|
106
|
+
.describe({ redactSecrets: true })
|
|
107
|
+
.find((d) => d.ns === ns);
|
|
108
|
+
if (descriptor === undefined) {
|
|
109
|
+
sendJson(res, 200, { ok: false, code: 'internal', message: `namespace "${ns}" disposed after mutate` });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
sendJson(res, 200, { ok: true, value: { descriptor } });
|
|
113
|
+
})
|
|
114
|
+
.catch((e) => {
|
|
115
|
+
sendJson(res, 200, isConflict(e)
|
|
116
|
+
? { ok: false, code: 'settings-conflict', message: e instanceof Error ? e.message : String(e) }
|
|
117
|
+
: { ok: false, code: 'internal', message: e instanceof Error ? e.message : String(e) });
|
|
118
|
+
});
|
|
119
|
+
}).catch((e) => {
|
|
120
|
+
sendJson(res, 200, { ok: false, code: 'bad-request', message: e instanceof Error ? e.message : String(e) });
|
|
121
|
+
});
|
|
122
|
+
},
|
|
123
|
+
}));
|
|
124
|
+
return offs;
|
|
125
|
+
}
|
package/lib/cli.js
CHANGED
|
File without changes
|
package/lib/client.js
CHANGED
|
@@ -1701,6 +1701,13 @@ window.__ModuleLoader__.load({
|
|
|
1701
1701
|
* default so the section reads as an open page of drawers.
|
|
1702
1702
|
*/
|
|
1703
1703
|
function PermissiveCard({ t, scope }) {
|
|
1704
|
+
if (!scope) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1705
|
+
style: {
|
|
1706
|
+
padding: 16,
|
|
1707
|
+
color: "var(--dsw-alias-label-tertiary, #888)"
|
|
1708
|
+
},
|
|
1709
|
+
children: [t("card.unavailable"), "(scope 未解析 — 诊断占位)"]
|
|
1710
|
+
});
|
|
1704
1711
|
const [open, setOpen] = (0, react.useState)(true);
|
|
1705
1712
|
const [apiKeyDraft, setApiKeyDraft] = (0, react.useState)("");
|
|
1706
1713
|
const [allowText, setAllowText] = (0, react.useState)("");
|
|
@@ -1708,7 +1715,13 @@ window.__ModuleLoader__.load({
|
|
|
1708
1715
|
const snapshot = (0, react.useSyncExternalStore)((listener) => scope.subscribe(listener), () => scope.getSnapshot());
|
|
1709
1716
|
const unavailable = snapshot.status === "unavailable";
|
|
1710
1717
|
const readonly = unavailable || !snapshot.writable;
|
|
1711
|
-
const
|
|
1718
|
+
const raw = snapshot.value ?? {};
|
|
1719
|
+
const listOf = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
1720
|
+
const value = {
|
|
1721
|
+
...raw,
|
|
1722
|
+
allowlist: listOf(raw.allowlist),
|
|
1723
|
+
denyKeywords: listOf(raw.denyKeywords)
|
|
1724
|
+
};
|
|
1712
1725
|
const strategies = value.permissiveStrategies ?? {};
|
|
1713
1726
|
const hasApiKey = typeof value.classifierApiKey === "string" && value.classifierApiKey !== "";
|
|
1714
1727
|
const effective = value.permissive === true;
|
|
@@ -3087,6 +3100,83 @@ window.__ModuleLoader__.load({
|
|
|
3087
3100
|
}) : null]
|
|
3088
3101
|
});
|
|
3089
3102
|
}
|
|
3103
|
+
/** 浮窗齿轮 + 固定定位浮层承载完整审批门卡。自绘定位,不依赖宿主容器布局。 */
|
|
3104
|
+
function FloatingPermissiveGate(props) {
|
|
3105
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
3106
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3107
|
+
style: {
|
|
3108
|
+
position: "fixed",
|
|
3109
|
+
right: 16,
|
|
3110
|
+
bottom: 64,
|
|
3111
|
+
zIndex: 1e4
|
|
3112
|
+
},
|
|
3113
|
+
children: [
|
|
3114
|
+
open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3115
|
+
onClick: () => setOpen(false),
|
|
3116
|
+
style: {
|
|
3117
|
+
position: "fixed",
|
|
3118
|
+
top: 0,
|
|
3119
|
+
left: 0,
|
|
3120
|
+
right: 0,
|
|
3121
|
+
bottom: 0,
|
|
3122
|
+
background: "rgba(0,0,0,.45)",
|
|
3123
|
+
zIndex: 1e4
|
|
3124
|
+
}
|
|
3125
|
+
}) : null,
|
|
3126
|
+
open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3127
|
+
style: {
|
|
3128
|
+
position: "fixed",
|
|
3129
|
+
top: "6vh",
|
|
3130
|
+
left: "50%",
|
|
3131
|
+
transform: "translateX(-50%)",
|
|
3132
|
+
width: "min(760px, 92vw)",
|
|
3133
|
+
maxHeight: "84vh",
|
|
3134
|
+
overflowY: "auto",
|
|
3135
|
+
zIndex: 10001
|
|
3136
|
+
},
|
|
3137
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3138
|
+
type: "button",
|
|
3139
|
+
onClick: () => setOpen(false),
|
|
3140
|
+
style: {
|
|
3141
|
+
position: "sticky",
|
|
3142
|
+
top: 6,
|
|
3143
|
+
float: "right",
|
|
3144
|
+
marginRight: 8,
|
|
3145
|
+
zIndex: 2,
|
|
3146
|
+
font: "inherit",
|
|
3147
|
+
cursor: "pointer",
|
|
3148
|
+
borderRadius: 8,
|
|
3149
|
+
border: "1px solid rgba(127,127,127,.4)",
|
|
3150
|
+
background: "rgba(30,30,32,.85)",
|
|
3151
|
+
color: "inherit",
|
|
3152
|
+
padding: "2px 10px"
|
|
3153
|
+
},
|
|
3154
|
+
children: "✕ 关闭"
|
|
3155
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PermissiveCard, {
|
|
3156
|
+
t: props.t,
|
|
3157
|
+
scope: props.scope
|
|
3158
|
+
})]
|
|
3159
|
+
}) : null,
|
|
3160
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3161
|
+
type: "button",
|
|
3162
|
+
title: "审批门设置",
|
|
3163
|
+
onClick: () => setOpen(!open),
|
|
3164
|
+
style: {
|
|
3165
|
+
width: 40,
|
|
3166
|
+
height: 40,
|
|
3167
|
+
borderRadius: "50%",
|
|
3168
|
+
border: "1px solid rgba(127,127,127,.4)",
|
|
3169
|
+
background: "rgba(30,30,32,.85)",
|
|
3170
|
+
color: "inherit",
|
|
3171
|
+
cursor: "pointer",
|
|
3172
|
+
fontSize: 16,
|
|
3173
|
+
boxShadow: "0 4px 16px rgba(0,0,0,.35)"
|
|
3174
|
+
},
|
|
3175
|
+
children: "🛡"
|
|
3176
|
+
})
|
|
3177
|
+
]
|
|
3178
|
+
});
|
|
3179
|
+
}
|
|
3090
3180
|
//#endregion
|
|
3091
3181
|
//#region src/client/feed.ts
|
|
3092
3182
|
/** Fetch decision events for one session; `since > 0` returns only newer ones. */
|
|
@@ -4234,17 +4324,118 @@ window.__ModuleLoader__.load({
|
|
|
4234
4324
|
*
|
|
4235
4325
|
* Both variants are scoped sub-injects: on a host without the service the fiber
|
|
4236
4326
|
* waits forever WITHOUT blocking the plugin's other faces (same posture as the
|
|
4237
|
-
* notice strip's slot-inject when its slot holder is absent).
|
|
4327
|
+
* notice strip's slot-inject when its slot holder is absent). session-guard
|
|
4328
|
+
* 4.1.0 proves the `settingsScope` branch resolves on 0.1.5-rc.3 (its
|
|
4329
|
+
* shell.overlay gear registers inside this callback and renders there).
|
|
4238
4330
|
*/
|
|
4239
4331
|
function resolveSettingsScope(ctx, namespace, onScope) {
|
|
4240
4332
|
ctx.inject(["configForms"], (configForms) => {
|
|
4241
|
-
onScope(configForms.get(namespace));
|
|
4333
|
+
onScope(configForms.get(namespace), "configForms");
|
|
4242
4334
|
});
|
|
4243
4335
|
ctx.inject(["settingsScope"], (settingsScope) => {
|
|
4244
|
-
onScope(settingsScope.bind({ namespace }));
|
|
4336
|
+
onScope(settingsScope.bind({ namespace }), "settingsScope");
|
|
4245
4337
|
});
|
|
4246
4338
|
}
|
|
4247
4339
|
//#endregion
|
|
4340
|
+
//#region src/client/bridge-scope.ts
|
|
4341
|
+
const DESCRIBE = "/api/dsh-perm-gate/settings/describe";
|
|
4342
|
+
const MUTATE = "/api/dsh-perm-gate/settings/mutate";
|
|
4343
|
+
var BridgeDocHandle = class {
|
|
4344
|
+
snapshot = {
|
|
4345
|
+
status: "pending",
|
|
4346
|
+
value: void 0,
|
|
4347
|
+
writable: true
|
|
4348
|
+
};
|
|
4349
|
+
listeners = /* @__PURE__ */ new Set();
|
|
4350
|
+
revision;
|
|
4351
|
+
inflight;
|
|
4352
|
+
constructor() {
|
|
4353
|
+
this.refresh();
|
|
4354
|
+
}
|
|
4355
|
+
getSnapshot() {
|
|
4356
|
+
return this.snapshot;
|
|
4357
|
+
}
|
|
4358
|
+
subscribe(listener) {
|
|
4359
|
+
this.listeners.add(listener);
|
|
4360
|
+
return () => {
|
|
4361
|
+
this.listeners.delete(listener);
|
|
4362
|
+
};
|
|
4363
|
+
}
|
|
4364
|
+
async set(field, value) {
|
|
4365
|
+
await this.mutate([{
|
|
4366
|
+
op: "set",
|
|
4367
|
+
path: [field],
|
|
4368
|
+
value
|
|
4369
|
+
}]);
|
|
4370
|
+
}
|
|
4371
|
+
async unset(field) {
|
|
4372
|
+
await this.mutate([{
|
|
4373
|
+
op: "unset",
|
|
4374
|
+
path: [field]
|
|
4375
|
+
}]);
|
|
4376
|
+
}
|
|
4377
|
+
notify() {
|
|
4378
|
+
for (const l of [...this.listeners]) l();
|
|
4379
|
+
}
|
|
4380
|
+
async request(path, body) {
|
|
4381
|
+
const response = await fetch(path, {
|
|
4382
|
+
method: "POST",
|
|
4383
|
+
headers: { "content-type": "application/json" },
|
|
4384
|
+
body: JSON.stringify(body ?? {})
|
|
4385
|
+
});
|
|
4386
|
+
if (!response.ok) throw new Error(`bridge ${path} HTTP ${response.status}`);
|
|
4387
|
+
return await response.json();
|
|
4388
|
+
}
|
|
4389
|
+
async refresh() {
|
|
4390
|
+
if (this.inflight !== void 0) return this.inflight;
|
|
4391
|
+
this.inflight = (async () => {
|
|
4392
|
+
try {
|
|
4393
|
+
const body = await this.request(DESCRIBE);
|
|
4394
|
+
if (body.ok && body.value?.descriptor !== void 0) {
|
|
4395
|
+
const d = body.value.descriptor;
|
|
4396
|
+
this.revision = typeof d.revision === "number" ? d.revision : void 0;
|
|
4397
|
+
this.snapshot = {
|
|
4398
|
+
status: "ready",
|
|
4399
|
+
value: d.value,
|
|
4400
|
+
writable: body.value.writable !== false
|
|
4401
|
+
};
|
|
4402
|
+
} else this.snapshot = {
|
|
4403
|
+
status: "error",
|
|
4404
|
+
value: void 0,
|
|
4405
|
+
writable: false
|
|
4406
|
+
};
|
|
4407
|
+
} catch {
|
|
4408
|
+
this.snapshot = {
|
|
4409
|
+
status: "error",
|
|
4410
|
+
value: void 0,
|
|
4411
|
+
writable: false
|
|
4412
|
+
};
|
|
4413
|
+
} finally {
|
|
4414
|
+
this.inflight = void 0;
|
|
4415
|
+
}
|
|
4416
|
+
this.notify();
|
|
4417
|
+
})();
|
|
4418
|
+
return this.inflight;
|
|
4419
|
+
}
|
|
4420
|
+
async mutate(ops) {
|
|
4421
|
+
const body = await this.request(MUTATE, {
|
|
4422
|
+
ops,
|
|
4423
|
+
expectedRevision: this.revision
|
|
4424
|
+
});
|
|
4425
|
+
if (body.ok && body.value?.descriptor !== void 0) {
|
|
4426
|
+
const d = body.value.descriptor;
|
|
4427
|
+
this.revision = typeof d.revision === "number" ? d.revision : void 0;
|
|
4428
|
+
this.snapshot = {
|
|
4429
|
+
status: "ready",
|
|
4430
|
+
value: d.value,
|
|
4431
|
+
writable: this.snapshot.writable
|
|
4432
|
+
};
|
|
4433
|
+
} else if (body.code === "settings-conflict") await this.refresh();
|
|
4434
|
+
else throw new Error(`bridge mutate failed: ${body.code ?? "unknown"} ${body.message ?? ""}`);
|
|
4435
|
+
this.notify();
|
|
4436
|
+
}
|
|
4437
|
+
};
|
|
4438
|
+
//#endregion
|
|
4248
4439
|
//#region src/client/index.ts
|
|
4249
4440
|
/** The profile entry id of this plugin — the `configForms` key (kept in lockstep with cordis.patch.yml). */
|
|
4250
4441
|
const PERMISSIVE_NS = "dsh-perm-gate";
|
|
@@ -4283,7 +4474,21 @@ window.__ModuleLoader__.load({
|
|
|
4283
4474
|
}, HistoryView);
|
|
4284
4475
|
});
|
|
4285
4476
|
const t = ctx.locale.bind(NS);
|
|
4286
|
-
|
|
4477
|
+
const safe = (tag, fn) => {
|
|
4478
|
+
try {
|
|
4479
|
+
fn();
|
|
4480
|
+
} catch (e) {
|
|
4481
|
+
(globalThis.__pgSurf ??= []).push(`${tag}: ${e?.message ?? String(e)}`);
|
|
4482
|
+
console.warn(`[dsh-perm-gate] client surface '${tag}' failed:`, e);
|
|
4483
|
+
}
|
|
4484
|
+
};
|
|
4485
|
+
const scopeRef = {};
|
|
4486
|
+
resolveSettingsScope(ctx, PERMISSIVE_NS, (scope, _generation) => {
|
|
4487
|
+
scopeRef.scope = scope;
|
|
4488
|
+
});
|
|
4489
|
+
const bridgeScope = new BridgeDocHandle();
|
|
4490
|
+
const cardInjected = () => ({ scope: scopeRef.scope ?? bridgeScope });
|
|
4491
|
+
safe("settings.section", () => {
|
|
4287
4492
|
ctx.slots.inject("settings.section", function* () {
|
|
4288
4493
|
yield ctx.slots.register({
|
|
4289
4494
|
name: "settings.section",
|
|
@@ -4291,16 +4496,50 @@ window.__ModuleLoader__.load({
|
|
|
4291
4496
|
order: 30,
|
|
4292
4497
|
label: () => t("section.title"),
|
|
4293
4498
|
locale: NS,
|
|
4294
|
-
inject:
|
|
4499
|
+
inject: cardInjected
|
|
4295
4500
|
}, PermissiveCard);
|
|
4296
4501
|
});
|
|
4502
|
+
});
|
|
4503
|
+
safe("plugins.bundle.config", () => {
|
|
4297
4504
|
ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
|
|
4298
4505
|
name: "plugins.bundle.config",
|
|
4299
4506
|
key: "dsh-perm-gate",
|
|
4300
4507
|
locale: NS,
|
|
4301
|
-
inject:
|
|
4508
|
+
inject: cardInjected
|
|
4509
|
+
}, PermissiveCard));
|
|
4510
|
+
});
|
|
4511
|
+
safe("settings.plugins.tab", () => {
|
|
4512
|
+
ctx.slots.inject("settings.plugins.tab", () => ctx.slots.register({
|
|
4513
|
+
name: "settings.plugins.tab",
|
|
4514
|
+
id: PERMISSIVE_NS,
|
|
4515
|
+
order: 50,
|
|
4516
|
+
label: () => t("section.title"),
|
|
4517
|
+
locale: NS,
|
|
4518
|
+
inject: cardInjected
|
|
4519
|
+
}, PermissiveCard));
|
|
4520
|
+
});
|
|
4521
|
+
safe("settings.plugin.item", () => {
|
|
4522
|
+
const itemSlots = ctx.slots;
|
|
4523
|
+
itemSlots.inject("settings.plugin.item", () => itemSlots.register({
|
|
4524
|
+
name: "settings.plugin.item",
|
|
4525
|
+
id: PERMISSIVE_NS,
|
|
4526
|
+
key: PERMISSIVE_NS,
|
|
4527
|
+
order: 40,
|
|
4528
|
+
locale: NS,
|
|
4529
|
+
inject: cardInjected
|
|
4302
4530
|
}, PermissiveCard));
|
|
4303
4531
|
});
|
|
4532
|
+
safe("shell.overlay", () => {
|
|
4533
|
+
const overlaySlots = ctx.slots;
|
|
4534
|
+
overlaySlots.inject("shell.overlay", () => overlaySlots.register({
|
|
4535
|
+
name: "shell.overlay",
|
|
4536
|
+
id: PERMISSIVE_NS,
|
|
4537
|
+
inject: () => ({
|
|
4538
|
+
t,
|
|
4539
|
+
scope: scopeRef.scope ?? bridgeScope
|
|
4540
|
+
})
|
|
4541
|
+
}, FloatingPermissiveGate));
|
|
4542
|
+
});
|
|
4304
4543
|
}
|
|
4305
4544
|
//#endregion
|
|
4306
4545
|
exports.apply = apply;
|