@jxgame2020/dsh-token-quota 0.1.18 → 0.1.19

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 CHANGED
@@ -4,7 +4,7 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
- let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
7
+ let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
8
8
  let react_jsx_runtime = require("react/jsx-runtime");
9
9
  let react = require("react");
10
10
  //#region lib/types/client/store.js
@@ -26,7 +26,7 @@ window.__ModuleLoader__.load({
26
26
  * @returns the store handle.
27
27
  */
28
28
  function createTokenQuotaPanelStore() {
29
- return (0, _deepseek_ai_dsh_client_runtime_client.defineStore)({
29
+ return (0, _deepseek_ai_dsh_client_store.defineStore)({
30
30
  init: () => ({
31
31
  snapshot: null,
32
32
  groups: [],
@@ -79,7 +79,7 @@ window.__ModuleLoader__.load({
79
79
  d.dimWhenIdle = dimWhenIdle;
80
80
  },
81
81
  setUpgrade: (d, upgrade) => {
82
- const normalized = upgrade !== null && upgrade.latestVersion !== "" && upgrade.latestVersion !== "0.1.18" ? upgrade : null;
82
+ const normalized = upgrade !== null && upgrade.latestVersion !== "" && upgrade.latestVersion !== "0.1.19" ? upgrade : null;
83
83
  if (d.upgrade === null || d.upgrade.latestVersion !== normalized?.latestVersion) d.upgradeDismissed = false;
84
84
  d.upgrade = normalized;
85
85
  },
@@ -185,91 +185,91 @@ window.__ModuleLoader__.load({
185
185
  document.head.appendChild(tag);
186
186
  }
187
187
  var TokenQuotaPanel_module_css_default = {
188
- "tabIcon": "NPLLIq_tabIcon",
189
- "upgradeCommands": "NPLLIq_upgradeCommands",
190
- "versionNew": "NPLLIq_versionNew",
191
- "checkErrorHint": "NPLLIq_checkErrorHint",
192
- "collapse": "NPLLIq_collapse",
193
- "rowName": "NPLLIq_rowName",
194
- "upgradeBannerFlash": "NPLLIq_upgradeBannerFlash",
195
- "dialogHint": "NPLLIq_dialogHint",
196
- "panelDim": "NPLLIq_panelDim",
197
- "upgradeHint": "NPLLIq_upgradeHint",
188
+ "panel": "NPLLIq_panel",
189
+ "rowHeader": "NPLLIq_rowHeader",
190
+ "upgradeCommandRow": "NPLLIq_upgradeCommandRow",
191
+ "logToolbar": "NPLLIq_logToolbar",
192
+ "gearBtn": "NPLLIq_gearBtn",
198
193
  "logPagerInfo": "NPLLIq_logPagerInfo",
199
- "resetSelect": "NPLLIq_resetSelect",
200
- "logClearDialog": "NPLLIq_logClearDialog",
201
- "dialogClose": "NPLLIq_dialogClose",
202
- "selectBtn": "NPLLIq_selectBtn",
203
- "logClearDate": "NPLLIq_logClearDate",
204
- "rowMeta": "NPLLIq_rowMeta",
205
- "tabLabel": "NPLLIq_tabLabel",
206
- "dialogSection": "NPLLIq_dialogSection",
207
- "copyBtn": "NPLLIq_copyBtn",
208
- "resetRow": "NPLLIq_resetRow",
194
+ "fillWarn": "NPLLIq_fillWarn",
195
+ "headerText": "NPLLIq_headerText",
196
+ "dialogLabel": "NPLLIq_dialogLabel",
197
+ "dialogBtn": "NPLLIq_dialogBtn",
198
+ "monitorHint": "NPLLIq_monitorHint",
199
+ "logPagerPages": "NPLLIq_logPagerPages",
200
+ "subtitle": "NPLLIq_subtitle",
201
+ "upgradeCommands": "NPLLIq_upgradeCommands",
209
202
  "dialogActions": "NPLLIq_dialogActions",
203
+ "title": "NPLLIq_title",
210
204
  "fillOver": "NPLLIq_fillOver",
211
- "save": "NPLLIq_save",
212
- "logModelCol": "NPLLIq_logModelCol",
213
- "fillIdle": "NPLLIq_fillIdle",
214
- "header": "NPLLIq_header",
215
- "logUsedCol": "NPLLIq_logUsedCol",
216
- "radioRow": "NPLLIq_radioRow",
217
- "dialogBtn": "NPLLIq_dialogBtn",
218
- "body": "NPLLIq_body",
219
- "monitorName": "NPLLIq_monitorName",
220
- "dialog": "NPLLIq_dialog",
221
- "switch": "NPLLIq_switch",
205
+ "monitorRow": "NPLLIq_monitorRow",
206
+ "panelDimStrong": "NPLLIq_panelDimStrong",
222
207
  "currentBadge": "NPLLIq_currentBadge",
223
- "subtitle": "NPLLIq_subtitle",
224
- "upgradeBanner": "NPLLIq_upgradeBanner",
225
- "copyBtnDone": "NPLLIq_copyBtnDone",
226
208
  "checkUpdatesRow": "NPLLIq_checkUpdatesRow",
209
+ "input": "NPLLIq_input",
210
+ "checkErrorHint": "NPLLIq_checkErrorHint",
211
+ "dialogOverlay": "NPLLIq_dialogOverlay",
212
+ "tabLabel": "NPLLIq_tabLabel",
213
+ "upgradeClose": "NPLLIq_upgradeClose",
214
+ "dialogClose": "NPLLIq_dialogClose",
227
215
  "logScroll": "NPLLIq_logScroll",
228
- "logToolbar": "NPLLIq_logToolbar",
229
- "logPagerControls": "NPLLIq_logPagerControls",
216
+ "fullNotice": "NPLLIq_fullNotice",
217
+ "settingsBtn": "NPLLIq_settingsBtn",
218
+ "upgradeBanner": "NPLLIq_upgradeBanner",
219
+ "upgradeBannerFlash": "NPLLIq_upgradeBannerFlash",
220
+ "logPager": "NPLLIq_logPager",
230
221
  "logPageSizeSelect": "NPLLIq_logPageSizeSelect",
222
+ "rowName": "NPLLIq_rowName",
223
+ "logClearDate": "NPLLIq_logClearDate",
224
+ "collapse": "NPLLIq_collapse",
225
+ "bar": "NPLLIq_bar",
226
+ "controls": "NPLLIq_controls",
227
+ "copyBtnDone": "NPLLIq_copyBtnDone",
228
+ "body": "NPLLIq_body",
229
+ "logPagerBtn": "NPLLIq_logPagerBtn",
230
+ "notice": "NPLLIq_notice",
231
+ "monitorName": "NPLLIq_monitorName",
232
+ "logClearBtn": "NPLLIq_logClearBtn",
233
+ "dialogBody": "NPLLIq_dialogBody",
234
+ "resetRow": "NPLLIq_resetRow",
235
+ "dialogHint": "NPLLIq_dialogHint",
236
+ "panelDim": "NPLLIq_panelDim",
231
237
  "logDialog": "NPLLIq_logDialog",
232
- "logDayCol": "NPLLIq_logDayCol",
233
- "monitorList": "NPLLIq_monitorList",
234
- "panelDimStrong": "NPLLIq_panelDimStrong",
235
- "monitorHint": "NPLLIq_monitorHint",
236
- "logTable": "NPLLIq_logTable",
237
- "logPager": "NPLLIq_logPager",
238
- "dialogOverlay": "NPLLIq_dialogOverlay",
239
- "noticeError": "NPLLIq_noticeError",
240
238
  "tab": "NPLLIq_tab",
241
- "upgradeBannerText": "NPLLIq_upgradeBannerText",
242
- "logClearBtn": "NPLLIq_logClearBtn",
243
- "notice": "NPLLIq_notice",
244
- "fillWarn": "NPLLIq_fillWarn",
239
+ "upgradeHint": "NPLLIq_upgradeHint",
240
+ "header": "NPLLIq_header",
241
+ "switch": "NPLLIq_switch",
242
+ "noticeError": "NPLLIq_noticeError",
245
243
  "upgradeCommand": "NPLLIq_upgradeCommand",
244
+ "row": "NPLLIq_row",
245
+ "rowMeta": "NPLLIq_rowMeta",
246
+ "checkUpdatesLabel": "NPLLIq_checkUpdatesLabel",
247
+ "dialog": "NPLLIq_dialog",
246
248
  "dialogHeader": "NPLLIq_dialogHeader",
247
- "title": "NPLLIq_title",
249
+ "logModelCol": "NPLLIq_logModelCol",
250
+ "logUsedCol": "NPLLIq_logUsedCol",
248
251
  "logSearch": "NPLLIq_logSearch",
249
- "controls": "NPLLIq_controls",
250
- "input": "NPLLIq_input",
251
- "logHeader": "NPLLIq_logHeader",
252
- "settingsBtn": "NPLLIq_settingsBtn",
253
- "upgradeCommandRow": "NPLLIq_upgradeCommandRow",
252
+ "logPagerControls": "NPLLIq_logPagerControls",
253
+ "fillIdle": "NPLLIq_fillIdle",
254
+ "monitorList": "NPLLIq_monitorList",
255
+ "upgradeBannerText": "NPLLIq_upgradeBannerText",
254
256
  "dialogBtnPrimary": "NPLLIq_dialogBtnPrimary",
255
- "logPagerBtn": "NPLLIq_logPagerBtn",
256
- "panel": "NPLLIq_panel",
257
257
  "version": "NPLLIq_version",
258
- "upgradeClose": "NPLLIq_upgradeClose",
259
- "headerText": "NPLLIq_headerText",
260
- "fullNotice": "NPLLIq_fullNotice",
261
- "logPagerPages": "NPLLIq_logPagerPages",
262
- "headerActions": "NPLLIq_headerActions",
263
- "rowHeader": "NPLLIq_rowHeader",
264
- "bar": "NPLLIq_bar",
265
258
  "dialogTitle": "NPLLIq_dialogTitle",
266
- "checkUpdatesLabel": "NPLLIq_checkUpdatesLabel",
267
- "gearBtn": "NPLLIq_gearBtn",
268
- "monitorRow": "NPLLIq_monitorRow",
259
+ "dialogSection": "NPLLIq_dialogSection",
269
260
  "checkBtn": "NPLLIq_checkBtn",
270
- "dialogLabel": "NPLLIq_dialogLabel",
271
- "dialogBody": "NPLLIq_dialogBody",
272
- "row": "NPLLIq_row"
261
+ "save": "NPLLIq_save",
262
+ "selectBtn": "NPLLIq_selectBtn",
263
+ "logDayCol": "NPLLIq_logDayCol",
264
+ "resetSelect": "NPLLIq_resetSelect",
265
+ "copyBtn": "NPLLIq_copyBtn",
266
+ "logClearDialog": "NPLLIq_logClearDialog",
267
+ "radioRow": "NPLLIq_radioRow",
268
+ "versionNew": "NPLLIq_versionNew",
269
+ "tabIcon": "NPLLIq_tabIcon",
270
+ "logTable": "NPLLIq_logTable",
271
+ "logHeader": "NPLLIq_logHeader",
272
+ "headerActions": "NPLLIq_headerActions"
273
273
  };
274
274
  //#endregion
275
275
  //#region lib/types/client/TokenQuotaPanel.js
@@ -411,8 +411,7 @@ window.__ModuleLoader__.load({
411
411
  (0, react.useEffect)(() => {
412
412
  const onFocusIn = (event) => {
413
413
  const target = event.target;
414
- const isInput = target !== null && (target.matches("input, textarea, [contenteditable=\"true\"]") || target.closest("input, textarea, [contenteditable=\"true\"]") !== null);
415
- setInputActive(isInput);
414
+ setInputActive(target !== null && (target.matches("input, textarea, [contenteditable=\"true\"]") || target.closest("input, textarea, [contenteditable=\"true\"]") !== null));
416
415
  };
417
416
  window.addEventListener("focusin", onFocusIn);
418
417
  return () => {
@@ -572,13 +571,13 @@ window.__ModuleLoader__.load({
572
571
  title: t("upgradeAvailableHint"),
573
572
  children: [
574
573
  "v",
575
- "0.1.18",
574
+ "0.1.19",
576
575
  " → v",
577
576
  upgrade.latestVersion
578
577
  ]
579
578
  }) : (0, react_jsx_runtime.jsxs)("span", {
580
579
  className: TokenQuotaPanel_module_css_default.version,
581
- children: ["v", "0.1.18"]
580
+ children: ["v", "0.1.19"]
582
581
  })]
583
582
  }), (0, react_jsx_runtime.jsx)("div", {
584
583
  className: TokenQuotaPanel_module_css_default.subtitle,
@@ -1279,10 +1278,10 @@ window.__ModuleLoader__.load({
1279
1278
  const TOKEN_QUOTA_NAMESPACE = "token-quota";
1280
1279
  /** Snapshot pull cadence in milliseconds. */
1281
1280
  const POLL_INTERVAL_MS = 3e3;
1282
- /** Required services: slot registry, connection RPC, locale, settings scope. */
1281
+ /** Required services: slot registry, sessions, locale, settings scope. */
1283
1282
  const inject = [
1284
1283
  "slots",
1285
- "connection",
1284
+ "sessions",
1286
1285
  "locale",
1287
1286
  "settingsScope"
1288
1287
  ];
@@ -1299,7 +1298,7 @@ window.__ModuleLoader__.load({
1299
1298
  }), "token-quota-ui: panel dictionaries");
1300
1299
  const t = ctx.locale.bind(NS);
1301
1300
  const scope = ctx.settingsScope.bind({ namespace: TOKEN_QUOTA_NAMESPACE });
1302
- const connection = ctx.get("connection");
1301
+ const sessions = ctx.get("sessions");
1303
1302
  const store = createTokenQuotaPanelStore();
1304
1303
  let bound;
1305
1304
  let lastGroups = [];
@@ -1366,11 +1365,11 @@ window.__ModuleLoader__.load({
1366
1365
  bound?.setFullNotice(t("fullSwitchFailed"));
1367
1366
  return;
1368
1367
  }
1369
- connection.api.sessions.selectModel({
1368
+ ctx.remote.session.selectModel({
1370
1369
  sessionId: lastSessionId,
1371
1370
  provider: target.provider,
1372
1371
  model: target.model
1373
- }).then(({ result }) => {
1372
+ }).then((result) => {
1374
1373
  if (result.ok) {
1375
1374
  lastCurrent = result.value.selected;
1376
1375
  flashFullNotice(t("fullSwitchTo").replace("{model}", target.model));
@@ -1381,14 +1380,21 @@ window.__ModuleLoader__.load({
1381
1380
  });
1382
1381
  };
1383
1382
  let pullCount = 0;
1383
+ /** Resolve the session's current model from its durable modelSelection projection. */
1384
+ const currentOf = (sessionId) => {
1385
+ const binding = sessions.binding(sessionId);
1386
+ if (binding === void 0) return null;
1387
+ return binding.session.projections.faceOf("modelSelection").getSnapshot()?.lastUsed ?? null;
1388
+ };
1384
1389
  const refreshCurrent = () => {
1385
1390
  if (lastSessionId === void 0) return;
1386
- connection.api.sessions.models({ sessionId: lastSessionId }).then(({ result }) => {
1391
+ ctx.remote.session.modelCatalog().then((result) => {
1387
1392
  if (result.ok) {
1388
- const currentChanged = lastCurrent?.provider !== result.value.current?.provider || lastCurrent?.model !== result.value.current?.model;
1393
+ const current = currentOf(lastSessionId);
1394
+ const currentChanged = lastCurrent?.provider !== current?.provider || lastCurrent?.model !== current?.model;
1389
1395
  lastGroups = result.value.groups;
1390
- lastCurrent = result.value.current;
1391
- bound?.setDirectory(result.value.groups, result.value.current);
1396
+ lastCurrent = current;
1397
+ bound?.setDirectory(result.value.groups, current);
1392
1398
  if (currentChanged) fetch("/token-quota", { headers: { accept: "application/json" } }).then((response) => response.ok ? response.json() : void 0, () => void 0).then((snapshot) => {
1393
1399
  if (snapshot !== void 0) {
1394
1400
  bound?.setSnapshot(snapshot);
@@ -1437,11 +1443,12 @@ window.__ModuleLoader__.load({
1437
1443
  lastSessionId = sessionId;
1438
1444
  bound?.setLoading(true);
1439
1445
  bound?.setError(null);
1440
- connection.api.sessions.models({ sessionId }).then(({ result }) => {
1446
+ ctx.remote.session.modelCatalog().then((result) => {
1441
1447
  if (result.ok) {
1448
+ const current = currentOf(sessionId);
1442
1449
  lastGroups = result.value.groups;
1443
- lastCurrent = result.value.current;
1444
- bound?.setDirectory(result.value.groups, result.value.current);
1450
+ lastCurrent = current;
1451
+ bound?.setDirectory(result.value.groups, current);
1445
1452
  bound?.setLoading(false);
1446
1453
  } else {
1447
1454
  bound?.setError(`${result.error.code}: ${result.error.message}`);
@@ -1512,11 +1519,11 @@ window.__ModuleLoader__.load({
1512
1519
  scope.set("reset", reset);
1513
1520
  };
1514
1521
  const selectModel = (sessionId, provider, model) => {
1515
- connection.api.sessions.selectModel({
1522
+ ctx.remote.session.selectModel({
1516
1523
  sessionId,
1517
1524
  provider,
1518
1525
  model
1519
- }).then(({ result }) => {
1526
+ }).then((result) => {
1520
1527
  if (result.ok) {
1521
1528
  lastCurrent = result.value.selected;
1522
1529
  bound?.setFullNotice(null);
package/lib/index.js CHANGED
@@ -6,7 +6,6 @@ import { Service } from "@deepseek-ai/cordis";
6
6
  import z from "@deepseek-ai/schemastery";
7
7
  import "@deepseek-ai/dsh-agent";
8
8
  import { LlmError } from "@deepseek-ai/dsh-llm";
9
- import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
10
9
  //#region lib/types/types.js
11
10
  /**
12
11
  * Shared wire/type vocabulary for the daily token-quota plugin.
@@ -66,7 +65,7 @@ const PACKAGE_NAME = "@jxgame2020/dsh-token-quota";
66
65
  /** npm registry endpoint for the plugin's latest version. */
67
66
  const REGISTRY_LATEST_URL = "https://registry.npmjs.org/@jxgame2020%2Fdsh-token-quota/latest";
68
67
  /** How often the Host re-checks for a newer version when update checks are enabled. */
69
- const UPDATE_CHECK_INTERVAL_MS = 864e5;
68
+ const UPDATE_CHECK_INTERVAL_MS = 1440 * 60 * 1e3;
70
69
  /** Map a detected package manager to its install verb. */
71
70
  function installVerb(manager) {
72
71
  switch (manager) {
@@ -146,8 +145,7 @@ function detectManager(dir) {
146
145
  * `undefined` when no profile declares the plugin (e.g. a bundle-managed mount).
147
146
  */
148
147
  function findProfile() {
149
- const home = process.env.DSH_HOME !== void 0 && process.env.DSH_HOME.trim().length > 0 ? process.env.DSH_HOME : join(homedir(), ".dsh");
150
- const profilesDir = join(home, "profiles");
148
+ const profilesDir = join(process.env.DSH_HOME !== void 0 && process.env.DSH_HOME.trim().length > 0 ? process.env.DSH_HOME : join(homedir(), ".dsh"), "profiles");
151
149
  let names;
152
150
  try {
153
151
  names = readdirSync(profilesDir);
@@ -235,8 +233,7 @@ function cycleKey(now, reset) {
235
233
  }
236
234
  /** Default counter file under the Harness home (overridable through config). */
237
235
  function defaultStoragePath() {
238
- const home = process.env.DSH_HOME ?? join(homedir(), ".dsh");
239
- return join(home, "token-quota.json");
236
+ return join(process.env.DSH_HOME ?? join(homedir(), ".dsh"), "token-quota.json");
240
237
  }
241
238
  /** Combined input + output + cache token count of one provider usage report. */
242
239
  function usageTokens(usage) {
@@ -304,49 +301,51 @@ var TokenQuotaService = class TokenQuotaService extends Service {
304
301
  super(ctx, "tokenQuota");
305
302
  this.storagePath = config.storagePath !== void 0 && config.storagePath.length > 0 ? config.storagePath : defaultStoragePath();
306
303
  this.load();
307
- installSettingsSection(ctx, settingsNamespace(TOKEN_QUOTA_NAMESPACE), TOKEN_QUOTA_SETTINGS_SCHEMA, { limits: {} }, {
308
- setSource: (current) => {
309
- this.settingsSource = () => {
310
- const doc = current();
311
- return {
312
- limits: doc.limits ?? {},
313
- monitored: doc.monitored ?? [],
314
- onFull: doc.onFull === "switchPriority" ? "switchAll" : doc.onFull ?? "stop",
315
- checkUpdates: doc.checkUpdates ?? true,
316
- dimWhenIdle: doc.dimWhenIdle ?? false,
317
- reset: doc.reset ?? void 0
304
+ ctx.inject(["settings"], (settingsCtx) => {
305
+ settingsCtx.settings.installSection(ctx, TOKEN_QUOTA_NAMESPACE, TOKEN_QUOTA_SETTINGS_SCHEMA, { limits: {} }, {
306
+ setSource: (current) => {
307
+ this.settingsSource = () => {
308
+ const doc = current();
309
+ return {
310
+ limits: doc.limits ?? {},
311
+ monitored: doc.monitored ?? [],
312
+ onFull: doc.onFull === "switchPriority" ? "switchAll" : doc.onFull ?? "stop",
313
+ checkUpdates: doc.checkUpdates ?? true,
314
+ dimWhenIdle: doc.dimWhenIdle ?? false,
315
+ reset: doc.reset ?? void 0
316
+ };
318
317
  };
319
- };
320
- },
321
- validate: (value) => {
322
- for (const [key, limit] of Object.entries(value.limits ?? {})) {
323
- assertTokenQuotaLimit(limit, key);
324
- splitTokenQuotaKey(key);
325
- }
326
- },
327
- onChange: () => {
328
- const doc = this.settingsSource();
329
- this.limits = { ...doc.limits };
330
- this.monitored = doc.monitored !== void 0 && doc.monitored.length > 0 ? new Set(doc.monitored) : void 0;
331
- this.checkUpdates = doc.checkUpdates;
332
- this.syncUpdateChecking();
333
- if (doc.reset !== void 0 && typeof doc.reset === "object" && doc.reset !== null && typeof doc.reset.offsetHours === "number" && typeof doc.reset.hour === "number" && typeof doc.reset.minute === "number") {
334
- const configured = doc.reset;
335
- this.reset = {
336
- offsetHours: Math.max(-12, Math.min(14, Math.round(configured.offsetHours))),
337
- hour: Math.max(0, Math.min(23, Math.round(configured.hour))),
338
- minute: Math.max(0, Math.min(59, Math.round(configured.minute)))
339
- };
340
- this.rollCycleIfNeeded();
341
- } else {
342
- this.reset = {
343
- offsetHours: localOffsetHours(),
344
- hour: 0,
345
- minute: 0
346
- };
347
- this.rollCycleIfNeeded();
318
+ },
319
+ validate: (value) => {
320
+ for (const [key, limit] of Object.entries(value.limits ?? {})) {
321
+ assertTokenQuotaLimit(limit, key);
322
+ splitTokenQuotaKey(key);
323
+ }
324
+ },
325
+ onChange: () => {
326
+ const doc = this.settingsSource();
327
+ this.limits = { ...doc.limits };
328
+ this.monitored = doc.monitored !== void 0 && doc.monitored.length > 0 ? new Set(doc.monitored) : void 0;
329
+ this.checkUpdates = doc.checkUpdates;
330
+ this.syncUpdateChecking();
331
+ if (doc.reset !== void 0 && typeof doc.reset === "object" && doc.reset !== null && typeof doc.reset.offsetHours === "number" && typeof doc.reset.hour === "number" && typeof doc.reset.minute === "number") {
332
+ const configured = doc.reset;
333
+ this.reset = {
334
+ offsetHours: Math.max(-12, Math.min(14, Math.round(configured.offsetHours))),
335
+ hour: Math.max(0, Math.min(23, Math.round(configured.hour))),
336
+ minute: Math.max(0, Math.min(59, Math.round(configured.minute)))
337
+ };
338
+ this.rollCycleIfNeeded();
339
+ } else {
340
+ this.reset = {
341
+ offsetHours: localOffsetHours(),
342
+ hour: 0,
343
+ minute: 0
344
+ };
345
+ this.rollCycleIfNeeded();
346
+ }
348
347
  }
349
- }
348
+ });
350
349
  });
351
350
  this.tryMountRoute();
352
351
  if (this.disposeRoute === void 0) this.disposeRouteWatcher = ctx.on("internal/service", (name) => {
@@ -454,7 +453,7 @@ var TokenQuotaService = class TokenQuotaService extends Service {
454
453
  return this.upgradeCheck;
455
454
  }
456
455
  async doRefreshUpgrade() {
457
- const current = "0.1.18";
456
+ const current = "0.1.19";
458
457
  let latest;
459
458
  try {
460
459
  const response = await fetch(REGISTRY_LATEST_URL, {
@@ -550,7 +549,7 @@ var TokenQuotaService = class TokenQuotaService extends Service {
550
549
  /** Read the current snapshot (also used by tests and inspection). */
551
550
  readSnapshot() {
552
551
  this.rollCycleIfNeeded();
553
- const keys = /* @__PURE__ */ new Set([...Object.keys(this.usage), ...Object.keys(this.limits)]);
552
+ const keys = new Set([...Object.keys(this.usage), ...Object.keys(this.limits)]);
554
553
  const entries = [];
555
554
  for (const key of keys) {
556
555
  const parts = this.safeSplit(key);
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=bound-test.d.ts.map
@@ -0,0 +1,5 @@
1
+ import { createTokenQuotaPanelStore } from "./client/store.js";
2
+ const store = createTokenQuotaPanelStore();
3
+ let bound;
4
+ bound?.setSnapshot(null);
5
+ //# sourceMappingURL=bound-test.js.map
@@ -1,5 +1,5 @@
1
1
  import type { PropsLocale, PropsRuntime, PropsStore } from '@deepseek-ai/dsh-client-ui-slots';
2
- import type { SessionId } from '@deepseek-ai/dsh-api-remotes/client';
2
+ import type { SessionId } from '@deepseek-ai/dsh-session/types';
3
3
  import type { TokenQuotaFullAction, TokenQuotaReset } from '@jxgame2020/dsh-token-quota/types';
4
4
  import type { createTokenQuotaPanelStore } from './store.ts';
5
5
  /** Injected business face: data loading and mutations wired in `apply`. */
@@ -0,0 +1,2 @@
1
+ export declare function test(): void;
2
+ //# sourceMappingURL=bound-test.d.ts.map
@@ -0,0 +1,7 @@
1
+ import { createTokenQuotaPanelStore } from "./store.js";
2
+ export function test() {
3
+ const store = createTokenQuotaPanelStore();
4
+ let bound;
5
+ bound?.setFullNotice(null);
6
+ }
7
+ //# sourceMappingURL=bound-test.js.map
@@ -0,0 +1,2 @@
1
+ export declare function test(): void;
2
+ //# sourceMappingURL=import-test.d.ts.map
@@ -0,0 +1,3 @@
1
+ import { createTokenQuotaPanelStore } from "./store.js";
2
+ export function test() { const store = createTokenQuotaPanelStore(); let bound; bound?.setFullNotice(null); }
3
+ //# sourceMappingURL=import-test.js.map
@@ -12,7 +12,7 @@
12
12
  * full-quota strategy runs: `stop` shows a notice, the other three
13
13
  * auto-switch to a suitable model.
14
14
  */
15
- import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
15
+ import type { Context as ClientContext } from '@deepseek-ai/cordis';
16
16
  import { type TokenQuotaKey } from './locales.ts';
17
17
  export type { TokenQuotaPanelInjected } from './TokenQuotaPanel.tsx';
18
18
  export type { ModelQuotaRow, TokenQuotaPanelState } from './store.ts';
@@ -24,7 +24,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
24
24
  'token-quota': TokenQuotaKey;
25
25
  }
26
26
  }
27
- /** Required services: slot registry, connection RPC, locale, settings scope. */
27
+ /** Required services: slot registry, sessions, locale, settings scope. */
28
28
  export declare const inject: string[];
29
29
  /**
30
30
  * Client plugin body: poll the Host snapshot route, run the full-quota
@@ -8,8 +8,8 @@ const NS = 'token-quota';
8
8
  const TOKEN_QUOTA_NAMESPACE = 'token-quota';
9
9
  /** Snapshot pull cadence in milliseconds. */
10
10
  const POLL_INTERVAL_MS = 3000;
11
- /** Required services: slot registry, connection RPC, locale, settings scope. */
12
- export const inject = ['slots', 'connection', 'locale', 'settingsScope'];
11
+ /** Required services: slot registry, sessions, locale, settings scope. */
12
+ export const inject = ['slots', 'sessions', 'locale', 'settingsScope'];
13
13
  /**
14
14
  * Client plugin body: poll the Host snapshot route, run the full-quota
15
15
  * strategy, wire the injected face (directory load, limit write, model
@@ -20,7 +20,10 @@ export function apply(ctx) {
20
20
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'token-quota-ui: panel dictionaries');
21
21
  const t = ctx.locale.bind(NS);
22
22
  const scope = ctx.settingsScope.bind({ namespace: TOKEN_QUOTA_NAMESPACE });
23
- const connection = ctx.get('connection');
23
+ // The Host `dsh-session` package's `ctx.sessions: SessionStore` merge can
24
+ // shadow the client ISessions face in a combined type-check; the runtime
25
+ // 'sessions' service is the ISessions implementation regardless.
26
+ const sessions = ctx.get('sessions');
24
27
  // Store handle shared with the registration; the framework instantiates the
25
28
  // live store per entry and hands its bound actions to the inject factory,
26
29
  // so the apply-world only ever writes through `bound`.
@@ -107,11 +110,11 @@ export function apply(ctx) {
107
110
  bound?.setFullNotice(t('fullSwitchFailed'));
108
111
  return;
109
112
  }
110
- void connection.api.sessions.selectModel({
113
+ void ctx.remote.session.selectModel({
111
114
  sessionId: lastSessionId,
112
115
  provider: target.provider,
113
116
  model: target.model,
114
- }).then(({ result }) => {
117
+ }).then((result) => {
115
118
  if (result.ok) {
116
119
  lastCurrent = result.value.selected;
117
120
  flashFullNotice(t('fullSwitchTo').replace('{model}', target.model));
@@ -130,16 +133,26 @@ export function apply(ctx) {
130
133
  // switch on full quota) moves the 「当前」 badge without waiting the full
131
134
  // directory-refresh cadence.
132
135
  let pullCount = 0;
136
+ /** Resolve the session's current model from its durable modelSelection projection. */
137
+ const currentOf = (sessionId) => {
138
+ const binding = sessions.binding(sessionId);
139
+ if (binding === undefined)
140
+ return null;
141
+ const face = binding.session.projections.faceOf('modelSelection');
142
+ const state = face.getSnapshot();
143
+ return state?.lastUsed ?? null;
144
+ };
133
145
  const refreshCurrent = () => {
134
146
  if (lastSessionId === undefined)
135
147
  return;
136
- void connection.api.sessions.models({ sessionId: lastSessionId }).then(({ result }) => {
148
+ void ctx.remote.session.modelCatalog().then((result) => {
137
149
  if (result.ok) {
138
- const currentChanged = lastCurrent?.provider !== result.value.current?.provider
139
- || lastCurrent?.model !== result.value.current?.model;
150
+ const current = currentOf(lastSessionId);
151
+ const currentChanged = lastCurrent?.provider !== current?.provider
152
+ || lastCurrent?.model !== current?.model;
140
153
  lastGroups = result.value.groups;
141
- lastCurrent = result.value.current;
142
- bound?.setDirectory(result.value.groups, result.value.current);
154
+ lastCurrent = current;
155
+ bound?.setDirectory(result.value.groups, current);
143
156
  // If the server silently switched to a different model (e.g. the
144
157
  // auto-switch on quota exhaustion), re-run the full-quota strategy
145
158
  // immediately against the new current model so any further fallback
@@ -201,11 +214,12 @@ export function apply(ctx) {
201
214
  lastSessionId = sessionId;
202
215
  bound?.setLoading(true);
203
216
  bound?.setError(null);
204
- void connection.api.sessions.models({ sessionId }).then(({ result }) => {
217
+ void ctx.remote.session.modelCatalog().then((result) => {
205
218
  if (result.ok) {
219
+ const current = currentOf(sessionId);
206
220
  lastGroups = result.value.groups;
207
- lastCurrent = result.value.current;
208
- bound?.setDirectory(result.value.groups, result.value.current);
221
+ lastCurrent = current;
222
+ bound?.setDirectory(result.value.groups, current);
209
223
  bound?.setLoading(false);
210
224
  }
211
225
  else {
@@ -282,7 +296,7 @@ export function apply(ctx) {
282
296
  void scope.set('reset', reset);
283
297
  };
284
298
  const selectModel = (sessionId, provider, model) => {
285
- void connection.api.sessions.selectModel({ sessionId, provider, model }).then(({ result }) => {
299
+ void ctx.remote.session.selectModel({ sessionId, provider, model }).then((result) => {
286
300
  if (result.ok) {
287
301
  lastCurrent = result.value.selected;
288
302
  // A manual pick clears any leftover auto-switch / exhausted notice.
@@ -7,8 +7,8 @@
7
7
  *
8
8
  * @module @deepseek-ai/dsh-client-ui-token-quota/client/store
9
9
  */
10
- import { type EngineStoreHandle } from '@deepseek-ai/dsh-client-runtime/client';
11
- import type { ModelProviderGroup, ModelSelection } from '@deepseek-ai/dsh-api-remotes/client';
10
+ import { type EngineStoreHandle } from '@deepseek-ai/dsh-client-store';
11
+ import type { ModelProviderGroup, ModelSelection } from '@deepseek-ai/dsh-api-session-controller/types';
12
12
  import type { TokenQuotaFullAction, TokenQuotaLog, TokenQuotaReset, TokenQuotaSnapshot, TokenQuotaUpgrade } from '@jxgame2020/dsh-token-quota/types';
13
13
  /** One rendered model row: directory identity merged with the quota snapshot. */
14
14
  export interface ModelQuotaRow {
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * @module @deepseek-ai/dsh-client-ui-token-quota/client/store
9
9
  */
10
- import { defineStore } from '@deepseek-ai/dsh-client-runtime/client';
10
+ import { defineStore } from '@deepseek-ai/dsh-client-store';
11
11
  /** Build the stable per-model key shared by the counter, settings, and snapshot. */
12
12
  function tokenQuotaKey(provider, model) {
13
13
  return `${provider}/${model}`;
@@ -0,0 +1,2 @@
1
+ export declare function test(): void;
2
+ //# sourceMappingURL=import-test.d.ts.map
@@ -0,0 +1,3 @@
1
+ import { createTokenQuotaPanelStore } from "./store.js";
2
+ export function test() { const store = createTokenQuotaPanelStore(); let bound; bound?.setFullNotice(null); }
3
+ //# sourceMappingURL=import-test.js.map
@@ -38,7 +38,6 @@ import z from '@deepseek-ai/schemastery';
38
38
  // the service listens to without importing any runtime value.
39
39
  import '@deepseek-ai/dsh-agent';
40
40
  import { LlmError } from '@deepseek-ai/dsh-llm';
41
- import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings';
42
41
  import { TOKEN_QUOTA_EXCEEDED_CODE, TOKEN_QUOTA_NAMESPACE, tokenQuotaKey, } from "./types.js";
43
42
  import { assertTokenQuotaLimit, splitTokenQuotaKey } from "./invariant.js";
44
43
  /** The plugin's package name, used to locate the profile dependency and the registry endpoint. */
@@ -307,60 +306,65 @@ export class TokenQuotaService extends Service {
307
306
  : defaultStoragePath();
308
307
  this.load();
309
308
  // Limits live in the user settings document; the Web panel writes them and
310
- // the section watcher pushes every change back here.
311
- installSettingsSection(ctx, settingsNamespace(TOKEN_QUOTA_NAMESPACE), TOKEN_QUOTA_SETTINGS_SCHEMA, { limits: {} }, {
312
- setSource: (current) => {
313
- // Normalize the resolved (schema-shaped) document into the plugin's
314
- // required shape: empty monitored = monitor everything, absent onFull
315
- // falls back to 'stop'.
316
- this.settingsSource = () => {
317
- const doc = current();
318
- return {
319
- limits: doc.limits ?? {},
320
- monitored: doc.monitored ?? [],
321
- // Legacy `switchPriority` (removed in 0.1.7) maps to `switchAll`:
322
- // capped models first, then uncapped — both monitored-only.
323
- onFull: doc.onFull === 'switchPriority' ? 'switchAll' : (doc.onFull ?? 'stop'),
324
- checkUpdates: doc.checkUpdates ?? true,
325
- dimWhenIdle: doc.dimWhenIdle ?? false,
326
- reset: doc.reset ?? undefined,
309
+ // the section watcher pushes every change back here. DSH 0.1.5 replaced
310
+ // the standalone `installSettingsSection` helper with
311
+ // `SettingsProvider.installSection`, reached through `ctx.inject` so the
312
+ // settings service is guaranteed to be up before registration.
313
+ ctx.inject(['settings'], (settingsCtx) => {
314
+ settingsCtx.settings.installSection(ctx, TOKEN_QUOTA_NAMESPACE, TOKEN_QUOTA_SETTINGS_SCHEMA, { limits: {} }, {
315
+ setSource: (current) => {
316
+ // Normalize the resolved (schema-shaped) document into the plugin's
317
+ // required shape: empty monitored = monitor everything, absent onFull
318
+ // falls back to 'stop'.
319
+ this.settingsSource = () => {
320
+ const doc = current();
321
+ return {
322
+ limits: doc.limits ?? {},
323
+ monitored: doc.monitored ?? [],
324
+ // Legacy `switchPriority` (removed in 0.1.7) maps to `switchAll`:
325
+ // capped models first, then uncapped — both monitored-only.
326
+ onFull: doc.onFull === 'switchPriority' ? 'switchAll' : (doc.onFull ?? 'stop'),
327
+ checkUpdates: doc.checkUpdates ?? true,
328
+ dimWhenIdle: doc.dimWhenIdle ?? false,
329
+ reset: doc.reset ?? undefined,
330
+ };
327
331
  };
328
- };
329
- },
330
- validate: (value) => {
331
- for (const [key, limit] of Object.entries(value.limits ?? {})) {
332
- assertTokenQuotaLimit(limit, key);
333
- splitTokenQuotaKey(key);
334
- }
335
- },
336
- onChange: () => {
337
- const doc = this.settingsSource();
338
- this.limits = { ...doc.limits };
339
- this.monitored = doc.monitored !== undefined && doc.monitored.length > 0
340
- ? new Set(doc.monitored)
341
- : undefined;
342
- this.checkUpdates = doc.checkUpdates;
343
- this.syncUpdateChecking();
344
- if (doc.reset !== undefined
345
- && typeof doc.reset === 'object'
346
- && doc.reset !== null
347
- && typeof doc.reset.offsetHours === 'number'
348
- && typeof doc.reset.hour === 'number'
349
- && typeof doc.reset.minute === 'number') {
350
- const configured = doc.reset;
351
- this.reset = {
352
- offsetHours: Math.max(-12, Math.min(14, Math.round(configured.offsetHours))),
353
- hour: Math.max(0, Math.min(23, Math.round(configured.hour))),
354
- minute: Math.max(0, Math.min(59, Math.round(configured.minute))),
355
- };
356
- this.rollCycleIfNeeded();
357
- }
358
- else {
359
- // null / malformed (unset) = machine-local midnight — the default.
360
- this.reset = { offsetHours: localOffsetHours(), hour: 0, minute: 0 };
361
- this.rollCycleIfNeeded();
362
- }
363
- },
332
+ },
333
+ validate: (value) => {
334
+ for (const [key, limit] of Object.entries(value.limits ?? {})) {
335
+ assertTokenQuotaLimit(limit, key);
336
+ splitTokenQuotaKey(key);
337
+ }
338
+ },
339
+ onChange: () => {
340
+ const doc = this.settingsSource();
341
+ this.limits = { ...doc.limits };
342
+ this.monitored = doc.monitored !== undefined && doc.monitored.length > 0
343
+ ? new Set(doc.monitored)
344
+ : undefined;
345
+ this.checkUpdates = doc.checkUpdates;
346
+ this.syncUpdateChecking();
347
+ if (doc.reset !== undefined
348
+ && typeof doc.reset === 'object'
349
+ && doc.reset !== null
350
+ && typeof doc.reset.offsetHours === 'number'
351
+ && typeof doc.reset.hour === 'number'
352
+ && typeof doc.reset.minute === 'number') {
353
+ const configured = doc.reset;
354
+ this.reset = {
355
+ offsetHours: Math.max(-12, Math.min(14, Math.round(configured.offsetHours))),
356
+ hour: Math.max(0, Math.min(23, Math.round(configured.hour))),
357
+ minute: Math.max(0, Math.min(59, Math.round(configured.minute))),
358
+ };
359
+ this.rollCycleIfNeeded();
360
+ }
361
+ else {
362
+ // null / malformed (unset) = machine-local midnight — the default.
363
+ this.reset = { offsetHours: localOffsetHours(), hour: 0, minute: 0 };
364
+ this.rollCycleIfNeeded();
365
+ }
366
+ },
367
+ });
364
368
  });
365
369
  // Snapshot route: optional — only mounted when a webServer service exists
366
370
  // (the Web profile); headless deployments keep the enforcement without it.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jxgame2020/dsh-token-quota",
3
3
  "description": "Daily per-model token quota accounting, enforcement, and a floating quota panel for the DeepSeek Harness — a fully self-contained plugin (no core-Harness wiring, no generated RPC contract)",
4
- "version": "0.1.18",
4
+ "version": "0.1.19",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -31,8 +31,9 @@
31
31
  "dsh": {
32
32
  "client": {
33
33
  "inject": [
34
+ "@deepseek-ai/dsh-api-session-controller",
34
35
  "@deepseek-ai/dsh-client-locale",
35
- "@deepseek-ai/dsh-client-runtime",
36
+ "@deepseek-ai/dsh-client-store",
36
37
  "@deepseek-ai/dsh-client-ui-settings",
37
38
  "@deepseek-ai/dsh-client-ui-slots",
38
39
  "@deepseek-ai/dsh-api-remotes"
@@ -56,28 +57,31 @@
56
57
  "peerDependencies": {
57
58
  "@deepseek-ai/cordis": "*",
58
59
  "@deepseek-ai/dsh-agent": "*",
60
+ "@deepseek-ai/dsh-api-gateway": "*",
59
61
  "@deepseek-ai/dsh-api-remotes": "*",
62
+ "@deepseek-ai/dsh-api-session-controller": "*",
63
+ "@deepseek-ai/dsh-attachment": "*",
64
+ "@deepseek-ai/dsh-brand": "*",
60
65
  "@deepseek-ai/dsh-client-connection": "*",
61
66
  "@deepseek-ai/dsh-client-locale": "*",
62
- "@deepseek-ai/dsh-client-runtime": "*",
67
+ "@deepseek-ai/dsh-client-store": "*",
63
68
  "@deepseek-ai/dsh-client-ui-layout": "*",
69
+ "@deepseek-ai/dsh-client-ui-renderer": "*",
70
+ "@deepseek-ai/dsh-client-ui-session": "*",
64
71
  "@deepseek-ai/dsh-client-ui-settings": "*",
65
72
  "@deepseek-ai/dsh-client-ui-slots": "*",
66
73
  "@deepseek-ai/dsh-invariants": "*",
74
+ "@deepseek-ai/dsh-jobs": "*",
67
75
  "@deepseek-ai/dsh-llm": "*",
68
76
  "@deepseek-ai/dsh-session": "*",
69
- "@deepseek-ai/dsh-settings": "*",
70
- "react": "^18.2.0",
71
- "@deepseek-ai/dsh-host-apiproxy": "*",
72
- "@deepseek-ai/dsh-attachment": "*",
73
- "@deepseek-ai/dsh-brand": "*",
74
- "@deepseek-ai/dsh-jobs": "*",
75
77
  "@deepseek-ai/dsh-session-projection": "*",
78
+ "@deepseek-ai/dsh-settings": "*",
76
79
  "@deepseek-ai/dsh-tools": "*",
77
80
  "@deepseek-ai/dsh-user-approval": "*",
78
81
  "@deepseek-ai/dsh-user-questions": "*",
79
82
  "@deepseek-ai/dsh-workspace": "*",
80
- "@deepseek-ai/schemastery": "*"
83
+ "@deepseek-ai/schemastery": "*",
84
+ "react": "^18.2.0"
81
85
  },
82
86
  "peerDependenciesMeta": {
83
87
  "@deepseek-ai/cordis": {