@kukonce/dsh-loomy 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.4 — 2026-11-14
4
+
5
+ ### Fixed — the pill survives the default-model boot
6
+
7
+ A fresh conversation shows the session's default model without writing a selection: the host's
8
+ modelSelection projection carries `next: null` (the Agent's initial options supply the route)
9
+ and `lastUsed: null` (no request yet). A projection-only gate read that as "no provider" and hid
10
+ the pill although a Loomy model was on screen — Loomy's own catalog default (Spark-X2.6) made
11
+ this the common case.
12
+
13
+ The gate now also reads the composer's model trigger from the DOM (`span[data-provider-id]`,
14
+ rendered by the model-selection package): if the visible trigger is a Loomy route, the pill
15
+ mounts even with a null projection. A 2-second watcher notices trigger changes and wakes the
16
+ gate; the projection stays authoritative while it has data (an in-flight switch beats the DOM).
17
+
18
+ Code paths otherwise unchanged.
19
+
20
+ ## 0.3.3 — 2026-11-14
21
+
22
+ README 的效果图改走 npm CDN(jsdelivr),不再依赖 GitHub 仓库可达——账号暂停期间 npm 网页上图片也能打开。代码与 0.3.2 一致。
23
+
3
24
  ## 0.3.2 — 2026-11-14
4
25
 
5
26
  版本号发布:README 加了积分胶囊的效果图(docs/pill-composer.png),代码与 0.3.1 完全一致。
package/README.md CHANGED
@@ -10,7 +10,8 @@
10
10
  @loomy/dsh-loomy/<id> ← 每个账号一条
11
11
  ```
12
12
 
13
- ![选择 Loomy 模型时,输入框右侧会显示积分胶囊](docs/pill-composer.png)
13
+ <!-- 图片走 npm CDN(jsdelivr 镜像 npm 包内容):GitHub 仓库不可用时依然能显示 -->
14
+ ![选择 Loomy 模型时,输入框右侧会显示积分胶囊](https://cdn.jsdelivr.net/npm/@kukonce/dsh-loomy/docs/pill-composer.png)
14
15
 
15
16
  > 选中 Loomy 模型后,输入框右下角出现积分胶囊:绿点表示当日额度还有余量,
16
17
  > 显示「每日赠送积分 · 剩余 / 总额 · 可用合计 · 今日已领」,随对话实时扣减,
package/client/index.js CHANGED
@@ -1003,14 +1003,91 @@ window.__ModuleLoader__.load({
1003
1003
  open ? h(PillPopover, { t, reading, stale, error, updatedAt }) : null)
1004
1004
  }
1005
1005
 
1006
+ /**
1007
+ * What the composer's model trigger is currently DISPLAYING, read from the DOM.
1008
+ *
1009
+ * The modelSelection projection's `next` is null while the Agent's initial options
1010
+ * supply the route and `lastUsed` is null before the first request — so on a fresh
1011
+ * conversation that merely SHOWS the default model, both are null and a
1012
+ * projection-only gate would hide the pill although a Loomy model is on screen.
1013
+ *
1014
+ * The composer's model trigger is rendered by the model-selection package as
1015
+ * `<span data-provider-id="..." data-model-id="...">` — that attribute is the
1016
+ * authoritative record of what is on screen, and reading it costs nothing and
1017
+ * occupies no slot (registering a component on the label slot would replace the
1018
+ * host's fallback renderer and blank the model name).
1019
+ *
1020
+ * A module-level watcher polls that attribute every few seconds and, whenever the
1021
+ * visible provider CHANGES, bumps a counter and dispatches an event so a mounted
1022
+ * gate re-renders on the transition — the projection subscription alone never
1023
+ * fires for the default-model boot case.
1024
+ */
1025
+ const TRIGGER_WATCH = {
1026
+ timer: null,
1027
+ provider: undefined,
1028
+ version: 0,
1029
+ listeners: new Set()
1030
+ }
1031
+ const TRIGGER_EVENT = 'loomy:trigger-provider-changed'
1032
+
1033
+ function visibleTriggerProvider() {
1034
+ if (typeof document === 'undefined') return undefined
1035
+ const nodes = document.querySelectorAll('span[data-provider-id]')
1036
+ if (nodes.length === 0) return undefined
1037
+ // The composer shows exactly one model label per open surface; menus/rows exist
1038
+ // only while open. The last match is the live one; any loomy match wins anyway
1039
+ // because the gate only asks "is loomy on screen".
1040
+ let seen = undefined
1041
+ for (const node of nodes) {
1042
+ const id = node.getAttribute('data-provider-id')
1043
+ if (typeof id === 'string' && id.length > 0) seen = id
1044
+ }
1045
+ return seen
1046
+ }
1047
+
1048
+ function watchTriggerProvider() {
1049
+ if (TRIGGER_WATCH.timer !== null || typeof document === 'undefined') return
1050
+ const tick = () => {
1051
+ const provider = visibleTriggerProvider()
1052
+ if (provider !== TRIGGER_WATCH.provider) {
1053
+ TRIGGER_WATCH.provider = provider
1054
+ TRIGGER_WATCH.version += 1
1055
+ try { document.dispatchEvent(new CustomEvent(TRIGGER_EVENT)) } catch {}
1056
+ }
1057
+ }
1058
+ tick()
1059
+ TRIGGER_WATCH.timer = setInterval(tick, 2000)
1060
+ }
1061
+
1062
+ function unwatchTriggerProvider() {
1063
+ if (TRIGGER_WATCH.timer !== null) {
1064
+ clearInterval(TRIGGER_WATCH.timer)
1065
+ TRIGGER_WATCH.timer = null
1066
+ }
1067
+ }
1068
+
1006
1069
  /**
1007
1070
  * The gate: calls `useProjection` exactly once per render, unconditionally, and
1008
- * mounts the pill only when the conversation points at a Loomy account. Any other
1071
+ * mounts the pill only when the conversation points at a Loomy account. The
1072
+ * projection is preferred (it knows an in-flight switch before the DOM catches
1073
+ * up); the trigger watcher fills the default-model gap described above. Any other
1009
1074
  * provider renders nothing and starts nothing — the polling hook lives inside the
1010
1075
  * pill body, so a hidden pill also makes no requests.
1011
1076
  */
1012
1077
  function LoomyComposerPill(props) {
1078
+ const [, bumpVersion] = React.useState(0)
1079
+ React.useEffect(() => {
1080
+ const onChange = () => bumpVersion((current) => current + 1)
1081
+ document.addEventListener(TRIGGER_EVENT, onChange)
1082
+ watchTriggerProvider()
1083
+ return () => document.removeEventListener(TRIGGER_EVENT, onChange)
1084
+ }, [])
1013
1085
  const provider = selectedProviderOf(props.useProjection?.('modelSelection'))
1086
+ ?? (TRIGGER_WATCH.provider !== undefined &&
1087
+ typeof TRIGGER_WATCH.provider === 'string' &&
1088
+ TRIGGER_WATCH.provider.startsWith('@loomy/dsh-loomy/')
1089
+ ? TRIGGER_WATCH.provider
1090
+ : undefined)
1014
1091
  if (typeof provider !== 'string' || !provider.startsWith('@loomy/dsh-loomy/')) return null
1015
1092
  return h(LoomyPillBody, { t: props.t })
1016
1093
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kukonce/dsh-loomy",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Loomy (讯飞 Loomy) model provider for DeepSeek Harness — session login, live model catalog, daily 5000-credit renewal",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",