dsh-autoctx 0.0.0-stage → 1.2.4

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.js ADDED
@@ -0,0 +1,835 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-autoctx",
3
+ factory: (require) => {
4
+ const react = require("react");
5
+ const { jsx, jsxs } = require("react/jsx-runtime");
6
+ const ui = require("@deepseek-ai/dsh-client-ui-primitives");
7
+ const en = {
8
+ summary: "Auto routing, subagents and context compaction decided by Jev.",
9
+ pageIntro: "Jev decides who works on a task (reviewer, advisor, workers), when to think and when to compact context; it learns from finished tasks in this workspace. The composer card shows each session's choices, costs and pending weight changes.",
10
+ title: "Auto context",
11
+ mode: "Mode",
12
+ modeHint: "auto asks Jev for every decision; single keeps one agent; native keeps native compaction.",
13
+ apiKey: "Jev API key",
14
+ apiKeyHint: "Leave blank to keep the configured key.",
15
+ jevModel: "Jev model",
16
+ jevModelHint: "Type the model name: jev-1.13 (billed to the key's workspace) or jev-1.13-free (free tier, rate limited).",
17
+ maxDecisions: "Decision budget",
18
+ maxDecisionsHint: "Jev decision calls per root task.",
19
+ maxChildren: "Children per task",
20
+ maxChildrenHint: "How many children one task may start.",
21
+ maxConcurrent: "Concurrent children",
22
+ maxConcurrentHint: "How many children may run at once.",
23
+ pressureTokens: "Context pressure",
24
+ pressureTokensHint: "Context size that starts compaction decisions.",
25
+ retainTokens: "Retained tokens",
26
+ retainTokensHint: "Recent tokens always kept during compaction.",
27
+ economyGuidance: "Execution economy note",
28
+ economyGuidanceHint: "Append one note before the root task's first request asking for batched, narrow tool use.",
29
+ reviewBeforeFinish: "Review before finish",
30
+ reviewBeforeFinishHint: "Start one independent review child before a root turn that changed files finishes. Requires an API key and a non-single mode.",
31
+ reviewMaxRequests: "Review request cap",
32
+ reviewMaxRequestsHint: "Model requests allowed for one review child.",
33
+ reviewerModel: "Reviewer model",
34
+ reviewerModelHint: "same reviews with the executor's model (fork); auto lets Jev pick among the candidate models from priors, measurements and this workspace's experience; or a catalog id such as glm-5.3-flash.",
35
+ advisorModel: "Advisor model",
36
+ advisorModelHint: "off disables advice; auto lets Jev pick among the candidate models; or a catalog id. One short consultation per trigger.",
37
+ allowPaidModels: "Allow paid models",
38
+ allowPaidModelsHint: "Let roles use models whose dsh provider is a paid route.",
39
+ advisorMaxConsults: "Advisor consultations",
40
+ advisorMaxConsultsHint: "Consultations allowed per root task.",
41
+ advisorStallSteps: "Advisor stall steps",
42
+ advisorStallStepsHint: "Root requests without new evidence before the advisor is consulted (min 3).",
43
+ keepRatio: "Keep ratio",
44
+ keepRatioHint: "Share of a compacted region's tokens kept by budgeted selection (0.1–0.8).",
45
+ yamlNote: "Routes, strategy and weights stay in the profile's cordis.patch.yml.",
46
+ unavailable: "This plugin is not available for configuration.",
47
+ readOnly: "This deployment stores settings read-only.",
48
+ overridden: "Overridden",
49
+ reset: "Reset",
50
+ invalid: "Enter a number; leave blank to inherit the default.",
51
+ keySet: "Configured",
52
+ keyUnset: "Not configured",
53
+ save: "Save",
54
+ saving: "Saving…",
55
+ saveFailed: "The deployment did not accept these values; they were left for you to correct.",
56
+ learning: "Learning",
57
+ learningHint: "Default for workspaces without a ledger yet (frozen unless changed): auto (beta) applies Jev's weight proposals and logs them; manual collects them into one card for approval; frozen only records evidence. Switch an existing workspace in the composer card.",
58
+ adaptiveEffort: "Thinking effort",
59
+ adaptiveEffortHint: "Effort used when Jev decides a turn needs thinking (low, high or xhigh); it stays for the turn and changes only at a context checkpoint. off never raises it.",
60
+ contextSelection: "Context compaction",
61
+ contextSelectionHint: "jev keeps the most relevant evidence verbatim and weighs replacing now against the cache cost; summary lets the executor summarize the region.",
62
+ jevEndpoint: "Jev endpoint",
63
+ jevEndpointHint: "Decision API URL; change only for a compatible gateway.",
64
+ modelRoutes: "Candidate models",
65
+ modelRoutesHint: "Models Jev may pick for reviewer, advisor and workers, as model=provider separated by commas (e.g. deepseek-v4.1-flash=workbuddy, glm-5.3-flash=workbuddy). The executor's own model is always a candidate.",
66
+ ceilingRatio: "Pressure ceiling",
67
+ ceilingRatioHint: "Multiple of the context pressure at which the session must shrink, even if Jev would defer.",
68
+ sectionJev: "Jev",
69
+ sectionSubagents: "Subagents",
70
+ sectionContext: "Context",
71
+ statsMode: "Mode",
72
+ statsDecisions: "Decisions",
73
+ statsSubagents: "Subagents",
74
+ statsRunning: "running",
75
+ statsDone: "done",
76
+ statsContext: "Context",
77
+ statsCompactions: "Compactions",
78
+ statsSaved: "saved {n} tokens",
79
+ statsReviews: "Reviews",
80
+ statsJevUnavailable: "Jev unavailable",
81
+ statsJevUnavailableHint: "HTTP {status}; this task uses local fallbacks",
82
+ statsReviewsLgtm: "LGTM",
83
+ statsLastRoute: "Last route",
84
+ pendingDot: "Weight changes are waiting for approval",
85
+ statsCost: "Cost",
86
+ costExecutor: "executor",
87
+ costChildren: "children",
88
+ costJev: "Jev",
89
+ costCompaction: "compaction",
90
+ costUnpriced: "+{n} unpriced",
91
+ statsCache: "Cache",
92
+ cacheReported: "Provider-reported cache hit",
93
+ cacheExecutor: "executor",
94
+ cacheCalls: "{n} calls",
95
+ statsPrefix: "Prefix reuse",
96
+ prefixValue: "{seeded}/{forks} forks seeded with parent history",
97
+ prefixHint: "Session prefix reuse, not a cache hit: a reused prefix may still miss in the provider cache.",
98
+ statsDeferrals: "{n} deferred for cost",
99
+ statsPlan: "Turn plan",
100
+ planNoTag: "untagged",
101
+ thinkingOn: "thinking {effort}",
102
+ thinkingOff: "thinking off",
103
+ planProb: "Jev p {p}",
104
+ thinking_off: "thinking off",
105
+ thinking_decision_points: "thinking at decision points",
106
+ thinking_always: "thinking always",
107
+ planVia: "via {via}",
108
+ viaJev: "Jev",
109
+ viaKept: "kept",
110
+ viaNone: "none",
111
+ viaCheckpoint: "checkpoint",
112
+ viaChoice: "model choice",
113
+ viaFallback: "fallback",
114
+ viaConfig: "config",
115
+ schedTitle: "Scheduling & learning",
116
+ learnMode: "Learning mode",
117
+ learnAuto: "auto",
118
+ learnManual: "manual",
119
+ learnFrozen: "frozen",
120
+ learnAutoHint: "Apply Jev's weight suggestions automatically and record every change.",
121
+ learnManualHint: "Collect suggestions into one card for you to confirm.",
122
+ learnFrozenHint: "Record evidence only; weights are not adjusted.",
123
+ choiceConfidence: "Jev confidence (uncalibrated)",
124
+ colModel: "Model",
125
+ colProbability: "Jev choice probability",
126
+ colWeight: "Workspace weight",
127
+ colTag: "Tag",
128
+ colRole: "Role",
129
+ srcUser: "user",
130
+ srcLearned: "learned",
131
+ srcNone: "no evidence",
132
+ filtered: "Filtered",
133
+ weightsTitle: "Workspace weights",
134
+ noWeights: "No weights recorded yet.",
135
+ lastAuto: "Auto-adjusted {n} items",
136
+ pauseAuto: "Pause auto",
137
+ revertLast: "Restore previous version",
138
+ reverted: "Restored",
139
+ pendingTitle: "Weight changes to confirm (v{v})",
140
+ pendingUpdated: "Updated",
141
+ basis: "basis",
142
+ target: "target",
143
+ uncertainty: "uncertainty {x} (Jev, uncalibrated)",
144
+ evidence: "{n} evidence",
145
+ approve: "Approve this version",
146
+ keepCurrent: "Keep current",
147
+ feedbackNote: "Native thumbs up/down on answers (optional categories: result, instructions, cost, …) are used as learning evidence.",
148
+ actionFailed: "The server did not accept this action."
149
+ };
150
+ const zh = {
151
+ summary: "自动路由、子代理与上下文压缩均由 Jev 决策。",
152
+ pageIntro: "由 Jev 决定任务由谁参与(复核、顾问、子任务)、何时思考、何时压缩上下文,并从本工作区已完成的任务中学习。输入框统计卡片会显示每个会话的选择、成本与待确认的权重调整。",
153
+ title: "Auto 上下文",
154
+ mode: "模式",
155
+ modeHint: "auto 每次决策都询问 Jev;single 只用单代理;native 保持原生压缩。",
156
+ apiKey: "Jev API Key",
157
+ apiKeyHint: "留空表示保持当前密钥。",
158
+ jevModel: "Jev 模型",
159
+ jevModelHint: "填写模型名称:jev-1.13(按 key 所在 workspace 计费)或 jev-1.13-free(免费额度,有限流)。",
160
+ maxDecisions: "决策预算",
161
+ maxDecisionsHint: "每个根任务可用的 Jev 决策调用次数。",
162
+ maxChildren: "每任务子代理数",
163
+ maxChildrenHint: "一个任务最多可以启动的子代理数量。",
164
+ maxConcurrent: "并发子代理",
165
+ maxConcurrentHint: "同时运行的子代理数量上限。",
166
+ pressureTokens: "上下文压力",
167
+ pressureTokensHint: "上下文达到该规模时开始压缩决策。",
168
+ retainTokens: "保留 tokens",
169
+ retainTokensHint: "压缩时始终保留的近期 tokens。",
170
+ economyGuidance: "执行经济提示",
171
+ economyGuidanceHint: "在根任务首次请求前追加一条提示,要求批量、精准地使用工具。",
172
+ reviewBeforeFinish: "完成前复核",
173
+ reviewBeforeFinishHint: "根任务修改文件后、结束本轮前,启动一个独立复核子代理。需配置 API key,且不处于单代理模式。",
174
+ reviewMaxRequests: "复核请求上限",
175
+ reviewMaxRequestsHint: "单个复核子代理可用的模型请求次数。",
176
+ reviewerModel: "复核模型",
177
+ reviewerModelHint: "same 用执行模型(fork)复核;auto 由 Jev 结合先验、实测与本工作区经验在候选模型中选择;也可填模型目录里的 id(如 glm-5.3-flash)。",
178
+ advisorModel: "顾问模型",
179
+ advisorModelHint: "off 关闭顾问;auto 由 Jev 在候选模型中选择;也可填模型目录里的 id。每次触发只咨询一次。",
180
+ allowPaidModels: "允许付费模型",
181
+ allowPaidModelsHint: "允许角色使用 dsh provider 属于付费线路的模型。",
182
+ advisorMaxConsults: "顾问咨询次数",
183
+ advisorMaxConsultsHint: "每个根任务允许的顾问咨询次数。",
184
+ advisorStallSteps: "顾问停滞步数",
185
+ advisorStallStepsHint: "根任务连续多少请求没有新证据后咨询顾问(最少 3)。",
186
+ keepRatio: "保留比例",
187
+ keepRatioHint: "预算选择保留被压缩区域 tokens 的比例(0.1–0.8)。",
188
+ yamlNote: "路由、策略与权重仍在 profile 的 cordis.patch.yml 里维护。",
189
+ unavailable: "该插件当前无法配置。",
190
+ readOnly: "本部署的设置为只读。",
191
+ overridden: "已覆盖",
192
+ reset: "恢复默认",
193
+ invalid: "请填数字;留空表示使用默认值。",
194
+ keySet: "已配置",
195
+ keyUnset: "未配置",
196
+ save: "保存",
197
+ saving: "保存中…",
198
+ saveFailed: "本部署没有接受这些值,已保留供你修改。",
199
+ learning: "学习模式",
200
+ learningHint: "尚无台账的工作区的默认值(默认 frozen):auto(beta)自动应用 Jev 的权重建议并记录变更;manual 把建议汇总成一张待确认卡片;frozen 只记录证据、不调整。已有工作区请在输入框统计卡片中切换。",
201
+ adaptiveEffort: "思考强度",
202
+ adaptiveEffortHint: "Jev 判断本轮需要思考时使用的强度(low、high 或 xhigh);整轮保持,只在上下文检查点改变。off 表示从不开启。",
203
+ contextSelection: "上下文压缩",
204
+ contextSelectionHint: "jev:原样保留最相关的证据,并权衡现在替换与缓存损失;summary:由执行模型总结该区域。",
205
+ jevEndpoint: "Jev 接口地址",
206
+ jevEndpointHint: "决策 API 地址;仅在使用兼容网关时修改。",
207
+ modelRoutes: "候选模型",
208
+ modelRoutesHint: "Jev 可为复核、顾问和子任务选择的模型,格式为 模型=provider,用逗号分隔(如 deepseek-v4.1-flash=workbuddy, glm-5.3-flash=workbuddy)。执行模型本身始终是候选。",
209
+ ceilingRatio: "压力上限",
210
+ ceilingRatioHint: "上下文压力的倍数;达到后即使 Jev 想推迟也必须压缩。",
211
+ sectionJev: "Jev",
212
+ sectionSubagents: "子代理",
213
+ sectionContext: "上下文",
214
+ statsMode: "模式",
215
+ statsDecisions: "决策",
216
+ statsSubagents: "子代理",
217
+ statsRunning: "运行中",
218
+ statsDone: "已完成",
219
+ statsContext: "上下文",
220
+ statsCompactions: "压缩次数",
221
+ statsSaved: "已节省 {n} tokens",
222
+ statsReviews: "复核",
223
+ statsJevUnavailable: "Jev 不可用",
224
+ statsJevUnavailableHint: "HTTP {status};本任务改用本地回退",
225
+ statsReviewsLgtm: "通过",
226
+ statsLastRoute: "最近路由",
227
+ pendingDot: "有待确认的权重调整",
228
+ statsCost: "成本",
229
+ costExecutor: "执行",
230
+ costChildren: "子代理",
231
+ costJev: "Jev",
232
+ costCompaction: "压缩",
233
+ costUnpriced: "+{n} 未计价",
234
+ statsCache: "缓存",
235
+ cacheReported: "接口报告缓存命中",
236
+ cacheExecutor: "执行器",
237
+ cacheCalls: "{n} 次调用",
238
+ statsPrefix: "会话前缀复用",
239
+ prefixValue: "{seeded}/{forks} 个 fork 带父会话历史启动",
240
+ prefixHint: "这是会话前缀复用,不是缓存命中:复用的前缀在接口缓存中仍可能未命中。",
241
+ statsDeferrals: "因成本推迟 {n} 次",
242
+ statsPlan: "本轮计划",
243
+ planNoTag: "无标签",
244
+ thinkingOn: "思考 {effort}",
245
+ thinkingOff: "思考关闭",
246
+ planProb: "Jev 概率 {p}",
247
+ thinking_off: "不思考",
248
+ thinking_decision_points: "关键节点思考",
249
+ thinking_always: "全程思考",
250
+ planVia: "来源 {via}",
251
+ viaJev: "Jev",
252
+ viaKept: "沿用",
253
+ viaNone: "无",
254
+ viaCheckpoint: "检查点",
255
+ viaChoice: "模型选择",
256
+ viaFallback: "回退",
257
+ viaConfig: "配置",
258
+ schedTitle: "调度与学习",
259
+ learnMode: "学习模式",
260
+ learnAuto: "自动",
261
+ learnManual: "手动",
262
+ learnFrozen: "冻结",
263
+ learnAutoHint: "自动应用 Jev 的权重建议并记录变更。",
264
+ learnManualHint: "建议汇总成一张待确认卡片。",
265
+ learnFrozenHint: "只记录证据,不调整。",
266
+ choiceConfidence: "Jev 置信度(未校准)",
267
+ colModel: "模型",
268
+ colProbability: "Jev 选择概率",
269
+ colWeight: "工作区权重",
270
+ colTag: "标签",
271
+ colRole: "角色",
272
+ srcUser: "用户",
273
+ srcLearned: "学习",
274
+ srcNone: "无证据",
275
+ filtered: "已过滤",
276
+ weightsTitle: "工作区权重",
277
+ noWeights: "暂无权重记录。",
278
+ lastAuto: "已自动调整 {n} 项",
279
+ pauseAuto: "暂停自动",
280
+ revertLast: "恢复上一版本",
281
+ reverted: "已恢复",
282
+ pendingTitle: "待确认的权重调整 (v{v})",
283
+ pendingUpdated: "已更新",
284
+ basis: "依据",
285
+ target: "目标",
286
+ uncertainty: "不确定度 {x}(Jev 未校准)",
287
+ evidence: "{n} 条证据",
288
+ approve: "批准此版本",
289
+ keepCurrent: "保持当前",
290
+ feedbackNote: "对回答的原生点赞/点踩(可选分类:结果、指令、成本等)会作为学习证据。",
291
+ actionFailed: "服务端未接受此操作。"
292
+ };
293
+ const NS = "plugins.dshAutoctx";
294
+ const CONFIG_KEY = "dsh-autoctx#dsh-autoctx";
295
+ const inject = ["slots", "locale", "configForms"];
296
+ const NUMERIC_FIELDS = ["maxDecisions", "maxChildren", "maxConcurrent", "pressureTokens", "ceilingRatio", "retainTokens", "keepRatio", "reviewMaxRequests", "advisorMaxConsults", "advisorStallSteps"];
297
+ // Enumerated fields render as segmented controls.
298
+ const SEGMENTED = { mode: ["auto", "single", "native"], learning: ["auto", "manual", "frozen"], contextSelection: ["jev", "summary"] };
299
+ // modelRoutes is a model -> provider map, edited as "model=provider, model=provider".
300
+ const routesField = {
301
+ field: "modelRoutes",
302
+ format: (value) => value && typeof value === "object" ? Object.entries(value).map(([model, provider]) => model + "=" + provider).join(", ") : "",
303
+ parse: (text) => {
304
+ const parts = text.split(/[,\n]/).map((part) => part.trim()).filter(Boolean);
305
+ if (!parts.length) return { kind: "clear" };
306
+ const routes = {};
307
+ for (const part of parts) {
308
+ const [model, provider, extra] = part.split("=").map((x) => x.trim());
309
+ if (!model || !provider || extra !== undefined) return undefined;
310
+ routes[model] = provider;
311
+ }
312
+ return { kind: "set", value: routes };
313
+ }
314
+ };
315
+ const BOOLEAN_FIELDS = ["economyGuidance", "reviewBeforeFinish", "allowPaidModels"];
316
+ const booleanField = (field) => ({
317
+ field,
318
+ format: (value) => value === true ? "true" : value === false ? "false" : "",
319
+ parse: (text) => {
320
+ const trimmed = text.trim();
321
+ if (trimmed === "") return { kind: "clear" };
322
+ if (trimmed === "true") return { kind: "set", value: true };
323
+ if (trimmed === "false") return { kind: "set", value: false };
324
+ return undefined;
325
+ }
326
+ });
327
+ const specs = Object.fromEntries([
328
+ ...Object.keys(SEGMENTED).map((name) => ui.settingsTextField(name)),
329
+ ui.settingsTextField("jevModel"), ui.settingsTextField("jevEndpoint"), ui.settingsTextField("adaptiveEffort"),
330
+ ui.settingsTextField("reviewerModel"), ui.settingsTextField("advisorModel"), routesField,
331
+ ...NUMERIC_FIELDS.map((name) => ui.settingsNumberField(name)),
332
+ ...BOOLEAN_FIELDS.map(booleanField)
333
+ ].map((spec) => [spec.field, spec]));
334
+ // Its own entry in the settings navigation: the same form, bound to the plugin's config entry.
335
+ function AutoSettingsSection({ t, form }) {
336
+ const snapshot = react.useSyncExternalStore((listener) => form.subscribe(listener), () => form.getSnapshot());
337
+ return jsxs("div", { className: "dac-page", children: [
338
+ jsx("h2", { className: "dac-pageTitle", children: "DSH AutoCTX" }),
339
+ jsx("p", { className: "dac-pageIntro", children: t("pageIntro") }),
340
+ jsx(AutoConfigForm, { t, form: { state: snapshot, mutate: (ops, revision) => form.mutate(ops, revision) } })
341
+ ] });
342
+ }
343
+ function AutoConfigCard({ t, view, form }) {
344
+ if (view === "summary") return t("summary");
345
+ return jsx(AutoConfigForm, { t, form });
346
+ }
347
+ function AutoConfigForm({ t, form }) {
348
+ const [draft, setDraft] = react.useState(null);
349
+ const [saving, setSaving] = react.useState(false);
350
+ const [failed, setFailed] = react.useState(false);
351
+ const [keySet, setKeySet] = react.useState(false);
352
+ react.useEffect(() => {
353
+ fetch(new URL("./api/dsh-autoctx/stats", document.baseURI)).then((response) => response.ok ? response.json() : null)
354
+ .then((body) => setKeySet(!!body?.apiKeyConfigured)).catch(() => {});
355
+ }, [saving]);
356
+ const snapshot = form?.state;
357
+ const baseline = draft?.baseline ?? snapshot;
358
+ const available = snapshot?.status === "ready";
359
+ const writable = available && snapshot.writable;
360
+ const disabled = !writable || saving;
361
+ const stage = (name, text, clear = false) => {
362
+ setDraft((current) => ({ baseline: current?.baseline ?? snapshot,
363
+ fields: { ...current?.fields, [name]: { text, clear } } }));
364
+ setFailed(false);
365
+ };
366
+ const operations = [];
367
+ let invalid = false;
368
+ for (const [name, edit] of Object.entries(draft?.fields ?? {})) {
369
+ if (name === "apiKey") {
370
+ if (edit.text.trim()) operations.push({ op: "set", path: [name], value: edit.text });
371
+ continue;
372
+ }
373
+ const write = edit.clear ? { kind: "clear" } : specs[name].parse(edit.text);
374
+ if (!write) invalid = true;
375
+ else operations.push(write.kind === "clear" ? { op: "unset", path: [name] }
376
+ : { op: "set", path: [name], value: write.value });
377
+ }
378
+ const discard = () => { setDraft(null); setFailed(false); };
379
+ const save = async () => {
380
+ if (disabled || invalid || !operations.length) return;
381
+ setSaving(true);
382
+ setFailed(false);
383
+ try {
384
+ if (await form.mutate(operations, baseline.revision)) discard();
385
+ else setFailed(true);
386
+ } catch { setFailed(true); }
387
+ finally { setSaving(false); }
388
+ };
389
+ const field = (name) => {
390
+ const spec = specs[name], edit = draft?.fields[name];
391
+ const write = edit ? edit.clear ? { kind: "clear" } : spec.parse(edit.text) : undefined;
392
+ const props = {
393
+ id: "dac-" + name, label: t(name), hint: t(name + "Hint"), disabled,
394
+ text: edit?.text ?? spec.format(baseline?.value?.[name]),
395
+ overridden: edit ? write?.kind === "set" : Object.hasOwn(baseline?.user ?? {}, name),
396
+ invalid: !!edit && !write,
397
+ overriddenLabel: t("overridden"), resetLabel: t("reset"), invalidLabel: t("invalid"),
398
+ onEdit: (text) => stage(name, text),
399
+ onReset: () => stage(name, spec.format(baseline?.base?.[name]), true)
400
+ };
401
+ if (SEGMENTED[name]) return jsxs("div", { className: "dac-mode", children: [
402
+ jsxs("div", { className: "dac-modeHead", children: [
403
+ jsx("span", { className: "dac-label", children: props.label }),
404
+ props.overridden ? jsx("button", { type: "button", className: "dac-link", disabled, onClick: props.onReset, children: props.resetLabel }) : null
405
+ ] }),
406
+ jsx("div", { className: "dac-seg", role: "radiogroup", "aria-label": props.label, children: SEGMENTED[name].map((value) => jsx("button", {
407
+ type: "button", role: "radio", "aria-checked": props.text === value, disabled,
408
+ className: "dac-segItem", onClick: () => props.onEdit(value), children: value
409
+ }, value)) }),
410
+ jsx("div", { className: "dac-hint", children: props.hint })
411
+ ] }, name);
412
+ if (BOOLEAN_FIELDS.includes(name)) return jsxs("div", { className: "dac-toggle", children: [
413
+ jsxs("div", { className: "dac-toggleRow", children: [
414
+ jsx("span", { className: "dac-label", children: props.label }),
415
+ jsx(ui.Switch, {
416
+ checked: props.text === "true", label: props.label, disabled,
417
+ onChange: (next) => props.onEdit(next ? "true" : "false")
418
+ })
419
+ ] }),
420
+ props.overridden ? jsx("button", { type: "button", className: "dac-link", disabled, onClick: props.onReset, children: props.resetLabel }) : null,
421
+ jsx("div", { className: "dac-hint", children: props.hint })
422
+ ] }, name);
423
+ return jsx(ui.SettingsValueField, { ...props, numeric: NUMERIC_FIELDS.includes(name) }, name);
424
+ };
425
+ const section = (key) => jsx("h3", { className: "dac-sectionTitle", children: t(key) }, key);
426
+ return jsx(ui.SettingsForm, {
427
+ labels: Object.fromEntries(["unavailable", "readOnly", "saveFailed", "save", "saving"].map((key) => [key, t(key)])),
428
+ state: { available, writable, dirty: writable && (invalid || operations.length > 0), invalid, saving, failed },
429
+ onSave: save, onDiscard: discard,
430
+ children: jsxs("div", { className: "dac-form", children: [
431
+ field("mode"),
432
+ section("sectionJev"),
433
+ jsx(ui.SettingsSecretField, {
434
+ id: "dac-apiKey", label: t("apiKey"), hint: t("apiKeyHint"), disabled,
435
+ text: draft?.fields.apiKey?.text ?? "", configured: keySet,
436
+ stateLabel: t(keySet ? "keySet" : "keyUnset"),
437
+ onEdit: (text) => stage("apiKey", text)
438
+ }),
439
+ field("jevModel"), field("learning"), field("adaptiveEffort"), field("maxDecisions"), field("jevEndpoint"),
440
+ section("sectionSubagents"), field("modelRoutes"), field("reviewerModel"), field("advisorModel"),
441
+ field("allowPaidModels"), field("maxChildren"), field("maxConcurrent"),
442
+ field("reviewBeforeFinish"), field("reviewMaxRequests"),
443
+ field("advisorMaxConsults"), field("advisorStallSteps"),
444
+ section("sectionContext"), field("contextSelection"), field("pressureTokens"), field("ceilingRatio"),
445
+ field("retainTokens"), field("keepRatio"), field("economyGuidance"),
446
+ jsx("div", { className: "dac-hint", children: t("yamlNote") })
447
+ ] })
448
+ });
449
+ }
450
+ const compact = (n) => n === null || n === undefined ? "–" : n >= 1000 ? (n / 1000).toFixed(1).replace(/\.0$/, "") + "k" : String(n);
451
+ const ring = (done, total) => {
452
+ const ratio = total > 0 ? Math.min(1, done / total) : 0;
453
+ const warn = ratio >= 0.8;
454
+ return jsxs("svg", { className: "dac-ring", viewBox: "0 0 16 16", width: 14, height: 14, children: [
455
+ jsx("circle", { cx: 8, cy: 8, r: 6, fill: "none", stroke: "var(--dsw-alias-border-l3)", strokeWidth: 2 }, "bg"),
456
+ jsx("circle", {
457
+ cx: 8, cy: 8, r: 6, fill: "none",
458
+ stroke: warn ? "var(--dsw-alias-state-warn-primary)" : "var(--dsw-alias-state-business-primary)",
459
+ strokeWidth: 2, strokeLinecap: "round",
460
+ strokeDasharray: `${ratio * 37.7} 37.7`, transform: "rotate(-90 8 8)"
461
+ }, "fg")
462
+ ] });
463
+ };
464
+ const spark = jsx("svg", { viewBox: "0 0 16 16", width: 12, height: 12, "aria-hidden": true, children: jsx("path", {
465
+ d: "M8 1l1.8 4.4L14 6.2l-3.1 2.9.8 4.5L8 11.4l-3.7 2.2.8-4.5L2 6.2l4.2-.8L8 1z",
466
+ fill: "currentColor"
467
+ }) });
468
+ const bolt = jsx("svg", { viewBox: "0 0 16 16", width: 11, height: 11, "aria-hidden": true, children: jsx("path", {
469
+ d: "M9 1L3 9h4l-1 6 6-8H8l1-6z",
470
+ fill: "currentColor"
471
+ }) });
472
+ const CSS = `.dac-chip{box-sizing:border-box;display:inline-flex;align-items:center;gap:5px;height:22px;padding:0 8px;border-radius:var(--dsw-radius-md);border:.5px solid var(--dsw-alias-border-l3);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font-size:11.5px;line-height:20px;cursor:pointer;user-select:none;position:relative}
473
+ .dac-chip:hover,.dac-chip:focus-visible{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);outline:none}
474
+ .dac-chipNums{font-variant-numeric:tabular-nums;display:inline-flex;align-items:center;gap:3px}
475
+ .dac-ring{flex:none}
476
+ .dac-pop{position:absolute;bottom:100%;right:0;z-index:20;padding-bottom:8px;cursor:auto;user-select:text}
477
+ .dac-card{box-sizing:border-box;width:320px;max-width:calc(100vw - 24px);max-height:70vh;overflow-y:auto;padding:12px 14px;border-radius:var(--dsw-radius-lg);border:.5px solid var(--dsw-alias-border-l3);background:var(--dsw-alias-bg-layer-2);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px}
478
+ .dac-cardTitle{margin:0 0 8px;font-size:13px;font-weight:500;line-height:20px}
479
+ .dac-grid{display:grid;grid-template-columns:auto 1fr;gap:4px 12px;align-items:baseline}
480
+ .dac-key{color:var(--dsw-alias-label-secondary)}
481
+ .dac-bar{display:block;position:relative;height:3px;border-radius:var(--dsw-radius-xs);background:var(--dsw-alias-border-l3);margin-top:3px;overflow:hidden}
482
+ .dac-barFill{position:absolute;inset:0;right:auto;background:var(--dsw-alias-state-business-primary);border-radius:var(--dsw-radius-xs)}
483
+ .dac-barFill[data-warn=true]{background:var(--dsw-alias-state-warn-primary)}
484
+ .dac-sectionTitle{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px;margin:14px 0 0;padding-top:12px;border-top:.5px solid var(--dsw-alias-border-l2)}
485
+ .dac-form{display:flex;flex-direction:column;gap:6px;max-width:560px}
486
+ .dac-mode{display:flex;flex-direction:column;gap:6px;padding:4px 0 8px}
487
+ .dac-modeHead{display:flex;align-items:baseline;justify-content:space-between}
488
+ .dac-toggle{display:flex;flex-direction:column;gap:6px;padding:4px 0 8px}
489
+ .dac-toggleRow{display:flex;align-items:center;justify-content:space-between;gap:16px}
490
+ .dac-label{font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary)}
491
+ .dac-link{font:inherit;font-size:12px;color:var(--dsw-alias-state-business-primary);background:none;border:0;padding:0;cursor:pointer}
492
+ .dac-seg{display:inline-flex;align-self:flex-start;gap:2px;padding:2px;border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-3);border:.5px solid var(--dsw-alias-border-l3)}
493
+ .dac-segItem{font:inherit;font-size:12.5px;line-height:20px;min-width:72px;padding:3px 12px;border:0;border-radius:calc(var(--dsw-radius-md) - 2px);background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}
494
+ .dac-segItem:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
495
+ .dac-segItem[aria-checked=true]{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);box-shadow:0 1px 2px rgba(0,0,0,.08),0 0 0 .5px var(--dsw-alias-border-l3);font-weight:500}
496
+ .dac-segItem:focus-visible{outline:var(--dsw-focus-ring-width,2px) solid var(--dsw-alias-state-business-primary);outline-offset:1px}
497
+ .dac-hint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
498
+ .dac-page{box-sizing:border-box;height:100%;overflow:auto;padding:28px clamp(24px,4vw,48px) 48px;color:var(--dsw-alias-label-primary)}
499
+ .dac-page>*{max-width:720px}
500
+ .dac-pageTitle{margin:0;font-size:20px;font-weight:500;line-height:28px}
501
+ .dac-pageIntro{margin:4px 0 20px;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}
502
+ .dac-dot{position:absolute;top:-3px;right:-3px;width:7px;height:7px;border-radius:50%;background:var(--dsw-alias-state-warn-primary);box-shadow:0 0 0 1.5px var(--dsw-alias-bg-layer-1)}
503
+ .dac-dotInline{position:static;display:inline-block;box-shadow:none}
504
+ .dac-val{min-width:0;overflow-wrap:anywhere}
505
+ .dac-sub{color:var(--dsw-alias-label-secondary);font-size:11.5px}
506
+ .dac-expand{font:inherit;display:flex;align-items:center;gap:6px;width:100%;margin-top:10px;padding:8px 0 0;border:0;border-top:.5px solid var(--dsw-alias-border-l2);background:none;color:var(--dsw-alias-label-secondary);font-weight:500;text-align:left;cursor:pointer}
507
+ .dac-expand:hover{color:var(--dsw-alias-label-primary)}
508
+ .dac-sched{display:flex;flex-direction:column;gap:12px;margin-top:8px}
509
+ .dac-block{display:flex;flex-direction:column;gap:4px}
510
+ .dac-blockHead{font-weight:500;display:flex;align-items:center;gap:6px;flex-wrap:wrap}
511
+ .dac-pending{padding:8px 10px;border-radius:var(--dsw-radius-md);border:.5px solid var(--dsw-alias-state-warn-primary);background:var(--dsw-alias-bg-layer-3)}
512
+ .dac-item{padding:4px 0;border-top:.5px solid var(--dsw-alias-border-l2)}
513
+ .dac-tbl{display:grid;gap:2px 10px;font-variant-numeric:tabular-nums}
514
+ .dac-tbl>span{min-width:0;overflow-wrap:anywhere}
515
+ .dac-th{color:var(--dsw-alias-label-tertiary);font-size:11px}
516
+ .dac-strong{font-weight:500}
517
+ .dac-actions{display:flex;gap:6px;flex-wrap:wrap;margin-top:4px}
518
+ .dac-btn{font:inherit;font-size:12px;line-height:18px;padding:2px 10px;border-radius:var(--dsw-radius-md);border:.5px solid var(--dsw-alias-border-l3);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);cursor:pointer}
519
+ .dac-btn[data-primary=true]{background:var(--dsw-alias-state-business-primary);border-color:transparent;color:#fff}
520
+ .dac-btn:disabled{opacity:.5;cursor:default}
521
+ .dac-badge{padding:0 6px;border-radius:var(--dsw-radius-xs);background:var(--dsw-alias-state-warn-primary);color:#fff;font-size:11px;line-height:16px;font-weight:500}
522
+ .dac-segSm .dac-segItem{min-width:0;padding:2px 10px;font-size:12px}`;
523
+ const usd = (n) => "$" + (Number(n) || 0).toFixed(Math.abs(n) >= 1 ? 2 : 4);
524
+ const pct = (p) => p == null ? "–" : Math.round(p * 100) + "%";
525
+ const fixed2 = (n) => n == null ? "–" : Number(n).toFixed(2);
526
+ const text = (v) => v == null ? "" : typeof v === "object" ? JSON.stringify(v) : String(v);
527
+ const cap = (s) => s[0].toUpperCase() + s.slice(1);
528
+ const VIAS = ["jev", "kept", "none", "checkpoint", "choice", "fallback", "config"];
529
+ const LEARN_MODES = ["auto", "manual", "frozen"];
530
+ const SOURCES = { user: "srcUser", learned: "srcLearned", none: "srcNone" };
531
+ const viaLabel = (t, via) => VIAS.includes(via) ? t("via" + cap(via)) : text(via) || "–";
532
+ const keyParts = (key) => { const [tag, model, role] = String(key).split("|"); return [tag || "–", model || "–", role || "–"]; };
533
+ const weightLabel = (t, weight, source, n) => [fixed2(weight), SOURCES[source] ? t(SOURCES[source]) : text(source),
534
+ source !== "none" && n > 0 ? "n=" + n : null].filter(Boolean).join(" · ");
535
+ const row = (label, value) => jsxs(react.Fragment, { children: [
536
+ jsx("span", { className: "dac-key", children: label }),
537
+ jsxs("div", { className: "dac-val", children: value })
538
+ ] });
539
+ const table = (columns, head, rows) => jsx("div", { className: "dac-tbl", style: { gridTemplateColumns: columns }, children: [
540
+ ...head.map((label, i) => jsx("span", { className: "dac-th", children: label }, "h" + i)),
541
+ ...rows.flatMap((cells, r) => cells.map((cell, i) => jsx("span", {
542
+ className: cell?.className, title: cell?.title, children: typeof cell === "object" && cell ? cell.text : cell
543
+ }, r + ":" + i)))
544
+ ] });
545
+ function SchedulePanel({ t, stats, ledger, busy, failed, updated, post }) {
546
+ const mode = ledger?.mode ?? stats.learning?.mode;
547
+ const history = ledger?.history ?? [];
548
+ const lastAuto = [...history].reverse().find((entry) => entry.via === "auto");
549
+ const reverted = !!lastAuto && history.some((entry) => entry.via === "revert" && entry.of === lastAuto.version);
550
+ const changed = (lastAuto?.changes ?? []).filter((change) => "to" in change);
551
+ const pending = ledger?.pending;
552
+ const weights = ledger?.weights ?? [];
553
+ const button = (label, onClick, primary) => jsx("button", {
554
+ type: "button", className: "dac-btn", "data-primary": !!primary, disabled: busy, onClick, children: label
555
+ }, label);
556
+ return jsxs("div", { className: "dac-sched", children: [
557
+ mode ? jsxs("div", { className: "dac-block", children: [
558
+ jsx("div", { className: "dac-blockHead", children: t("learnMode") }),
559
+ jsx("div", { className: "dac-seg dac-segSm", role: "radiogroup", "aria-label": t("learnMode"), children: LEARN_MODES.map((value) => jsx("button", {
560
+ type: "button", role: "radio", "aria-checked": mode === value, disabled: busy || !ledger, className: "dac-segItem",
561
+ onClick: () => { if (mode !== value) post({ action: "mode", mode: value }); }, children: t("learn" + cap(value))
562
+ }, value)) }),
563
+ LEARN_MODES.includes(mode) ? jsx("div", { className: "dac-hint", children: t("learn" + cap(mode) + "Hint") }) : null,
564
+ stats.learning?.reason ? jsx("div", { className: "dac-hint", children: text(stats.learning.reason) }) : null
565
+ ] }) : null,
566
+ pending ? jsxs("div", { className: "dac-block dac-pending", children: [
567
+ jsxs("div", { className: "dac-blockHead", children: [
568
+ t("pendingTitle", { v: String(pending.version) }),
569
+ updated ? jsx("span", { className: "dac-badge", children: t("pendingUpdated") }) : null
570
+ ] }),
571
+ ...Object.entries(pending.items ?? {}).map(([key, item]) => jsxs("div", {
572
+ className: "dac-item", title: item.target != null ? t("target") + " " + fixed2(item.target) : undefined, children: [
573
+ jsx("div", { className: "dac-strong", children: keyParts(key).join(" · ") }),
574
+ jsx("div", { children: fixed2(item.current) + " → " + fixed2(item.proposed) }),
575
+ jsx("div", { className: "dac-hint", children: [
576
+ item.basis != null ? t("basis") + ": " + text(item.basis) : null,
577
+ item.uncertainty != null ? t("uncertainty", { x: fixed2(item.uncertainty) }) : null,
578
+ t("evidence", { n: String(item.evidence?.length ?? 0) })
579
+ ].filter(Boolean).join(" · ") })
580
+ ]
581
+ }, key)),
582
+ jsxs("div", { className: "dac-actions", children: [
583
+ button(t("approve"), () => post({ action: "approve", version: pending.version }), true),
584
+ button(t("keepCurrent"), () => post({ action: "reject", version: pending.version }))
585
+ ] })
586
+ ] }) : null,
587
+ ...Object.entries(stats.choices ?? {}).map(([role, choice]) => jsxs("div", { className: "dac-block", children: [
588
+ jsxs("div", { className: "dac-blockHead", children: [role + " ·", jsx("span", { children: choice.model ?? "–" })] }),
589
+ jsx("div", { className: "dac-sub", children: [
590
+ t("planVia", { via: viaLabel(t, choice.via) }), choice.tag,
591
+ choice.confidence != null ? t("choiceConfidence") + " " + pct(choice.confidence) : null
592
+ ].filter(Boolean).join(" · ") }),
593
+ choice.candidates?.length ? table("minmax(0,1fr) auto auto", [t("colModel"), t("colProbability"), t("colWeight")],
594
+ choice.candidates.map((candidate) => [
595
+ { text: candidate.model, title: candidate.summary, className: candidate.model === choice.model ? "dac-strong" : undefined },
596
+ pct(candidate.probability),
597
+ { text: weightLabel(t, candidate.weight, candidate.source, candidate.n), title: candidate.summary }
598
+ ])) : null,
599
+ choice.filtered?.length ? jsx("div", { className: "dac-hint", children: t("filtered") + ": "
600
+ + choice.filtered.map((entry) => entry.model + " (" + text(entry.reason) + ")").join(", ") }) : null
601
+ ] }, role)),
602
+ ledger ? jsxs("div", { className: "dac-block", children: [
603
+ jsx("div", { className: "dac-blockHead", children: t("weightsTitle") }),
604
+ weights.length ? table("auto minmax(0,1fr) auto auto", [t("colTag"), t("colModel"), t("colRole"), t("colWeight")],
605
+ weights.map((entry) => [...keyParts(entry.key), weightLabel(t, entry.weight, entry.source, entry.n)]
606
+ .map((cell) => ({ text: cell, title: entry.summary }))))
607
+ : jsx("div", { className: "dac-hint", children: t("noWeights") })
608
+ ] }) : null,
609
+ lastAuto ? jsxs("div", { className: "dac-block", children: [
610
+ jsx("div", { className: "dac-blockHead", children: t("lastAuto", { n: String(changed.length) }) }),
611
+ ...changed.map((change) => jsx("div", { className: "dac-sub",
612
+ children: keyParts(change.key).join(" · ") + ": " + fixed2(change.from) + " → " + fixed2(change.to) }, change.key)),
613
+ jsxs("div", { className: "dac-actions", children: [
614
+ mode === "auto" ? button(t("pauseAuto"), () => post({ action: "mode", mode: "manual" })) : null,
615
+ reverted ? jsx("span", { className: "dac-hint", children: t("reverted") })
616
+ : button(t("revertLast"), () => post({ action: "revert", version: lastAuto.version }))
617
+ ] })
618
+ ] }) : null,
619
+ failed ? jsx("div", { className: "dac-hint", children: t("actionFailed") }) : null,
620
+ jsx("div", { className: "dac-hint", children: t("feedbackNote") })
621
+ ] });
622
+ }
623
+ function StatsChip({ sessionId, t }) {
624
+ const [stats, setStats] = react.useState(null);
625
+ const [peek, setPeek] = react.useState(false);
626
+ const [pinned, setPinned] = react.useState(false);
627
+ const [sched, setSched] = react.useState(false);
628
+ const [ledger, setLedger] = react.useState(null);
629
+ const [busy, setBusy] = react.useState(false);
630
+ const [failed, setFailed] = react.useState(false);
631
+ const [updated, setUpdated] = react.useState(false);
632
+ const pendingRef = react.useRef(null);
633
+ const open = peek || pinned;
634
+ react.useEffect(() => {
635
+ if (!sessionId) return;
636
+ const url = new URL("./api/dsh-autoctx/stats?session=" + encodeURIComponent(sessionId), document.baseURI);
637
+ let alive = true;
638
+ setStats(null);
639
+ setLedger(null);
640
+ const pull = () => {
641
+ if (document.visibilityState !== "visible") return;
642
+ fetch(url).then((response) => response.ok ? response.json() : null)
643
+ .then((body) => { if (alive) setStats(body?.stats ?? null); })
644
+ .catch(() => {});
645
+ };
646
+ pull();
647
+ const timer = setInterval(pull, 3000);
648
+ return () => { alive = false; clearInterval(timer); };
649
+ }, [sessionId]);
650
+ // A pending card whose version changes under the user is flagged "updated" so approval needs a fresh look.
651
+ const receive = (body, stale) => {
652
+ if (!body?.ledger) return;
653
+ const version = body.ledger.pending?.version ?? null;
654
+ if (version === null) setUpdated(false);
655
+ else if (stale || (pendingRef.current !== null && version !== pendingRef.current)) setUpdated(true);
656
+ pendingRef.current = version;
657
+ setLedger(body.ledger);
658
+ };
659
+ react.useEffect(() => {
660
+ if (!sessionId) return;
661
+ const url = new URL("./api/dsh-autoctx/ledger?session=" + encodeURIComponent(sessionId), document.baseURI);
662
+ let alive = true;
663
+ pendingRef.current = null;
664
+ if (!open) setUpdated(false);
665
+ const pull = () => {
666
+ if (document.visibilityState !== "visible") return;
667
+ fetch(url).then((response) => response.ok ? response.json() : null)
668
+ .then((body) => { if (alive) receive(body); })
669
+ .catch(() => {});
670
+ };
671
+ pull();
672
+ const timer = open ? setInterval(pull, 3000) : null;
673
+ return () => { alive = false; clearInterval(timer); };
674
+ }, [sessionId, open]);
675
+ react.useEffect(() => {
676
+ if (!pinned) return;
677
+ const onKey = (event) => { if (event.key === "Escape") { setPinned(false); setPeek(false); } };
678
+ document.addEventListener("keydown", onKey);
679
+ return () => document.removeEventListener("keydown", onKey);
680
+ }, [pinned]);
681
+ const post = async (payload) => {
682
+ setBusy(true);
683
+ setFailed(false);
684
+ try {
685
+ const response = await fetch(new URL("./api/dsh-autoctx/ledger", document.baseURI), {
686
+ method: "POST", headers: { "content-type": "application/json" },
687
+ body: JSON.stringify({ session: sessionId, ...payload })
688
+ });
689
+ const body = await response.json().catch(() => null);
690
+ if (response.status === 409 && body?.ledger) receive(body, true);
691
+ else if (response.ok) receive(body);
692
+ else setFailed(true);
693
+ } catch { setFailed(true); }
694
+ finally { setBusy(false); }
695
+ };
696
+ if (!stats) return null;
697
+ const decisions = stats.maxDecisions > 0 ? Math.min(1, stats.decisions / stats.maxDecisions) : 0;
698
+ const context = stats.pressureTokens > 0 && stats.contextTokens !== null
699
+ ? Math.min(1, stats.contextTokens / stats.pressureTokens) : 0;
700
+ const bar = (ratio) => jsx("span", { className: "dac-bar", children: jsx("span", {
701
+ className: "dac-barFill", "data-warn": ratio >= 0.8,
702
+ style: { width: Math.round(ratio * 100) + "%" }
703
+ }) }, "bar");
704
+ const hasPending = stats.learning && !open ? stats.learning.pending != null : !!ledger?.pending;
705
+ const toggle = () => {
706
+ if (pinned) { setPinned(false); setPeek(false); }
707
+ else setPinned(true);
708
+ };
709
+ const cost = stats.cost;
710
+ const cache = Object.entries(stats.cache ?? {});
711
+ const plan = stats.plan;
712
+ const showSched = !!(stats.choices || stats.learning || ledger);
713
+ return jsxs("div", {
714
+ className: "dac-chip", tabIndex: 0, "aria-expanded": open,
715
+ onMouseEnter: () => setPeek(true), onMouseLeave: () => setPeek(false),
716
+ onFocus: () => setPeek(true),
717
+ onBlur: (event) => { if (!event.currentTarget.contains(event.relatedTarget)) setPeek(false); },
718
+ onClick: toggle,
719
+ onKeyDown: (event) => {
720
+ if (event.key === "Escape") { setPinned(false); setPeek(false); }
721
+ else if ((event.key === "Enter" || event.key === " ") && event.target === event.currentTarget) { event.preventDefault(); toggle(); }
722
+ },
723
+ children: [
724
+ spark,
725
+ ring(stats.contextTokens ?? 0, stats.pressureTokens),
726
+ jsxs("span", { className: "dac-chipNums", children: [
727
+ stats.decisions + "/" + stats.maxDecisions,
728
+ stats.activeChildren > 0 ? jsxs(react.Fragment, { children: [" · ", bolt, stats.activeChildren] }) : null
729
+ ] }),
730
+ hasPending ? jsx("span", { className: "dac-dot", title: t("pendingDot"), "aria-label": t("pendingDot") }) : null,
731
+ open ? jsx("div", { className: "dac-pop", onClick: (event) => { event.stopPropagation(); setPinned(true); }, children: jsxs("div", { className: "dac-card", children: [
732
+ jsx("p", { className: "dac-cardTitle", children: t("title") }),
733
+ jsxs("div", { className: "dac-grid", children: [
734
+ jsx("span", { className: "dac-key", children: t("statsMode") }),
735
+ jsx("span", { children: stats.mode }),
736
+ jsx("span", { className: "dac-key", children: t("statsDecisions") }),
737
+ jsxs("span", { children: [
738
+ stats.decisions + " / " + stats.maxDecisions,
739
+ bar(decisions)
740
+ ] }),
741
+ jsx("span", { className: "dac-key", children: t("statsSubagents") }),
742
+ jsxs("span", { children: [
743
+ stats.activeChildren + " " + t("statsRunning") + " · " + stats.childrenDone + " " + t("statsDone") + " · " + stats.delegations
744
+ ] }),
745
+ jsx("span", { className: "dac-key", children: t("statsContext") }),
746
+ jsxs("span", { children: [
747
+ "~" + compact(stats.contextTokens) + " / " + compact(stats.pressureTokens),
748
+ bar(context)
749
+ ] }),
750
+ jsx("span", { className: "dac-key", children: t("statsCompactions") }),
751
+ jsxs("span", { children: [
752
+ stats.compactions,
753
+ stats.tokensSaved > 0 ? " · " + t("statsSaved", { n: "~" + compact(stats.tokensSaved) }) : null,
754
+ stats.deferrals > 0 ? " · " + t("statsDeferrals", { n: String(stats.deferrals) }) : null
755
+ ] }),
756
+ cost ? row(t("statsCost"), [
757
+ usd((cost.executor ?? 0) + (cost.children ?? 0) + (cost.jev ?? 0) + (cost.compaction ?? 0)),
758
+ jsx("div", { className: "dac-sub", children: [
759
+ ...[["costExecutor", cost.executor], ["costChildren", cost.children], ["costJev", cost.jev], ["costCompaction", cost.compaction]]
760
+ .map(([key, value]) => t(key) + " " + usd(value)),
761
+ stats.unpriced > 0 ? t("costUnpriced", { n: String(stats.unpriced) }) : null
762
+ ].filter(Boolean).join(" · ") }, "sub")
763
+ ]) : null,
764
+ cache.length ? row(t("statsCache"), [
765
+ jsx("div", { className: "dac-sub", children: t("cacheReported") }, "head"),
766
+ ...cache.map(([key, entry]) => jsx("div", { children: (key === "executor" ? t("cacheExecutor") : key) + " "
767
+ + pct(entry.input > 0 ? entry.cacheRead / entry.input : null) + " · " + t("cacheCalls", { n: String(entry.calls ?? 0) }) }, key))
768
+ ]) : null,
769
+ stats.forks > 0 ? row(t("statsPrefix"), [
770
+ t("prefixValue", { seeded: String(stats.historySeeded ?? 0), forks: String(stats.forks) }),
771
+ jsx("div", { className: "dac-hint", children: t("prefixHint") }, "hint")
772
+ ]) : null,
773
+ plan ? row(t("statsPlan"), [[
774
+ plan.tag ?? t("planNoTag"),
775
+ plan.thinking ? t("thinking_" + plan.thinking) + (plan.thinking === "decision_points" && plan.raised ? " ×" + plan.raised : "")
776
+ : plan.effort ? t("thinkingOn", { effort: String(plan.effort) }) : t("thinkingOff"),
777
+ plan.probabilities && plan.thinking ? t("planProb", { p: pct(plan.probabilities[plan.thinking]) }) : null,
778
+ t("planVia", { via: viaLabel(t, plan.via) })
779
+ ].filter(Boolean).join(" · ")]) : null,
780
+ stats.jevUnavailable != null ? jsxs(react.Fragment, { children: [
781
+ jsx("span", { className: "dac-key", children: t("statsJevUnavailable") }),
782
+ jsx("span", { children: t("statsJevUnavailableHint", { status: String(stats.jevUnavailable) }) })
783
+ ] }) : null,
784
+ stats.reviews > 0 ? jsxs(react.Fragment, { children: [
785
+ jsx("span", { className: "dac-key", children: t("statsReviews") }),
786
+ jsx("span", { children: stats.reviews + " · " + stats.reviewsLgtm + " " + t("statsReviewsLgtm") })
787
+ ] }) : null,
788
+ stats.lastDecision ? jsxs(react.Fragment, { children: [
789
+ jsx("span", { className: "dac-key", children: t("statsLastRoute") }),
790
+ jsx("span", { children: stats.lastDecision })
791
+ ] }) : null
792
+ ] }),
793
+ showSched ? jsxs("button", { type: "button", className: "dac-expand", "aria-expanded": sched, onClick: () => setSched(!sched), children: [
794
+ (sched ? "▾ " : "▸ ") + t("schedTitle"),
795
+ hasPending ? jsx("span", { className: "dac-dot dac-dotInline", title: t("pendingDot") }) : null
796
+ ] }) : null,
797
+ showSched && sched ? jsx(SchedulePanel, { t, stats, ledger, busy, failed, updated, post }) : null
798
+ ] }) }) : null
799
+ ]
800
+ });
801
+ }
802
+ function apply(ctx) {
803
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-autoctx: dictionaries");
804
+ ctx.effect(() => {
805
+ const style = document.createElement("style");
806
+ style.textContent = CSS;
807
+ document.head.append(style);
808
+ return () => style.remove();
809
+ }, "dsh-autoctx: styles");
810
+ // The Host serves the plugin's config under its entry key; the bare entry id is accepted too.
811
+ ctx.effect(() => ctx.configForms.whileServed([CONFIG_KEY, "dsh-autoctx"], (served) => {
812
+ const form = ctx.configForms.get(served.has(CONFIG_KEY) ? CONFIG_KEY : "dsh-autoctx");
813
+ return ctx.slots.inject("settings.section", () => ctx.slots.register({
814
+ name: "settings.section",
815
+ id: "dsh-autoctx",
816
+ order: 60,
817
+ label: () => "DSH AutoCTX",
818
+ locale: NS
819
+ }, (props) => jsx(AutoSettingsSection, { ...props, form })));
820
+ }), "dsh-autoctx: settings section");
821
+ ctx.effect(() => ctx.slots.inject("plugins.row.config", () => ctx.slots.register({
822
+ name: "plugins.row.config",
823
+ key: CONFIG_KEY,
824
+ locale: NS
825
+ }, AutoConfigCard)), "dsh-autoctx: row config page");
826
+ ctx.effect(() => ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
827
+ name: "conversation.input.right",
828
+ id: "dsh-autoctx-stats",
829
+ order: 30,
830
+ locale: NS
831
+ }, StatsChip)), "dsh-autoctx: composer stats chip");
832
+ }
833
+ return { apply, inject };
834
+ }
835
+ });