@konnga/dsh-hud 0.9.2 → 0.10.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.
@@ -10,10 +10,11 @@
10
10
  *
11
11
  * - `sessions`(宿主服务):枚举活着的会话,给 `session-time` / `prompt-history` /
12
12
  * `session-statuses` 三个端点用。缺了就回"不可用",侧栏那枚 chip 不画,其余照旧。
13
- * - `sessionProjections` 的三个键(都是投影,**从完整持久日志折叠**,分页与压实都不影响):
13
+ * - `sessionProjections` 的四个键(都是投影,**从完整持久日志折叠**,分页与压实都不影响):
14
14
  * - `turnBoundary` → 当前回合号(归因、面板、胶囊用它说"第几轮");
15
15
  * - `sessionStats` → 会话计时胶囊要的模型/工具墙钟与轮次步数;
16
- * - `turnOutline` → 每条 turn 的原始 `prompt`,即输入框 `↑` 召回的数据源。
16
+ * - `turnOutline` → 每条 turn 的原始 `prompt`,即输入框 `↑` 召回的数据源;
17
+ * - `modelSelection` → 这个会话**现在用的提供方与模型**,是"按模型来源查余额"的入口。
17
18
  *
18
19
  * 四者全是**软依赖**:任何一个没到齐,对应功能安静降级,绝不把整块拖下水。
19
20
  */
@@ -22,6 +23,7 @@ let hostContext = null;
22
23
  let sessionTurnReader = null;
23
24
  let sessionStatsReader = null;
24
25
  let sessionOutlineReader = null;
26
+ let modelSelectionReader = null;
25
27
  /** 记下宿主 ctx:注入回调没到齐、或状态被别的实例卸载清掉时,用它按契约兜底问一次。 */
26
28
  export function rememberContext(ctx) {
27
29
  hostContext = ctx;
@@ -61,7 +63,63 @@ export function sessionOutline(session) {
61
63
  return sessionOutlineReader(session);
62
64
  }
63
65
  /**
64
- * 挂上三个软依赖。`stops` 是调用方收集清理函数的数组(卸载时逐个调用)。
66
+ * 这个会话现在用哪个提供方/模型(`modelSelection` 投影)。
67
+ *
68
+ * 取 `next ?? lastUsed`:用户刚在模型菜单里选了还没发消息时,`next` 才是他**将要**用的那个 ——
69
+ * 余额胶囊应该跟着它走,而不是等一条消息发出去才换账本。
70
+ *
71
+ * 投影没注册、或会话还没物化时回 `null`(这条路径安静降级,胶囊退回账号余额那一路)。
72
+ *
73
+ * @param session - 宿主会话对象。
74
+ * @returns `{provider, model}`;读不到回 `null`。
75
+ */
76
+ export function sessionModelSelection(session) {
77
+ if (modelSelectionReader === null || session === undefined || session === null)
78
+ return null;
79
+ return modelSelectionReader(session);
80
+ }
81
+ /**
82
+ * 按 id 找宿主会话(找不到回 `null`,不是错误)。
83
+ *
84
+ * 先走 `get(id)`(服务自己的字典,O(1)),没有再退回 `list()` 扫一遍:
85
+ * 前者是我们要的那条路,后者兜住"只有 list 的旧形状"。
86
+ *
87
+ * @param sessionId - 会话 id。
88
+ * @returns 宿主会话对象;找不到回 `null`。
89
+ */
90
+ export function sessionById(sessionId) {
91
+ if (sessionId === null || sessionId === '')
92
+ return null;
93
+ const sessions = sessionsService();
94
+ if (sessions === null)
95
+ return null;
96
+ try {
97
+ if (typeof sessions.get === 'function') {
98
+ const hit = sessions.get(sessionId);
99
+ if (hit !== undefined && hit !== null)
100
+ return hit;
101
+ }
102
+ }
103
+ catch {
104
+ // `get` 抛了不算失败:下面还有 `list()` 这条路。
105
+ }
106
+ try {
107
+ const list = sessions.list();
108
+ if (!Array.isArray(list))
109
+ return null;
110
+ for (const session of list) {
111
+ const header = session.header;
112
+ if (header && header.id === sessionId)
113
+ return session;
114
+ }
115
+ }
116
+ catch {
117
+ return null;
118
+ }
119
+ return null;
120
+ }
121
+ /**
122
+ * 挂上软依赖。`stops` 是调用方收集清理函数的数组(卸载时逐个调用)。
65
123
  *
66
124
  * 语义与原实现完全一致:依赖没到齐就**永远不回调**(Cordis 的动态注入),
67
125
  * 到齐后把读数器装好,卸载时再清成 null。
@@ -103,6 +161,33 @@ export function registerSoftDependencies(ctx, stops) {
103
161
  return null;
104
162
  }
105
163
  };
164
+ modelSelectionReader = (session) => {
165
+ try {
166
+ const state = scope.sessionProjections.stateOf(session, 'modelSelection');
167
+ if (state === undefined || state === null)
168
+ return null;
169
+ // 注意:`stateOf` 给的是**投影的 state**,不是线上那个 view。两者的字段名不同 ——
170
+ // state 是 `{lastUsed, pending}`,view 才是 `{lastUsed, next}`(见
171
+ // `model-selection-projection.js`:`view: state => ({ lastUsed, next: pending ?? lastUsed })`)。
172
+ // 读错字段的后果很隐蔽:`next` 永远不存在,于是永远退到 `lastUsed` ——
173
+ // 刚在模型菜单里换了模型、还没发消息时,余额还挂在旧提供方上。
174
+ const view = state;
175
+ // `pending` 优先(等价于线上 view 的 `next`):那是"将要用的账本"。
176
+ const raw = view.pending ?? view.next ?? view.lastUsed;
177
+ if (raw === null || typeof raw !== 'object')
178
+ return null;
179
+ const selection = raw;
180
+ if (typeof selection.provider !== 'string' || selection.provider === '')
181
+ return null;
182
+ return {
183
+ provider: selection.provider,
184
+ model: typeof selection.model === 'string' ? selection.model : null,
185
+ };
186
+ }
187
+ catch {
188
+ return null;
189
+ }
190
+ };
106
191
  sessionTurnReader = (session) => {
107
192
  try {
108
193
  const state = scope.sessionProjections.stateOf(session, 'turnBoundary');
@@ -119,6 +204,7 @@ export function registerSoftDependencies(ctx, stops) {
119
204
  sessionTurnReader = null;
120
205
  sessionStatsReader = null;
121
206
  sessionOutlineReader = null;
207
+ modelSelectionReader = null;
122
208
  };
123
209
  }));
124
210
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@konnga/dsh-hud",
3
- "version": "0.9.2",
3
+ "version": "0.10.0",
4
4
  "description": "DeepSeek Harness 的只读工作区 Git 面板:分支、ahead/behind、改动清单与差异,外加「这个会话改过哪些文件、审到哪了、跑了什么工具」。A read-only workspace Git panel for DeepSeek Harness — branch, changed files, diffs, plus per-session change attribution, review progress, and tool activity.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.28.5",