@ljwei-stak/dsh-model-router 0.16.0 → 0.16.1

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.
@@ -1591,18 +1591,27 @@ function applyQualityBiases(routes, biases) {
1591
1591
  var finite2 = (value) => typeof value === "number" && Number.isFinite(value);
1592
1592
  var clean3 = (value) => typeof value === "string" ? value.trim() : "";
1593
1593
  var routeKey3 = (provider, model) => `${provider}\0${model}`;
1594
+ var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1594
1595
  function applyFeedbackProfile(routes, profile) {
1595
- if (!Array.isArray(routes) || profile?.enabled !== true || !profile.adjustments || typeof profile.adjustments !== "object" || Array.isArray(profile.adjustments)) return routes;
1596
+ if (!Array.isArray(routes) || !record(profile) || typeof profile.enabled !== "boolean") return routes;
1596
1597
  return routes.map((route) => {
1597
- if (!route || typeof route !== "object") return route;
1598
+ if (!record(route)) return route;
1599
+ let base = route;
1600
+ if (Object.hasOwn(route, "preferenceAdjustments") || Object.hasOwn(route, "preferenceAdjustmentMeta")) {
1601
+ base = { ...route };
1602
+ delete base.preferenceAdjustments;
1603
+ delete base.preferenceAdjustmentMeta;
1604
+ }
1605
+ if (profile.enabled !== true || !record(profile.adjustments)) return base;
1598
1606
  const key = routeKey3(clean3(route.provider), clean3(route.model));
1599
- if (!Object.hasOwn(profile.adjustments, key)) return route;
1607
+ if (!Object.hasOwn(profile.adjustments, key)) return base;
1600
1608
  const value = profile.adjustments[key];
1601
- if (!value || typeof value !== "object" || Array.isArray(value)) return route;
1602
- const adjustments = Object.fromEntries(Object.entries(value).filter(([, bias]) => finite2(bias) && Math.abs(bias) <= 0.1));
1603
- if (!Object.keys(adjustments).length) return route;
1609
+ if (!record(value)) return base;
1610
+ const limit = finite2(profile.maxAdjustment) && profile.maxAdjustment >= 0 && profile.maxAdjustment <= 0.1 ? profile.maxAdjustment : 0.1;
1611
+ const adjustments = Object.fromEntries(Object.entries(value).filter(([, bias]) => finite2(bias) && Math.abs(bias) <= limit));
1612
+ if (!Object.keys(adjustments).length) return base;
1604
1613
  return {
1605
- ...route,
1614
+ ...base,
1606
1615
  preferenceAdjustments: adjustments,
1607
1616
  preferenceAdjustmentMeta: {
1608
1617
  policyVersion: profile.policyVersion,
@@ -1613,7 +1622,11 @@ function applyFeedbackProfile(routes, profile) {
1613
1622
  updatedAt: profile.updatedAt,
1614
1623
  halfLifeDays: profile.halfLifeDays,
1615
1624
  maxAdjustment: profile.maxAdjustment,
1616
- window: profile.window
1625
+ window: profile.window,
1626
+ cellStats: Object.hasOwn(profile.cellStats ?? {}, key) ? profile.cellStats[key] : {},
1627
+ evaluatedAt: profile.evaluatedAt,
1628
+ decayRefreshMs: profile.decayRefreshMs,
1629
+ revisionSemantics: profile.revisionSemantics
1617
1630
  }
1618
1631
  };
1619
1632
  });
@@ -2176,8 +2189,10 @@ function feedbackRatingArguments(run, item, direction) {
2176
2189
  var nonnegative2 = (value) => Number.isFinite(value) && value >= 0 ? value : 0;
2177
2190
  function learningView(learning, settings = {}) {
2178
2191
  return {
2179
- available: Boolean(learning && typeof learning === "object"),
2180
- enabled: typeof learning?.enabled === "boolean" ? learning.enabled : adaptiveSettingValue(settings, "feedbackLearningEnabled") !== false,
2192
+ available: Boolean(learning && typeof learning === "object" && !Array.isArray(learning)),
2193
+ // Current settings govern the next plan; a failed refresh must not make an
2194
+ // older profile claim that a newly disabled policy is still enabled.
2195
+ enabled: adaptiveSettingValue(settings, "feedbackLearningEnabled") !== false,
2181
2196
  feedbackCount: nonnegative2(learning?.feedbackCount),
2182
2197
  ignoredCount: nonnegative2(learning?.ignoredCount),
2183
2198
  effectiveWeight: nonnegative2(learning?.effectiveWeight),
@@ -2185,18 +2200,71 @@ function learningView(learning, settings = {}) {
2185
2200
  resetAt: nonnegative2(adaptiveSettingValue(settings, "feedbackResetAt"))
2186
2201
  };
2187
2202
  }
2203
+ function learningEvidenceRows(learning, limit = 12) {
2204
+ const cells = learning?.cellStats;
2205
+ if (!cells || typeof cells !== "object" || Array.isArray(cells)) return { rows: [], total: 0 };
2206
+ const rows = [];
2207
+ for (const [key, domains] of Object.entries(cells)) {
2208
+ const identity = key.split("\0");
2209
+ if (identity.length !== 2 || identity.some((part) => !part.trim()) || !domains || typeof domains !== "object" || Array.isArray(domains)) continue;
2210
+ for (const [domain, cell] of Object.entries(domains)) {
2211
+ if (!domain.trim() || !cell || typeof cell !== "object" || Array.isArray(cell) || !Number.isFinite(cell.adjustment) || Math.abs(cell.adjustment) > 0.1 || ["feedbackCount", "positiveCount", "negativeCount", "effectiveWeight", "effectiveSampleSize"].some((name) => !Number.isFinite(cell[name]) || cell[name] < 0) || !Number.isFinite(cell.shrinkage) || cell.shrinkage < 0 || cell.shrinkage > 1) continue;
2212
+ rows.push({
2213
+ id: JSON.stringify([key, domain]),
2214
+ route: identity.map((part) => part.slice(0, 100)).join("/"),
2215
+ domain: domain.slice(0, 80),
2216
+ feedbackCount: nonnegative2(cell.feedbackCount),
2217
+ positiveCount: nonnegative2(cell.positiveCount),
2218
+ negativeCount: nonnegative2(cell.negativeCount),
2219
+ effectiveWeight: nonnegative2(cell.effectiveWeight),
2220
+ effectiveSampleSize: nonnegative2(cell.effectiveSampleSize),
2221
+ shrinkage: Number.isFinite(cell.shrinkage) && cell.shrinkage >= 0 && cell.shrinkage <= 1 ? cell.shrinkage : 0,
2222
+ adjustment: cell.adjustment
2223
+ });
2224
+ }
2225
+ }
2226
+ rows.sort((left, right) => left.route === right.route ? left.domain.localeCompare(right.domain) : left.route.localeCompare(right.route));
2227
+ const count = Number.isInteger(limit) && limit > 0 ? Math.min(limit, 50) : 12;
2228
+ return { rows: rows.slice(0, count), total: rows.length };
2229
+ }
2230
+ var EXCLUSION_LABELS = Object.freeze({
2231
+ "unrated": "\u5C1A\u672A\u8BC4\u4EF7\uFF08\u4E0D\u662F\u4E0D\u6EE1\u610F\uFF09",
2232
+ "not-successful": "\u6CA1\u6709\u6210\u529F\u6267\u884C\u7ED3\u679C",
2233
+ "cli-model-unverified": "CLI \u5B9E\u9645\u6A21\u578B\u672A\u6838\u9A8C",
2234
+ "before-reset": "\u7ED3\u679C\u65E9\u4E8E\u5B66\u4E60\u8D77\u70B9",
2235
+ "superseded": "\u5DF2\u88AB\u540C\u4E00\u7ED3\u679C\u7684\u65B0\u8BB0\u5F55\u66FF\u4EE3",
2236
+ "missing-identity": "\u7F3A\u5C11\u8FD0\u884C\u6216\u6B65\u9AA4\u6807\u8BC6",
2237
+ "invalid-route": "\u8DEF\u7EBF\u6807\u8BC6\u65E0\u6548",
2238
+ "invalid-time": "\u7ED3\u679C\u6216\u8BC4\u4EF7\u65F6\u95F4\u65E0\u6548",
2239
+ "future-time": "\u65F6\u95F4\u665A\u4E8E\u5F53\u524D\u65F6\u523B"
2240
+ });
2241
+ function learningExclusionRows(learning) {
2242
+ return Object.entries(EXCLUSION_LABELS).flatMap(([reason, label]) => {
2243
+ const count = nonnegative2(learning?.exclusionReasons?.[reason]);
2244
+ return count > 0 ? [{ reason, label, count }] : [];
2245
+ });
2246
+ }
2188
2247
  var STATUS_LABEL = Object.freeze({ disabled: "\u672A\u542F\u7528", missing: "\u5C1A\u65E0\u5FEB\u7167", fresh: "\u5FEB\u7167\u6709\u6548", stale: "\u5FEB\u7167\u8FC7\u671F", error: "\u66F4\u65B0\u5931\u8D25" });
2189
- function dynamicSourceView(source) {
2190
- const status = Object.hasOwn(STATUS_LABEL, source?.status ?? "") ? source.status : "missing";
2248
+ var PUBLIC_ERROR_CODES = /* @__PURE__ */ new Set(["refresh-failed", "snapshot-rollback-rejected", "storage-failed", "refresh-interrupted"]);
2249
+ var positiveTime = (value) => Number.isFinite(value) && value > 0 ? value : null;
2250
+ function dynamicSourceView(source, enabled = true) {
2251
+ const status = enabled === false ? "disabled" : Object.hasOwn(STATUS_LABEL, source?.status ?? "") ? source.status : "missing";
2191
2252
  let publicSource = "";
2192
- if (typeof source?.source === "string") publicSource = publicEndpointDraft("pricingSnapshotEndpoint", source.source);
2253
+ if (enabled !== false && typeof source?.source === "string") publicSource = publicEndpointDraft("pricingSnapshotEndpoint", source.source);
2193
2254
  return {
2194
2255
  status,
2195
2256
  label: STATUS_LABEL[status],
2196
2257
  source: publicSource,
2197
- version: typeof source?.version === "string" ? source.version.slice(0, 160) : null,
2198
- verifiedAt: Number.isFinite(source?.verifiedAt) && source.verifiedAt > 0 ? source.verifiedAt : null,
2199
- count: nonnegative2(source?.modelCount ?? source?.rowCount)
2258
+ version: enabled !== false && typeof source?.version === "string" ? source.version.slice(0, 160) : null,
2259
+ verifiedAt: enabled !== false && Number.isFinite(source?.verifiedAt) && source.verifiedAt > 0 ? source.verifiedAt : null,
2260
+ count: enabled === false ? 0 : nonnegative2(source?.modelCount ?? source?.rowCount),
2261
+ refreshing: enabled !== false && source?.refreshing === true,
2262
+ pending: enabled !== false && source?.pending === true,
2263
+ lastAttemptAt: enabled === false ? null : positiveTime(source?.lastAttemptAt),
2264
+ lastSuccessAt: enabled === false ? null : positiveTime(source?.lastSuccessAt),
2265
+ errorCode: enabled !== false && PUBLIC_ERROR_CODES.has(source?.error) ? source.error : "",
2266
+ endpoint: enabled === false ? "" : publicEndpointDraft("pricingSnapshotEndpoint", source?.configuredEndpoint ?? source?.endpoint ?? ""),
2267
+ lastGoodEndpoint: enabled === false ? "" : publicEndpointDraft("pricingSnapshotEndpoint", source?.lastGoodEndpoint ?? "")
2200
2268
  };
2201
2269
  }
2202
2270
 
@@ -2234,21 +2302,36 @@ function Meter({ label, meter }) {
2234
2302
  function useSettingsWriter(settingsScope) {
2235
2303
  const [snapshot, setSnapshot] = import_react2.default.useState(() => settingsScope.getSnapshot());
2236
2304
  const [notice, setNotice] = import_react2.default.useState("");
2305
+ const [busy, setBusy] = import_react2.default.useState(false);
2306
+ const pending = import_react2.default.useRef(false);
2307
+ const mounted = import_react2.default.useRef(true);
2308
+ import_react2.default.useEffect(() => {
2309
+ mounted.current = true;
2310
+ return () => {
2311
+ mounted.current = false;
2312
+ };
2313
+ }, []);
2237
2314
  import_react2.default.useEffect(() => settingsScope.subscribe(() => setSnapshot(settingsScope.getSnapshot())), [settingsScope]);
2238
- const writable = snapshot.status === "ready" && snapshot.writable === true;
2315
+ const writable = snapshot.status === "ready" && snapshot.writable === true && !busy;
2239
2316
  const write = async (key, value) => {
2240
- if (!writable) return false;
2317
+ const current = settingsScope.getSnapshot();
2318
+ if (pending.current || current.status !== "ready" || current.writable !== true) return false;
2319
+ pending.current = true;
2320
+ setBusy(true);
2241
2321
  setNotice("");
2242
2322
  try {
2243
- const accepted = await settingsScope.mutate([{ op: "set", path: [key], value }], snapshot.revision);
2244
- if (!accepted) setNotice("\u8BBE\u7F6E\u672A\u4FDD\u5B58\uFF0C\u53EF\u80FD\u5DF2\u5728\u5176\u4ED6\u9875\u9762\u4FEE\u6539\uFF0C\u8BF7\u91CD\u8BD5\u3002");
2323
+ const accepted = await settingsScope.mutate([{ op: "set", path: [key], value }], current.revision);
2324
+ if (!accepted && mounted.current) setNotice("\u8BBE\u7F6E\u672A\u4FDD\u5B58\uFF0C\u53EF\u80FD\u5DF2\u5728\u5176\u4ED6\u9875\u9762\u4FEE\u6539\uFF0C\u8BF7\u91CD\u8BD5\u3002");
2245
2325
  return accepted;
2246
2326
  } catch (error) {
2247
- setNotice(text(error?.message) || "\u8BBE\u7F6E\u4FDD\u5B58\u5931\u8D25\u3002");
2327
+ if (mounted.current) setNotice(text(error?.message) || "\u8BBE\u7F6E\u4FDD\u5B58\u5931\u8D25\u3002");
2248
2328
  return false;
2329
+ } finally {
2330
+ pending.current = false;
2331
+ if (mounted.current) setBusy(false);
2249
2332
  }
2250
2333
  };
2251
- return { value: snapshot.value ?? {}, writable, write, notice };
2334
+ return { value: snapshot.value ?? {}, writable, busy, write, notice };
2252
2335
  }
2253
2336
  var ADAPTIVE_FIELD_LABEL = {
2254
2337
  feedbackHalfLifeDays: "\u53CD\u9988\u534A\u8870\u671F\uFF08\u5929\uFF09",
@@ -2260,12 +2343,18 @@ function AdaptiveNumberControl({ field: field2, value, writable, onSave }) {
2260
2343
  const [draft, setDraft] = import_react2.default.useState("");
2261
2344
  const [notice, setNotice] = import_react2.default.useState("");
2262
2345
  const save = async () => {
2346
+ let next;
2263
2347
  try {
2264
- const next = parseAdaptiveNumber(field2.key, draft);
2265
- setNotice("");
2266
- if (await onSave(field2.key, next)) setDraft("");
2348
+ next = parseAdaptiveNumber(field2.key, draft);
2267
2349
  } catch {
2268
2350
  setNotice(`\u8BF7\u8F93\u5165 ${field2.min} \u5230 ${field2.max} \u7684\u6570\u5B57\u3002`);
2351
+ return;
2352
+ }
2353
+ setNotice("");
2354
+ try {
2355
+ if (await onSave(field2.key, next)) setDraft((current) => current === draft ? "" : current);
2356
+ } catch {
2357
+ setNotice("\u8BBE\u7F6E\u4FDD\u5B58\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002");
2269
2358
  }
2270
2359
  };
2271
2360
  return /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-control-group" }, /* @__PURE__ */ import_react2.default.createElement("label", { className: "mr-control-label", htmlFor: `mr-${field2.key}` }, ADAPTIVE_FIELD_LABEL[field2.key]), /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-inline" }, /* @__PURE__ */ import_react2.default.createElement(
@@ -2291,12 +2380,18 @@ function PublicEndpointControl({ name, label, value, writable, onSave }) {
2291
2380
  const [notice, setNotice] = import_react2.default.useState("");
2292
2381
  const current = publicEndpointDraft(name, value);
2293
2382
  const save = async () => {
2383
+ let next;
2294
2384
  try {
2295
- const next = parsePublicEndpoint(name, draft ?? current);
2296
- setNotice("");
2297
- if (await onSave(name, next)) setDraft(null);
2385
+ next = parsePublicEndpoint(name, draft ?? current);
2298
2386
  } catch {
2299
2387
  setNotice("\u8BF7\u8F93\u5165\u4E0D\u542B\u8BA4\u8BC1\u3001\u67E5\u8BE2\u53C2\u6570\u6216\u7247\u6BB5\u7684\u516C\u5F00 HTTPS \u5730\u5740\uFF1B\u4EF7\u683C\u5FEB\u7167\u53EF\u7559\u7A7A\u3002");
2388
+ return;
2389
+ }
2390
+ setNotice("");
2391
+ try {
2392
+ if (await onSave(name, next)) setDraft((currentDraft) => currentDraft === draft ? null : currentDraft);
2393
+ } catch {
2394
+ setNotice("\u5730\u5740\u4FDD\u5B58\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002");
2300
2395
  }
2301
2396
  };
2302
2397
  return /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-control-group" }, /* @__PURE__ */ import_react2.default.createElement("label", { className: "mr-control-label", htmlFor: `mr-${name}` }, label), /* @__PURE__ */ import_react2.default.createElement(
@@ -2315,25 +2410,42 @@ function PublicEndpointControl({ name, label, value, writable, onSave }) {
2315
2410
  void save();
2316
2411
  } }, "\u4FDD\u5B58\u5730\u5740"), notice && /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-error", role: "alert" }, notice));
2317
2412
  }
2318
- function PublicSourceStatus({ label, source }) {
2319
- const view = dynamicSourceView(source);
2320
- return /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-control-group" }, /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-control-label" }, label, "\uFF1A", view.label), /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-caption" }, view.version ? `\u7248\u672C ${view.version} \xB7 ` : "", view.count, " \u6761\u8BB0\u5F55", view.verifiedAt ? ` \xB7 \u6700\u8FD1\u6821\u9A8C ${new Date(view.verifiedAt).toLocaleString()}` : ""), view.source && /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-caption" }, "\u516C\u5F00\u6765\u6E90\uFF1A", view.source), view.status === "error" && /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-caption" }, "\u66F4\u65B0\u5931\u8D25\uFF1B\u662F\u5426\u53EF\u7528\u7531 Host \u6821\u9A8C\uFF0C\u4E0D\u5C06\u7F3A\u5931\u6570\u636E\u5F53\u4F5C\u96F6\u6210\u672C\u6216\u6EE1\u5206\u3002"));
2413
+ function PublicSourceStatus({ label, source, enabled }) {
2414
+ const view = dynamicSourceView(source, enabled);
2415
+ return /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-control-group" }, /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-control-label" }, label, "\uFF1A", view.label), view.status !== "disabled" && /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-caption" }, view.version ? `\u7248\u672C ${view.version} \xB7 ` : "", view.count, " \u6761\u8BB0\u5F55", view.verifiedAt ? ` \xB7 \u6700\u8FD1\u6821\u9A8C ${new Date(view.verifiedAt).toLocaleString()}` : ""), view.source && /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-caption" }, "\u516C\u5F00\u6765\u6E90\uFF1A", view.source), (view.refreshing || view.pending) && /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-caption", role: "status" }, "\u516C\u5F00\u6E90\u6B63\u5728\u5237\u65B0\uFF1B\u4E0A\u9762\u7684\u7248\u672C\u4ECD\u662F\u6700\u8FD1\u4E00\u6B21\u5DF2\u4FDD\u5B58\u5FEB\u7167\uFF0C\u4E0D\u662F\u672C\u6B21\u8BF7\u6C42\u5DF2\u6210\u529F\u3002"), view.lastAttemptAt && /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-caption" }, "\u6700\u8FD1\u5C1D\u8BD5 ", new Date(view.lastAttemptAt).toLocaleString(), view.lastSuccessAt ? ` \xB7 \u6700\u8FD1\u6210\u529F ${new Date(view.lastSuccessAt).toLocaleString()}` : ""), view.status === "error" && /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-caption" }, "\u66F4\u65B0\u5931\u8D25\uFF1B\u662F\u5426\u53EF\u7528\u7531 Host \u6821\u9A8C\uFF0C\u4E0D\u5C06\u7F3A\u5931\u6570\u636E\u5F53\u4F5C\u96F6\u6210\u672C\u6216\u6EE1\u5206\u3002"), view.status === "stale" && /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-caption" }, "\u4E0A\u6B21\u6709\u6548\u5FEB\u7167\u5DF2\u8FC7\u671F\u6216\u672C\u6B21\u66F4\u65B0\u5931\u8D25\uFF1BHost \u53EF\u80FD\u6CBF\u7528\u65E7\u6570\u636E\uFF0C\u4E0D\u80FD\u5F53\u4F5C\u6700\u65B0\u4EF7\u683C\u6216\u80FD\u529B\u3002"), view.errorCode === "storage-failed" && /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-caption" }, "\u672C\u6B21\u72B6\u6001\u4FDD\u5B58\u5931\u8D25\uFF1B\u53EA\u80FD\u4F7F\u7528\u6B64\u524D\u5DF2\u4FDD\u5B58\u7684\u5F53\u524D\u6E90\u5FEB\u7167\uFF0C\u8BF7\u91CD\u8BD5\u5237\u65B0\u3002"), view.errorCode === "refresh-interrupted" && /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-caption" }, "\u4E0A\u6B21\u516C\u5F00\u6570\u636E\u5237\u65B0\u4E2D\u65AD\uFF1B\u8BF7\u5237\u65B0\u6458\u8981\u67E5\u770B Host \u7684\u540E\u7EED\u91CD\u8BD5\u72B6\u6001\u3002"), view.lastGoodEndpoint && view.endpoint && view.lastGoodEndpoint !== view.endpoint && /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-caption" }, "\u6B64\u524D\u6765\u6E90\u7684\u6709\u6548\u5FEB\u7167\u4ECD\u4FDD\u7559\u5728\u672C\u5730\uFF0C\u4F46\u4E0D\u5E94\u7528\u5230\u5F53\u524D\u516C\u5F00\u6E90\u3002"), view.status === "disabled" && /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-caption" }, "\u5F53\u524D\u8BBE\u7F6E\u4E0D\u4F7F\u7528\u6B64\u516C\u5F00\u6E90\uFF1B\u4FDD\u7559\u7684\u5386\u53F2\u5FEB\u7167\u4E0D\u4EE3\u8868\u6B63\u5728\u4F7F\u7528\u3002"));
2416
+ }
2417
+ function LearningEvidence({ learning }) {
2418
+ const evidence = learningEvidenceRows(learning);
2419
+ const exclusions = learningExclusionRows(learning);
2420
+ return /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, null, evidence.total > 0 && /* @__PURE__ */ import_react2.default.createElement("details", { className: "mr-tool-log" }, /* @__PURE__ */ import_react2.default.createElement("summary", null, "\u67E5\u770B\u5206\u6A21\u578B / \u4EFB\u52A1\u7C7B\u578B\u7684\u5DF2\u89C2\u5BDF\u53CD\u9988\uFF08", evidence.total, " \u7EC4\uFF09"), /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-caption" }, "\u53EA\u63CF\u8FF0\u5DF2\u9009\u62E9\u5E76\u83B7\u5F97\u8BC4\u4EF7\u7684\u7ED3\u679C\uFF0C\u4E0D\u4EE3\u8868\u6240\u6709\u6A21\u578B\u7684\u771F\u5B9E\u8D28\u91CF\u6216\u6536\u76CA\uFF1B\u6709\u6548\u6837\u672C\u91CF\u53CD\u6620\u6743\u91CD\u96C6\u4E2D\u7A0B\u5EA6\uFF0C\u4E0D\u662F\u8D28\u91CF\u7F6E\u4FE1\u5EA6\u3002"), /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-table-wrap" }, /* @__PURE__ */ import_react2.default.createElement("table", { className: "mr-table" }, /* @__PURE__ */ import_react2.default.createElement("thead", null, /* @__PURE__ */ import_react2.default.createElement("tr", null, /* @__PURE__ */ import_react2.default.createElement("th", null, "\u8DEF\u7EBF / \u4EFB\u52A1\u7C7B\u578B"), /* @__PURE__ */ import_react2.default.createElement("th", null, "\u6709\u7528 / \u4E0D\u597D"), /* @__PURE__ */ import_react2.default.createElement("th", null, "\u504F\u597D\u6548\u7528\u8C03\u6574"))), /* @__PURE__ */ import_react2.default.createElement("tbody", null, evidence.rows.map((row) => /* @__PURE__ */ import_react2.default.createElement("tr", { key: row.id }, /* @__PURE__ */ import_react2.default.createElement("td", null, row.route, /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-caption" }, row.domain)), /* @__PURE__ */ import_react2.default.createElement("td", null, row.positiveCount, " / ", row.negativeCount, /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-caption" }, "\u5DF2\u89C2\u5BDF ", row.feedbackCount, " \xB7 \u6709\u6548\u6743\u91CD ", row.effectiveWeight.toFixed(2), " \xB7 \u6709\u6548\u6837\u672C\u91CF ", row.effectiveSampleSize.toFixed(2))), /* @__PURE__ */ import_react2.default.createElement("td", null, row.adjustment > 0 ? "+" : "", row.adjustment.toFixed(4), /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-caption" }, "\u6536\u7F29\u7CFB\u6570 ", row.shrinkage.toFixed(2)))))))), evidence.total > evidence.rows.length && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-caption" }, "\u4EC5\u5C55\u793A\u524D ", evidence.rows.length, " \u7EC4\uFF1B\u4E0D\u662F\u6240\u6709\u6A21\u578B\u7684\u8986\u76D6\u7387\u3002")), exclusions.length > 0 && /* @__PURE__ */ import_react2.default.createElement("details", { className: "mr-tool-log" }, /* @__PURE__ */ import_react2.default.createElement("summary", null, "\u67E5\u770B\u672A\u53C2\u4E0E\u5B66\u4E60\u7684\u539F\u56E0"), /* @__PURE__ */ import_react2.default.createElement("ul", { className: "mr-checklist" }, exclusions.map((row) => /* @__PURE__ */ import_react2.default.createElement("li", { key: row.reason }, row.label, "\uFF1A", row.count)))));
2321
2421
  }
2322
- function AdaptiveControls({ ledger, settings, onChanged }) {
2422
+ function AdaptiveControls({ ledger, settings, onChanged, refreshing }) {
2323
2423
  const value = settings.value;
2324
2424
  const learning = learningView(ledger?.learning, value);
2325
2425
  const [resetBusy, setResetBusy] = import_react2.default.useState(false);
2426
+ const [refreshNotice, setRefreshNotice] = import_react2.default.useState("");
2427
+ const resetPending = import_react2.default.useRef(false);
2326
2428
  const save = async (key, next) => {
2327
2429
  const accepted = await settings.write(key, next);
2328
- if (accepted) onChanged?.();
2430
+ if (accepted) {
2431
+ setRefreshNotice("");
2432
+ try {
2433
+ await onChanged?.();
2434
+ } catch {
2435
+ setRefreshNotice("\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\uFF0C\u4F46\u6458\u8981\u5237\u65B0\u5931\u8D25\uFF1B\u8BF7\u5237\u65B0\u6267\u884C\u8BB0\u5F55\u540E\u91CD\u65B0\u89C4\u5212\u3002");
2436
+ }
2437
+ }
2329
2438
  return accepted;
2330
2439
  };
2331
2440
  const reset = async () => {
2332
- if (!window.confirm("\u4ECE\u73B0\u5728\u91CD\u65B0\u5B66\u4E60\u4F60\u7684\u504F\u597D\uFF1F\u6B64\u524D\u53CD\u9988\u5C06\u4E0D\u518D\u53C2\u4E0E\u8C03\u6574\uFF0C\u4F46\u6267\u884C\u8BB0\u5F55\u3001\u8D39\u7528\u5386\u53F2\u548C\u539F\u8BC4\u4EF7\u4E0D\u4F1A\u5220\u9664\u3002")) return;
2333
- setResetBusy(true);
2441
+ if (resetPending.current || !settings.writable) return;
2442
+ resetPending.current = true;
2334
2443
  try {
2444
+ if (!window.confirm("\u4ECE\u73B0\u5728\u91CD\u65B0\u5B66\u4E60\u4F60\u7684\u504F\u597D\uFF1F\u6B64\u524D\u53CD\u9988\u5C06\u4E0D\u518D\u53C2\u4E0E\u8C03\u6574\uFF0C\u4F46\u6267\u884C\u8BB0\u5F55\u3001\u8D39\u7528\u5386\u53F2\u548C\u539F\u8BC4\u4EF7\u4E0D\u4F1A\u5220\u9664\u3002")) return;
2445
+ setResetBusy(true);
2335
2446
  await save("feedbackResetAt", Date.now());
2336
2447
  } finally {
2448
+ resetPending.current = false;
2337
2449
  setResetBusy(false);
2338
2450
  }
2339
2451
  };
@@ -2356,7 +2468,7 @@ function AdaptiveControls({ ledger, settings, onChanged }) {
2356
2468
  writable: settings.writable,
2357
2469
  onSave: save
2358
2470
  }
2359
- ))), learning.available ? /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-caption", role: "status" }, learning.enabled ? "\u5B66\u4E60\u5DF2\u542F\u7528" : "\u5B66\u4E60\u5DF2\u505C\u7528", " \xB7 \u6709\u6548\u53CD\u9988 ", learning.feedbackCount, " \xB7 \u6709\u6548\u6743\u91CD ", learning.effectiveWeight.toFixed(2), " \xB7 \u5FFD\u7565 ", learning.ignoredCount, learning.policyVersion ? ` \xB7 \u7B56\u7565 ${learning.policyVersion}` : "") : /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-caption", role: "status" }, "\u5B66\u4E60\u6458\u8981\u5C1A\u672A\u8BFB\u53D6\uFF1B\u4FDD\u5B58\u540E\u5237\u65B0\u6267\u884C\u8BB0\u5F55\u67E5\u770B\u3002"), learning.resetAt > 0 && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-caption" }, "\u5B66\u4E60\u8D77\u70B9\uFF1A", new Date(learning.resetAt).toLocaleString(), "\uFF0C\u6B64\u524D\u53CD\u9988\u4E0D\u518D\u53C2\u4E0E\u8C03\u6574\u3002"), /* @__PURE__ */ import_react2.default.createElement("button", { className: "mr-button mr-button-secondary", type: "button", disabled: !settings.writable || resetBusy, onClick: () => {
2471
+ ))), settings.busy && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-caption", role: "status" }, "\u6B63\u5728\u4FDD\u5B58\u8BBE\u7F6E\uFF0C\u8BF7\u7A0D\u5019\u2026"), refreshing && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-caption", role: "status" }, "\u6B63\u5728\u8BFB\u53D6\u53CD\u9988\u4E0E\u516C\u5F00\u6570\u636E\u6458\u8981\u2026"), refreshNotice && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-error", role: "alert" }, refreshNotice), learning.available ? /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-caption", role: "status" }, learning.enabled ? "\u5B66\u4E60\u5DF2\u542F\u7528" : "\u5B66\u4E60\u5DF2\u505C\u7528", " \xB7 \u6700\u8FD1\u4E00\u6B21\u8D26\u672C\u6458\u8981\uFF1A\u6709\u6548\u53CD\u9988 ", learning.feedbackCount, " \xB7 \u6709\u6548\u6743\u91CD ", learning.effectiveWeight.toFixed(2), " \xB7 \u5FFD\u7565 ", learning.ignoredCount, learning.policyVersion ? ` \xB7 \u7B56\u7565 ${learning.policyVersion}` : "") : /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-caption", role: "status" }, "\u5B66\u4E60\u6458\u8981\u5C1A\u672A\u8BFB\u53D6\uFF1B\u4FDD\u5B58\u540E\u5237\u65B0\u6267\u884C\u8BB0\u5F55\u67E5\u770B\u3002"), /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-caption" }, "\u6458\u8981\u662F\u6700\u8FD1\u4E00\u6B21\u8BFB\u53D6\u7684\u5DF2\u89C2\u5BDF\u8BC1\u636E\u3002\u4FEE\u6539\u8BC4\u4EF7 / \u5B66\u4E60\u8BBE\u7F6E\u540E\u9700\u5237\u65B0\u6458\u8981\u5E76\u91CD\u65B0\u751F\u6210\u5EFA\u8BAE\uFF1B\u5237\u65B0\u6309\u68C0\u67E5\u95F4\u9694\u590D\u7528\u516C\u5F00\u5FEB\u7167\uFF0C\u4E0D\u4FDD\u8BC1\u6BCF\u6B21\u4E0B\u8F7D\u65B0\u6570\u636E\u3002"), /* @__PURE__ */ import_react2.default.createElement(LearningEvidence, { learning: ledger?.learning }), learning.resetAt > 0 && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-caption" }, "\u5B66\u4E60\u8D77\u70B9\uFF1A", new Date(learning.resetAt).toLocaleString(), "\uFF0C\u6B64\u524D\u53CD\u9988\u4E0D\u518D\u53C2\u4E0E\u8C03\u6574\u3002"), /* @__PURE__ */ import_react2.default.createElement("button", { className: "mr-button mr-button-secondary", type: "button", disabled: !settings.writable || resetBusy, onClick: () => {
2360
2472
  void reset();
2361
2473
  } }, resetBusy ? "\u6B63\u5728\u8BBE\u7F6E\u2026" : "\u4ECE\u73B0\u5728\u91CD\u65B0\u5B66\u4E60"), /* @__PURE__ */ import_react2.default.createElement("h3", { className: "mr-section-title" }, "\u52A8\u6001\u516C\u5171\u6570\u636E"), /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-caption" }, "\u9ED8\u8BA4\u5173\u95ED\u3002\u660E\u786E\u542F\u7528\u540E\uFF0C\u5728\u6253\u5F00 / \u5237\u65B0\u8D26\u672C\u53CA\u6267\u884C\u524D\u6309\u68C0\u67E5\u95F4\u9694\u8BFB\u53D6\u516C\u5F00 LiveBench \u548C\u53EF\u9009\u4EF7\u683C\u5FEB\u7167\uFF1B\u4EF7\u683C\u4E0E\u80FD\u529B\u5148\u9A8C\u4FDD\u7559\u7248\u672C\uFF0C\u4E0D\u4EE3\u8868\u771F\u5B9E\u4EFB\u52A1\u6536\u76CA\u3002\u53EA\u6709\u6253\u5F00\u516C\u5171\u6E90\u5F00\u5173\u624D\u4F1A\u8BFB\u53D6\u8FDC\u7A0B\u6570\u636E\uFF0C\u4E0D\u53D1\u9001\u4EFB\u52A1\u6216\u53CD\u9988\u3002"), /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-controls" }, /* @__PURE__ */ import_react2.default.createElement("label", { className: "mr-check" }, /* @__PURE__ */ import_react2.default.createElement(
2362
2474
  "input",
@@ -2376,9 +2488,9 @@ function AdaptiveControls({ ledger, settings, onChanged }) {
2376
2488
  writable: settings.writable,
2377
2489
  onSave: save
2378
2490
  }
2379
- ), /* @__PURE__ */ import_react2.default.createElement(PublicEndpointControl, { name: "liveBenchEndpoint", label: "LiveBench \u516C\u5F00\u6E90", value: adaptiveSettingValue(value, "liveBenchEndpoint"), writable: settings.writable, onSave: save }), /* @__PURE__ */ import_react2.default.createElement(PublicEndpointControl, { name: "pricingSnapshotEndpoint", label: "\u4EF7\u683C\u5FEB\u7167\u516C\u5F00\u6E90\uFF08\u53EF\u9009\uFF09", value: adaptiveSettingValue(value, "pricingSnapshotEndpoint"), writable: settings.writable, onSave: save }), /* @__PURE__ */ import_react2.default.createElement(PublicSourceStatus, { label: "LiveBench", source: ledger?.dynamicData?.liveBench }), /* @__PURE__ */ import_react2.default.createElement(PublicSourceStatus, { label: "\u4EF7\u683C", source: ledger?.dynamicData?.pricing })));
2491
+ ), /* @__PURE__ */ import_react2.default.createElement(PublicEndpointControl, { name: "liveBenchEndpoint", label: "LiveBench \u516C\u5F00\u6E90", value: adaptiveSettingValue(value, "liveBenchEndpoint"), writable: settings.writable, onSave: save }), /* @__PURE__ */ import_react2.default.createElement(PublicEndpointControl, { name: "pricingSnapshotEndpoint", label: "\u4EF7\u683C\u5FEB\u7167\u516C\u5F00\u6E90\uFF08\u53EF\u9009\uFF09", value: adaptiveSettingValue(value, "pricingSnapshotEndpoint"), writable: settings.writable, onSave: save }), /* @__PURE__ */ import_react2.default.createElement(PublicSourceStatus, { label: "LiveBench", source: ledger?.dynamicData?.liveBench, enabled: adaptiveSettingValue(value, "dynamicDataEnabled") === true }), /* @__PURE__ */ import_react2.default.createElement(PublicSourceStatus, { label: "\u4EF7\u683C", source: ledger?.dynamicData?.pricing, enabled: adaptiveSettingValue(value, "dynamicDataEnabled") === true && Boolean(adaptiveSettingValue(value, "pricingSnapshotEndpoint")) })));
2380
2492
  }
2381
- function CostControlCard({ ledger, settingsScope, onChanged, error }) {
2493
+ function CostControlCard({ ledger, settingsScope, onChanged, error, refreshing = false }) {
2382
2494
  const settings = useSettingsWriter(settingsScope);
2383
2495
  const [dailyDraft, setDailyDraft] = import_react2.default.useState("");
2384
2496
  const [monthlyDraft, setMonthlyDraft] = import_react2.default.useState("");
@@ -2416,7 +2528,7 @@ function CostControlCard({ ledger, settingsScope, onChanged, error }) {
2416
2528
  void set("allowManualReassign", event.target.checked);
2417
2529
  } }), "\u5141\u8BB8\u624B\u52A8\u6539\u6D3E\u5DE5\u4F5C\u5305"), /* @__PURE__ */ import_react2.default.createElement("label", { className: "mr-check" }, /* @__PURE__ */ import_react2.default.createElement("input", { type: "checkbox", checked: value.confirmUnsandboxedCli !== false, disabled: !settings.writable, onChange: (event) => {
2418
2530
  void set("confirmUnsandboxedCli", event.target.checked);
2419
- } }), "\u76F4\u63A5\u542F\u52A8\u65E0\u6C99\u7BB1 CLI \u524D\u5148\u786E\u8BA4")), /* @__PURE__ */ import_react2.default.createElement(AdaptiveControls, { ledger, settings, onChanged }), settings.notice && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-error", role: "alert" }, settings.notice)));
2531
+ } }), "\u76F4\u63A5\u542F\u52A8\u65E0\u6C99\u7BB1 CLI \u524D\u5148\u786E\u8BA4")), /* @__PURE__ */ import_react2.default.createElement(AdaptiveControls, { ledger, settings, onChanged, refreshing }), settings.notice && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-error", role: "alert" }, settings.notice)));
2420
2532
  }
2421
2533
  function DagView({ packages, renderNode, label = "\u5B50\u4EFB\u52A1\u4F9D\u8D56\u56FE" }) {
2422
2534
  const layers = dagLayers(packages);
@@ -2432,13 +2544,13 @@ function ChannelText({ item }) {
2432
2544
  return /* @__PURE__ */ import_react2.default.createElement("span", { className: item.channel === "official-cli" ? "mr-pill mr-pill-channel-ok" : "mr-pill" }, item.channel === "official-cli" ? `\u5B98\u65B9 CLI \xB7 ${item.toolId ?? ""}` : "\u6A21\u578B\u76EE\u5F55 API");
2433
2545
  }
2434
2546
  function RunNode({ run, item, routes, allowReassign, busy, onRate, onRerun }) {
2435
- if (item.status === "paused") return /* @__PURE__ */ import_react2.default.createElement(PausedNode, { run, item, busy, onRerun });
2436
2547
  const [target, setTarget] = import_react2.default.useState("");
2548
+ if (item.status === "paused") return /* @__PURE__ */ import_react2.default.createElement(PausedNode, { run, item, busy, onRerun });
2437
2549
  const cost = packageCost(item);
2438
2550
  const rerun = rerunSupport(run);
2439
2551
  const canRerun = rerun.supported && !item.ok && !busy;
2440
2552
  const others = routes.filter((route) => `${route.provider}/${route.model}` !== `${item.provider}/${item.model}`);
2441
- return /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, null, /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-package-route" }, item.provider, "/", item.model, item.reassigned ? "\uFF08\u5DF2\u6539\u6D3E\uFF09" : ""), /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-channel-line" }, /* @__PURE__ */ import_react2.default.createElement(ChannelText, { item }), /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-caption" }, "\u9884\u4F30 ", money(item.estimatedCost), " \xB7 \u5B9E\u9645 ", cost.budget, item.difficulty ? ` \xB7 \u96BE\u5EA6 ${BAND[item.difficulty] ?? item.difficulty}` : "")), cost.reference && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-package-copy" }, cost.reference), item.actualModel && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-package-copy" }, "CLI \u56DE\u62A5\u6A21\u578B\uFF1A", item.actualModel), (item.billing || item.billingMode) && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-package-copy" }, "\u8BA1\u8D39\uFF1A", BILLING_CHANNEL_LABEL[item.billing] ?? "\u2014", item.subscriptionRoute ? ` \xB7 \u5957\u9910\u8DEF\u7EBF ${item.subscriptionRoute.provider}/${item.subscriptionRoute.model}` : "", item.billingMode && item.billingMode !== "subscription-first" ? ` \xB7 ${BILLING_MODE_LABEL[item.billingMode] ?? item.billingMode}` : ""), billingSwitchText(item) && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-package-copy mr-warn-text" }, "\u8BA1\u8D39\u5207\u6362\uFF1A", billingSwitchText(item)), item.fallback?.reason && item.fallback.reason !== billingSwitchText(item) && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-package-copy" }, "\u56DE\u9000\u539F\u56E0\uFF1A", item.fallback.reason), item.fallback?.error && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-package-copy mr-fallback-error" }, "CLI \u539F\u59CB\u9519\u8BEF\uFF1A", /* @__PURE__ */ import_react2.default.createElement("code", null, item.fallback.error)), !item.ok && item.error && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-package-copy mr-fallback-error" }, item.error), item.review && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-package-copy" }, "\u5F3A\u6A21\u578B\u62BD\u67E5\uFF08", item.review.provider, "/", item.review.model, "\uFF09\uFF1A", item.review.score ? `${item.review.score}/5` : "\u672A\u8BC4\u5206", " ", item.review.summary), item.answer && /* @__PURE__ */ import_react2.default.createElement("details", { className: "mr-tool-log" }, /* @__PURE__ */ import_react2.default.createElement("summary", null, "\u67E5\u770B\u7ED3\u679C"), /* @__PURE__ */ import_react2.default.createElement("pre", null, item.answer, item.answerTruncated ? "\n\u2026\uFF08\u5DF2\u622A\u65AD\uFF09" : "")), /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-node-actions" }, item.ok && /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, null, /* @__PURE__ */ import_react2.default.createElement("button", { className: "mr-button mr-button-secondary mr-mini", type: "button", "aria-pressed": item.rating === 1, disabled: busy, onClick: () => onRate(...feedbackRatingArguments(run, item, "up")) }, "\u{1F44D} \u6709\u7528"), /* @__PURE__ */ import_react2.default.createElement("button", { className: "mr-button mr-button-secondary mr-mini", type: "button", "aria-pressed": item.rating === -1, disabled: busy, onClick: () => onRate(...feedbackRatingArguments(run, item, "down")) }, "\u{1F44E} \u4E0D\u597D")), canRerun && /* @__PURE__ */ import_react2.default.createElement("button", { className: "mr-button mr-mini", type: "button", title: rerun.confirm || void 0, onClick: () => onRerun(run.id, item.id, null) }, run.kind === "tool" ? "\u91CD\u65B0\u6267\u884C\u6B64\u8C03\u7528" : rerun.writes ? "\u5728\u65B0\u5DE5\u4F5C\u533A\u7EED\u8DD1" : "\u91CD\u8DD1\u6B64\u6B65"), !rerun.supported && !item.ok && item.ran && /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-caption" }, rerun.reason), rerun.supported && rerun.reassign && !(rerun.writes && item.ok) && allowReassign && others.length > 0 && !busy && /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-inline" }, /* @__PURE__ */ import_react2.default.createElement("select", { className: "mr-input mr-mini-select", "aria-label": "\u6539\u6D3E\u5230", value: target, onChange: (event) => setTarget(event.target.value) }, /* @__PURE__ */ import_react2.default.createElement("option", { value: "" }, "\u6539\u6D3E\u5230\u2026"), others.map((route) => /* @__PURE__ */ import_react2.default.createElement("option", { key: `${route.provider}/${route.model}`, value: `${route.provider}\0${route.model}` }, route.provider, "/", route.model))), /* @__PURE__ */ import_react2.default.createElement("button", { className: "mr-button mr-button-secondary mr-mini", type: "button", disabled: !target, onClick: () => {
2553
+ return /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, null, /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-package-route" }, item.provider, "/", item.model, item.reassigned ? "\uFF08\u5DF2\u6539\u6D3E\uFF09" : ""), /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-channel-line" }, /* @__PURE__ */ import_react2.default.createElement(ChannelText, { item }), /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-caption" }, "\u9884\u4F30 ", money(item.estimatedCost), " \xB7 \u5B9E\u9645 ", cost.budget, item.difficulty ? ` \xB7 \u96BE\u5EA6 ${BAND[item.difficulty] ?? item.difficulty}` : "")), cost.reference && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-package-copy" }, cost.reference), item.actualModel && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-package-copy" }, "CLI \u56DE\u62A5\u6A21\u578B\uFF1A", item.actualModel), (item.billing || item.billingMode) && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-package-copy" }, "\u8BA1\u8D39\uFF1A", BILLING_CHANNEL_LABEL[item.billing] ?? "\u2014", item.subscriptionRoute ? ` \xB7 \u5957\u9910\u8DEF\u7EBF ${item.subscriptionRoute.provider}/${item.subscriptionRoute.model}` : "", item.billingMode && item.billingMode !== "subscription-first" ? ` \xB7 ${BILLING_MODE_LABEL[item.billingMode] ?? item.billingMode}` : ""), billingSwitchText(item) && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-package-copy mr-warn-text" }, "\u8BA1\u8D39\u5207\u6362\uFF1A", billingSwitchText(item)), item.fallback?.reason && item.fallback.reason !== billingSwitchText(item) && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-package-copy" }, "\u56DE\u9000\u539F\u56E0\uFF1A", item.fallback.reason), item.fallback?.error && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-package-copy mr-fallback-error" }, "CLI \u539F\u59CB\u9519\u8BEF\uFF1A", /* @__PURE__ */ import_react2.default.createElement("code", null, item.fallback.error)), !item.ok && item.error && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-package-copy mr-fallback-error" }, item.error), item.review && /* @__PURE__ */ import_react2.default.createElement("p", { className: "mr-package-copy" }, "\u5F3A\u6A21\u578B\u62BD\u67E5\uFF08", item.review.provider, "/", item.review.model, "\uFF09\uFF1A", item.review.score ? `${item.review.score}/5` : "\u672A\u8BC4\u5206", " ", item.review.summary), item.answer && /* @__PURE__ */ import_react2.default.createElement("details", { className: "mr-tool-log" }, /* @__PURE__ */ import_react2.default.createElement("summary", null, "\u67E5\u770B\u7ED3\u679C"), /* @__PURE__ */ import_react2.default.createElement("pre", null, item.answer, item.answerTruncated ? "\n\u2026\uFF08\u5DF2\u622A\u65AD\uFF09" : "")), /* @__PURE__ */ import_react2.default.createElement("div", { className: "mr-node-actions" }, item.ok && /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, null, /* @__PURE__ */ import_react2.default.createElement("button", { className: "mr-button mr-button-secondary mr-mini", type: "button", title: item.rating === 1 ? "\u518D\u6B21\u70B9\u51FB\u64A4\u56DE\u6709\u7528\u8BC4\u4EF7" : "\u8BC4\u4EF7\u6B64\u7ED3\u679C\u6709\u7528", "aria-pressed": item.rating === 1, disabled: busy, onClick: () => onRate(...feedbackRatingArguments(run, item, "up")) }, "\u{1F44D} \u6709\u7528"), /* @__PURE__ */ import_react2.default.createElement("button", { className: "mr-button mr-button-secondary mr-mini", type: "button", title: item.rating === -1 ? "\u518D\u6B21\u70B9\u51FB\u64A4\u56DE\u4E0D\u597D\u8BC4\u4EF7" : "\u8BC4\u4EF7\u6B64\u7ED3\u679C\u4E0D\u597D", "aria-pressed": item.rating === -1, disabled: busy, onClick: () => onRate(...feedbackRatingArguments(run, item, "down")) }, "\u{1F44E} \u4E0D\u597D")), canRerun && /* @__PURE__ */ import_react2.default.createElement("button", { className: "mr-button mr-mini", type: "button", title: rerun.confirm || void 0, onClick: () => onRerun(run.id, item.id, null) }, run.kind === "tool" ? "\u91CD\u65B0\u6267\u884C\u6B64\u8C03\u7528" : rerun.writes ? "\u5728\u65B0\u5DE5\u4F5C\u533A\u7EED\u8DD1" : "\u91CD\u8DD1\u6B64\u6B65"), !rerun.supported && !item.ok && item.ran && /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-caption" }, rerun.reason), rerun.supported && rerun.reassign && !(rerun.writes && item.ok) && allowReassign && others.length > 0 && !busy && /* @__PURE__ */ import_react2.default.createElement("span", { className: "mr-inline" }, /* @__PURE__ */ import_react2.default.createElement("select", { className: "mr-input mr-mini-select", "aria-label": "\u6539\u6D3E\u5230", value: target, onChange: (event) => setTarget(event.target.value) }, /* @__PURE__ */ import_react2.default.createElement("option", { value: "" }, "\u6539\u6D3E\u5230\u2026"), others.map((route) => /* @__PURE__ */ import_react2.default.createElement("option", { key: `${route.provider}/${route.model}`, value: `${route.provider}\0${route.model}` }, route.provider, "/", route.model))), /* @__PURE__ */ import_react2.default.createElement("button", { className: "mr-button mr-button-secondary mr-mini", type: "button", disabled: !target, onClick: () => {
2442
2554
  const [provider, model] = target.split("\0");
2443
2555
  onRerun(run.id, item.id, { provider, model });
2444
2556
  } }, "\u6539\u6D3E\u5E76\u91CD\u8DD1"))));
@@ -12003,7 +12115,7 @@ var xterm_default = `/**
12003
12115
  `;
12004
12116
 
12005
12117
  // .dsh-plugin/client/host-version.mjs
12006
- var ROUTER_CLIENT_VERSION = true ? "0.16.0" : "";
12118
+ var ROUTER_CLIENT_VERSION = true ? "0.16.1" : "";
12007
12119
  var STALE_HOST_MESSAGE = "\u63D2\u4EF6\u540E\u53F0\u7248\u672C\u8F83\u65E7\uFF0C\u8BF7\u5B8C\u5168\u9000\u51FA\u5E76\u91CD\u542F Harness\uFF08\u5305\u62EC\u6258\u76D8\u56FE\u6807\uFF09\u540E\u518D\u4F7F\u7528\u3002";
12008
12120
  function isMissingRemoteMethod(message) {
12009
12121
  const value = String(message ?? "");
@@ -12962,13 +13074,18 @@ var WORKSPACE_VIEWS = [
12962
13074
  ];
12963
13075
  function useWorkbenchData({ toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries }) {
12964
13076
  const [health, setHealth] = import_react5.default.useState({ report: null, error: "", refreshing: false });
12965
- const [ledger, setLedger] = import_react5.default.useState({ value: null, error: "" });
13077
+ const [ledger, setLedger] = import_react5.default.useState({ value: null, error: "", refreshing: false });
12966
13078
  const [boundaries, setBoundaries] = import_react5.default.useState({ value: null, error: "" });
12967
13079
  const [busy, setBusy] = import_react5.default.useState(false);
12968
13080
  const mounted = import_react5.default.useRef(true);
12969
13081
  const ledgerRequest = import_react5.default.useRef(0);
12970
- import_react5.default.useEffect(() => () => {
12971
- mounted.current = false;
13082
+ const mutationBusy = import_react5.default.useRef(false);
13083
+ import_react5.default.useEffect(() => {
13084
+ mounted.current = true;
13085
+ return () => {
13086
+ mounted.current = false;
13087
+ ledgerRequest.current += 1;
13088
+ };
12972
13089
  }, []);
12973
13090
  const call = async (operation, fallback) => {
12974
13091
  if (typeof operation !== "function") throw new Error("\u5DE5\u4F5C\u53F0\u670D\u52A1\u5C1A\u672A\u52A0\u8F7D\uFF0C\u8BF7\u66F4\u65B0\u63D2\u4EF6\u540E\u91CD\u8BD5\u3002");
@@ -12985,13 +13102,18 @@ function useWorkbenchData({ toolHealth, completeOnboarding, loadLedger, rateResu
12985
13102
  };
12986
13103
  const refreshLedger = async () => {
12987
13104
  const request = ++ledgerRequest.current;
13105
+ if (mounted.current) setLedger((previous) => ({ ...previous, error: "", refreshing: true }));
12988
13106
  try {
12989
13107
  const value = await call(loadLedger, "\u6267\u884C\u8BB0\u5F55\u8BFB\u53D6\u5931\u8D25\u3002");
12990
- if (mounted.current && request === ledgerRequest.current) setLedger({ value, error: "" });
12991
- if (request !== ledgerRequest.current) return null;
13108
+ if (mounted.current && request === ledgerRequest.current) setLedger({ value, error: "", refreshing: false });
13109
+ if (!mounted.current || request !== ledgerRequest.current) return null;
12992
13110
  return value;
12993
13111
  } catch (error) {
12994
- if (mounted.current && request === ledgerRequest.current) setLedger((previous) => ({ ...previous, error: text5(error?.message) || "\u6267\u884C\u8BB0\u5F55\u8BFB\u53D6\u5931\u8D25\u3002" }));
13112
+ if (mounted.current && request === ledgerRequest.current) setLedger((previous) => ({
13113
+ ...previous,
13114
+ refreshing: false,
13115
+ error: `${text5(error?.message) || "\u6267\u884C\u8BB0\u5F55\u8BFB\u53D6\u5931\u8D25\u3002"}${previous.value ? " \u4E0B\u65B9\u4FDD\u7559\u6700\u8FD1\u4E00\u6B21\u6458\u8981\uFF0C\u5C1A\u672A\u91CD\u65B0\u6838\u9A8C\u3002" : ""}`
13116
+ }));
12995
13117
  return null;
12996
13118
  }
12997
13119
  };
@@ -13012,6 +13134,8 @@ function useWorkbenchData({ toolHealth, completeOnboarding, loadLedger, rateResu
13012
13134
  }
13013
13135
  };
13014
13136
  const rate = async (runId, packageId, rating, expectedFinishedAt) => {
13137
+ if (mutationBusy.current) return;
13138
+ mutationBusy.current = true;
13015
13139
  setBusy(true);
13016
13140
  try {
13017
13141
  await call(() => rateResult({ runId, packageId, rating, ...expectedFinishedAt === void 0 ? {} : { expectedFinishedAt } }), "\u8BC4\u4EF7\u4FDD\u5B58\u5931\u8D25\u3002");
@@ -13019,10 +13143,12 @@ function useWorkbenchData({ toolHealth, completeOnboarding, loadLedger, rateResu
13019
13143
  } catch (error) {
13020
13144
  if (mounted.current) setLedger((previous) => ({ ...previous, error: text5(error?.message) || "\u8BC4\u4EF7\u4FDD\u5B58\u5931\u8D25\u3002" }));
13021
13145
  } finally {
13146
+ mutationBusy.current = false;
13022
13147
  if (mounted.current) setBusy(false);
13023
13148
  }
13024
13149
  };
13025
13150
  const rerun = async (runId, packageId, override, choice = null) => {
13151
+ if (mutationBusy.current) return;
13026
13152
  const run = ledger.value?.runs?.find((item2) => item2.id === runId);
13027
13153
  const item = run?.packages?.find((entry) => entry.id === packageId);
13028
13154
  const reasons = rerunConfirmations({ run, item, override, choice, ledger: ledger.value, health: health.report, toolFor: toolForProvider, headlessIds: HEADLESS_TOOLS });
@@ -13031,6 +13157,8 @@ function useWorkbenchData({ toolHealth, completeOnboarding, loadLedger, rateResu
13031
13157
  ${reasons.map((entry, index) => `${index + 1}. ${entry.text}`).join("\n")}
13032
13158
  \u5168\u90E8\u786E\u8BA4\u5E76\u7EE7\u7EED\u5417\uFF1F`)) return;
13033
13159
  const codes = new Set(reasons.map((entry) => entry.code));
13160
+ if (mutationBusy.current) return;
13161
+ mutationBusy.current = true;
13034
13162
  setBusy(true);
13035
13163
  try {
13036
13164
  const request = {
@@ -13051,6 +13179,7 @@ ${reasons.map((entry, index) => `${index + 1}. ${entry.text}`).join("\n")}
13051
13179
  } catch (error) {
13052
13180
  if (mounted.current) setLedger((previous) => ({ ...previous, error: `${text5(error?.message) || "\u91CD\u8DD1\u5931\u8D25\u3002"} \u53EF\u4EE5\u4FEE\u6B63\u540E\u518D\u70B9\u201C\u91CD\u8DD1\u6B64\u6B65\u201D\uFF0C\u6216\u5728\u5B98\u65B9\u4F1A\u8BDD\u4E2D\u8C03\u7528 model_router_rerun_step\u3002` }));
13053
13181
  } finally {
13182
+ mutationBusy.current = false;
13054
13183
  if (mounted.current) setBusy(false);
13055
13184
  }
13056
13185
  };
@@ -13187,6 +13316,8 @@ function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, install
13187
13316
  const ledger = await workbench.refreshLedger();
13188
13317
  if (request !== generationRequest.current || inputs !== generationInputs.current || !mounted.current) return;
13189
13318
  if (!ledger) throw new Error("\u8BF7\u5148\u6062\u590D\u6267\u884C\u8BB0\u5F55\u8FDE\u63A5\uFF1B\u65E0\u6CD5\u6838\u5BF9\u6700\u65B0\u6570\u636E\u4E0E\u5B66\u4E60\u72B6\u6001\u3002");
13319
+ if (ledger.storageAvailable === false) throw new Error("\u6267\u884C\u8BB0\u5F55\u5B58\u50A8\u6682\u4E0D\u53EF\u7528\uFF0C\u65E0\u6CD5\u6838\u5BF9\u8D39\u7528\u4E0E\u5B66\u4E60\u72B6\u6001\uFF1B\u8BF7\u6062\u590D\u8FDE\u63A5\u540E\u518D\u89C4\u5212\u3002");
13320
+ if (ledger.budget?.historyVerified === false) throw new Error("\u8D39\u7528\u5386\u53F2\u4E0D\u5B8C\u6574\uFF0C\u8BF7\u4ECE\u5907\u4EFD\u6062\u590D\u5E76\u6838\u9A8C\u540E\u518D\u89C4\u5212\u3002");
13190
13321
  if (signature2 !== routingSettingsSignature(settingsScope.getSnapshot().value ?? {})) throw new Error("\u89C4\u5212\u8BBE\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u91CD\u65B0\u751F\u6210\u5EFA\u8BAE\u3002");
13191
13322
  const next = createWorkspacePlan(task, catalogState.catalog, {
13192
13323
  ...ledger.routingData,
@@ -13294,11 +13425,12 @@ function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, install
13294
13425
  CostControlCard,
13295
13426
  {
13296
13427
  ledger: workbench.ledger.value,
13297
- error: workbench.ledger.error && !workbench.ledger.value ? workbench.ledger.error : "",
13428
+ error: workbench.ledger.error || workbench.ledger.value?.storageNotice || "",
13429
+ refreshing: workbench.ledger.refreshing,
13298
13430
  settingsScope,
13299
13431
  onChanged: () => {
13300
13432
  invalidatePlan();
13301
- void workbench.refreshLedger();
13433
+ return workbench.refreshLedger();
13302
13434
  }
13303
13435
  }
13304
13436
  ), /* @__PURE__ */ import_react5.default.createElement(
@@ -14,6 +14,7 @@ import {
14
14
  import { createRouterState } from './shared/router-state.mjs'
15
15
  import { applyFeedbackProfile, buildFeedbackProfile } from './shared/adaptive-feedback.mjs'
16
16
  import { applyPricingSnapshot, createDynamicDataRefresher, dynamicDataSummary, routingData } from './shared/dynamic-data.mjs'
17
+ import { projectDynamicData } from './shared/dynamic-data-view.mjs'
17
18
  import {
18
19
  budgetCheck, buildRunRecord, formatUsd, buildTeamRunRecord, buildToolRunRecord, billingOf, mergeRerun,
19
20
  spending, storedResults, actualCost, teamExecutionResults,
@@ -180,8 +181,14 @@ export function routerStateStore() {
180
181
  }
181
182
 
182
183
  async function savedState() {
183
- try { return await routerStateStore().read() }
184
- catch { return { onboarding: { completedAt: null }, health: null, runs: [] } }
184
+ try {
185
+ const saved = await routerStateStore().read()
186
+ const storageAvailable = !(saved.notices ?? []).some(notice => notice.kind === 'state-recovery-pending')
187
+ return { ...saved, storageAvailable, historyVerified: storageAvailable && saved.historyIncomplete !== true }
188
+ } catch {
189
+ return { onboarding: { completedAt: null }, health: null, runs: [], storageAvailable: false, historyVerified: false,
190
+ notices: [{ kind: 'state-read-failed', at: Date.now(), message: '执行记录暂时无法读取,费用与学习摘要尚未核验;请恢复存储连接后重试。' }] }
191
+ }
185
192
  }
186
193
 
187
194
  const NOTICE_DAYS = 7
@@ -344,6 +351,9 @@ function budgetSettings(config) {
344
351
 
345
352
  function budgetFor(config, saved, estimateUsd) {
346
353
  const settings = budgetSettings(config)
354
+ if ((saved.storageAvailable === false || saved.historyVerified === false) && (settings.dailyLimitUsd > 0 || settings.monthlyLimitUsd > 0)) {
355
+ throw new Error('无法读取费用历史,不能核验日/月预算;请恢复存储连接后再执行。')
356
+ }
347
357
  const spent = spending(saved.runs, undefined, saved.archivedSpending)
348
358
  return { ...budgetCheck({ estimateUsd, spent, ...settings }), spent, ...settings }
349
359
  }
@@ -395,18 +405,10 @@ async function routingSavedState(config) {
395
405
  try { refreshedData = await dynamicRefresher.refresh(options) } catch { /* state/read errors use the normal fallback */ }
396
406
  }
397
407
  const saved = await savedState()
398
- // The refresher's projection excludes actively disabled sources while
399
- // preserving their last-good snapshots on disk for recovery.
400
- if (refreshedData) saved.dynamicData = refreshedData
401
- if (saved.dynamicData) {
402
- saved.dynamicData = { ...saved.dynamicData, status: { ...saved.dynamicData.status } }
403
- for (const [kind, endpoint] of [['pricing', options.pricingSnapshotEndpoint], ['liveBench', options.liveBenchEndpoint]]) {
404
- if (!text(endpoint)) {
405
- saved.dynamicData[kind] = null
406
- saved.dynamicData.status[kind] = { disabled: true, endpoint: '', error: '' }
407
- }
408
- }
409
- }
408
+ // Config can change while a public request is in flight. Reproject against
409
+ // the current settings, not the options captured before awaiting refresh.
410
+ // Cached source A must not masquerade as a newly configured source B.
411
+ saved.dynamicData = projectDynamicData(refreshedData ?? saved.dynamicData, dynamicOptions(config))
410
412
  saved.routingLearning = buildFeedbackProfile(saved.runs, feedbackOptions(config))
411
413
  return saved
412
414
  }
@@ -1100,11 +1102,15 @@ export async function ledgerSummary(config = {}, { limit = 30 } = {}) {
1100
1102
  return {
1101
1103
  runs: saved.runs.slice(-limit).reverse().map(run => ({
1102
1104
  ...run,
1103
- task: run.task.slice(0, 2_000),
1104
- packages: run.packages.map(item => ({ ...item, answer: item.answer.slice(0, 1_500) })),
1105
+ task: text(run.task).slice(0, 2_000),
1106
+ packages: (Array.isArray(run.packages) ? run.packages : []).filter(item => item && typeof item === 'object')
1107
+ .map(item => ({ ...item, answer: text(item.answer).slice(0, 1_500) })),
1105
1108
  })),
1106
1109
  spent,
1107
- budget: { ...settings, ...budgetCheck({ estimateUsd: null, spent, ...settings }) },
1110
+ budget: { ...settings, ...budgetCheck({ estimateUsd: null, spent, ...settings }), historyVerified: saved.historyVerified !== false },
1111
+ storageAvailable: saved.storageAvailable !== false,
1112
+ storageNotice: saved.storageAvailable === false ? '执行记录存储暂不可用;下方费用与学习摘要尚未核验,不能当作零花费或没有反馈。'
1113
+ : saved.historyVerified === false ? '此前执行记录已损坏或不完整,当前摘要只包含可读记录;不能视为完整费用历史,请从备份恢复后再核验预算。' : '',
1108
1114
  // Legacy scalar quality biases are intentionally no longer learned.
1109
1115
  biases: {},
1110
1116
  learning: saved.routingLearning,