@chaoset/adaptive-perf 0.7.0 → 0.7.2
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/client/client.cjs +191 -167
- package/client/index.tsx +477 -0
- package/lib/config-store.d.ts +32 -0
- package/lib/config-store.d.ts.map +1 -0
- package/lib/config-store.js +107 -0
- package/lib/config-store.js.map +1 -0
- package/lib/index.d.ts +350 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +1574 -0
- package/lib/index.js.map +1 -0
- package/lib/remote.d.ts +37 -0
- package/lib/remote.d.ts.map +1 -0
- package/lib/{remote.mjs → remote.js} +50 -52
- package/lib/remote.js.map +1 -0
- package/lib/typert.host.d.ts +55 -0
- package/lib/typert.host.d.ts.map +1 -0
- package/lib/typert.host.js +37 -37
- package/lib/typert.host.js.map +1 -0
- package/package.json +12 -10
- package/lib/config-store.mjs +0 -97
- package/lib/index.mjs +0 -1470
package/client/index.tsx
ADDED
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { TypertRemoteContribution } from "@deepseek-ai/dsh-typert-protocol";
|
|
3
|
+
|
|
4
|
+
// ── 样式(注入 style 标签,模仿官方卡片外观)──────────────────────────
|
|
5
|
+
var css = ".ap_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;min-width:0;overflow:hidden}.ap_card[data-open=true]{border-color:var(--dsw-alias-border-l1);box-shadow:var(--dsw-shadow-lv1)}.ap_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:10px;align-items:center;gap:12px;padding:12px 14px;display:flex}.ap_header:hover,.ap_card[data-open=true]>.ap_header{background:var(--dsw-alias-interactive-bg-hover)}.ap_title{flex:1;min-width:0;font-size:14px;font-weight:600;line-height:20px}.ap_badge{white-space:nowrap;background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 10%,transparent);color:var(--dsw-alias-state-success-primary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:16px}.ap_body{border-top:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);padding:10px 14px 12px}.ap_field{flex-direction:column;gap:4px;padding:8px 0;display:flex}.ap_label{font-size:12px;font-weight:500;line-height:18px}.ap_input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:32px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:6px;padding:0 10px;font-size:13px;line-height:20px}.ap_textarea{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);font:inherit;color:var(--dsw-alias-label-primary);border-radius:6px;padding:6px 10px;font-size:12px;line-height:18px;resize:vertical;min-height:120px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.ap_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:16px}.ap_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:8px 0 2px;display:flex}.ap_save{font:inherit;cursor:pointer;border:1px solid transparent;border-radius:6px;padding:4px 14px;font-size:12px;line-height:18px;background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.ap_discard{font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:4px 14px;font-size:12px;line-height:18px;background:0 0;color:inherit}.ap_status{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary);flex:1}.ap_status.ap_error{color:var(--dsw-alias-state-danger-primary)}";
|
|
6
|
+
var tagId = "@chaoset/adaptive-perf/client.css";
|
|
7
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"" + tagId + "\"]") === null) {
|
|
8
|
+
var tag = document.createElement("style");
|
|
9
|
+
tag.dataset.plugin = "@chaoset/adaptive-perf";
|
|
10
|
+
tag.dataset.pluginCss = tagId;
|
|
11
|
+
tag.textContent = css;
|
|
12
|
+
document.head.appendChild(tag);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ── 字典 ─────────────────────────────────────────────────────────────
|
|
16
|
+
const NS = "settings.plugins.adaptivePerf";
|
|
17
|
+
const zh = {
|
|
18
|
+
title: "极简性能自适应(adaptive-perf)",
|
|
19
|
+
hint: "让标准模式 / PTC 模式达到极简模式级别的高性能:抑制运行时上下文快照,启动即精简工具目录,按需求信号自动放行工具族。",
|
|
20
|
+
unsaved: "有未保存的修改",
|
|
21
|
+
discard: "放弃修改",
|
|
22
|
+
saving: "保存中…",
|
|
23
|
+
save: "保存",
|
|
24
|
+
saveFailed: "保存失败",
|
|
25
|
+
saved: "已保存,热生效(新会话立即生效)",
|
|
26
|
+
loadFailed: "读取配置失败",
|
|
27
|
+
invalidFamilies: "families 必须是合法 JSON 对象",
|
|
28
|
+
enabled: "启用插件",
|
|
29
|
+
presets: "目标 preset",
|
|
30
|
+
fieldPresets: "逗号分隔的 preset id(如 standard, code)",
|
|
31
|
+
suppressRuntimeContext: "抑制运行时上下文快照",
|
|
32
|
+
fieldSuppressRuntimeContext: "等同极简模式的 includeRuntimeContext: false,每次请求省掉快照文本(零功能损失)",
|
|
33
|
+
leanByDefault: "启动即精简工具目录",
|
|
34
|
+
fieldLeanByDefault: "默认隐藏编排类工具族(子代理/工作流/ralph/goal),核心编码工具保留",
|
|
35
|
+
escalateOnKeyword: "关键词自动放行",
|
|
36
|
+
fieldEscalateOnKeyword: "用户消息命中工具族触发词(如“子代理”)时,该会话放行对应工具族",
|
|
37
|
+
escalateOnUnknownTool: "失败自动放行",
|
|
38
|
+
fieldEscalateOnUnknownTool: "PTC 程序调用被隐藏工具报 UNKNOWN_TOOL 时自动放行对应族",
|
|
39
|
+
suppressInjectedContext: "常驻抑制注入上下文",
|
|
40
|
+
fieldSuppressInjectedContext: "整个会话剥离技能目录提醒(skill-catalog)与 AGENTS.md 摘要(agent-instructions),不再在晋升后恢复——替代品是 skill_search/skill_load 按需发现与一次性 instruction-hint(参照 dsh-anchored-standard:注入在场即使后置也扰动轨迹)",
|
|
41
|
+
families: "工具族(高级)",
|
|
42
|
+
fieldFamilies: "JSON:{ 族名: { enabled, tools: [工具名], keywords: [触发词] } }。保存后热生效。",
|
|
43
|
+
coreTools: "核心工具(永不隐藏)",
|
|
44
|
+
fieldCoreTools: "展示用:这些工具不进入任何限制族。未列入任何族的工具也默认保留。",
|
|
45
|
+
bootstrapEnabled: "首轮锚定(bootstrap)",
|
|
46
|
+
fieldBootstrapEnabled: "请求 #1 只暴露 Minimal 真实工具对并剥离自动注入上下文,首个持久信号(promoteOn)后恢复——让首轮轨迹锚定极简(参照 dsh-anchored-standard 实测:工具 schema + 注入提醒决定轨迹)",
|
|
47
|
+
bootstrapRealPair: "挂载真实 Minimal 工具对",
|
|
48
|
+
fieldBootstrapRealPair: "把官方 minimal preset 同款插件(持久 PTY bash + str_replace_editor)挂进会话作用域,按名阴影 standard 的 sandboxed bash——schema 与 Minimal 逐字相同才能锚定(issue #11:任何 standard 系 schema 11/11 落入 standard-like);依赖官方插件包,缺失时自动降级",
|
|
49
|
+
bootstrapTools: "首轮工具",
|
|
50
|
+
fieldBootstrapTools: "请求 #1 可见的工具(真实 Minimal 对:bash, str_replace_editor)。逗号分隔。",
|
|
51
|
+
bootstrapPromoteOn: "晋升触发",
|
|
52
|
+
fieldBootstrapPromoteOn: "either(默认):首个 tool/call 或 assistant/message 先到者晋升;tool-call / assistant-message 可单独指定",
|
|
53
|
+
bootstrapSuppressed: "剥离的注入上下文",
|
|
54
|
+
bootstrapDiscovery: "常驻发现工具(晋升后)",
|
|
55
|
+
fieldBootstrapSuppressed: "请求 #1 剥离的自动注入 source.kind(技能目录提醒 skill-catalog、AGENTS.md 摘要 agent-instructions);逗号分隔,留空=不剥离",
|
|
56
|
+
fieldBootstrapDiscovery: "晋升后常驻目录的发现工具(dev_tool_search 搜索完整目录并按名解锁;skill_search / skill_load 按需加载技能);逗号分隔",
|
|
57
|
+
bootstrapMaxTokens: "首轮输出预算封顶(token)",
|
|
58
|
+
fieldBootstrapMaxTokens: "请求 #1 的 maxTokens 封顶,晋升后自动剥离;0 = 不封顶(opt-in)",
|
|
59
|
+
minimalPromptEnabled: "极简提示词层(语域锚定)",
|
|
60
|
+
fieldMinimalPromptEnabled: "把系统提示拉回极简语域:屏蔽全局引导段 + 替换 persona。参考实现的完整锚定条件——仅收窄工具不足以翻转思维链风格(let me → let's/i'll/i need)",
|
|
61
|
+
minimalPromptPersona: "persona 文本",
|
|
62
|
+
fieldMinimalPromptPersona: "按名替换 deployment:persona;默认与极简模式逐字相同。留空 = 不替换(保留原 persona)",
|
|
63
|
+
minimalPromptSuppressSections: "屏蔽全局引导段",
|
|
64
|
+
fieldMinimalPromptSuppressSections: "屏蔽 harness:identity / harness:source / app:web-surface 三个全局段(等同极简 complete persona 的效果;plan-mode 与 PTC 的 SDK 段不受影响)"
|
|
65
|
+
};
|
|
66
|
+
const en = {
|
|
67
|
+
title: "Adaptive performance (adaptive-perf)",
|
|
68
|
+
hint: "Bring standard / PTC presets to minimal-mode-level performance: suppress the runtime-context snapshot, start with a lean tool catalog, and re-enable tool families on demand.",
|
|
69
|
+
unsaved: "Unsaved changes",
|
|
70
|
+
discard: "Discard",
|
|
71
|
+
saving: "Saving…",
|
|
72
|
+
save: "Save",
|
|
73
|
+
saveFailed: "Save failed",
|
|
74
|
+
saved: "Saved; hot-applied (new sessions take effect immediately)",
|
|
75
|
+
loadFailed: "Failed to load config",
|
|
76
|
+
invalidFamilies: "families must be valid JSON object",
|
|
77
|
+
enabled: "Enable plugin",
|
|
78
|
+
presets: "Target presets",
|
|
79
|
+
fieldPresets: "Comma-separated preset ids (e.g. standard, code)",
|
|
80
|
+
suppressRuntimeContext: "Suppress runtime-context snapshot",
|
|
81
|
+
fieldSuppressRuntimeContext: "Same as minimal's includeRuntimeContext: false; drops the snapshot text from every request (zero feature loss)",
|
|
82
|
+
leanByDefault: "Lean tool catalog at session start",
|
|
83
|
+
fieldLeanByDefault: "Hide orchestration families (subagent/workflow/ralph/goal) by default; core coding tools stay",
|
|
84
|
+
escalateOnKeyword: "Keyword escalation",
|
|
85
|
+
fieldEscalateOnKeyword: "A user message matching a family's trigger words re-enables that family for the session",
|
|
86
|
+
escalateOnUnknownTool: "Failure escalation",
|
|
87
|
+
fieldEscalateOnUnknownTool: "A PTC program calling a hidden tool (UNKNOWN_TOOL) re-enables that family",
|
|
88
|
+
suppressInjectedContext: "Always suppress injected context",
|
|
89
|
+
fieldSuppressInjectedContext: "Strip the skill-catalog reminder and the AGENTS.md digest from every request, not just request #1 — replaced by on-demand skill_search/skill_load and a one-shot instruction hint (per dsh-anchored-standard: the injections perturb the trajectory even after promotion)",
|
|
90
|
+
families: "Tool families (advanced)",
|
|
91
|
+
fieldFamilies: "JSON: { familyId: { enabled, tools: [names], keywords: [triggers] } }. Hot-applied on save.",
|
|
92
|
+
coreTools: "Core tools (never hidden)",
|
|
93
|
+
fieldCoreTools: "Informational: these never enter any restriction family. Tools not listed in any family stay visible too.",
|
|
94
|
+
bootstrapEnabled: "First-request bootstrap",
|
|
95
|
+
fieldBootstrapEnabled: "Request #1 exposes only the real Minimal tool pair and drops auto-injected context; the first durable signal (promoteOn) restores the catalog — anchors the first-request trajectory to minimal (per dsh-anchored-standard measurements)",
|
|
96
|
+
bootstrapRealPair: "Mount the real Minimal tool pair",
|
|
97
|
+
fieldBootstrapRealPair: "Mount the official minimal-preset plugins (persistent PTY bash + str_replace_editor) into the session scope, shadowing the standard sandboxed bash by name — only byte-identical schemas anchor (issue #11: every standard-family schema fell standard-like 11/11); degrades gracefully when the official packages are missing",
|
|
98
|
+
bootstrapTools: "Bootstrap tools",
|
|
99
|
+
fieldBootstrapTools: "Tools visible on request #1 (the real Minimal pair: bash, str_replace_editor). Comma-separated.",
|
|
100
|
+
bootstrapPromoteOn: "Promotion trigger",
|
|
101
|
+
fieldBootstrapPromoteOn: "either (default): first tool/call or assistant/message promotes; tool-call / assistant-message select one",
|
|
102
|
+
bootstrapSuppressed: "Suppressed injected context",
|
|
103
|
+
bootstrapDiscovery: "Resident discovery tools (post-promotion)",
|
|
104
|
+
fieldBootstrapSuppressed: "Auto-injected source.kinds stripped on request #1 (skill-catalog reminder, agent-instructions digest); comma-separated, empty = strip nothing",
|
|
105
|
+
fieldBootstrapDiscovery: "Resident discovery tools after promotion (dev_tool_search searches the full catalog and unlocks by name; skill_search / skill_load load skills on demand); comma-separated",
|
|
106
|
+
bootstrapMaxTokens: "First-request output cap (tokens)",
|
|
107
|
+
fieldBootstrapMaxTokens: "maxTokens cap for request #1, stripped after promotion; 0 = no cap (opt-in)",
|
|
108
|
+
minimalPromptEnabled: "Minimal prompt layer (register anchor)",
|
|
109
|
+
fieldMinimalPromptEnabled: "Pull the system prompt back to the minimal register: suppress the global orientation sections and replace the persona. The reference's full anchoring condition — tool narrowing alone does not flip the thinking style (let me → let's/i'll/i need)",
|
|
110
|
+
minimalPromptPersona: "Persona text",
|
|
111
|
+
fieldMinimalPromptPersona: "Shadows deployment:persona by name; defaults to the exact minimal-mode text. Empty = keep the original persona",
|
|
112
|
+
minimalPromptSuppressSections: "Suppress global orientation sections",
|
|
113
|
+
fieldMinimalPromptSuppressSections: "Shadow harness:identity / harness:source / app:web-surface to empty (same effect as minimal's complete persona; plan-mode and the PTC SDK sections are unaffected)"
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// ── 表单字段 ─────────────────────────────────────────────────────────
|
|
117
|
+
function Field(props: any) {
|
|
118
|
+
return React.createElement(
|
|
119
|
+
"label",
|
|
120
|
+
{ className: "ap_field" },
|
|
121
|
+
React.createElement("span", { className: "ap_label" }, props.label),
|
|
122
|
+
props.children,
|
|
123
|
+
props.hint !== void 0 ? React.createElement("p", { className: "ap_hint" }, props.hint) : null
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
function TextField(props: any) {
|
|
127
|
+
return React.createElement(Field, { label: props.label, hint: props.hint },
|
|
128
|
+
React.createElement("input", {
|
|
129
|
+
className: "ap_input",
|
|
130
|
+
value: props.value,
|
|
131
|
+
onChange: (e: any) => props.onChange(e.target.value),
|
|
132
|
+
disabled: props.disabled === true
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
function BoolField(props: any) {
|
|
137
|
+
return React.createElement(Field, { label: props.label, hint: props.hint },
|
|
138
|
+
React.createElement("input", {
|
|
139
|
+
type: "checkbox",
|
|
140
|
+
checked: props.value === true,
|
|
141
|
+
onChange: (e: any) => props.onChange(e.target.checked),
|
|
142
|
+
disabled: props.disabled === true
|
|
143
|
+
})
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
function familiesToText(families: any) {
|
|
147
|
+
return JSON.stringify(families, null, 2);
|
|
148
|
+
}
|
|
149
|
+
function textToFamilies(text: any) {
|
|
150
|
+
const parsed = JSON.parse(text);
|
|
151
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
152
|
+
throw new TypeError("families must be a JSON object");
|
|
153
|
+
}
|
|
154
|
+
for (const [id, fam] of Object.entries(parsed as Record<string, any>)) {
|
|
155
|
+
if (fam === null || typeof fam !== "object" || Array.isArray(fam)) {
|
|
156
|
+
throw new TypeError("family \"" + id + "\" must be an object");
|
|
157
|
+
}
|
|
158
|
+
if (fam.enabled !== void 0 && typeof fam.enabled !== "boolean") {
|
|
159
|
+
throw new TypeError("family \"" + id + "\".enabled must be a boolean");
|
|
160
|
+
}
|
|
161
|
+
for (const key of ["tools", "keywords"]) {
|
|
162
|
+
if (fam[key] !== void 0 && !(Array.isArray(fam[key]) && fam[key].every((v) => typeof v === "string"))) {
|
|
163
|
+
throw new TypeError("family \"" + id + "\"." + key + " must be an array of strings");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return parsed;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── 配置卡片 ─────────────────────────────────────────────────────────
|
|
171
|
+
function AdaptivePerfCard(props: any) {
|
|
172
|
+
const t = props.t;
|
|
173
|
+
const [open, setOpen] = React.useState(false);
|
|
174
|
+
const [cfg, setCfg] = React.useState<any>(null);
|
|
175
|
+
const [draft, setDraft] = React.useState<any>(null);
|
|
176
|
+
const [familiesText, setFamiliesText] = React.useState("");
|
|
177
|
+
const [saving, setSaving] = React.useState(false);
|
|
178
|
+
const [status, setStatus] = React.useState<any>(null);
|
|
179
|
+
|
|
180
|
+
React.useEffect(() => {
|
|
181
|
+
let cancelled = false;
|
|
182
|
+
props.getConfig().then((value: any) => {
|
|
183
|
+
if (cancelled) return;
|
|
184
|
+
setCfg(value);
|
|
185
|
+
setDraft(value);
|
|
186
|
+
setFamiliesText(familiesToText(value.families));
|
|
187
|
+
}).catch((error: any) => {
|
|
188
|
+
if (!cancelled) setStatus({ kind: "error", text: t("loadFailed") + ": " + (error && error.message || error) });
|
|
189
|
+
});
|
|
190
|
+
return () => { cancelled = true; };
|
|
191
|
+
}, []);
|
|
192
|
+
|
|
193
|
+
let familiesInvalid = false;
|
|
194
|
+
let familiesError = null;
|
|
195
|
+
if (draft !== null) {
|
|
196
|
+
try {
|
|
197
|
+
textToFamilies(familiesText);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
familiesInvalid = true;
|
|
200
|
+
familiesError = (error as Error)?.message ?? String(error);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const dirty = draft !== null && (JSON.stringify(draft) !== JSON.stringify(cfg) || familiesInvalid === false && familiesToText(draft.families) !== familiesText);
|
|
204
|
+
const invalid = draft !== null && familiesInvalid;
|
|
205
|
+
const discard = () => {
|
|
206
|
+
setDraft(cfg);
|
|
207
|
+
setFamiliesText(cfg === null ? "" : familiesToText(cfg.families));
|
|
208
|
+
setStatus(null);
|
|
209
|
+
};
|
|
210
|
+
const save = () => {
|
|
211
|
+
if (draft === null || familiesInvalid) return;
|
|
212
|
+
let next = draft;
|
|
213
|
+
try {
|
|
214
|
+
next = { ...draft, families: textToFamilies(familiesText) };
|
|
215
|
+
} catch (error) {
|
|
216
|
+
setStatus({ kind: "error", text: t("invalidFamilies") + ": " + ((error as Error)?.message ?? String(error)) });
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
setSaving(true);
|
|
220
|
+
setStatus(null);
|
|
221
|
+
props.setConfig(next).then(() => {
|
|
222
|
+
setCfg(next);
|
|
223
|
+
setDraft(next);
|
|
224
|
+
setFamiliesText(familiesToText(next.families));
|
|
225
|
+
setStatus({ kind: "ok", text: t("saved") });
|
|
226
|
+
}).catch((error: any) => {
|
|
227
|
+
setStatus({ kind: "error", text: t("saveFailed") + ": " + (error && error.message || error) });
|
|
228
|
+
}).finally(() => setSaving(false));
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
return React.createElement(
|
|
232
|
+
"li",
|
|
233
|
+
{ className: "ap_card", "data-open": open },
|
|
234
|
+
React.createElement(
|
|
235
|
+
"button",
|
|
236
|
+
{
|
|
237
|
+
className: "ap_header",
|
|
238
|
+
type: "button",
|
|
239
|
+
onClick: () => setOpen(!open),
|
|
240
|
+
"aria-expanded": open
|
|
241
|
+
},
|
|
242
|
+
React.createElement("span", { className: "ap_title" }, t("title")),
|
|
243
|
+
dirty ? React.createElement("span", { className: "ap_badge" }, t("unsaved")) : null,
|
|
244
|
+
React.createElement("span", null, open ? "▲" : "▼")
|
|
245
|
+
),
|
|
246
|
+
open ? React.createElement(
|
|
247
|
+
"div",
|
|
248
|
+
{ className: "ap_body" },
|
|
249
|
+
React.createElement("p", { className: "ap_hint" }, t("hint")),
|
|
250
|
+
React.createElement(BoolField, {
|
|
251
|
+
label: t("enabled"),
|
|
252
|
+
value: draft === null ? true : draft.enabled,
|
|
253
|
+
disabled: draft === null,
|
|
254
|
+
onChange: (v: any) => setDraft({ ...draft, enabled: v })
|
|
255
|
+
}),
|
|
256
|
+
React.createElement(TextField, {
|
|
257
|
+
label: t("presets"),
|
|
258
|
+
hint: t("fieldPresets"),
|
|
259
|
+
value: draft === null ? "standard, code, cordis" : (draft.presets || []).join(", "),
|
|
260
|
+
disabled: draft === null,
|
|
261
|
+
onChange: (v: any) => setDraft({ ...draft, presets: v.split(/[,,\s]+/).filter((x: any) => x.length > 0) })
|
|
262
|
+
}),
|
|
263
|
+
React.createElement(BoolField, {
|
|
264
|
+
label: t("suppressRuntimeContext"),
|
|
265
|
+
hint: t("fieldSuppressRuntimeContext"),
|
|
266
|
+
value: draft === null ? true : draft.suppressRuntimeContext,
|
|
267
|
+
disabled: draft === null,
|
|
268
|
+
onChange: (v: any) => setDraft({ ...draft, suppressRuntimeContext: v })
|
|
269
|
+
}),
|
|
270
|
+
React.createElement(BoolField, {
|
|
271
|
+
label: t("leanByDefault"),
|
|
272
|
+
hint: t("fieldLeanByDefault"),
|
|
273
|
+
value: draft === null ? false : draft.leanByDefault,
|
|
274
|
+
disabled: draft === null,
|
|
275
|
+
onChange: (v: any) => setDraft({ ...draft, leanByDefault: v })
|
|
276
|
+
}),
|
|
277
|
+
React.createElement(BoolField, {
|
|
278
|
+
label: t("escalateOnKeyword"),
|
|
279
|
+
hint: t("fieldEscalateOnKeyword"),
|
|
280
|
+
value: draft === null ? true : draft.escalateOnKeyword,
|
|
281
|
+
disabled: draft === null,
|
|
282
|
+
onChange: (v: any) => setDraft({ ...draft, escalateOnKeyword: v })
|
|
283
|
+
}),
|
|
284
|
+
React.createElement(BoolField, {
|
|
285
|
+
label: t("escalateOnUnknownTool"),
|
|
286
|
+
hint: t("fieldEscalateOnUnknownTool"),
|
|
287
|
+
value: draft === null ? true : draft.escalateOnUnknownTool,
|
|
288
|
+
disabled: draft === null,
|
|
289
|
+
onChange: (v: any) => setDraft({ ...draft, escalateOnUnknownTool: v })
|
|
290
|
+
}),
|
|
291
|
+
React.createElement(BoolField, {
|
|
292
|
+
label: t("suppressInjectedContext"),
|
|
293
|
+
hint: t("fieldSuppressInjectedContext"),
|
|
294
|
+
value: draft === null ? false : draft.suppressInjectedContext,
|
|
295
|
+
disabled: draft === null,
|
|
296
|
+
onChange: (v: any) => setDraft({ ...draft, suppressInjectedContext: v })
|
|
297
|
+
}),
|
|
298
|
+
React.createElement(BoolField, {
|
|
299
|
+
label: t("bootstrapEnabled"),
|
|
300
|
+
hint: t("fieldBootstrapEnabled"),
|
|
301
|
+
value: draft === null ? true : !!draft.bootstrap && draft.bootstrap.enabled,
|
|
302
|
+
disabled: draft === null,
|
|
303
|
+
onChange: (v: any) => setDraft({ ...draft, bootstrap: { ...(draft.bootstrap || {}), enabled: v } })
|
|
304
|
+
}),
|
|
305
|
+
React.createElement(BoolField, {
|
|
306
|
+
label: t("bootstrapRealPair"),
|
|
307
|
+
hint: t("fieldBootstrapRealPair"),
|
|
308
|
+
value: draft === null ? true : !!draft.bootstrap && draft.bootstrap.realPair,
|
|
309
|
+
disabled: draft === null || (draft.bootstrap && draft.bootstrap.enabled === false),
|
|
310
|
+
onChange: (v: any) => setDraft({ ...draft, bootstrap: { ...(draft.bootstrap || {}), realPair: v } })
|
|
311
|
+
}),
|
|
312
|
+
React.createElement(TextField, {
|
|
313
|
+
label: t("bootstrapTools"),
|
|
314
|
+
hint: t("fieldBootstrapTools"),
|
|
315
|
+
value: draft === null ? "" : ((draft.bootstrap && draft.bootstrap.tools) || []).join(", "),
|
|
316
|
+
disabled: draft === null || (draft.bootstrap && draft.bootstrap.enabled === false),
|
|
317
|
+
onChange: (v: any) => setDraft({ ...draft, bootstrap: { ...(draft.bootstrap || {}), tools: v.split(/[,,\s]+/).filter((x: any) => x.length > 0) } })
|
|
318
|
+
}),
|
|
319
|
+
React.createElement(TextField, {
|
|
320
|
+
label: t("bootstrapPromoteOn"),
|
|
321
|
+
hint: t("fieldBootstrapPromoteOn"),
|
|
322
|
+
value: draft === null ? "" : (draft.bootstrap && draft.bootstrap.promoteOn) || "either",
|
|
323
|
+
disabled: draft === null || (draft.bootstrap && draft.bootstrap.enabled === false),
|
|
324
|
+
onChange: (v: any) => setDraft({ ...draft, bootstrap: { ...(draft.bootstrap || {}), promoteOn: (v.trim() || "either") } })
|
|
325
|
+
}),
|
|
326
|
+
React.createElement(TextField, {
|
|
327
|
+
label: t("bootstrapSuppressed"),
|
|
328
|
+
hint: t("fieldBootstrapSuppressed"),
|
|
329
|
+
value: draft === null ? "" : ((draft.bootstrap && draft.bootstrap.suppressedContextSources) || []).join(", "),
|
|
330
|
+
disabled: draft === null || (draft.bootstrap && draft.bootstrap.enabled === false),
|
|
331
|
+
onChange: (v: any) => setDraft({ ...draft, bootstrap: { ...(draft.bootstrap || {}), suppressedContextSources: v.split(/[,,\s]+/).filter((x: any) => x.length > 0) } })
|
|
332
|
+
}),
|
|
333
|
+
React.createElement(TextField, {
|
|
334
|
+
label: t("bootstrapDiscovery"),
|
|
335
|
+
hint: t("fieldBootstrapDiscovery"),
|
|
336
|
+
value: draft === null ? "" : ((draft.bootstrap && draft.bootstrap.discoveryTools) || []).join(", "),
|
|
337
|
+
disabled: draft === null || (draft.bootstrap && draft.bootstrap.enabled === false),
|
|
338
|
+
onChange: (v: any) => setDraft({ ...draft, bootstrap: { ...(draft.bootstrap || {}), discoveryTools: v.split(/[,,\s]+/).filter((x: any) => x.length > 0) } })
|
|
339
|
+
}),
|
|
340
|
+
React.createElement(TextField, {
|
|
341
|
+
label: t("bootstrapMaxTokens"),
|
|
342
|
+
hint: t("fieldBootstrapMaxTokens"),
|
|
343
|
+
value: draft === null ? "" : String((draft.bootstrap && draft.bootstrap.maxTokens) || 0),
|
|
344
|
+
disabled: draft === null || (draft.bootstrap && draft.bootstrap.enabled === false),
|
|
345
|
+
onChange: (v: any) => {
|
|
346
|
+
const n = Number(v.trim());
|
|
347
|
+
setDraft({ ...draft, bootstrap: { ...(draft.bootstrap || {}), maxTokens: Number.isSafeInteger(n) && n > 0 ? n : 0 } });
|
|
348
|
+
}
|
|
349
|
+
}),
|
|
350
|
+
React.createElement(BoolField, {
|
|
351
|
+
label: t("minimalPromptEnabled"),
|
|
352
|
+
hint: t("fieldMinimalPromptEnabled"),
|
|
353
|
+
value: draft === null ? true : (draft.minimalPrompt && draft.minimalPrompt.enabled === true),
|
|
354
|
+
disabled: draft === null,
|
|
355
|
+
onChange: (v: any) => setDraft({ ...draft, minimalPrompt: { ...(draft.minimalPrompt || {}), enabled: v } })
|
|
356
|
+
}),
|
|
357
|
+
React.createElement(BoolField, {
|
|
358
|
+
label: t("minimalPromptSuppressSections"),
|
|
359
|
+
hint: t("fieldMinimalPromptSuppressSections"),
|
|
360
|
+
value: draft === null ? true : (draft.minimalPrompt && draft.minimalPrompt.suppressSections === true),
|
|
361
|
+
disabled: draft === null || (draft.minimalPrompt && draft.minimalPrompt.enabled === false),
|
|
362
|
+
onChange: (v: any) => setDraft({ ...draft, minimalPrompt: { ...(draft.minimalPrompt || {}), suppressSections: v } })
|
|
363
|
+
}),
|
|
364
|
+
React.createElement(TextField, {
|
|
365
|
+
label: t("minimalPromptPersona"),
|
|
366
|
+
hint: t("fieldMinimalPromptPersona"),
|
|
367
|
+
value: draft === null ? "" : ((draft.minimalPrompt && draft.minimalPrompt.persona) || ""),
|
|
368
|
+
disabled: draft === null || (draft.minimalPrompt && draft.minimalPrompt.enabled === false),
|
|
369
|
+
onChange: (v: any) => setDraft({ ...draft, minimalPrompt: { ...(draft.minimalPrompt || {}), persona: v } })
|
|
370
|
+
}),
|
|
371
|
+
React.createElement(Field, {
|
|
372
|
+
label: t("families"),
|
|
373
|
+
hint: familiesInvalid ? t("invalidFamilies") + (familiesError !== null ? " — " + familiesError : "") : t("fieldFamilies")
|
|
374
|
+
},
|
|
375
|
+
React.createElement("textarea", {
|
|
376
|
+
className: "ap_textarea",
|
|
377
|
+
value: familiesText,
|
|
378
|
+
disabled: draft === null,
|
|
379
|
+
spellCheck: false,
|
|
380
|
+
onChange: (e: any) => setFamiliesText(e.target.value)
|
|
381
|
+
})
|
|
382
|
+
),
|
|
383
|
+
React.createElement(Field, {
|
|
384
|
+
label: t("coreTools"),
|
|
385
|
+
hint: t("fieldCoreTools")
|
|
386
|
+
},
|
|
387
|
+
React.createElement("p", { className: "ap_hint" }, draft === null ? "" : (draft.coreTools || []).join(", "))
|
|
388
|
+
),
|
|
389
|
+
React.createElement(
|
|
390
|
+
"div",
|
|
391
|
+
{ className: "ap_footer" },
|
|
392
|
+
React.createElement(
|
|
393
|
+
"span",
|
|
394
|
+
{ className: "ap_status" + (status !== null && status.kind === "error" ? " ap_error" : ""), role: "status" },
|
|
395
|
+
status !== null ? status.text : ""
|
|
396
|
+
),
|
|
397
|
+
React.createElement(
|
|
398
|
+
"button",
|
|
399
|
+
{ className: "ap_discard", type: "button", disabled: saving || draft === null || !dirty, onClick: discard },
|
|
400
|
+
t("discard")
|
|
401
|
+
),
|
|
402
|
+
React.createElement(
|
|
403
|
+
"button",
|
|
404
|
+
{ className: "ap_save", type: "button", disabled: saving || draft === null || invalid || !dirty, onClick: save },
|
|
405
|
+
saving ? t("saving") : t("save")
|
|
406
|
+
)
|
|
407
|
+
)
|
|
408
|
+
) : null
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ── 插件 apply ───────────────────────────────────────────────────────
|
|
413
|
+
// DSH 客户端的 remote.<ns> 服务不会自动生成:必须由客户端代码用
|
|
414
|
+
// ctx.remote.$mount(contribution) 显式挂载(官方 dsh-api-remotes 即如此)。
|
|
415
|
+
// 若只把 remote.adaptivePerfConfig 写进 inject 而不挂载,命名空间永远
|
|
416
|
+
// 不存在,客户端插件会一直 pending,web boot 报 "did not activate"。
|
|
417
|
+
// 因此本插件先挂载自己的命名空间,再注册设置卡片。
|
|
418
|
+
const inject: any[] = ["slots", "locale", "remote"];
|
|
419
|
+
const passthroughSchema = { parse: (value: any) => value };
|
|
420
|
+
const REMOTE_CONTRIBUTION: TypertRemoteContribution = {
|
|
421
|
+
package: "@chaoset/adaptive-perf",
|
|
422
|
+
descriptors: [
|
|
423
|
+
{
|
|
424
|
+
id: "@chaoset/adaptive-perf#adaptivePerfConfig/get",
|
|
425
|
+
service: "adaptivePerfConfig",
|
|
426
|
+
namespace: "adaptivePerfConfig",
|
|
427
|
+
method: "get",
|
|
428
|
+
invocation: { kind: "direct" },
|
|
429
|
+
parameters: [],
|
|
430
|
+
result: { mode: "strict", typeSymbol: "adaptivePerfConfig/get:result", schema: passthroughSchema }
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
id: "@chaoset/adaptive-perf#adaptivePerfConfig/set",
|
|
434
|
+
service: "adaptivePerfConfig",
|
|
435
|
+
namespace: "adaptivePerfConfig",
|
|
436
|
+
method: "set",
|
|
437
|
+
invocation: { kind: "direct" },
|
|
438
|
+
parameters: [{
|
|
439
|
+
name: "partial",
|
|
440
|
+
wire: "partial",
|
|
441
|
+
source: "json",
|
|
442
|
+
codec: { mode: "strict", typeSymbol: "adaptivePerfConfig/set:partial", schema: passthroughSchema }
|
|
443
|
+
}],
|
|
444
|
+
result: { mode: "strict", typeSymbol: "adaptivePerfConfig/set:result", schema: passthroughSchema }
|
|
445
|
+
}
|
|
446
|
+
]
|
|
447
|
+
};
|
|
448
|
+
async function apply(ctx: any) {
|
|
449
|
+
const t = ctx.locale.bind(NS);
|
|
450
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "adaptive-perf: dictionaries");
|
|
451
|
+
// 先挂载命名空间,再用 ctx.get 取回服务:cordis 的属性访问(ctx.remote.X)
|
|
452
|
+
// 要求 X 出现在 inject 里,而本插件的命名空间由自己挂载,若写进 inject
|
|
453
|
+
// 会和自己等待的服务形成死锁,因此用 ctx.get(对未声明的服务合法)。
|
|
454
|
+
await ctx.remote.$mount(REMOTE_CONTRIBUTION);
|
|
455
|
+
const configService = ctx.get("remote.adaptivePerfConfig");
|
|
456
|
+
if (configService === void 0) throw new Error("adaptive-perf: remote.adaptivePerfConfig did not materialize after mount");
|
|
457
|
+
const getConfig = () => configService.get().then((result: any) => {
|
|
458
|
+
if (!result.ok) throw new Error("adaptivePerfConfig.get failed: " + result.error.code + ": " + result.error.message);
|
|
459
|
+
return result.value.config;
|
|
460
|
+
});
|
|
461
|
+
const setConfig = (partial: any) => configService.set(partial).then((result: any) => {
|
|
462
|
+
if (!result.ok) throw new Error("adaptivePerfConfig.set failed: " + result.error.code + ": " + result.error.message);
|
|
463
|
+
return result.value;
|
|
464
|
+
});
|
|
465
|
+
ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
|
|
466
|
+
name: "settings.plugin.item",
|
|
467
|
+
// keyed slot:key 为卡片编辑的设置 namespace(与 host 侧 serviceKey 一致),
|
|
468
|
+
// 设置页按 key 与宿主服务的 namespace 配对 dispatch。
|
|
469
|
+
key: "adaptivePerfConfig",
|
|
470
|
+
id: "adaptive-perf",
|
|
471
|
+
order: 40,
|
|
472
|
+
locale: NS,
|
|
473
|
+
inject: () => ({ getConfig, setConfig })
|
|
474
|
+
}, AdaptivePerfCard));
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export { apply, inject };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config-store — 插件配置持久化
|
|
3
|
+
*
|
|
4
|
+
* 配置文件位于 $DSH_HOME/plugins/<name>/config.json(默认 ~/.dsh,与
|
|
5
|
+
* file:// 部署模式共用)。生效顺序(后者覆盖前者):
|
|
6
|
+
* 1. 插件内置默认值(DEFAULT_CONFIG)
|
|
7
|
+
* 2. cordis.patch.yml 传入的 config(安装时生成的默认块)
|
|
8
|
+
* 3. config.json(设置页 UI 保存,权威)
|
|
9
|
+
*
|
|
10
|
+
* 保存后通过 onUpdate 回调立即热更新运行中的配置(包装闭包读共享 state)。
|
|
11
|
+
* 写入采用随机临时文件 + fsync + rename,权限 0600;损坏 JSON 只告警并回退。
|
|
12
|
+
*/
|
|
13
|
+
export interface ConfigStoreOptions {
|
|
14
|
+
name: string;
|
|
15
|
+
defaults: Record<string, any>;
|
|
16
|
+
patchConfig: Record<string, any>;
|
|
17
|
+
onUpdate?: (merged: Record<string, any>, next: Record<string, any>) => void;
|
|
18
|
+
validate?: (partial: Record<string, any>) => void;
|
|
19
|
+
warn?: (message: string) => void;
|
|
20
|
+
}
|
|
21
|
+
export interface ConfigStore {
|
|
22
|
+
file: string;
|
|
23
|
+
effective(): Record<string, any>;
|
|
24
|
+
set(partial: Record<string, any>): Record<string, any>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 创建配置存储。
|
|
28
|
+
* @param options - { name, defaults, patchConfig, onUpdate,
|
|
29
|
+
* validate?: (partial) => void, warn?: (message) => void }
|
|
30
|
+
*/
|
|
31
|
+
export declare function createConfigStore(options: ConfigStoreOptions): ConfigStore;
|
|
32
|
+
//# sourceMappingURL=config-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-store.d.ts","sourceRoot":"","sources":["../src/config-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,CAAC;IAC5E,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,CAAC;IAClD,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACxD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW,CAyE1E"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config-store — 插件配置持久化
|
|
3
|
+
*
|
|
4
|
+
* 配置文件位于 $DSH_HOME/plugins/<name>/config.json(默认 ~/.dsh,与
|
|
5
|
+
* file:// 部署模式共用)。生效顺序(后者覆盖前者):
|
|
6
|
+
* 1. 插件内置默认值(DEFAULT_CONFIG)
|
|
7
|
+
* 2. cordis.patch.yml 传入的 config(安装时生成的默认块)
|
|
8
|
+
* 3. config.json(设置页 UI 保存,权威)
|
|
9
|
+
*
|
|
10
|
+
* 保存后通过 onUpdate 回调立即热更新运行中的配置(包装闭包读共享 state)。
|
|
11
|
+
* 写入采用随机临时文件 + fsync + rename,权限 0600;损坏 JSON 只告警并回退。
|
|
12
|
+
*/
|
|
13
|
+
import { randomUUID } from "node:crypto";
|
|
14
|
+
import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { dirname, join, resolve } from "node:path";
|
|
16
|
+
import { homedir } from "node:os";
|
|
17
|
+
/**
|
|
18
|
+
* 创建配置存储。
|
|
19
|
+
* @param options - { name, defaults, patchConfig, onUpdate,
|
|
20
|
+
* validate?: (partial) => void, warn?: (message) => void }
|
|
21
|
+
*/
|
|
22
|
+
export function createConfigStore(options) {
|
|
23
|
+
// 与 harness 的 DSH_HOME 约定保持一致:默认 ~/.dsh,可用 $DSH_HOME 覆盖。
|
|
24
|
+
const dshHome = process.env.DSH_HOME?.trim() ? resolve(process.env.DSH_HOME) : join(homedir(), ".dsh");
|
|
25
|
+
const file = join(dshHome, "plugins", options.name, "config.json");
|
|
26
|
+
const warn = options.warn ?? (() => { });
|
|
27
|
+
let readWarningShown = false;
|
|
28
|
+
function readJson() {
|
|
29
|
+
try {
|
|
30
|
+
if (!existsSync(file))
|
|
31
|
+
return {};
|
|
32
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
33
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
34
|
+
readWarningShown = false;
|
|
35
|
+
return parsed;
|
|
36
|
+
}
|
|
37
|
+
if (!readWarningShown) {
|
|
38
|
+
readWarningShown = true;
|
|
39
|
+
warn(`config file ${file} must contain a JSON object; using empty config`);
|
|
40
|
+
}
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (!readWarningShown) {
|
|
45
|
+
readWarningShown = true;
|
|
46
|
+
warn(`failed to read config file ${file}: ${error?.message ?? String(error)}; using empty config`);
|
|
47
|
+
}
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** 当前生效配置(默认 + patch + json 合并)。 */
|
|
52
|
+
function effective() {
|
|
53
|
+
return { ...options.defaults, ...options.patchConfig, ...readJson() };
|
|
54
|
+
}
|
|
55
|
+
/** 保存部分配置到 config.json 并触发热更新,返回新的生效配置。 */
|
|
56
|
+
function set(partial) {
|
|
57
|
+
if (partial === null || typeof partial !== "object" || Array.isArray(partial)) {
|
|
58
|
+
throw new TypeError("set expects a plain config object");
|
|
59
|
+
}
|
|
60
|
+
options.validate?.(partial);
|
|
61
|
+
const merged = { ...readJson(), ...partial };
|
|
62
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
63
|
+
const tmp = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
64
|
+
let fd;
|
|
65
|
+
try {
|
|
66
|
+
fd = openSync(tmp, "w", 0o600);
|
|
67
|
+
writeFileSync(fd, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
68
|
+
fsyncSync(fd);
|
|
69
|
+
closeSync(fd);
|
|
70
|
+
fd = void 0;
|
|
71
|
+
renameSync(tmp, file);
|
|
72
|
+
// POSIX 目录项持久化(Windows 上目录句柄不可打开时忽略)。
|
|
73
|
+
try {
|
|
74
|
+
const dir = openSync(dirname(file), "r");
|
|
75
|
+
try {
|
|
76
|
+
fsyncSync(dir);
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
closeSync(dir);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch { }
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (fd !== void 0)
|
|
86
|
+
try {
|
|
87
|
+
closeSync(fd);
|
|
88
|
+
}
|
|
89
|
+
catch { }
|
|
90
|
+
try {
|
|
91
|
+
unlinkSync(tmp);
|
|
92
|
+
}
|
|
93
|
+
catch { }
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
const next = { ...options.defaults, ...options.patchConfig, ...merged };
|
|
97
|
+
try {
|
|
98
|
+
options.onUpdate?.(merged, next);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
warn(`onUpdate failed after config save: ${error?.message ?? String(error)}`);
|
|
102
|
+
}
|
|
103
|
+
return next;
|
|
104
|
+
}
|
|
105
|
+
return { file, effective, set };
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=config-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-store.js","sourceRoot":"","sources":["../src/config-store.ts"],"names":[],"mappings":"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"}
|