@jxgame2020/dsh-token-quota 0.1.6 → 0.1.7

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
@@ -129,59 +129,59 @@ window.__ModuleLoader__.load({
129
129
  document.head.appendChild(tag);
130
130
  }
131
131
  var TokenQuotaPanel_module_css_default = {
132
- "dialogBtnPrimary": "NPLLIq_dialogBtnPrimary",
133
- "tabLabel": "NPLLIq_tabLabel",
134
- "monitorHint": "NPLLIq_monitorHint",
135
- "monitorRow": "NPLLIq_monitorRow",
136
132
  "fillOver": "NPLLIq_fillOver",
137
- "bar": "NPLLIq_bar",
138
- "panel": "NPLLIq_panel",
139
- "header": "NPLLIq_header",
140
- "controls": "NPLLIq_controls",
133
+ "fillIdle": "NPLLIq_fillIdle",
134
+ "monitorList": "NPLLIq_monitorList",
141
135
  "switch": "NPLLIq_switch",
142
- "resetSelect": "NPLLIq_resetSelect",
143
- "input": "NPLLIq_input",
144
- "save": "NPLLIq_save",
145
- "selectBtn": "NPLLIq_selectBtn",
146
- "dialogActions": "NPLLIq_dialogActions",
136
+ "subtitle": "NPLLIq_subtitle",
137
+ "monitorName": "NPLLIq_monitorName",
138
+ "currentBadge": "NPLLIq_currentBadge",
139
+ "collapse": "NPLLIq_collapse",
147
140
  "row": "NPLLIq_row",
148
141
  "fillWarn": "NPLLIq_fillWarn",
149
- "settingsBtn": "NPLLIq_settingsBtn",
150
- "dialogSection": "NPLLIq_dialogSection",
151
- "dialogTitle": "NPLLIq_dialogTitle",
152
- "dialogClose": "NPLLIq_dialogClose",
142
+ "rowHeader": "NPLLIq_rowHeader",
143
+ "input": "NPLLIq_input",
144
+ "radioRow": "NPLLIq_radioRow",
145
+ "logHeader": "NPLLIq_logHeader",
146
+ "logModelCol": "NPLLIq_logModelCol",
147
+ "logDayCol": "NPLLIq_logDayCol",
148
+ "dialogBtn": "NPLLIq_dialogBtn",
149
+ "dialogActions": "NPLLIq_dialogActions",
153
150
  "logDialog": "NPLLIq_logDialog",
154
- "tab": "NPLLIq_tab",
151
+ "notice": "NPLLIq_notice",
152
+ "tabLabel": "NPLLIq_tabLabel",
153
+ "dialog": "NPLLIq_dialog",
155
154
  "logScroll": "NPLLIq_logScroll",
156
- "fillIdle": "NPLLIq_fillIdle",
157
- "logTable": "NPLLIq_logTable",
158
- "body": "NPLLIq_body",
159
- "logUsedCol": "NPLLIq_logUsedCol",
160
155
  "resetRow": "NPLLIq_resetRow",
161
- "headerText": "NPLLIq_headerText",
162
- "notice": "NPLLIq_notice",
163
- "headerActions": "NPLLIq_headerActions",
164
- "monitorName": "NPLLIq_monitorName",
165
- "rowName": "NPLLIq_rowName",
166
156
  "rowMeta": "NPLLIq_rowMeta",
167
- "dialog": "NPLLIq_dialog",
168
- "currentBadge": "NPLLIq_currentBadge",
169
- "subtitle": "NPLLIq_subtitle",
170
- "dialogBtn": "NPLLIq_dialogBtn",
171
- "logDayCol": "NPLLIq_logDayCol",
172
- "dialogHeader": "NPLLIq_dialogHeader",
173
- "dialogLabel": "NPLLIq_dialogLabel",
174
- "gearBtn": "NPLLIq_gearBtn",
175
- "logModelCol": "NPLLIq_logModelCol",
157
+ "tab": "NPLLIq_tab",
158
+ "monitorHint": "NPLLIq_monitorHint",
159
+ "title": "NPLLIq_title",
176
160
  "noticeError": "NPLLIq_noticeError",
161
+ "monitorRow": "NPLLIq_monitorRow",
162
+ "dialogBtnPrimary": "NPLLIq_dialogBtnPrimary",
177
163
  "tabIcon": "NPLLIq_tabIcon",
178
- "logHeader": "NPLLIq_logHeader",
164
+ "rowName": "NPLLIq_rowName",
165
+ "save": "NPLLIq_save",
179
166
  "fullNotice": "NPLLIq_fullNotice",
180
- "monitorList": "NPLLIq_monitorList",
181
- "radioRow": "NPLLIq_radioRow",
182
- "title": "NPLLIq_title",
183
- "rowHeader": "NPLLIq_rowHeader",
184
- "collapse": "NPLLIq_collapse"
167
+ "dialogClose": "NPLLIq_dialogClose",
168
+ "header": "NPLLIq_header",
169
+ "bar": "NPLLIq_bar",
170
+ "dialogHeader": "NPLLIq_dialogHeader",
171
+ "headerText": "NPLLIq_headerText",
172
+ "selectBtn": "NPLLIq_selectBtn",
173
+ "resetSelect": "NPLLIq_resetSelect",
174
+ "logTable": "NPLLIq_logTable",
175
+ "panel": "NPLLIq_panel",
176
+ "controls": "NPLLIq_controls",
177
+ "headerActions": "NPLLIq_headerActions",
178
+ "body": "NPLLIq_body",
179
+ "settingsBtn": "NPLLIq_settingsBtn",
180
+ "gearBtn": "NPLLIq_gearBtn",
181
+ "logUsedCol": "NPLLIq_logUsedCol",
182
+ "dialogTitle": "NPLLIq_dialogTitle",
183
+ "dialogSection": "NPLLIq_dialogSection",
184
+ "dialogLabel": "NPLLIq_dialogLabel"
185
185
  };
186
186
  //#endregion
187
187
  //#region lib/types/client/TokenQuotaPanel.js
@@ -220,10 +220,6 @@ window.__ModuleLoader__.load({
220
220
  {
221
221
  value: "switchAll",
222
222
  labelKey: "fullSwitchAll"
223
- },
224
- {
225
- value: "switchPriority",
226
- labelKey: "fullSwitchPriority"
227
223
  }
228
224
  ];
229
225
  /**
@@ -765,8 +761,7 @@ window.__ModuleLoader__.load({
765
761
  fullActionLabel: "满额后处理",
766
762
  fullStop: "停止请求并提示",
767
763
  fullSwitchQuota: "自动切换到其它限额模型",
768
- fullSwitchAll: "自动切换到其它可用模型(含非限额)",
769
- fullSwitchPriority: "自动切换(优先非限额,其次未监控)",
764
+ fullSwitchAll: "自动切换(优先限额模型,其次非限额)",
770
765
  fullNotice: "当前模型今日额度已用尽,请求已停止。请选择其它模型或调整限额。",
771
766
  fullSwitchFailed: "没有可切换的模型,请手动选择其它模型或调整限额",
772
767
  fullSwitchTo: "已自动切换到 {model}",
@@ -811,8 +806,7 @@ window.__ModuleLoader__.load({
811
806
  fullActionLabel: "When a model is full",
812
807
  fullStop: "Stop and prompt",
813
808
  fullSwitchQuota: "Switch to another quota model",
814
- fullSwitchAll: "Switch to any other model (incl. uncapped)",
815
- fullSwitchPriority: "Prefer uncapped, then unmonitored",
809
+ fullSwitchAll: "Switch models (quota models first, then uncapped)",
816
810
  fullNotice: "Today’s quota for the current model is exhausted; the request was stopped. Pick another model or raise its limit.",
817
811
  fullSwitchFailed: "No switchable model — pick one manually or raise a limit",
818
812
  fullSwitchTo: "Auto-switched to {model}",
@@ -899,14 +893,13 @@ window.__ModuleLoader__.load({
899
893
  used: entry?.used ?? 0
900
894
  });
901
895
  }
902
- const available = (candidate) => isMonitoredKey(candidate.key) && (candidate.limit <= 0 || candidate.used < candidate.limit);
903
- if (lastOnFull === "switchQuota") return candidates.filter((candidate) => isMonitoredKey(candidate.key) && candidate.limit > 0 && candidate.used < candidate.limit).sort((a, b) => a.used / a.limit - b.used / b.limit)[0];
904
- if (lastOnFull === "switchAll") return candidates.filter(available)[0];
905
- const uncapped = candidates.find((candidate) => isMonitoredKey(candidate.key) && candidate.limit <= 0);
906
- if (uncapped !== void 0) return uncapped;
907
- const unmonitored = candidates.find((candidate) => !isMonitoredKey(candidate.key));
908
- if (unmonitored !== void 0) return unmonitored;
909
- return candidates.find(available);
896
+ const cappedFree = (candidate) => isMonitoredKey(candidate.key) && candidate.limit > 0 && candidate.used < candidate.limit;
897
+ if (lastOnFull === "switchQuota") return candidates.filter(cappedFree).sort((a, b) => a.used / a.limit - b.used / b.limit)[0];
898
+ if (lastOnFull === "switchAll") {
899
+ const withQuota = candidates.filter(cappedFree).sort((a, b) => a.used / a.limit - b.used / b.limit);
900
+ if (withQuota.length > 0) return withQuota[0];
901
+ return candidates.find((candidate) => isMonitoredKey(candidate.key) && candidate.limit <= 0);
902
+ }
910
903
  };
911
904
  /** Run the configured full-quota strategy once per exhausted model. */
912
905
  const actOnFull = (snapshot) => {
@@ -960,7 +953,8 @@ window.__ModuleLoader__.load({
960
953
  pullCount += 1;
961
954
  const doc = scope.getSnapshot().value;
962
955
  lastMonitored = doc?.monitored !== void 0 && doc.monitored.length > 0 ? doc.monitored : null;
963
- lastOnFull = doc?.onFull ?? "stop";
956
+ const rawOnFull = doc?.onFull ?? "stop";
957
+ lastOnFull = rawOnFull === "switchPriority" ? "switchAll" : rawOnFull;
964
958
  lastReset = doc?.reset !== void 0 && doc.reset !== null && typeof doc.reset === "object" && typeof doc.reset.offsetHours === "number" ? doc.reset : null;
965
959
  bound?.setSettings(lastMonitored, lastOnFull);
966
960
  bound?.setReset(lastReset);
package/lib/index.js CHANGED
@@ -157,7 +157,7 @@ var TokenQuotaService = class TokenQuotaService extends Service {
157
157
  return {
158
158
  limits: doc.limits ?? {},
159
159
  monitored: doc.monitored ?? [],
160
- onFull: doc.onFull ?? "stop",
160
+ onFull: doc.onFull === "switchPriority" ? "switchAll" : doc.onFull ?? "stop",
161
161
  reset: doc.reset ?? void 0
162
162
  };
163
163
  };
@@ -358,11 +358,6 @@ var TokenQuotaService = class TokenQuotaService extends Service {
358
358
  };
359
359
  };
360
360
  const isMonitored = (key) => this.isMonitored(key);
361
- const isAvailable = (key) => {
362
- if (!isMonitored(key)) return false;
363
- const { limit, used } = availability(key);
364
- return limit <= 0 || used < limit;
365
- };
366
361
  const candidates = this.cachedModels.filter(({ provider, model }) => tokenQuotaKey(provider, model) !== currentKey).map(({ provider, model }) => {
367
362
  const key = tokenQuotaKey(provider, model);
368
363
  const { limit, used } = availability(key);
@@ -377,13 +372,10 @@ var TokenQuotaService = class TokenQuotaService extends Service {
377
372
  });
378
373
  switch (onFull) {
379
374
  case "switchQuota": return candidates.filter((c) => c.monitored && c.limit > 0 && c.used < c.limit).sort((a, b) => a.used / a.limit - b.used / b.limit)[0];
380
- case "switchAll": return candidates.filter((c) => isAvailable(c.key))[0];
381
- case "switchPriority": {
382
- const uncapped = candidates.find((c) => c.monitored && c.limit <= 0);
383
- if (uncapped !== void 0) return uncapped;
384
- const unmonitored = candidates.find((c) => !c.monitored);
385
- if (unmonitored !== void 0) return unmonitored;
386
- return candidates.find((c) => isAvailable(c.key));
375
+ case "switchAll": {
376
+ const cappedFree = candidates.filter((c) => c.monitored && c.limit > 0 && c.used < c.limit).sort((a, b) => a.used / a.limit - b.used / b.limit);
377
+ if (cappedFree.length > 0) return cappedFree[0];
378
+ return candidates.find((c) => c.monitored && c.limit <= 0);
387
379
  }
388
380
  default: return;
389
381
  }
@@ -34,7 +34,6 @@ const FULL_ACTIONS = [
34
34
  { value: 'stop', labelKey: 'fullStop' },
35
35
  { value: 'switchQuota', labelKey: 'fullSwitchQuota' },
36
36
  { value: 'switchAll', labelKey: 'fullSwitchAll' },
37
- { value: 'switchPriority', labelKey: 'fullSwitchPriority' },
38
37
  ];
39
38
  /**
40
39
  * Pointer-driven drag of a fixed-positioned element. Deltas are applied
@@ -70,23 +70,23 @@ export function apply(ctx) {
70
70
  });
71
71
  }
72
72
  }
73
- const available = (candidate) => isMonitoredKey(candidate.key) && (candidate.limit <= 0 || candidate.used < candidate.limit);
73
+ const cappedFree = (candidate) => isMonitoredKey(candidate.key) && candidate.limit > 0 && candidate.used < candidate.limit;
74
74
  if (lastOnFull === 'switchQuota') {
75
75
  return candidates
76
- .filter(candidate => isMonitoredKey(candidate.key) && candidate.limit > 0 && candidate.used < candidate.limit)
76
+ .filter(cappedFree)
77
77
  .sort((a, b) => (a.used / a.limit) - (b.used / b.limit))[0];
78
78
  }
79
79
  if (lastOnFull === 'switchAll') {
80
- return candidates.filter(available)[0];
80
+ // Monitored only: capped models with headroom first (lowest fill ratio),
81
+ // then uncapped monitored models as fallback.
82
+ const withQuota = candidates
83
+ .filter(cappedFree)
84
+ .sort((a, b) => (a.used / a.limit) - (b.used / b.limit));
85
+ if (withQuota.length > 0)
86
+ return withQuota[0];
87
+ return candidates.find(candidate => isMonitoredKey(candidate.key) && candidate.limit <= 0);
81
88
  }
82
- // switchPriority: uncapped first, then unmonitored, then capped-but-free.
83
- const uncapped = candidates.find(candidate => isMonitoredKey(candidate.key) && candidate.limit <= 0);
84
- if (uncapped !== undefined)
85
- return uncapped;
86
- const unmonitored = candidates.find(candidate => !isMonitoredKey(candidate.key));
87
- if (unmonitored !== undefined)
88
- return unmonitored;
89
- return candidates.find(available);
89
+ return undefined;
90
90
  };
91
91
  /** Run the configured full-quota strategy once per exhausted model. */
92
92
  const actOnFull = (snapshot) => {
@@ -159,7 +159,11 @@ export function apply(ctx) {
159
159
  pullCount += 1;
160
160
  const doc = scope.getSnapshot().value;
161
161
  lastMonitored = doc?.monitored !== undefined && doc.monitored.length > 0 ? doc.monitored : null;
162
- lastOnFull = doc?.onFull ?? 'stop';
162
+ // Legacy `switchPriority` (removed in 0.1.7) maps to `switchAll`.
163
+ // The runtime document may still hold the old value until the user
164
+ // re-saves the settings dialog.
165
+ const rawOnFull = (doc?.onFull ?? 'stop');
166
+ lastOnFull = rawOnFull === 'switchPriority' ? 'switchAll' : rawOnFull;
163
167
  lastReset = doc?.reset !== undefined && doc.reset !== null
164
168
  && typeof doc.reset === 'object'
165
169
  && typeof doc.reset.offsetHours === 'number'
@@ -34,7 +34,6 @@ export declare const zh: {
34
34
  fullStop: string;
35
35
  fullSwitchQuota: string;
36
36
  fullSwitchAll: string;
37
- fullSwitchPriority: string;
38
37
  fullNotice: string;
39
38
  fullSwitchFailed: string;
40
39
  fullSwitchTo: string;
@@ -82,7 +81,6 @@ export declare const en: {
82
81
  fullStop: string;
83
82
  fullSwitchQuota: string;
84
83
  fullSwitchAll: string;
85
- fullSwitchPriority: string;
86
84
  fullNotice: string;
87
85
  fullSwitchFailed: string;
88
86
  fullSwitchTo: string;
@@ -33,8 +33,7 @@ export const zh = {
33
33
  fullActionLabel: '满额后处理',
34
34
  fullStop: '停止请求并提示',
35
35
  fullSwitchQuota: '自动切换到其它限额模型',
36
- fullSwitchAll: '自动切换到其它可用模型(含非限额)',
37
- fullSwitchPriority: '自动切换(优先非限额,其次未监控)',
36
+ fullSwitchAll: '自动切换(优先限额模型,其次非限额)',
38
37
  fullNotice: '当前模型今日额度已用尽,请求已停止。请选择其它模型或调整限额。',
39
38
  fullSwitchFailed: '没有可切换的模型,请手动选择其它模型或调整限额',
40
39
  fullSwitchTo: '已自动切换到 {model}',
@@ -79,8 +78,7 @@ export const en = {
79
78
  fullActionLabel: 'When a model is full',
80
79
  fullStop: 'Stop and prompt',
81
80
  fullSwitchQuota: 'Switch to another quota model',
82
- fullSwitchAll: 'Switch to any other model (incl. uncapped)',
83
- fullSwitchPriority: 'Prefer uncapped, then unmonitored',
81
+ fullSwitchAll: 'Switch models (quota models first, then uncapped)',
84
82
  fullNotice: 'Today\u2019s quota for the current model is exhausted; the request was stopped. Pick another model or raise its limit.',
85
83
  fullSwitchFailed: 'No switchable model \u2014 pick one manually or raise a limit',
86
84
  fullSwitchTo: 'Auto-switched to {model}',
@@ -45,6 +45,10 @@ import { assertTokenQuotaLimit, splitTokenQuotaKey } from "./invariant.js";
45
45
  const TOKEN_QUOTA_SETTINGS_SCHEMA = z.object({
46
46
  limits: z.dict(z.number().step(1).min(0)).default({}),
47
47
  monitored: z.array(z.string()).default([]),
48
+ // `switchPriority` is a legacy value accepted for documents saved by older
49
+ // plugin versions; the runtime normalizes it to `switchAll` (see
50
+ // settingsSource below) so existing users keep auto-switching behavior
51
+ // without reconfiguring.
48
52
  onFull: z.union(['stop', 'switchQuota', 'switchAll', 'switchPriority']).default('stop'),
49
53
  // `reset` is a user-facing preference outside the validated surface: the
50
54
  // panel writes it and the host validates the shape at runtime. `z.any` with
@@ -147,7 +151,9 @@ export class TokenQuotaService extends Service {
147
151
  return {
148
152
  limits: doc.limits ?? {},
149
153
  monitored: doc.monitored ?? [],
150
- onFull: doc.onFull ?? 'stop',
154
+ // Legacy `switchPriority` (removed in 0.1.7) maps to `switchAll`:
155
+ // capped models first, then uncapped — both monitored-only.
156
+ onFull: doc.onFull === 'switchPriority' ? 'switchAll' : (doc.onFull ?? 'stop'),
151
157
  reset: doc.reset ?? undefined,
152
158
  };
153
159
  };
@@ -362,12 +368,6 @@ export class TokenQuotaService extends Service {
362
368
  return { limit, used };
363
369
  };
364
370
  const isMonitored = (key) => this.isMonitored(key);
365
- const isAvailable = (key) => {
366
- if (!isMonitored(key))
367
- return false;
368
- const { limit, used } = availability(key);
369
- return limit <= 0 || used < limit;
370
- };
371
371
  // Build candidate list excluding the currently exhausted model.
372
372
  const candidates = this.cachedModels
373
373
  .filter(({ provider, model }) => tokenQuotaKey(provider, model) !== currentKey)
@@ -386,21 +386,16 @@ export class TokenQuotaService extends Service {
386
386
  return eligible[0];
387
387
  }
388
388
  case 'switchAll': {
389
- // Any monitored model that has headroom (capped or unlimited).
390
- const eligible = candidates.filter(c => isAvailable(c.key));
391
- return eligible[0];
392
- }
393
- case 'switchPriority': {
394
- // 1. Unlimited monitored models. 2. Unmonitored models (never capped).
395
- // 3. Capped-but-free monitored models.
396
- const uncapped = candidates.find(c => c.monitored && c.limit <= 0);
397
- if (uncapped !== undefined)
398
- return uncapped;
399
- const unmonitored = candidates.find(c => !c.monitored);
400
- if (unmonitored !== undefined)
401
- return unmonitored;
402
- const free = candidates.find(c => isAvailable(c.key));
403
- return free;
389
+ // Monitored models only. Capped models with headroom first (lowest
390
+ // fill ratio wins, spreading load), then uncapped monitored models as
391
+ // fallback — quotas are preferred, uncapped only when every capped
392
+ // monitored model is exhausted.
393
+ const cappedFree = candidates
394
+ .filter(c => c.monitored && c.limit > 0 && c.used < c.limit)
395
+ .sort((a, b) => (a.used / a.limit) - (b.used / b.limit));
396
+ if (cappedFree.length > 0)
397
+ return cappedFree[0];
398
+ return candidates.find(c => c.monitored && c.limit <= 0);
404
399
  }
405
400
  default:
406
401
  return undefined;
@@ -16,10 +16,10 @@ export declare const TOKEN_QUOTA_NAMESPACE = "token-quota";
16
16
  export declare const TOKEN_QUOTA_EXCEEDED_CODE = "TOKEN_QUOTA_EXCEEDED";
17
17
  /**
18
18
  * What to do once a monitored, quota-capped model reaches its daily cap.
19
- * Selected in the panel's settings dialog; the panel acts on it (b/c/d auto
19
+ * Selected in the panel's settings dialog; the panel acts on it (b/c auto
20
20
  * switch, a stops and prompts).
21
21
  */
22
- export declare const TOKEN_QUOTA_FULL_ACTIONS: readonly ["stop", "switchQuota", "switchAll", "switchPriority"];
22
+ export declare const TOKEN_QUOTA_FULL_ACTIONS: readonly ["stop", "switchQuota", "switchAll"];
23
23
  /** One of the {@link TOKEN_QUOTA_FULL_ACTIONS} values. */
24
24
  export type TokenQuotaFullAction = typeof TOKEN_QUOTA_FULL_ACTIONS[number];
25
25
  /**
@@ -58,8 +58,9 @@ export interface TokenQuotaSettings {
58
58
  /**
59
59
  * Behavior when a monitored, capped model hits its daily cap:
60
60
  * `'stop'` stops and prompts, `'switchQuota'` auto-switches to another
61
- * capped-but-available model, `'switchAll'` switches to any other available
62
- * model, `'switchPriority'` prefers uncapped models then unmonitored ones.
61
+ * monitored capped-but-available model, `'switchAll'` auto-switches to
62
+ * another monitored available model capped models first, uncapped ones
63
+ * as fallback. Only monitored models ever participate in a switch.
63
64
  */
64
65
  onFull: TokenQuotaFullAction;
65
66
  /**
@@ -16,10 +16,10 @@ export const TOKEN_QUOTA_NAMESPACE = 'token-quota';
16
16
  export const TOKEN_QUOTA_EXCEEDED_CODE = 'TOKEN_QUOTA_EXCEEDED';
17
17
  /**
18
18
  * What to do once a monitored, quota-capped model reaches its daily cap.
19
- * Selected in the panel's settings dialog; the panel acts on it (b/c/d auto
19
+ * Selected in the panel's settings dialog; the panel acts on it (b/c auto
20
20
  * switch, a stops and prompts).
21
21
  */
22
- export const TOKEN_QUOTA_FULL_ACTIONS = ['stop', 'switchQuota', 'switchAll', 'switchPriority'];
22
+ export const TOKEN_QUOTA_FULL_ACTIONS = ['stop', 'switchQuota', 'switchAll'];
23
23
  /** Default reset: machine-local midnight (offset = host timezone, 00:00). */
24
24
  export const TOKEN_QUOTA_DEFAULT_RESET = { offsetHours: 0, hour: 0, minute: 0 };
25
25
  /** Build the stable per-model key shared by the counter, settings, and snapshot. */
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.6",
4
+ "version": "0.1.7",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },