dsh-session-superagent 0.8.29 → 0.8.30

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
@@ -2,6 +2,35 @@
2
2
 
3
3
  格式参照 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
4
4
 
5
+ ## [0.8.30] - 2026-11-02 —— 0.8.28 没修好:宿主重载是「重新 import 一份模块」
6
+
7
+ ### 真机判定实验(20:00 重载后,routes 6 / failed [])
8
+ 面板(HTTP)建的 watcher **仍然报**:
9
+
10
+ ```
11
+ lastError: cannot get required service "agents" in inactive context
12
+ consecutiveFailures: 0 / nextAt: None
13
+ ```
14
+
15
+ 说明 0.8.28 的模块级 `activeRoutes` 没有生效 —— 因为**宿主重载是重新 import 一份模块**,
16
+ 模块级变量全新(数组是空的),新实例根本不知道旧实例注册过什么。
17
+
18
+ ### 修复
19
+ 把三个必须跨实例共享的结构挂到 `globalThis` 上(用 `Symbol.for` 做键):
20
+
21
+ | 结构 | 为什么必须共享 |
22
+ |---|---|
23
+ | `activeRoutes`(路由 disposer) | 新实例要能摘掉旧实例的路由,否则旧 ctx 继续吃 HTTP |
24
+ | `timers`(定时器句柄) | 旧实例的 tick 还持有已 dispose 的 ctx,必须清掉 |
25
+ | `inFlight`(并发闸) | 旧实例没跑完的那轮,新实例不能同时再跑一遍 |
26
+
27
+ ### 守门
28
+ 新增 `test/cross-instance.test.mjs`:用 `import('../lib/index.js?instance=1')` 与 `?instance=2`
29
+ 模拟两次 import,断言第二个实例会摘掉第一个实例注册的 6 条路由。
30
+
31
+ ### 测试
32
+ - 合计 **92 例**。
33
+
5
34
  ## [0.8.29] - 2026-11-02 —— `watch_doctor` 现在会告诉你「面板接口死没死」
6
35
 
7
36
  这一轮的真机事故(重复路由 → 六条接口全灭)有个共同特征:**工具还是好的,只有面板挂了**。
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  <img src="assets/icon-180.png" width="88" height="88" alt="">
4
4
 
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
- [![Version](https://img.shields.io/badge/version-0.8.29-green.svg)](https://github.com/MYCF711/dsh-session-superagent/releases)
6
+ [![Version](https://img.shields.io/badge/version-0.8.30-green.svg)](https://github.com/MYCF711/dsh-session-superagent/releases)
7
7
  [![npm](https://img.shields.io/npm/v/dsh-session-superagent.svg)](https://www.npmjs.com/package/dsh-session-superagent)
8
8
  [![dsh](https://img.shields.io/badge/DeepSeek%20Harness-plugin-orange.svg)](https://github.com/topics/dsh-plugin)
9
9
 
package/client.js CHANGED
@@ -14,7 +14,7 @@ window.__ModuleLoader__.load({
14
14
  const { useCallback, useEffect, useRef, useState } = React
15
15
 
16
16
  // 客户端版本探针:每次模块加载写一次,用来确认浏览器里跑的是哪一版
17
- const CLIENT_VERSION = '0.8.29'
17
+ const CLIENT_VERSION = '0.8.30'
18
18
  const BASE = '/session-superagent'
19
19
  const ENDPOINT = BASE + '/state'
20
20
  const POLL_MS = 5000
package/lib/index.js CHANGED
@@ -43,12 +43,24 @@ const DEFAULT_COOLDOWN_MS = 180_000
43
43
 
44
44
  /**
45
45
  * 当前挂在这个进程上的路由 disposer。
46
+ *
46
47
  * 宿主重载时若没走 dispose,旧实例会继续吃 HTTP 请求,而它的 ctx 可能已经 dispose ——
47
48
  * 真机表现:面板建的 watcher 第一轮就报
48
49
  * `cannot get required service "agents" in inactive context`(2026-11-02 复验)。
49
- * 所以每次 apply 注册路由前,先把上一次的摘掉。
50
+ *
51
+ * 关键:宿主重载常常是**重新 import 一份模块**(模块级变量全新),所以光用模块级数组
52
+ * 接不过去 —— 必须挂在 `globalThis` 上(用 Symbol.for 做键,多个模块实例共享同一个)。
50
53
  */
51
- const activeRoutes = []
54
+ // 这三个结构必须跨模块实例共享:宿主重载常常是重新 import 一份模块,
55
+ // 模块级变量全新,但进程只有一个。新实例要能清掉旧实例的路由与定时器,
56
+ // 也要能看见旧实例「正在跑的那轮」(否则并发闸形同虚设)。
57
+ const TIMERS_KEY = Symbol.for('dsh-session-superagent.timers')
58
+ const INFLIGHT_KEY = Symbol.for('dsh-session-superagent.inFlight')
59
+ const sharedTimers = (globalThis[TIMERS_KEY] ||= new Map())
60
+ const sharedInFlight = (globalThis[INFLIGHT_KEY] ||= new Set())
61
+
62
+ const ROUTES_KEY = Symbol.for('dsh-session-superagent.activeRoutes')
63
+ const activeRoutes = (globalThis[ROUTES_KEY] ||= [])
52
64
 
53
65
  function readState() {
54
66
  try { return JSON.parse(readFileSync(STATE_FILE, 'utf8')) } catch { return { watchers: {} } }
@@ -546,10 +558,11 @@ async function pollOnce(ctx, entry, baseMs) {
546
558
  }
547
559
 
548
560
  export function apply(ctx) {
549
- const timers = new Map()
561
+ const timers = sharedTimers
550
562
  // 并发闸的唯一事实源:tick 和 HTTP「立刻」拿到的是两个不同的 entry 对象,
551
563
  // 靠 entry.polling 标记永远对不上(审核专员 probe-c 实证:同一条被唤醒 3 次)。
552
- const inFlight = new Set()
564
+ // 跨模块实例共享:旧实例还没跑完的那轮,新实例不能同时再跑一遍。
565
+ const inFlight = sharedInFlight
553
566
 
554
567
  const startWatcher = (entry) => {
555
568
  if (timers.has(entry.id)) return
@@ -600,6 +613,8 @@ export function apply(ctx) {
600
613
  if (!httpServer || typeof httpServer.register !== 'function') return
601
614
  for (const dispose of activeRoutes) { try { dispose() } catch {} }
602
615
  activeRoutes.length = 0
616
+ for (const handle of timers.values()) { try { clearInterval(handle) } catch {} }
617
+ timers.clear()
603
618
  const routes = []
604
619
  // 宿主热重载时旧实例的路由可能还没 dispose;一条重复会让整个 try 崩掉,
605
620
  // 面板和所有接口一起 404(2026-11-02 真机踩到 webserver: duplicate exact route)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-session-superagent",
3
- "version": "0.8.29",
3
+ "version": "0.8.30",
4
4
  "description": "Session-level superagent for DeepSeek Harness: watch a GitHub repository and wake a session when something needs doing. Credentials never reach the model.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",