@gavinc-cn/touchstone-dsh 0.1.1 → 0.3.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.
Files changed (90) hide show
  1. package/README.md +39 -11
  2. package/README.zh.md +25 -10
  3. package/backend/lib/access.js +44 -0
  4. package/backend/lib/agents.js +341 -0
  5. package/backend/lib/args.js +94 -0
  6. package/backend/lib/auth.js +195 -0
  7. package/backend/lib/board.js +6154 -0
  8. package/backend/lib/builtin-assets.js +1177 -0
  9. package/backend/lib/casescan.js +456 -0
  10. package/backend/lib/chat.js +2053 -0
  11. package/backend/lib/cookie.js +76 -0
  12. package/backend/lib/db.js +2260 -0
  13. package/backend/lib/dshdriver.js +1148 -0
  14. package/backend/lib/dshevents.js +851 -0
  15. package/backend/lib/export-cases.js +261 -0
  16. package/backend/lib/feishu.js +1738 -0
  17. package/backend/lib/feishu_cfg.js +228 -0
  18. package/backend/lib/feishu_conv.js +1484 -0
  19. package/backend/lib/feishu_device.js +428 -0
  20. package/backend/lib/feishu_inbound.js +1674 -0
  21. package/backend/lib/feishu_provision.js +855 -0
  22. package/backend/lib/feishu_rest.js +398 -0
  23. package/backend/lib/feishu_slash.js +568 -0
  24. package/backend/lib/feishu_ws.js +950 -0
  25. package/backend/lib/http.js +312 -0
  26. package/backend/lib/lib.js +973 -0
  27. package/backend/lib/loadcase.js +749 -0
  28. package/backend/lib/loadgen.js +1407 -0
  29. package/backend/lib/localbus.js +164 -0
  30. package/backend/lib/lock.js +405 -0
  31. package/backend/lib/proc.js +278 -0
  32. package/backend/lib/prompts.js +1021 -0
  33. package/backend/lib/pybase64.js +114 -0
  34. package/backend/lib/pyjson.js +214 -0
  35. package/backend/lib/query.js +125 -0
  36. package/backend/lib/rag.js +1310 -0
  37. package/backend/lib/runner.js +4520 -0
  38. package/backend/lib/servercaps.js +182 -0
  39. package/backend/lib/serverstate.js +237 -0
  40. package/backend/lib/sessparse.js +1176 -0
  41. package/backend/lib/sse.js +207 -0
  42. package/backend/lib/static.js +84 -0
  43. package/backend/lib/taskjson.js +302 -0
  44. package/backend/lib/waitq.js +2114 -0
  45. package/backend/lib/worktree.js +588 -0
  46. package/backend/lib/zstd-frames.js +218 -0
  47. package/backend/routes/admin.js +553 -0
  48. package/backend/routes/agents.js +149 -0
  49. package/backend/routes/assets.js +145 -0
  50. package/backend/routes/board.js +1220 -0
  51. package/backend/routes/feishu.js +269 -0
  52. package/backend/routes/feishu_app.js +215 -0
  53. package/backend/routes/feishu_hook.js +358 -0
  54. package/backend/routes/index.js +297 -0
  55. package/backend/routes/projects.js +1059 -0
  56. package/backend/routes/sessions.js +946 -0
  57. package/backend/routes/tasks.js +1530 -0
  58. package/backend/schema/fixtures/ancient.sql +189 -0
  59. package/backend/schema/fixtures/pre_alter.sql +177 -0
  60. package/backend/schema/migrations.json +154 -0
  61. package/backend/schema/schema.json +1451 -0
  62. package/backend/schema/schema.sql +216 -0
  63. package/backend/server.js +498 -0
  64. package/board.py +805 -72
  65. package/chat.py +590 -17
  66. package/db.py +356 -140
  67. package/dsh-plugin/cordis.patch.yml +7 -3
  68. package/dsh-plugin/install.sh +28 -11
  69. package/dsh-plugin/lib/agent-driver.js +710 -89
  70. package/dsh-plugin/lib/client.bundle.js +94 -9
  71. package/dsh-plugin/lib/index.js +529 -39
  72. package/dsh-plugin/lib/pack-closure.mjs +291 -0
  73. package/dsh-plugin/scripts/prepack-check.mjs +30 -2
  74. package/dsh-plugin/src/client.js +94 -9
  75. package/dshdriver.py +74 -5
  76. package/dshevents.py +65 -3
  77. package/feishu.py +1197 -34
  78. package/feishu_conv.py +920 -0
  79. package/lifecycle.py +116 -13
  80. package/package.json +6 -1
  81. package/requirements.txt +2 -1
  82. package/server.py +218 -26
  83. package/sessparse.py +150 -44
  84. package/touchstone.py +6 -3
  85. package/waitq.py +23 -0
  86. package/webui/dist-plugin/assets/index-ChP3sg6y.css +1 -0
  87. package/webui/dist-plugin/assets/index-UrMyeAXF.js +484 -0
  88. package/webui/dist-plugin/index.html +2 -2
  89. package/webui/dist-plugin/assets/index-B0WoP1Qy.css +0 -1
  90. package/webui/dist-plugin/assets/index-DtwKoi1T.js +0 -473
package/dshevents.py CHANGED
@@ -61,6 +61,11 @@ class EventHub:
61
61
  # 与 `_sessions` 同样遵守「断连=未知」不变量——None 时调用方一律不动作。
62
62
  self._archived = None
63
63
  self._connected = False
64
+ # 快照对齐是否**可信**(2026-10-08 批次,B):只有「(重)连时拿到过权威快照」
65
+ # 才置 True——空快照且插件未声明完整时保留旧表但置 False(见 `_align`)。
66
+ # 读口 `aligned()` = connected ∧ _aligned:调用方据此决定「能否按缺席推断
67
+ # 会话已结束」(`board._iw_once`)与「能否按实况搬列」(`board.recover`)。
68
+ self._aligned = False
64
69
  self._last_seq = 0
65
70
  self._frames = 0
66
71
  self._reconnects = 0
@@ -101,6 +106,22 @@ class EventHub:
101
106
  with self._cond:
102
107
  return self._connected
103
108
 
109
+ def aligned(self):
110
+ """注册表是否**可信**(= 链路在线 ∧ 曾用插件权威快照对齐过)。
111
+
112
+ 为什么需要它(2026-10-08 批次 B,bug_report/20261008_1935):
113
+ 插件重新 apply(热重载)时驱动实例重建、`/live` 会短暂返回空表——旧实现
114
+ `self._sessions = fresh` 直接清空注册表,调用方随即把「未知」当成
115
+ 「会话已结束/空闲」:`board.recover()` 把**正在运行的卡**搬去待审核,
116
+ 且调和器此后再也读不到该会话 ⇒ 不自愈。现在空快照保留旧表并置
117
+ `_aligned=False`,本读口给出「是否可信」的单点判据:
118
+ - False ⇒ 一律不据实况写列(未知 ≠ 空闲,同「断连=未知」不变量);
119
+ - True ⇒ 会话不在注册表即可判「已结束」(`board._iw_once` 的补口)。
120
+ 插件半(A 批次)在 `/live` 里声明 `complete` 后,真·零会话的宿主也能可信。
121
+ """
122
+ with self._cond:
123
+ return bool(self._connected and self._aligned)
124
+
104
125
  def get(self, sid):
105
126
  """单会话实时态;未连接或未知会话返回 None(=未知,不是空闲)。
106
127
 
@@ -146,6 +167,7 @@ class EventHub:
146
167
  """诊断信息(/api 健康与测试断言用)。"""
147
168
  with self._cond:
148
169
  return {"connected": self._connected, "sessions": len(self._sessions),
170
+ "aligned": bool(self._connected and self._aligned),
149
171
  "last_seq": self._last_seq, "frames": self._frames,
150
172
  "reconnects": self._reconnects, "changed": self._changed,
151
173
  "archived": None if self._archived is None else len(self._archived),
@@ -226,8 +248,20 @@ class EventHub:
226
248
  新的状态轮询」。归档集取不到时保持「未知」(None),调用方一律不动作。
227
249
  """
228
250
  try:
229
- rows = (dshdriver.live() or {}).get("sessions") or []
230
- except Exception: # noqa: BLE001 — 对齐失败不阻断重连
251
+ # 用 live_snapshot()(原始 dict)而不是 live()(行表 list)——2026-10-08 修正:
252
+ # 旧写法 `dshdriver.live() or {}` 把行表当 dict 取 `.get("sessions")`,
253
+ # `/live` 非空时必抛 AttributeError 被下面 except 吞掉 ⇒ 中枢**永久未对齐**
254
+ # (aligned() 恒 False,C/D 两处补口在真机等于失效)。契约见
255
+ # tests/test_session_visibility.py::test_live_readers_contract。
256
+ resp = dshdriver.live_snapshot() or {}
257
+ rows = resp.get("sessions") or []
258
+ # 插件完整声明(A 批次后新增,旧插件缺省 None=未声明):只有它才让
259
+ # 「空表」具备「宿主里确实没有会话」的语义,否则空表一律按未知处理。
260
+ complete = resp.get("complete") is True
261
+ except Exception as exc: # noqa: BLE001 — 对齐失败不阻断重连
262
+ self._set_aligned(False) # 取不到快照=不能确认任何事
263
+ print(f"[dshevents] /live 对齐失败({exc}):保留 {len(self._sessions)} 条"
264
+ f"既有会话、标记未对齐", flush=True)
231
265
  return
232
266
  try:
233
267
  archived = {str(s) for s in (dshdriver.archived() or []) if s}
@@ -265,11 +299,34 @@ class EventHub:
265
299
  "inbox": old.get("inbox") or [],
266
300
  "updated_at": now,
267
301
  }
268
- self._sessions = fresh
302
+ # —— 快照可信判定(2026-10-08 批次 B)——
303
+ # 非空快照 = 权威整表;空快照只有插件声明完整(complete)才算权威空。
304
+ # 其余情况(热重载后驱动实例重建、不重枚举已有会话 ⇒ 空表)=**未知**:
305
+ # 旧表原样保留(宁可多等不可误放行),只把可信标志置 False——
306
+ # 调用方一律不据此推断忙/闲,也绝不据「缺席」判会话已结束。
307
+ trusted = bool(fresh) or complete
308
+ if trusted:
309
+ self._sessions = fresh
310
+ kept = len(self._sessions)
311
+ self._aligned = trusted
269
312
  if archived is not None:
270
313
  self._archived = archived
271
314
  self._changed += 1
272
315
  self._cond.notify_all()
316
+ if not trusted and kept:
317
+ # 留痕(插件形态落 <库目录>/plugin-backend.log):这是可诊断的降级态,
318
+ # 不是静默——旧表保留期间看板列不做忙/闲写入,直到下一次权威快照。
319
+ print(f"[dshevents] /live 空快照且插件未声明完整:保留 {kept} 条既有会话、"
320
+ f"标记未对齐(热重载后驱动实例重建的已知形态,见 "
321
+ f"bug_report/20261008_1935)", flush=True)
322
+
323
+ def _set_aligned(self, value):
324
+ """置「快照可信」标志(变更才唤醒订阅者;调用方持锁外语义同 `_set_connected`)。"""
325
+ with self._cond:
326
+ if self._aligned != bool(value):
327
+ self._aligned = bool(value)
328
+ self._changed += 1
329
+ self._cond.notify_all()
273
330
 
274
331
  def _set_connected(self, value):
275
332
  with self._cond:
@@ -401,6 +458,11 @@ def connected():
401
458
  return HUB.connected()
402
459
 
403
460
 
461
+ def aligned():
462
+ """注册表可信读口(在线 ∧ 曾用权威快照对齐);未对齐 ⇒ 调用方不写列、不推断。"""
463
+ return HUB.aligned()
464
+
465
+
404
466
  def get(sid):
405
467
  return HUB.get(sid)
406
468