dsh-context 0.53.4 → 0.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +149 -4
- package/lib/index.d.ts +24 -1
- package/lib/index.js +175 -10
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -329,6 +329,10 @@ window.__ModuleLoader__.load({
|
|
|
329
329
|
"ov.kpi.sessionsSub": "含 {n} 个会话",
|
|
330
330
|
"ov.kpi.toolSub": "工具执行 {dur}",
|
|
331
331
|
"ov.kpi.wallSub": "模型调用 {n}",
|
|
332
|
+
"balance.title": "DeepSeek 余额",
|
|
333
|
+
"balance.tip.total": "总余额",
|
|
334
|
+
"balance.tip.granted": "赠送余额",
|
|
335
|
+
"balance.tip.toppedUp": "充值余额",
|
|
332
336
|
"ov.heat.title": "活跃度",
|
|
333
337
|
"ov.heat.sub": "按日期筛选",
|
|
334
338
|
"ov.heat.empty": "暂无活跃数据 — 会话产生模型请求后,这里会显示每日用量",
|
|
@@ -695,6 +699,10 @@ window.__ModuleLoader__.load({
|
|
|
695
699
|
"ov.kpi.sessionsSub": "across {n} sessions",
|
|
696
700
|
"ov.kpi.toolSub": "tool runs {dur}",
|
|
697
701
|
"ov.kpi.wallSub": "{n} model calls",
|
|
702
|
+
"balance.title": "DeepSeek balance",
|
|
703
|
+
"balance.tip.total": "Total balance",
|
|
704
|
+
"balance.tip.granted": "Granted balance",
|
|
705
|
+
"balance.tip.toppedUp": "Topped-up balance",
|
|
698
706
|
"ov.heat.title": "Activity",
|
|
699
707
|
"ov.heat.sub": "Filter by date",
|
|
700
708
|
"ov.heat.empty": "No activity yet — daily usage appears once sessions make model requests",
|
|
@@ -6199,6 +6207,141 @@ window.__ModuleLoader__.load({
|
|
|
6199
6207
|
return t("ov.time.d", { n: Math.floor(hours / 24) });
|
|
6200
6208
|
}
|
|
6201
6209
|
//#endregion
|
|
6210
|
+
//#region src/client/balance.ts
|
|
6211
|
+
const BALANCE_ROUTE = "/api/dsh-context/balance";
|
|
6212
|
+
/** How long a settled answer (including a `null`) serves identical reads. */
|
|
6213
|
+
const TTL_MS$1 = 6e4;
|
|
6214
|
+
/** One platform amount ('110.00', or an already-numeric producer variant), or null. */
|
|
6215
|
+
function amountOf(value) {
|
|
6216
|
+
const n = typeof value === "string" ? Number(value) : value;
|
|
6217
|
+
return typeof n === "number" && Number.isFinite(n) && n >= 0 ? n : null;
|
|
6218
|
+
}
|
|
6219
|
+
/**
|
|
6220
|
+
* Narrow the delivered payload to a render-safe value (the same boundary
|
|
6221
|
+
* rigor as `timelineOf`): each entry's currency and amounts re-proved, an
|
|
6222
|
+
* entry failing the shape drops whole, and a payload with no valid entry is
|
|
6223
|
+
* no balance — the caller renders nothing rather than half a figure.
|
|
6224
|
+
*/
|
|
6225
|
+
function platformBalanceOf(value) {
|
|
6226
|
+
const data = asRecord(value);
|
|
6227
|
+
if (data === null) return null;
|
|
6228
|
+
const infos = Array.isArray(data.balances) ? data.balances : [];
|
|
6229
|
+
const balances = [];
|
|
6230
|
+
for (const info of infos) try {
|
|
6231
|
+
const entry = asRecord(info);
|
|
6232
|
+
if (entry === null) continue;
|
|
6233
|
+
const currency = entry.currency;
|
|
6234
|
+
const total = amountOf(entry.total);
|
|
6235
|
+
const granted = amountOf(entry.granted);
|
|
6236
|
+
const toppedUp = amountOf(entry.toppedUp);
|
|
6237
|
+
if (typeof currency !== "string" || currency === "" || total === null || granted === null || toppedUp === null) continue;
|
|
6238
|
+
balances.push({
|
|
6239
|
+
currency,
|
|
6240
|
+
total,
|
|
6241
|
+
granted,
|
|
6242
|
+
toppedUp
|
|
6243
|
+
});
|
|
6244
|
+
} catch {
|
|
6245
|
+
continue;
|
|
6246
|
+
}
|
|
6247
|
+
return balances.length > 0 ? {
|
|
6248
|
+
isAvailable: data.isAvailable === true,
|
|
6249
|
+
balances
|
|
6250
|
+
} : null;
|
|
6251
|
+
}
|
|
6252
|
+
/**
|
|
6253
|
+
* The entry matching the display currency (zh → CNY, en → USD), falling back
|
|
6254
|
+
* to the account's first entry when the platform reports none in it — a
|
|
6255
|
+
* number in the account's own currency beats no number.
|
|
6256
|
+
*/
|
|
6257
|
+
function balanceEntryOf(balance, currency) {
|
|
6258
|
+
if (balance === null || balance === void 0 || balance.balances.length === 0) return null;
|
|
6259
|
+
const want = currency === "cny" ? "CNY" : "USD";
|
|
6260
|
+
return balance.balances.find((entry) => entry.currency === want) ?? balance.balances[0];
|
|
6261
|
+
}
|
|
6262
|
+
let cached$1 = null;
|
|
6263
|
+
/**
|
|
6264
|
+
* The capsule's read: one route POST, narrowed through
|
|
6265
|
+
* `platformBalanceOf`, deduplicated in flight, TTL-cached. Never rejects.
|
|
6266
|
+
*/
|
|
6267
|
+
function fetchPlatformBalance() {
|
|
6268
|
+
if (cached$1 !== null && Date.now() - cached$1.at < TTL_MS$1) return cached$1.promise;
|
|
6269
|
+
const at = Date.now();
|
|
6270
|
+
const promise = (async () => {
|
|
6271
|
+
try {
|
|
6272
|
+
const response = await fetch(BALANCE_ROUTE, {
|
|
6273
|
+
method: "POST",
|
|
6274
|
+
headers: { "content-type": "application/json" }
|
|
6275
|
+
});
|
|
6276
|
+
if (!response.ok) return null;
|
|
6277
|
+
const r = asRecord(await response.json());
|
|
6278
|
+
if (r === null || r.ok !== true || r.value === null || r.value === void 0) return null;
|
|
6279
|
+
return platformBalanceOf(r.value);
|
|
6280
|
+
} catch {
|
|
6281
|
+
return null;
|
|
6282
|
+
}
|
|
6283
|
+
})();
|
|
6284
|
+
cached$1 = {
|
|
6285
|
+
at,
|
|
6286
|
+
promise
|
|
6287
|
+
};
|
|
6288
|
+
return promise;
|
|
6289
|
+
}
|
|
6290
|
+
//#endregion
|
|
6291
|
+
//#region src/client/components/balanceCapsule.tsx
|
|
6292
|
+
/**
|
|
6293
|
+
* BalanceCapsule — the DeepSeek open-platform balance pill in the Context
|
|
6294
|
+
* Dashboard's header (the plugin's one account-level figure, beside the
|
|
6295
|
+
* session-level ones). Mounted only by the dashboard header; it reads the
|
|
6296
|
+
* plugin's balance route once per open through client/balance.ts and renders
|
|
6297
|
+
* NOTHING while the read is pending, absent, or failed — the pill exists
|
|
6298
|
+
* only when there is a live figure to show. The tooltip carries the
|
|
6299
|
+
* breakdown (total / granted / topped-up) in the entry's own currency; the
|
|
6300
|
+
* entry follows the active locale (zh → CNY) with the account's first
|
|
6301
|
+
* currency as the fallback.
|
|
6302
|
+
*/
|
|
6303
|
+
/** The symbol prefix for the currencies the platform serves; others show their code. */
|
|
6304
|
+
function symbolOf(currency) {
|
|
6305
|
+
if (currency === "CNY") return "¥";
|
|
6306
|
+
if (currency === "USD") return "$";
|
|
6307
|
+
return currency + " ";
|
|
6308
|
+
}
|
|
6309
|
+
function makeBalanceCapsule(ctx, kit) {
|
|
6310
|
+
const { t } = kit;
|
|
6311
|
+
return function BalanceCapsule() {
|
|
6312
|
+
const [balance, setBalance] = (0, react.useState)(null);
|
|
6313
|
+
(0, react.useEffect)(() => {
|
|
6314
|
+
let on = true;
|
|
6315
|
+
fetchPlatformBalance().then((v) => {
|
|
6316
|
+
if (on) setBalance(v);
|
|
6317
|
+
});
|
|
6318
|
+
return () => {
|
|
6319
|
+
on = false;
|
|
6320
|
+
};
|
|
6321
|
+
}, []);
|
|
6322
|
+
const locale = ctx.locale;
|
|
6323
|
+
const entry = balanceEntryOf(balance, (typeof locale.getLocale === "function" ? locale.getLocale().active : "en") === "zh" ? "cny" : "usd");
|
|
6324
|
+
if (entry === null) return null;
|
|
6325
|
+
const money = (amount) => symbolOf(entry.currency) + amount.toFixed(2);
|
|
6326
|
+
const tip = [
|
|
6327
|
+
t("balance.tip.total") + ": " + money(entry.total),
|
|
6328
|
+
t("balance.tip.granted") + ": " + money(entry.granted),
|
|
6329
|
+
t("balance.tip.toppedUp") + ": " + money(entry.toppedUp)
|
|
6330
|
+
].join("\n");
|
|
6331
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
6332
|
+
className: "lc-ov-balance",
|
|
6333
|
+
title: tip,
|
|
6334
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6335
|
+
className: "lc-ov-balance-label",
|
|
6336
|
+
children: t("balance.title")
|
|
6337
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6338
|
+
className: "lc-ov-balance-value",
|
|
6339
|
+
children: money(entry.total)
|
|
6340
|
+
})]
|
|
6341
|
+
});
|
|
6342
|
+
};
|
|
6343
|
+
}
|
|
6344
|
+
//#endregion
|
|
6202
6345
|
//#region src/shared/days.ts
|
|
6203
6346
|
/**
|
|
6204
6347
|
* The local-day key (`YYYY-MM-DD`) shared by the activity fold (host) and the
|
|
@@ -6660,6 +6803,7 @@ window.__ModuleLoader__.load({
|
|
|
6660
6803
|
const { t, fmtDuration } = kit;
|
|
6661
6804
|
const Heatmap = makeHeatmap(kit);
|
|
6662
6805
|
const OverviewCard = makeOverviewCard(kit);
|
|
6806
|
+
const BalanceCapsule = makeBalanceCapsule(ctx, kit);
|
|
6663
6807
|
const ErrorBoundary = makeErrorBoundary(t);
|
|
6664
6808
|
/** The display currency follows the active locale (zh → CNY), read per render — the slot outlet re-renders on a locale switch. */
|
|
6665
6809
|
function activeCurrency() {
|
|
@@ -6744,6 +6888,7 @@ window.__ModuleLoader__.load({
|
|
|
6744
6888
|
className: "lc-ov-title",
|
|
6745
6889
|
children: t("ov.title")
|
|
6746
6890
|
}),
|
|
6891
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BalanceCapsule, {}),
|
|
6747
6892
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6748
6893
|
className: "lc-gran lc-ov-range",
|
|
6749
6894
|
role: "group",
|
|
@@ -9494,7 +9639,7 @@ window.__ModuleLoader__.load({
|
|
|
9494
9639
|
return function PluginInfo() {
|
|
9495
9640
|
const [latest, setLatest] = (0, react.useState)(null);
|
|
9496
9641
|
(0, react.useEffect)(() => {
|
|
9497
|
-
if ("0.
|
|
9642
|
+
if ("0.54.0".includes("-dev")) return;
|
|
9498
9643
|
let on = true;
|
|
9499
9644
|
fetchLatestVersion().then((v) => {
|
|
9500
9645
|
if (on && v) setLatest(v);
|
|
@@ -9503,8 +9648,8 @@ window.__ModuleLoader__.load({
|
|
|
9503
9648
|
on = false;
|
|
9504
9649
|
};
|
|
9505
9650
|
}, []);
|
|
9506
|
-
const update = latest !== null && isNewerVersion(latest, "0.
|
|
9507
|
-
const nameText = "dsh-context (v0.
|
|
9651
|
+
const update = latest !== null && isNewerVersion(latest, "0.54.0") ? latest : null;
|
|
9652
|
+
const nameText = "dsh-context (v0.54.0)";
|
|
9508
9653
|
const nameValue = [nameText];
|
|
9509
9654
|
if (update) nameValue.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
9510
9655
|
className: "lc-pi-update",
|
|
@@ -11427,7 +11572,7 @@ window.__ModuleLoader__.load({
|
|
|
11427
11572
|
}
|
|
11428
11573
|
//#endregion
|
|
11429
11574
|
//#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/overview.css.mjs
|
|
11430
|
-
const css = ".lc-ov-entry{box-sizing:border-box;width:calc(100% + 4px);height:42px;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:8px;margin:0 -2px;padding:0 10px 0 8px;font-family:inherit;font-size:14px;line-height:22px;display:flex;overflow:hidden}.lc-ov-entry:hover{background:var(--dsw-alias-interactive-bg-hover)}.lc-ov-entry-rail{border-radius:50%;flex:none;justify-content:center;gap:0;width:36px;height:36px;margin:0;padding:0}.lc-ov-entry-icon{flex:none}.lc-ov-entry-label{text-align:left;white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;overflow:hidden}.lc-ov-backdrop{z-index:200;background:var(--dsw-alias-bg-mask-1,#00000073);justify-content:center;align-items:center;display:flex;position:fixed;inset:0;container:lc-ov/inline-size}.lc-ov-card{box-sizing:border-box;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);width:min(1120px,100vw - 32px);max-height:min(86vh,900px);box-shadow:var(--dsw-shadow-lv3,0 12px 32px #0006);color:var(--dsw-alias-label-primary);border-radius:12px;flex-direction:column;padding:16px 18px 18px;font-size:13px;display:flex;overflow:hidden}.lc-ov-head{flex-wrap:wrap;flex:none;align-items:center;gap:10px;margin-bottom:14px;display:flex}.lc-ov-head-icon{flex:none}.lc-ov-title{white-space:nowrap;font-size:15px;font-weight:600}.lc-ov-head .lc-modal-close{flex:none;margin-left:0}.lc-ov-body{flex:auto;grid-template-columns:minmax(0,3fr) minmax(0,7fr);gap:10px;min-height:0;display:grid}.lc-ov-left{scrollbar-gutter:stable;flex-direction:column;gap:10px;min-height:0;padding-right:8px;display:flex;overflow-y:auto}.lc-ov-right{scrollbar-gutter:stable;min-height:0;padding-right:8px;overflow-y:auto}@container lc-ov (width<=932px){.lc-ov-card{overflow-y:auto}.lc-ov-body{display:block}.lc-ov-left{margin-bottom:12px}.lc-ov-left,.lc-ov-right{min-height:auto;padding-right:0;overflow:visible}.lc-ov-heat-card{flex:none}}.lc-ov-left>.lc-card{box-sizing:border-box;min-width:0;margin-bottom:0}.lc-ov-kpis{flex:none;grid-template-rows:repeat(3,1fr);grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;display:grid}.lc-ov-kpi{align-items:center;padding:10px 8px}.lc-ov-kpi .lc-stat-value{font-size:20px}.lc-ov-heat-card{flex-direction:column;flex:1 0 auto;display:flex}.lc-heat{flex:1;justify-content:center;align-items:center;gap:4px;display:flex}.lc-heat-wds,.lc-heat-col{padding-top:13px}.lc-heat-wds{flex-direction:column;gap:3px;display:flex}.lc-heat-wd{width:10px;height:20px;color:var(--dsw-alias-label-secondary);text-align:center;flex:none;justify-content:center;align-items:center;font-size:9px;display:flex}.lc-heat-cols{gap:3px;display:flex}.lc-heat-col{flex-direction:column;gap:3px;display:flex;position:relative}.lc-heat-mon{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:9px;line-height:13px;position:absolute;top:0;left:0}.lc-heat-cell{box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);cursor:default;border:0;border-radius:20%;width:20px;height:20px;padding:0}button.lc-heat-cell{cursor:pointer}button.lc-heat-cell:hover{box-shadow:0 0 0 1.5px color-mix(in srgb, var(--color-blue-500) 70%, transparent)}.lc-heat-future{background:0 0}.lc-heat-0{background:color-mix(in srgb, var(--dsw-alias-label-secondary) 16%, transparent)}.lc-heat-1{background:color-mix(in srgb, var(--color-blue-500) 28%, transparent)}.lc-heat-2{background:color-mix(in srgb, var(--color-blue-500) 50%, transparent)}.lc-heat-3{background:color-mix(in srgb, var(--color-blue-500) 72%, transparent)}.lc-heat-4{background:color-mix(in srgb, var(--color-blue-500) 92%, transparent)}.lc-heat-on{box-shadow:0 0 0 1.5px var(--color-blue-500)}.lc-ov-list-head{flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:10px;display:flex}.lc-ov-list-title{white-space:nowrap;font-weight:600}.lc-ov-list-count{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;background:var(--dsw-alias-bg-layer-2);border-radius:8px;padding:1px 7px;font-size:11px}.lc-ov-day-chip{border:1px solid color-mix(in srgb, var(--color-blue-500) 45%, transparent);color:var(--color-blue-500);background:color-mix(in srgb, var(--color-blue-500) 10%, transparent);cursor:pointer;white-space:nowrap;border-radius:999px;padding:1px 10px;font-family:inherit;font-size:11px;line-height:1.4}.lc-ov-search{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);min-width:110px;font:inherit;color:var(--dsw-alias-label-primary);transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;outline:none;flex:150px;padding:4px 10px;font-size:12px}.lc-ov-search:focus{border-color:var(--dsw-alias-label-secondary)}.lc-ov-search::placeholder{color:var(--dsw-alias-label-secondary)}.lc-ov-groups{flex-wrap:wrap;gap:6px;margin:-2px 0 10px;display:flex}.lc-ov-chip{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;white-space:nowrap;text-overflow:ellipsis;max-width:220px;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border-radius:999px;align-items:baseline;gap:5px;padding:2px 10px;font-size:11px;line-height:1.5;display:inline-flex;overflow:hidden}.lc-ov-chip:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-secondary)}.lc-ov-chip-on,.lc-ov-chip-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);border-color:#0000}.lc-ov-chip-n{font-variant-numeric:tabular-nums;opacity:.65;font-size:10px}.lc-ov-chip-on .lc-ov-chip-n{opacity:.85}.lc-ov-grid{grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:10px;display:grid}.lc-ov-session{text-align:left;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;font:inherit;color:inherit;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}.lc-ov-session:hover{border-color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}.lc-ov-session-current{border-color:color-mix(in srgb, var(--color-blue-500) 65%, transparent)}.lc-ov-session-head{align-items:flex-start;gap:6px;min-width:0;display:flex}.lc-ov-running{background:var(--color-green-500);border-radius:50%;flex:none;width:7px;height:7px}.lc-ov-session-title{-webkit-line-clamp:2;overflow-wrap:anywhere;-webkit-box-orient:vertical;flex:auto;min-width:0;font-weight:600;display:-webkit-box;overflow:hidden}.lc-ov-current{color:var(--color-blue-500);border:1px solid color-mix(in srgb, var(--color-blue-500) 45%, transparent);border-radius:4px;flex:none;padding:0 4px;font-size:10px;line-height:1.5}.lc-ov-session-times{flex-direction:column;flex:none;align-items:flex-end;gap:1px;display:flex}.lc-ov-session-time{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:11px}.lc-ov-session-created{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-variant-numeric:tabular-nums;font-size:10px;line-height:1.4}.lc-ov-session-crumb{min-width:0;color:var(--dsw-alias-label-secondary);white-space:nowrap;align-items:center;gap:5px;font-size:11px;display:flex;overflow:hidden}.lc-ov-crumb-group{text-overflow:ellipsis;flex:none;max-width:55%;overflow:hidden}.lc-ov-crumb-sep{opacity:.55;flex:none}.lc-ov-crumb-project{text-overflow:ellipsis;min-width:0;font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--dsw-alias-label-primary);background:color-mix(in srgb, var(--dsw-alias-label-secondary) 16%, transparent);border-radius:999px;flex:0 auto;padding:0 8px;font-size:10px;line-height:1.6;overflow:hidden}.lc-ov-session-body{align-items:center;gap:14px;min-width:0;display:flex}.lc-ov-mini-stats{flex-direction:column;flex:auto;gap:3px;min-width:0;display:flex}.lc-ov-mini-stat{justify-content:space-between;align-items:baseline;gap:10px;font-size:11px;display:flex}.lc-ov-mini-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;flex:none}.lc-ov-mini-value{font-variant-numeric:tabular-nums;white-space:nowrap;font-weight:600}.lc-ov-session-empty{color:var(--dsw-alias-label-secondary);padding:6px 0;font-size:11px;line-height:1.5}.lc-ov-session-preview{border-top:1px solid color-mix(in srgb, var(--dsw-alias-border-l2) 60%, transparent);align-items:baseline;gap:6px;min-width:0;margin-top:auto;padding-top:6px;display:flex}.lc-ov-session-preview-label{color:var(--dsw-alias-label-secondary);flex:none;font-size:10px}.lc-ov-session-preview-text{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary);flex:0 auto;font-size:11px;line-height:1.5;overflow:hidden}.lc-ov-pager{justify-content:center;align-items:center;gap:10px;margin-top:12px;display:flex}.lc-ov-pager-btn{border:1px solid var(--dsw-alias-border-l2);width:26px;height:22px;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border-radius:6px;flex:none;justify-content:center;align-items:center;line-height:1;display:flex}.lc-ov-pager-btn:hover:enabled{border-color:var(--dsw-alias-label-secondary)}.lc-ov-pager-btn:disabled{opacity:.4;cursor:default}.lc-ov-pager-n{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:11px}";
|
|
11575
|
+
const css = ".lc-ov-entry{box-sizing:border-box;width:calc(100% + 4px);height:42px;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:8px;margin:0 -2px;padding:0 10px 0 8px;font-family:inherit;font-size:14px;line-height:22px;display:flex;overflow:hidden}.lc-ov-entry:hover{background:var(--dsw-alias-interactive-bg-hover)}.lc-ov-entry-rail{border-radius:50%;flex:none;justify-content:center;gap:0;width:36px;height:36px;margin:0;padding:0}.lc-ov-entry-icon{flex:none}.lc-ov-entry-label{text-align:left;white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;overflow:hidden}.lc-ov-backdrop{z-index:200;background:var(--dsw-alias-bg-mask-1,#00000073);justify-content:center;align-items:center;display:flex;position:fixed;inset:0;container:lc-ov/inline-size}.lc-ov-card{box-sizing:border-box;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);width:min(1120px,100vw - 32px);max-height:min(86vh,900px);box-shadow:var(--dsw-shadow-lv3,0 12px 32px #0006);color:var(--dsw-alias-label-primary);border-radius:12px;flex-direction:column;padding:16px 18px 18px;font-size:13px;display:flex;overflow:hidden}.lc-ov-head{flex-wrap:wrap;flex:none;align-items:center;gap:10px;margin-bottom:14px;display:flex}.lc-ov-head-icon{flex:none}.lc-ov-title{white-space:nowrap;font-size:15px;font-weight:600}.lc-ov-balance{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);cursor:default;border-radius:999px;flex:none;align-items:center;gap:6px;max-width:100%;padding:2px 10px;font-size:12px;line-height:18px;display:inline-flex}.lc-ov-balance-label{color:var(--dsw-alias-label-secondary);white-space:nowrap}.lc-ov-balance-value{font-variant-numeric:tabular-nums;white-space:nowrap;font-weight:600}.lc-ov-head .lc-modal-close{flex:none;margin-left:0}.lc-ov-body{flex:auto;grid-template-columns:minmax(0,3fr) minmax(0,7fr);gap:10px;min-height:0;display:grid}.lc-ov-left{scrollbar-gutter:stable;flex-direction:column;gap:10px;min-height:0;padding-right:8px;display:flex;overflow-y:auto}.lc-ov-right{scrollbar-gutter:stable;min-height:0;padding-right:8px;overflow-y:auto}@container lc-ov (width<=932px){.lc-ov-card{overflow-y:auto}.lc-ov-body{display:block}.lc-ov-left{margin-bottom:12px}.lc-ov-left,.lc-ov-right{min-height:auto;padding-right:0;overflow:visible}.lc-ov-heat-card{flex:none}}.lc-ov-left>.lc-card{box-sizing:border-box;min-width:0;margin-bottom:0}.lc-ov-kpis{flex:none;grid-template-rows:repeat(3,1fr);grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;display:grid}.lc-ov-kpi{align-items:center;padding:10px 8px}.lc-ov-kpi .lc-stat-value{font-size:20px}.lc-ov-heat-card{flex-direction:column;flex:1 0 auto;display:flex}.lc-heat{flex:1;justify-content:center;align-items:center;gap:4px;display:flex}.lc-heat-wds,.lc-heat-col{padding-top:13px}.lc-heat-wds{flex-direction:column;gap:3px;display:flex}.lc-heat-wd{width:10px;height:20px;color:var(--dsw-alias-label-secondary);text-align:center;flex:none;justify-content:center;align-items:center;font-size:9px;display:flex}.lc-heat-cols{gap:3px;display:flex}.lc-heat-col{flex-direction:column;gap:3px;display:flex;position:relative}.lc-heat-mon{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:9px;line-height:13px;position:absolute;top:0;left:0}.lc-heat-cell{box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);cursor:default;border:0;border-radius:20%;width:20px;height:20px;padding:0}button.lc-heat-cell{cursor:pointer}button.lc-heat-cell:hover{box-shadow:0 0 0 1.5px color-mix(in srgb, var(--color-blue-500) 70%, transparent)}.lc-heat-future{background:0 0}.lc-heat-0{background:color-mix(in srgb, var(--dsw-alias-label-secondary) 16%, transparent)}.lc-heat-1{background:color-mix(in srgb, var(--color-blue-500) 28%, transparent)}.lc-heat-2{background:color-mix(in srgb, var(--color-blue-500) 50%, transparent)}.lc-heat-3{background:color-mix(in srgb, var(--color-blue-500) 72%, transparent)}.lc-heat-4{background:color-mix(in srgb, var(--color-blue-500) 92%, transparent)}.lc-heat-on{box-shadow:0 0 0 1.5px var(--color-blue-500)}.lc-ov-list-head{flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:10px;display:flex}.lc-ov-list-title{white-space:nowrap;font-weight:600}.lc-ov-list-count{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;background:var(--dsw-alias-bg-layer-2);border-radius:8px;padding:1px 7px;font-size:11px}.lc-ov-day-chip{border:1px solid color-mix(in srgb, var(--color-blue-500) 45%, transparent);color:var(--color-blue-500);background:color-mix(in srgb, var(--color-blue-500) 10%, transparent);cursor:pointer;white-space:nowrap;border-radius:999px;padding:1px 10px;font-family:inherit;font-size:11px;line-height:1.4}.lc-ov-search{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);min-width:110px;font:inherit;color:var(--dsw-alias-label-primary);transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;outline:none;flex:150px;padding:4px 10px;font-size:12px}.lc-ov-search:focus{border-color:var(--dsw-alias-label-secondary)}.lc-ov-search::placeholder{color:var(--dsw-alias-label-secondary)}.lc-ov-groups{flex-wrap:wrap;gap:6px;margin:-2px 0 10px;display:flex}.lc-ov-chip{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;white-space:nowrap;text-overflow:ellipsis;max-width:220px;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border-radius:999px;align-items:baseline;gap:5px;padding:2px 10px;font-size:11px;line-height:1.5;display:inline-flex;overflow:hidden}.lc-ov-chip:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-secondary)}.lc-ov-chip-on,.lc-ov-chip-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);border-color:#0000}.lc-ov-chip-n{font-variant-numeric:tabular-nums;opacity:.65;font-size:10px}.lc-ov-chip-on .lc-ov-chip-n{opacity:.85}.lc-ov-grid{grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:10px;display:grid}.lc-ov-session{text-align:left;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;font:inherit;color:inherit;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}.lc-ov-session:hover{border-color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}.lc-ov-session-current{border-color:color-mix(in srgb, var(--color-blue-500) 65%, transparent)}.lc-ov-session-head{align-items:flex-start;gap:6px;min-width:0;display:flex}.lc-ov-running{background:var(--color-green-500);border-radius:50%;flex:none;width:7px;height:7px}.lc-ov-session-title{-webkit-line-clamp:2;overflow-wrap:anywhere;-webkit-box-orient:vertical;flex:auto;min-width:0;font-weight:600;display:-webkit-box;overflow:hidden}.lc-ov-current{color:var(--color-blue-500);border:1px solid color-mix(in srgb, var(--color-blue-500) 45%, transparent);border-radius:4px;flex:none;padding:0 4px;font-size:10px;line-height:1.5}.lc-ov-session-times{flex-direction:column;flex:none;align-items:flex-end;gap:1px;display:flex}.lc-ov-session-time{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:11px}.lc-ov-session-created{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-variant-numeric:tabular-nums;font-size:10px;line-height:1.4}.lc-ov-session-crumb{min-width:0;color:var(--dsw-alias-label-secondary);white-space:nowrap;align-items:center;gap:5px;font-size:11px;display:flex;overflow:hidden}.lc-ov-crumb-group{text-overflow:ellipsis;flex:none;max-width:55%;overflow:hidden}.lc-ov-crumb-sep{opacity:.55;flex:none}.lc-ov-crumb-project{text-overflow:ellipsis;min-width:0;font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--dsw-alias-label-primary);background:color-mix(in srgb, var(--dsw-alias-label-secondary) 16%, transparent);border-radius:999px;flex:0 auto;padding:0 8px;font-size:10px;line-height:1.6;overflow:hidden}.lc-ov-session-body{align-items:center;gap:14px;min-width:0;display:flex}.lc-ov-mini-stats{flex-direction:column;flex:auto;gap:3px;min-width:0;display:flex}.lc-ov-mini-stat{justify-content:space-between;align-items:baseline;gap:10px;font-size:11px;display:flex}.lc-ov-mini-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;flex:none}.lc-ov-mini-value{font-variant-numeric:tabular-nums;white-space:nowrap;font-weight:600}.lc-ov-session-empty{color:var(--dsw-alias-label-secondary);padding:6px 0;font-size:11px;line-height:1.5}.lc-ov-session-preview{border-top:1px solid color-mix(in srgb, var(--dsw-alias-border-l2) 60%, transparent);align-items:baseline;gap:6px;min-width:0;margin-top:auto;padding-top:6px;display:flex}.lc-ov-session-preview-label{color:var(--dsw-alias-label-secondary);flex:none;font-size:10px}.lc-ov-session-preview-text{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary);flex:0 auto;font-size:11px;line-height:1.5;overflow:hidden}.lc-ov-pager{justify-content:center;align-items:center;gap:10px;margin-top:12px;display:flex}.lc-ov-pager-btn{border:1px solid var(--dsw-alias-border-l2);width:26px;height:22px;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border-radius:6px;flex:none;justify-content:center;align-items:center;line-height:1;display:flex}.lc-ov-pager-btn:hover:enabled{border-color:var(--dsw-alias-label-secondary)}.lc-ov-pager-btn:disabled{opacity:.4;cursor:default}.lc-ov-pager-n{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:11px}";
|
|
11431
11576
|
const tagId = "dsh-context/overview.css";
|
|
11432
11577
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
11433
11578
|
const tag = document.createElement("style");
|
package/lib/index.d.ts
CHANGED
|
@@ -796,10 +796,33 @@ interface HeaderRecord {
|
|
|
796
796
|
interface ContextHeaders {
|
|
797
797
|
headers: HeaderRecord[];
|
|
798
798
|
}
|
|
799
|
+
/** One currency's DeepSeek open-platform balance figures. */
|
|
800
|
+
interface PlatformBalanceEntry {
|
|
801
|
+
/** The ISO code the platform reported (`CNY` / `USD`). */
|
|
802
|
+
currency: string;
|
|
803
|
+
/** Total available funds: the granted and topped-up balances summed. */
|
|
804
|
+
total: number;
|
|
805
|
+
/** The not-expired granted (gift) balance. */
|
|
806
|
+
granted: number;
|
|
807
|
+
/** The topped-up balance. */
|
|
808
|
+
toppedUp: number;
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* The DeepSeek open-platform balance, served by the plugin's
|
|
812
|
+
* `/api/dsh-context/balance` fetch route (host/balance.ts). `null` on the
|
|
813
|
+
* wire — and nothing rendered client-side — whenever the platform is not
|
|
814
|
+
* configured or the read fails: the capsule only ever shows a live figure.
|
|
815
|
+
*/
|
|
816
|
+
interface PlatformBalance {
|
|
817
|
+
/** Whether the platform reports the balance sufficient for API calls. */
|
|
818
|
+
isAvailable: boolean;
|
|
819
|
+
/** One entry per currency the account holds; at least one. */
|
|
820
|
+
balances: PlatformBalanceEntry[];
|
|
821
|
+
}
|
|
799
822
|
//#endregion
|
|
800
823
|
//#region src/host/index.d.ts
|
|
801
824
|
export declare const name = "dsh-context";
|
|
802
825
|
export declare const inject: string[];
|
|
803
826
|
export declare function apply(ctx: Context, config: Config): void;
|
|
804
827
|
//#endregion
|
|
805
|
-
export type { ActivityDay, ActivityState, Category, ContextActivity, ContextEventRecord, ContextHeaders, ContextTimeline, ContextTimelineDetail, HeaderRecord, HeaderTool, HeadersState, RequestRecord, Snapshot, SurfaceNode, TimelineCounts, TimelineLast, TimelineState };
|
|
828
|
+
export type { ActivityDay, ActivityState, Category, ContextActivity, ContextEventRecord, ContextHeaders, ContextTimeline, ContextTimelineDetail, HeaderRecord, HeaderTool, HeadersState, PlatformBalance, PlatformBalanceEntry, RequestRecord, Snapshot, SurfaceNode, TimelineCounts, TimelineLast, TimelineState };
|
package/lib/index.js
CHANGED
|
@@ -2057,6 +2057,163 @@ function createToolAttribution(ctx) {
|
|
|
2057
2057
|
return { ownerOf: (name) => mcpSourceOf(name) ?? live.get(name) ?? pinnedSourceOf(name) ?? (boot.has(name) ? "<unknown-plugin>" : void 0) };
|
|
2058
2058
|
}
|
|
2059
2059
|
//#endregion
|
|
2060
|
+
//#region src/host/balance.ts
|
|
2061
|
+
/** The plugin's balance route, under the authenticated `/api` fence. */
|
|
2062
|
+
const BALANCE_ROUTE = "/api/dsh-context/balance";
|
|
2063
|
+
/** The settings namespace llm-deepseek installs its connection section under. */
|
|
2064
|
+
const DEEPSEEK_SETTINGS_NS = "llm-deepseek";
|
|
2065
|
+
/** llm-deepseek's default credential ref (the env-var name its section resolves). */
|
|
2066
|
+
const DEFAULT_API_KEY_ENV = "DEEPSEEK_API_KEY";
|
|
2067
|
+
/** The platform's public API root (llm-deepseek's PUBLIC_BASE_URL default). */
|
|
2068
|
+
const PUBLIC_BASE_URL = "https://api.deepseek.com";
|
|
2069
|
+
/** A settled read serves identical reads for this long; failures decay faster. */
|
|
2070
|
+
const CACHE_TTL_MS = 3e5;
|
|
2071
|
+
const CACHE_FAIL_TTL_MS = 6e4;
|
|
2072
|
+
/** One platform read's whole budget — never worth blocking the route longer. */
|
|
2073
|
+
const FETCH_TIMEOUT_MS = 1e4;
|
|
2074
|
+
/** Narrow an unknown value to a string-keyed record, or null. */
|
|
2075
|
+
function asRecord$1(value) {
|
|
2076
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2077
|
+
}
|
|
2078
|
+
/** One platform amount ('110.00', or an already-numeric producer variant), or null. */
|
|
2079
|
+
function amountOf(value) {
|
|
2080
|
+
const n = typeof value === "string" ? Number(value) : value;
|
|
2081
|
+
return typeof n === "number" && Number.isFinite(n) && n >= 0 ? n : null;
|
|
2082
|
+
}
|
|
2083
|
+
/**
|
|
2084
|
+
* Resolve the DeepSeek connection facts exactly as llm-deepseek serves its
|
|
2085
|
+
* requests: its settings section for the credential ref and endpoint, the
|
|
2086
|
+
* credentials service for the key. `null` whenever any fact is missing —
|
|
2087
|
+
* the platform is simply not configured for this deployment.
|
|
2088
|
+
*/
|
|
2089
|
+
async function resolveFacts(ctx) {
|
|
2090
|
+
const settings = ctx.get("settings");
|
|
2091
|
+
const section = typeof settings?.get === "function" ? asRecord$1(settings.get(DEEPSEEK_SETTINGS_NS)) : null;
|
|
2092
|
+
if (section === null) return null;
|
|
2093
|
+
const apiKeyEnv = typeof section.apiKeyEnv === "string" && section.apiKeyEnv !== "" ? section.apiKeyEnv : DEFAULT_API_KEY_ENV;
|
|
2094
|
+
const baseUrl = typeof section.baseURL === "string" && section.baseURL !== "" ? section.baseURL : PUBLIC_BASE_URL;
|
|
2095
|
+
const credentials = ctx.get("credentials");
|
|
2096
|
+
const resolve = typeof credentials?.resolve === "function" ? credentials.resolve.bind(credentials) : void 0;
|
|
2097
|
+
if (resolve === void 0) return null;
|
|
2098
|
+
let hit;
|
|
2099
|
+
try {
|
|
2100
|
+
hit = await resolve(apiKeyEnv);
|
|
2101
|
+
} catch {
|
|
2102
|
+
return null;
|
|
2103
|
+
}
|
|
2104
|
+
const apiKey = typeof hit?.value === "string" ? hit.value : "";
|
|
2105
|
+
return apiKey === "" ? null : {
|
|
2106
|
+
baseUrl,
|
|
2107
|
+
apiKey
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
/**
|
|
2111
|
+
* Narrow the platform's payload to the wire value (the boundary rigor every
|
|
2112
|
+
* parser owes untrusted input): each entry's currency and three amounts are
|
|
2113
|
+
* re-proved, an entry failing the shape drops whole, and a payload with no
|
|
2114
|
+
* valid entry is no balance at all.
|
|
2115
|
+
*/
|
|
2116
|
+
function balanceOfPayload(value) {
|
|
2117
|
+
const data = asRecord$1(value);
|
|
2118
|
+
if (data === null) return null;
|
|
2119
|
+
const infos = Array.isArray(data.balance_infos) ? data.balance_infos : [];
|
|
2120
|
+
const balances = [];
|
|
2121
|
+
for (const info of infos) try {
|
|
2122
|
+
const entry = asRecord$1(info);
|
|
2123
|
+
if (entry === null) continue;
|
|
2124
|
+
const currency = entry.currency;
|
|
2125
|
+
const total = amountOf(entry.total_balance);
|
|
2126
|
+
const granted = amountOf(entry.granted_balance);
|
|
2127
|
+
const toppedUp = amountOf(entry.topped_up_balance);
|
|
2128
|
+
if (typeof currency !== "string" || currency === "" || total === null || granted === null || toppedUp === null) continue;
|
|
2129
|
+
balances.push({
|
|
2130
|
+
currency,
|
|
2131
|
+
total,
|
|
2132
|
+
granted,
|
|
2133
|
+
toppedUp
|
|
2134
|
+
});
|
|
2135
|
+
} catch {
|
|
2136
|
+
continue;
|
|
2137
|
+
}
|
|
2138
|
+
return balances.length > 0 ? {
|
|
2139
|
+
isAvailable: data.is_available === true,
|
|
2140
|
+
balances
|
|
2141
|
+
} : null;
|
|
2142
|
+
}
|
|
2143
|
+
/** The outbound platform fetch through the real global fetch. */
|
|
2144
|
+
const defaultFetcher = (url, init) => fetch(url, init);
|
|
2145
|
+
let fetcher = defaultFetcher;
|
|
2146
|
+
let cache = null;
|
|
2147
|
+
let inFlight = null;
|
|
2148
|
+
/**
|
|
2149
|
+
* One platform read for these facts, shared across identical concurrent
|
|
2150
|
+
* reads and remembered briefly: successes for CACHE_TTL_MS, failures for
|
|
2151
|
+
* CACHE_FAIL_TTL_MS (a blip must not pin the capsule away for the full
|
|
2152
|
+
* window). Every failure path — transport, timeout, non-ok status, malformed
|
|
2153
|
+
* payload — resolves `null`; the route never throws into the transport.
|
|
2154
|
+
*/
|
|
2155
|
+
async function readBalance(facts) {
|
|
2156
|
+
const key = facts.baseUrl + "\0" + facts.apiKey;
|
|
2157
|
+
if (cache !== null && cache.key === key && Date.now() - cache.at < (cache.value === null ? CACHE_FAIL_TTL_MS : CACHE_TTL_MS)) return cache.value;
|
|
2158
|
+
if (inFlight !== null) return inFlight;
|
|
2159
|
+
inFlight = (async () => {
|
|
2160
|
+
let value = null;
|
|
2161
|
+
try {
|
|
2162
|
+
const response = await fetcher(facts.baseUrl.replace(/\/+$/, "") + "/user/balance", {
|
|
2163
|
+
headers: {
|
|
2164
|
+
accept: "application/json",
|
|
2165
|
+
authorization: "Bearer " + facts.apiKey
|
|
2166
|
+
},
|
|
2167
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
2168
|
+
});
|
|
2169
|
+
if (response.ok) value = balanceOfPayload(await response.json());
|
|
2170
|
+
} catch {}
|
|
2171
|
+
cache = {
|
|
2172
|
+
key,
|
|
2173
|
+
at: Date.now(),
|
|
2174
|
+
value
|
|
2175
|
+
};
|
|
2176
|
+
return value;
|
|
2177
|
+
})();
|
|
2178
|
+
const settled = inFlight;
|
|
2179
|
+
try {
|
|
2180
|
+
return await settled;
|
|
2181
|
+
} finally {
|
|
2182
|
+
inFlight = null;
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
/**
|
|
2186
|
+
* Serve the balance route whenever the connection service composes (see
|
|
2187
|
+
* host/detail.ts for the load-order contract). The registration rides the
|
|
2188
|
+
* injected fiber: the service unloading withdraws the route. A rejecting
|
|
2189
|
+
* registry only closes the capsule — never the plugin.
|
|
2190
|
+
*/
|
|
2191
|
+
function watchBalanceChannel(ctx) {
|
|
2192
|
+
ctx.inject(["connection"], (c) => {
|
|
2193
|
+
const connection = c.get("connection");
|
|
2194
|
+
const register = typeof connection?.fetch?.register === "function" ? connection.fetch.register.bind(connection.fetch) : void 0;
|
|
2195
|
+
if (register === void 0) return;
|
|
2196
|
+
const handler = async () => {
|
|
2197
|
+
const facts = await resolveFacts(ctx);
|
|
2198
|
+
const value = facts !== null ? await readBalance(facts) : null;
|
|
2199
|
+
return Response.json({
|
|
2200
|
+
ok: true,
|
|
2201
|
+
value
|
|
2202
|
+
}, { headers: { "cache-control": "no-store" } });
|
|
2203
|
+
};
|
|
2204
|
+
try {
|
|
2205
|
+
c.effect(() => register({
|
|
2206
|
+
path: BALANCE_ROUTE,
|
|
2207
|
+
methods: ["POST"],
|
|
2208
|
+
requestBody: "buffered",
|
|
2209
|
+
fetch: handler
|
|
2210
|
+
}), "dsh-context: balance route");
|
|
2211
|
+
} catch {
|
|
2212
|
+
return;
|
|
2213
|
+
}
|
|
2214
|
+
});
|
|
2215
|
+
}
|
|
2216
|
+
//#endregion
|
|
2060
2217
|
//#region src/host/backfill.ts
|
|
2061
2218
|
/** The plugin's warm-up trigger route, under the authenticated `/api` fence. */
|
|
2062
2219
|
const BACKFILL_ROUTE = "/api/dsh-context/backfill";
|
|
@@ -2067,8 +2224,15 @@ const BACKFILL_ROUTE = "/api/dsh-context/backfill";
|
|
|
2067
2224
|
* workspace sidebar rides it), so the pass breathes between sessions.
|
|
2068
2225
|
*/
|
|
2069
2226
|
const YIELD_MS = 100;
|
|
2070
|
-
/**
|
|
2071
|
-
|
|
2227
|
+
/**
|
|
2228
|
+
* The `name` family dsh raises for a durable log it cannot interpret. The
|
|
2229
|
+
* format edge throws `SessionFormatUnsupportedMigrationError`, but the
|
|
2230
|
+
* persistence seam translates it — together with every other "intact yet not
|
|
2231
|
+
* interpretable" refusal — into `SessionFormatUnsupportedError` before the
|
|
2232
|
+
* error escapes (issue #75): classify by the family prefix, since either
|
|
2233
|
+
* exact name may be the one that arrives.
|
|
2234
|
+
*/
|
|
2235
|
+
const UNSUPPORTED_FORMAT = "SessionFormatUnsupported";
|
|
2072
2236
|
function asRecord(value) {
|
|
2073
2237
|
return value !== null && typeof value === "object" ? value : null;
|
|
2074
2238
|
}
|
|
@@ -2123,15 +2287,15 @@ function isLive(sessions, id) {
|
|
|
2123
2287
|
}
|
|
2124
2288
|
}
|
|
2125
2289
|
/**
|
|
2126
|
-
* Whether the cold read failed because the running harness refuses
|
|
2127
|
-
*
|
|
2128
|
-
*
|
|
2129
|
-
*
|
|
2130
|
-
*
|
|
2290
|
+
* Whether the cold read failed because the running harness refuses this log's
|
|
2291
|
+
* format — a permanent, source-side refusal (the artifact is left unchanged,
|
|
2292
|
+
* so retrying cannot succeed). Matched by the error's documented `name`, so no
|
|
2293
|
+
* dsh symbol needs importing and every supported baseline classifies alike: an
|
|
2294
|
+
* unknown error shape just falls to the warn path.
|
|
2131
2295
|
*/
|
|
2132
|
-
function
|
|
2296
|
+
function isUnsupportedFormat(error) {
|
|
2133
2297
|
try {
|
|
2134
|
-
return asRecord(error)?.name
|
|
2298
|
+
return String(asRecord(error)?.name).startsWith(UNSUPPORTED_FORMAT);
|
|
2135
2299
|
} catch {
|
|
2136
2300
|
return false;
|
|
2137
2301
|
}
|
|
@@ -2210,7 +2374,7 @@ function watchActivityBackfill(ctx) {
|
|
|
2210
2374
|
cache.coldSnapshot(log.header, log.inheritedEventCount, log.events);
|
|
2211
2375
|
folded++;
|
|
2212
2376
|
} catch (error) {
|
|
2213
|
-
if (
|
|
2377
|
+
if (isUnsupportedFormat(error)) {
|
|
2214
2378
|
refused++;
|
|
2215
2379
|
ctx.logger.debug(`dsh-context: projection backfill skipped "${header.id}" (${messageOf(error)})`);
|
|
2216
2380
|
} else ctx.logger.warn(`dsh-context: projection backfill skipped "${header.id}" (${messageOf(error)})`);
|
|
@@ -3281,6 +3445,7 @@ function detectHarnessVersion(ctx, resolveUrl = resolveRunningModule, pluginRoot
|
|
|
3281
3445
|
const name = "dsh-context";
|
|
3282
3446
|
const inject = ["sessionProjections"];
|
|
3283
3447
|
function apply(ctx, config) {
|
|
3448
|
+
watchBalanceChannel(ctx);
|
|
3284
3449
|
const harnessVersion = detectHarnessVersion(ctx);
|
|
3285
3450
|
if (harnessVersion !== void 0 && !meetsBaseline(harnessVersion)) {
|
|
3286
3451
|
ctx.sessionProjections.register(createFallbackTimelineDefinition(harnessVersion));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-context",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.54.0",
|
|
4
4
|
"description": "A DeepSeek Harness plugin for context insight and management, with context dashboard and context command, for understanding how the context is made of, and how it evolves.",
|
|
5
5
|
"author": "bowenliang123",
|
|
6
6
|
"repository": {
|