@chaoset/adaptive-perf 0.7.3 → 0.9.0
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/CHANGELOG.md +42 -0
- package/README.md +2 -1
- package/client/client.cjs +171 -96
- package/client/index.tsx +93 -23
- package/lib/index.d.ts +18 -6
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +125 -32
- package/lib/index.js.map +1 -1
- package/lib/remote.d.ts +1 -1
- package/lib/remote.js +1 -1
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.0](https://github.com/winliyou/dsh-plugins) (2026-08-25)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* 设置卡片按语义分组折叠(首轮锚定 / 极简提示词层 / 高级 JSON 默认收起):20+ 字段不再一屏平铺,渐进披露;分组头带 `aria-expanded`
|
|
8
|
+
* `bootstrap.maxTokens` 数字输入改草稿态:编辑期保留原始输入(清空/删改中间态不立即跳回 0),失焦才归一化显示,消除输入被强制改写的突兀感
|
|
9
|
+
|
|
10
|
+
### Bug Fixes
|
|
11
|
+
|
|
12
|
+
* `session/event` 处理器对 `agents.get(session.id)` 的 `agent.id === session.id` 隐含契约加防御性兜底(按 `agent.session.id` 匹配)并注释说明:未来二者分离时晋升信号不再静默丢配导致 bootstrap restrict 不释放
|
|
13
|
+
* 修复 `applyFamilies` 的 JSDoc 注释与函数签名挤在同一行的格式瑕疵
|
|
14
|
+
|
|
15
|
+
## [0.8.0](https://github.com/winliyou/dsh-plugins) (2026-08-22)
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* **`keywordMatchMode` config** (`smart`(default) | `substring` | `word`): escalation keywords containing CJK keep substring matching (CJK has no word boundaries — `\b` would never match a pure-CJK keyword), pure-ASCII words match on word boundaries via lookarounds (`(?<![a-z0-9_])kw(?![a-z0-9_])`, more predictable than `\b`). Statement text like "the goal is to refactor" or "goalish" no longer falsely escalates the goal family on the substring "goal"; `substring` restores pre-0.8.0 behavior; validated in `remote.set` and normalized with fallback
|
|
20
|
+
* explicit empty arrays are now legal values for all string-list config fields (`presets`, `bootstrap.tools`, family tools/keywords…): previously an emptied list was silently reverted to the default on save, so "apply no preset" was impossible to express
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
* pre-step injected-context stripping now requires bootstrap enabled: with `bootstrap.enabled=false` injections were stripped while no discovery tools were registered as compensation — unrecoverable context loss
|
|
25
|
+
* hot-updating `bootstrap.promoteOn` now takes effect for already-tracked sessions: the phase cache is invalidated and rebuilt from the persistent journal (persistent promotion signals survive the rebuild)
|
|
26
|
+
* per-message overhead reduced: composed preset resolved once per agent instead of once per message; suppressed-context-sources Set cached by content key instead of rebuilt per message; preset lookup failure warning capped at 20 occurrences
|
|
27
|
+
* settings card: `promoteOn` is a select, `maxTokens` a number input (invalid input can no longer be saved); loading placeholder for `suppressInjectedContext` matches the real default; error notices render in the header badge so they are visible while collapsed; dirty detection compares field-by-field instead of order-sensitive JSON.stringify; error color referenced the non-existent `state-danger-primary` token so errors never rendered red (now `state-error-primary`)
|
|
28
|
+
* removed `filterBootstrapMessages` (exact duplicate of `stripSuppressedMessages`); fixed malformed nested JSDoc on dev_tool_search; description truncation operator-precedence bug (`split('\n')[0].slice(...)` could throw under `noUncheckedIndexedAccess` semantics)
|
|
29
|
+
|
|
30
|
+
## [0.7.3](https://github.com/winliyou/dsh-plugins) (2026-08-22)
|
|
31
|
+
|
|
32
|
+
### Bug Fixes
|
|
33
|
+
|
|
34
|
+
* **P0 dev_tool_search used the post-restrict catalog**: tools hidden by restrict layers were undiscoverable/unlockable; it now searches a pre-restrict snapshot captured at agent setup
|
|
35
|
+
* **P0 escalate() did not recompute the bootstrap deny set**: after an escalation signal, family tools stayed hidden behind the second restrict layer; escalate now resyncs the keep-set so the release takes effect on the next request
|
|
36
|
+
* real Minimal tool pair remounts idempotently via a state key: unrelated hot-updates no longer destroy/recreate persistent PTY bashes (cwd/env/background jobs survive)
|
|
37
|
+
* first-round maxTokens cap: listener always registered with in-callback branching (0→N hot-update works; N→0 strips the field instead of injecting 0); promotion strips any cap injected this session including stale values from before a hot-update change
|
|
38
|
+
* custom tool families defined in settings survive normalizeConfig (previously silently dropped)
|
|
39
|
+
* test mocks made host-faithful (schemas reflect active restrict layers); regression tests added for both P0s
|
|
40
|
+
|
|
41
|
+
## [0.7.2](https://github.com/winliyou/dsh-plugins) (2026-08-22)
|
|
42
|
+
|
|
43
|
+
* align with dsh 0.1.0-rc.8 dependency set
|
|
44
|
+
|
|
3
45
|
## [0.7.0](https://github.com/winliyou/dsh-plugins) (2026-08-18)
|
|
4
46
|
|
|
5
47
|
### Features
|
package/README.md
CHANGED
|
@@ -129,7 +129,8 @@ dsh plugin --profile web remove @chaoset/adaptive-perf
|
|
|
129
129
|
| `suppressRuntimeContext` | `true` | 抑制运行时上下文快照(零功能损失) |
|
|
130
130
|
| `suppressInjectedContext` | `true` | 常驻上下文抑制(默认开启):整个会话剥离技能目录/AGENTS.md 注入(发现工具承担可见性);`false` = 只在请求 #1 剥离,晋升后恢复 |
|
|
131
131
|
| `leanByDefault` | `false` | 工具目录精简(opt-in):`true` = 会话启动即隐藏编排类工具族;`false`(默认)= 零裁剪 |
|
|
132
|
-
| `escalateOnKeyword` | `true` | 用户消息命中触发词 →
|
|
132
|
+
| `escalateOnKeyword` | `true` | 用户消息命中触发词 → 放行该族(匹配方式见 `keywordMatchMode`) |
|
|
133
|
+
| `keywordMatchMode` | `"smart"` | 触发词匹配模式:`smart` = 含 CJK 的触发词子串匹配、纯 ASCII 词词边界匹配(`"goalish"` 不命中 `"goal"`,整词 `"the goal is"` 命中);`substring` = 一律子串;`word` = 一律词边界 |
|
|
133
134
|
| `escalateOnUnknownTool` | `true` | 工具调用失败(UNKNOWN_TOOL)→ 放行该族 |
|
|
134
135
|
| `coreTools` | 核心编码工具 | 展示用:不进入任何限制族 |
|
|
135
136
|
| `families` | 见下 | 工具族:`{ 族名: { enabled, tools, keywords } }` |
|
package/client/client.cjs
CHANGED
|
@@ -40,7 +40,7 @@ window.__ModuleLoader__.load({
|
|
|
40
40
|
});
|
|
41
41
|
module.exports = __toCommonJS(index_exports);
|
|
42
42
|
var React = __toESM(require("react"), 1);
|
|
43
|
-
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-
|
|
43
|
+
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_section{border-top:1px dashed var(--dsw-alias-border-l2);margin-top:8px;padding-top:2px}.ap_sectionHead{appearance:none;width:100%;font:inherit;color:var(--dsw-alias-label-secondary);text-align:left;cursor:pointer;background:0 0;border:0;border-radius:6px;align-items:center;justify-content:space-between;gap:8px;padding:6px 2px;display:flex}.ap_sectionHead:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.ap_sectionTitle{font-size:12px;font-weight:600;line-height:18px}.ap_sectionChevron{flex:none;font-size:10px}.ap_sectionBody{flex-direction:column;display:flex}.ap_status.ap_error{color:var(--dsw-alias-state-error-primary)}.ap_badge_error{white-space:nowrap;background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent);color:var(--dsw-alias-state-error-primary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:16px;max-width:260px;overflow:hidden;text-overflow:ellipsis}";
|
|
44
44
|
var tagId = "@chaoset/adaptive-perf/client.css";
|
|
45
45
|
if (typeof document !== "undefined" && document.querySelector('style[data-plugin-css="' + tagId + '"]') === null) {
|
|
46
46
|
tag = document.createElement("style");
|
|
@@ -64,7 +64,7 @@ window.__ModuleLoader__.load({
|
|
|
64
64
|
invalidFamilies: "families \u5FC5\u987B\u662F\u5408\u6CD5 JSON \u5BF9\u8C61",
|
|
65
65
|
enabled: "\u542F\u7528\u63D2\u4EF6",
|
|
66
66
|
presets: "\u76EE\u6807 preset",
|
|
67
|
-
fieldPresets: "\u9017\u53F7\u5206\u9694\u7684 preset id\uFF08\u5982 standard, code\uFF09",
|
|
67
|
+
fieldPresets: "\u9017\u53F7\u5206\u9694\u7684 preset id\uFF08\u5982 standard, code\uFF09\u3002\u7559\u7A7A = \u4E0D\u5E94\u7528\u4EFB\u4F55 preset",
|
|
68
68
|
suppressRuntimeContext: "\u6291\u5236\u8FD0\u884C\u65F6\u4E0A\u4E0B\u6587\u5FEB\u7167",
|
|
69
69
|
fieldSuppressRuntimeContext: "\u7B49\u540C\u6781\u7B80\u6A21\u5F0F\u7684 includeRuntimeContext: false\uFF0C\u6BCF\u6B21\u8BF7\u6C42\u7701\u6389\u5FEB\u7167\u6587\u672C\uFF08\u96F6\u529F\u80FD\u635F\u5931\uFF09",
|
|
70
70
|
leanByDefault: "\u542F\u52A8\u5373\u7CBE\u7B80\u5DE5\u5177\u76EE\u5F55",
|
|
@@ -98,7 +98,10 @@ window.__ModuleLoader__.load({
|
|
|
98
98
|
minimalPromptPersona: "persona \u6587\u672C",
|
|
99
99
|
fieldMinimalPromptPersona: "\u6309\u540D\u66FF\u6362 deployment:persona\uFF1B\u9ED8\u8BA4\u4E0E\u6781\u7B80\u6A21\u5F0F\u9010\u5B57\u76F8\u540C\u3002\u7559\u7A7A = \u4E0D\u66FF\u6362\uFF08\u4FDD\u7559\u539F persona\uFF09",
|
|
100
100
|
minimalPromptSuppressSections: "\u5C4F\u853D\u5168\u5C40\u5F15\u5BFC\u6BB5",
|
|
101
|
-
fieldMinimalPromptSuppressSections: "\u5C4F\u853D harness:identity / harness:source / app:web-surface \u4E09\u4E2A\u5168\u5C40\u6BB5\uFF08\u7B49\u540C\u6781\u7B80 complete persona \u7684\u6548\u679C\uFF1Bplan-mode \u4E0E PTC \u7684 SDK \u6BB5\u4E0D\u53D7\u5F71\u54CD\uFF09"
|
|
101
|
+
fieldMinimalPromptSuppressSections: "\u5C4F\u853D harness:identity / harness:source / app:web-surface \u4E09\u4E2A\u5168\u5C40\u6BB5\uFF08\u7B49\u540C\u6781\u7B80 complete persona \u7684\u6548\u679C\uFF1Bplan-mode \u4E0E PTC \u7684 SDK \u6BB5\u4E0D\u53D7\u5F71\u54CD\uFF09",
|
|
102
|
+
sectionBootstrap: "\u9996\u8F6E\u951A\u5B9A\uFF08bootstrap\uFF09",
|
|
103
|
+
sectionPrompt: "\u6781\u7B80\u63D0\u793A\u8BCD\u5C42\uFF08\u8BED\u57DF\u951A\u5B9A\uFF09",
|
|
104
|
+
sectionAdvanced: "\u9AD8\u7EA7\uFF1A\u5DE5\u5177\u65CF\u4E0E\u6838\u5FC3\u5DE5\u5177"
|
|
102
105
|
};
|
|
103
106
|
var en = {
|
|
104
107
|
title: "Adaptive performance (adaptive-perf)",
|
|
@@ -113,7 +116,7 @@ window.__ModuleLoader__.load({
|
|
|
113
116
|
invalidFamilies: "families must be valid JSON object",
|
|
114
117
|
enabled: "Enable plugin",
|
|
115
118
|
presets: "Target presets",
|
|
116
|
-
fieldPresets: "Comma-separated preset ids (e.g. standard, code)",
|
|
119
|
+
fieldPresets: "Comma-separated preset ids (e.g. standard, code). Empty = apply to no preset.",
|
|
117
120
|
suppressRuntimeContext: "Suppress runtime-context snapshot",
|
|
118
121
|
fieldSuppressRuntimeContext: "Same as minimal's includeRuntimeContext: false; drops the snapshot text from every request (zero feature loss)",
|
|
119
122
|
leanByDefault: "Lean tool catalog at session start",
|
|
@@ -147,7 +150,10 @@ window.__ModuleLoader__.load({
|
|
|
147
150
|
minimalPromptPersona: "Persona text",
|
|
148
151
|
fieldMinimalPromptPersona: "Shadows deployment:persona by name; defaults to the exact minimal-mode text. Empty = keep the original persona",
|
|
149
152
|
minimalPromptSuppressSections: "Suppress global orientation sections",
|
|
150
|
-
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)"
|
|
153
|
+
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)",
|
|
154
|
+
sectionBootstrap: "First-request bootstrap",
|
|
155
|
+
sectionPrompt: "Minimal prompt layer (register anchor)",
|
|
156
|
+
sectionAdvanced: "Advanced: tool families & core tools"
|
|
151
157
|
};
|
|
152
158
|
function Field(props) {
|
|
153
159
|
return React.createElement(
|
|
@@ -182,6 +188,20 @@ window.__ModuleLoader__.load({
|
|
|
182
188
|
})
|
|
183
189
|
);
|
|
184
190
|
}
|
|
191
|
+
function Section(props) {
|
|
192
|
+
const [expanded, setExpanded] = React.useState(props.defaultOpen !== false);
|
|
193
|
+
return React.createElement(
|
|
194
|
+
"div",
|
|
195
|
+
{ className: "ap_section" },
|
|
196
|
+
React.createElement(
|
|
197
|
+
"button",
|
|
198
|
+
{ type: "button", className: "ap_sectionHead", onClick: () => setExpanded(!expanded), "aria-expanded": expanded },
|
|
199
|
+
React.createElement("span", { className: "ap_sectionTitle" }, props.title),
|
|
200
|
+
React.createElement("span", { className: "ap_sectionChevron", "aria-hidden": true }, expanded ? "\u25B2" : "\u25BC")
|
|
201
|
+
),
|
|
202
|
+
expanded ? React.createElement("div", { className: "ap_sectionBody" }, props.children) : null
|
|
203
|
+
);
|
|
204
|
+
}
|
|
185
205
|
function familiesToText(families) {
|
|
186
206
|
return JSON.stringify(families, null, 2);
|
|
187
207
|
}
|
|
@@ -205,12 +225,25 @@ window.__ModuleLoader__.load({
|
|
|
205
225
|
}
|
|
206
226
|
return parsed;
|
|
207
227
|
}
|
|
228
|
+
function valuesEqual(a, b) {
|
|
229
|
+
if (a === b) return true;
|
|
230
|
+
if (typeof a !== typeof b || a === null || b === null) return false;
|
|
231
|
+
if (typeof a !== "object") return false;
|
|
232
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
233
|
+
if (Array.isArray(a)) {
|
|
234
|
+
return a.length === b.length && a.every((v, i) => valuesEqual(v, b[i]));
|
|
235
|
+
}
|
|
236
|
+
const ka = Object.keys(a);
|
|
237
|
+
const kb = Object.keys(b);
|
|
238
|
+
return ka.length === kb.length && ka.every((k) => Object.prototype.hasOwnProperty.call(b, k) && valuesEqual(a[k], b[k]));
|
|
239
|
+
}
|
|
208
240
|
function AdaptivePerfCard(props) {
|
|
209
241
|
const t = props.t;
|
|
210
242
|
const [open, setOpen] = React.useState(false);
|
|
211
243
|
const [cfg, setCfg] = React.useState(null);
|
|
212
244
|
const [draft, setDraft] = React.useState(null);
|
|
213
245
|
const [familiesText, setFamiliesText] = React.useState("");
|
|
246
|
+
const [maxTokensText, setMaxTokensText] = React.useState(null);
|
|
214
247
|
const [saving, setSaving] = React.useState(false);
|
|
215
248
|
const [status, setStatus] = React.useState(null);
|
|
216
249
|
React.useEffect(() => {
|
|
@@ -220,6 +253,7 @@ window.__ModuleLoader__.load({
|
|
|
220
253
|
setCfg(value);
|
|
221
254
|
setDraft(value);
|
|
222
255
|
setFamiliesText(familiesToText(value.families));
|
|
256
|
+
setMaxTokensText(null);
|
|
223
257
|
}).catch((error) => {
|
|
224
258
|
if (!cancelled) setStatus({ kind: "error", text: t("loadFailed") + ": " + (error && error.message || error) });
|
|
225
259
|
});
|
|
@@ -237,11 +271,12 @@ window.__ModuleLoader__.load({
|
|
|
237
271
|
familiesError = error?.message ?? String(error);
|
|
238
272
|
}
|
|
239
273
|
}
|
|
240
|
-
const dirty = draft !== null && (
|
|
274
|
+
const dirty = draft !== null && (!valuesEqual(draft, cfg) || familiesInvalid === false && familiesToText(draft.families) !== familiesText);
|
|
241
275
|
const invalid = draft !== null && familiesInvalid;
|
|
242
276
|
const discard = () => {
|
|
243
277
|
setDraft(cfg);
|
|
244
278
|
setFamiliesText(cfg === null ? "" : familiesToText(cfg.families));
|
|
279
|
+
setMaxTokensText(null);
|
|
245
280
|
setStatus(null);
|
|
246
281
|
};
|
|
247
282
|
const save = () => {
|
|
@@ -259,6 +294,7 @@ window.__ModuleLoader__.load({
|
|
|
259
294
|
setCfg(next);
|
|
260
295
|
setDraft(next);
|
|
261
296
|
setFamiliesText(familiesToText(next.families));
|
|
297
|
+
setMaxTokensText(null);
|
|
262
298
|
setStatus({ kind: "ok", text: t("saved") });
|
|
263
299
|
}).catch((error) => {
|
|
264
300
|
setStatus({ kind: "error", text: t("saveFailed") + ": " + (error && error.message || error) });
|
|
@@ -276,6 +312,7 @@ window.__ModuleLoader__.load({
|
|
|
276
312
|
"aria-expanded": open
|
|
277
313
|
},
|
|
278
314
|
React.createElement("span", { className: "ap_title" }, t("title")),
|
|
315
|
+
status !== null && status.kind === "error" ? React.createElement("span", { className: "ap_badge ap_badge_error", title: status.text }, status.text) : null,
|
|
279
316
|
dirty ? React.createElement("span", { className: "ap_badge" }, t("unsaved")) : null,
|
|
280
317
|
React.createElement("span", null, open ? "\u25B2" : "\u25BC")
|
|
281
318
|
),
|
|
@@ -327,104 +364,142 @@ window.__ModuleLoader__.load({
|
|
|
327
364
|
React.createElement(BoolField, {
|
|
328
365
|
label: t("suppressInjectedContext"),
|
|
329
366
|
hint: t("fieldSuppressInjectedContext"),
|
|
330
|
-
value: draft === null ?
|
|
367
|
+
value: draft === null ? true : draft.suppressInjectedContext,
|
|
331
368
|
disabled: draft === null,
|
|
332
369
|
onChange: (v) => setDraft({ ...draft, suppressInjectedContext: v })
|
|
333
370
|
}),
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
371
|
+
// ── 首轮锚定(bootstrap)分组 ──
|
|
372
|
+
React.createElement(
|
|
373
|
+
Section,
|
|
374
|
+
{ title: t("sectionBootstrap") },
|
|
375
|
+
React.createElement(BoolField, {
|
|
376
|
+
label: t("bootstrapEnabled"),
|
|
377
|
+
hint: t("fieldBootstrapEnabled"),
|
|
378
|
+
value: draft === null ? true : !!draft.bootstrap && draft.bootstrap.enabled,
|
|
379
|
+
disabled: draft === null,
|
|
380
|
+
onChange: (v) => setDraft({ ...draft, bootstrap: { ...draft.bootstrap || {}, enabled: v } })
|
|
381
|
+
}),
|
|
382
|
+
React.createElement(BoolField, {
|
|
383
|
+
label: t("bootstrapRealPair"),
|
|
384
|
+
hint: t("fieldBootstrapRealPair"),
|
|
385
|
+
value: draft === null ? true : !!draft.bootstrap && draft.bootstrap.realPair,
|
|
386
|
+
disabled: draft === null || draft.bootstrap && draft.bootstrap.enabled === false,
|
|
387
|
+
onChange: (v) => setDraft({ ...draft, bootstrap: { ...draft.bootstrap || {}, realPair: v } })
|
|
388
|
+
}),
|
|
389
|
+
React.createElement(TextField, {
|
|
390
|
+
label: t("bootstrapTools"),
|
|
391
|
+
hint: t("fieldBootstrapTools"),
|
|
392
|
+
value: draft === null ? "" : (draft.bootstrap && draft.bootstrap.tools || []).join(", "),
|
|
393
|
+
disabled: draft === null || draft.bootstrap && draft.bootstrap.enabled === false,
|
|
394
|
+
onChange: (v) => setDraft({ ...draft, bootstrap: { ...draft.bootstrap || {}, tools: v.split(/[,,\s]+/).filter((x) => x.length > 0) } })
|
|
395
|
+
}),
|
|
396
|
+
React.createElement(
|
|
397
|
+
Field,
|
|
398
|
+
{
|
|
399
|
+
label: t("bootstrapPromoteOn"),
|
|
400
|
+
hint: t("fieldBootstrapPromoteOn")
|
|
401
|
+
},
|
|
402
|
+
React.createElement(
|
|
403
|
+
"select",
|
|
404
|
+
{
|
|
405
|
+
className: "ap_input",
|
|
406
|
+
value: draft === null ? "either" : draft.bootstrap && draft.bootstrap.promoteOn || "either",
|
|
407
|
+
disabled: draft === null || draft.bootstrap && draft.bootstrap.enabled === false,
|
|
408
|
+
onChange: (e) => setDraft({ ...draft, bootstrap: { ...draft.bootstrap || {}, promoteOn: e.target.value } })
|
|
409
|
+
},
|
|
410
|
+
["either", "tool-call", "assistant-message"].map((v) => React.createElement("option", { key: v, value: v }, v))
|
|
411
|
+
)
|
|
412
|
+
),
|
|
413
|
+
React.createElement(TextField, {
|
|
414
|
+
label: t("bootstrapSuppressed"),
|
|
415
|
+
hint: t("fieldBootstrapSuppressed"),
|
|
416
|
+
value: draft === null ? "" : (draft.bootstrap && draft.bootstrap.suppressedContextSources || []).join(", "),
|
|
417
|
+
disabled: draft === null || draft.bootstrap && draft.bootstrap.enabled === false,
|
|
418
|
+
onChange: (v) => setDraft({ ...draft, bootstrap: { ...draft.bootstrap || {}, suppressedContextSources: v.split(/[,,\s]+/).filter((x) => x.length > 0) } })
|
|
419
|
+
}),
|
|
420
|
+
React.createElement(TextField, {
|
|
421
|
+
label: t("bootstrapDiscovery"),
|
|
422
|
+
hint: t("fieldBootstrapDiscovery"),
|
|
423
|
+
value: draft === null ? "" : (draft.bootstrap && draft.bootstrap.discoveryTools || []).join(", "),
|
|
424
|
+
disabled: draft === null || draft.bootstrap && draft.bootstrap.enabled === false,
|
|
425
|
+
onChange: (v) => setDraft({ ...draft, bootstrap: { ...draft.bootstrap || {}, discoveryTools: v.split(/[,,\s]+/).filter((x) => x.length > 0) } })
|
|
426
|
+
}),
|
|
427
|
+
React.createElement(
|
|
428
|
+
Field,
|
|
429
|
+
{
|
|
430
|
+
label: t("bootstrapMaxTokens"),
|
|
431
|
+
hint: t("fieldBootstrapMaxTokens")
|
|
432
|
+
},
|
|
433
|
+
React.createElement("input", {
|
|
434
|
+
className: "ap_input",
|
|
435
|
+
type: "number",
|
|
436
|
+
min: 0,
|
|
437
|
+
step: 1,
|
|
438
|
+
// 草稿态:编辑期间显示原始输入(清空/删改中间态不立即跳回 0),
|
|
439
|
+
// 失焦后才把显示归一化为生效数字,避免输入被强制改写的突兀感。
|
|
440
|
+
value: maxTokensText !== null ? maxTokensText : String(draft === null ? 0 : draft.bootstrap && draft.bootstrap.maxTokens || 0),
|
|
441
|
+
disabled: draft === null || draft.bootstrap && draft.bootstrap.enabled === false,
|
|
442
|
+
onChange: (e) => {
|
|
443
|
+
const n = Number(e.target.value);
|
|
444
|
+
setMaxTokensText(e.target.value);
|
|
445
|
+
setDraft({ ...draft, bootstrap: { ...draft.bootstrap || {}, maxTokens: Number.isSafeInteger(n) && n > 0 ? n : 0 } });
|
|
446
|
+
},
|
|
447
|
+
onBlur: () => setMaxTokensText(null)
|
|
448
|
+
})
|
|
449
|
+
)
|
|
450
|
+
),
|
|
451
|
+
// ── 极简提示词层分组 ──
|
|
407
452
|
React.createElement(
|
|
408
|
-
|
|
409
|
-
{
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
className: "ap_textarea",
|
|
415
|
-
value: familiesText,
|
|
453
|
+
Section,
|
|
454
|
+
{ title: t("sectionPrompt") },
|
|
455
|
+
React.createElement(BoolField, {
|
|
456
|
+
label: t("minimalPromptEnabled"),
|
|
457
|
+
hint: t("fieldMinimalPromptEnabled"),
|
|
458
|
+
value: draft === null ? true : draft.minimalPrompt && draft.minimalPrompt.enabled === true,
|
|
416
459
|
disabled: draft === null,
|
|
417
|
-
|
|
418
|
-
|
|
460
|
+
onChange: (v) => setDraft({ ...draft, minimalPrompt: { ...draft.minimalPrompt || {}, enabled: v } })
|
|
461
|
+
}),
|
|
462
|
+
React.createElement(BoolField, {
|
|
463
|
+
label: t("minimalPromptSuppressSections"),
|
|
464
|
+
hint: t("fieldMinimalPromptSuppressSections"),
|
|
465
|
+
value: draft === null ? true : draft.minimalPrompt && draft.minimalPrompt.suppressSections === true,
|
|
466
|
+
disabled: draft === null || draft.minimalPrompt && draft.minimalPrompt.enabled === false,
|
|
467
|
+
onChange: (v) => setDraft({ ...draft, minimalPrompt: { ...draft.minimalPrompt || {}, suppressSections: v } })
|
|
468
|
+
}),
|
|
469
|
+
React.createElement(TextField, {
|
|
470
|
+
label: t("minimalPromptPersona"),
|
|
471
|
+
hint: t("fieldMinimalPromptPersona"),
|
|
472
|
+
value: draft === null ? "" : draft.minimalPrompt && draft.minimalPrompt.persona || "",
|
|
473
|
+
disabled: draft === null || draft.minimalPrompt && draft.minimalPrompt.enabled === false,
|
|
474
|
+
onChange: (v) => setDraft({ ...draft, minimalPrompt: { ...draft.minimalPrompt || {}, persona: v } })
|
|
419
475
|
})
|
|
420
476
|
),
|
|
477
|
+
// ── 高级分组(默认收起):工具族 JSON 与核心工具展示 ──
|
|
421
478
|
React.createElement(
|
|
422
|
-
|
|
423
|
-
{
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
479
|
+
Section,
|
|
480
|
+
{ title: t("sectionAdvanced"), defaultOpen: false },
|
|
481
|
+
React.createElement(
|
|
482
|
+
Field,
|
|
483
|
+
{
|
|
484
|
+
label: t("families"),
|
|
485
|
+
hint: familiesInvalid ? t("invalidFamilies") + (familiesError !== null ? " \u2014 " + familiesError : "") : t("fieldFamilies")
|
|
486
|
+
},
|
|
487
|
+
React.createElement("textarea", {
|
|
488
|
+
className: "ap_textarea",
|
|
489
|
+
value: familiesText,
|
|
490
|
+
disabled: draft === null,
|
|
491
|
+
spellCheck: false,
|
|
492
|
+
onChange: (e) => setFamiliesText(e.target.value)
|
|
493
|
+
})
|
|
494
|
+
),
|
|
495
|
+
React.createElement(
|
|
496
|
+
Field,
|
|
497
|
+
{
|
|
498
|
+
label: t("coreTools"),
|
|
499
|
+
hint: t("fieldCoreTools")
|
|
500
|
+
},
|
|
501
|
+
React.createElement("p", { className: "ap_hint" }, draft === null ? "" : (draft.coreTools || []).join(", "))
|
|
502
|
+
)
|
|
428
503
|
),
|
|
429
504
|
React.createElement(
|
|
430
505
|
"div",
|