tianshu-harness 3.23.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 (255) hide show
  1. package/LICENSE +189 -0
  2. package/README.en.md +1066 -0
  3. package/README.ja.md +1214 -0
  4. package/README.ko.md +1216 -0
  5. package/README.md +309 -0
  6. package/completions/rivet.bash +118 -0
  7. package/completions/rivet.fish +110 -0
  8. package/completions/rivet.ps1 +230 -0
  9. package/completions/rivet.zsh +187 -0
  10. package/dist/account-OAEKORZE.js +47 -0
  11. package/dist/account-login-V6FOLHJR.js +65 -0
  12. package/dist/account-status-KHNPPAFL.js +23 -0
  13. package/dist/agent/worker-process/child.js +407 -0
  14. package/dist/browser-cli-FLNY5ZHI.js +19 -0
  15. package/dist/browser-readiness-LWVGSBD5.js +12 -0
  16. package/dist/build-backends-5KXKR2S4.js +13 -0
  17. package/dist/build-options-66Y4ZE7C.js +9 -0
  18. package/dist/bundled-skills/brainstorming/SKILL.md +139 -0
  19. package/dist/bundled-skills/visual-acceptance/SKILL.md +64 -0
  20. package/dist/chain-7MAMFXZ3.js +10 -0
  21. package/dist/checkpoint-MND4DUIX.js +33 -0
  22. package/dist/chunk-2E6CK2RC.js +392 -0
  23. package/dist/chunk-2EBWGAXD.js +51 -0
  24. package/dist/chunk-2HDP7RMR.js +119 -0
  25. package/dist/chunk-2HKD2UFM.js +1000 -0
  26. package/dist/chunk-2VYRLULP.js +51 -0
  27. package/dist/chunk-2XN7PM4N.js +125 -0
  28. package/dist/chunk-35H7GVNR.js +66 -0
  29. package/dist/chunk-3C3R7JXY.js +77 -0
  30. package/dist/chunk-3VGD2FXB.js +118 -0
  31. package/dist/chunk-424AP7FM.js +534 -0
  32. package/dist/chunk-4J5TZGOE.js +3707 -0
  33. package/dist/chunk-4QJ3Y5JM.js +898 -0
  34. package/dist/chunk-4U2ET7ZY.js +1326 -0
  35. package/dist/chunk-4XPFLICE.js +876 -0
  36. package/dist/chunk-5B2OCU5P.js +31707 -0
  37. package/dist/chunk-5GGOGLSN.js +73 -0
  38. package/dist/chunk-5JRZPLI6.js +251 -0
  39. package/dist/chunk-5TQ3JYBU.js +397 -0
  40. package/dist/chunk-66JPZSGX.js +55 -0
  41. package/dist/chunk-6HSVXHCZ.js +764 -0
  42. package/dist/chunk-6S5FPUS7.js +21 -0
  43. package/dist/chunk-722LQO64.js +131 -0
  44. package/dist/chunk-73NJ7IT7.js +354 -0
  45. package/dist/chunk-7FW3I7WC.js +95 -0
  46. package/dist/chunk-7KUCBNLB.js +173 -0
  47. package/dist/chunk-7RDOV3DF.js +201 -0
  48. package/dist/chunk-A5MNH4ER.js +79 -0
  49. package/dist/chunk-ADIVZ2D5.js +15 -0
  50. package/dist/chunk-AE3P3F6W.js +366 -0
  51. package/dist/chunk-AERM3VGP.js +280 -0
  52. package/dist/chunk-AEWHC4WM.js +246 -0
  53. package/dist/chunk-AOVQ7VIF.js +169 -0
  54. package/dist/chunk-AOYHP3WI.js +12411 -0
  55. package/dist/chunk-B3YEBABJ.js +7160 -0
  56. package/dist/chunk-BCQXEZPJ.js +154 -0
  57. package/dist/chunk-BKTQAYO2.js +418 -0
  58. package/dist/chunk-BSECIPAQ.js +80 -0
  59. package/dist/chunk-BUIMGNDD.js +52 -0
  60. package/dist/chunk-BXHV3GSB.js +10320 -0
  61. package/dist/chunk-C6ZVZCPP.js +139 -0
  62. package/dist/chunk-CEZNQVWM.js +82 -0
  63. package/dist/chunk-CGFZGR7G.js +1492 -0
  64. package/dist/chunk-CGOADM2X.js +337 -0
  65. package/dist/chunk-CI4R6P2B.js +24 -0
  66. package/dist/chunk-D2TCGRSX.js +73 -0
  67. package/dist/chunk-D4PTYFOG.js +154 -0
  68. package/dist/chunk-D54ZWG4T.js +717 -0
  69. package/dist/chunk-DEBI37QB.js +145 -0
  70. package/dist/chunk-DWROHPXP.js +1051 -0
  71. package/dist/chunk-DZ6UOJ5D.js +92 -0
  72. package/dist/chunk-EBJTVRQN.js +81 -0
  73. package/dist/chunk-EGEI2RQA.js +1167 -0
  74. package/dist/chunk-EHHUASO5.js +1305 -0
  75. package/dist/chunk-EPKTIOED.js +41 -0
  76. package/dist/chunk-ER5JW2QZ.js +5681 -0
  77. package/dist/chunk-ETHFACAB.js +315 -0
  78. package/dist/chunk-EXUE5W2F.js +112 -0
  79. package/dist/chunk-EXXICUIW.js +73 -0
  80. package/dist/chunk-FFQSZJOO.js +169 -0
  81. package/dist/chunk-FHFKPGDO.js +85 -0
  82. package/dist/chunk-FW3QKMFX.js +158 -0
  83. package/dist/chunk-GD4ZNZ7W.js +30 -0
  84. package/dist/chunk-GHVMWF2I.js +581 -0
  85. package/dist/chunk-GMKAXSYQ.js +297 -0
  86. package/dist/chunk-HBVF6BWO.js +325 -0
  87. package/dist/chunk-HV4YGVMS.js +379 -0
  88. package/dist/chunk-ILBCKAV6.js +89 -0
  89. package/dist/chunk-J4U4AW46.js +440 -0
  90. package/dist/chunk-J6ESSM47.js +1791 -0
  91. package/dist/chunk-JBZHIC6T.js +2309 -0
  92. package/dist/chunk-JCFWOULR.js +245 -0
  93. package/dist/chunk-JRF5L6OD.js +131 -0
  94. package/dist/chunk-JSS75KCG.js +2014 -0
  95. package/dist/chunk-JSXWIWWJ.js +225 -0
  96. package/dist/chunk-JYV7I2QH.js +98 -0
  97. package/dist/chunk-KIOXTQQL.js +1596 -0
  98. package/dist/chunk-KWVTXT5B.js +89 -0
  99. package/dist/chunk-KX22RD6R.js +33 -0
  100. package/dist/chunk-LA6IPTGH.js +709 -0
  101. package/dist/chunk-LH5VCGLB.js +1796 -0
  102. package/dist/chunk-LQQ6XJ2Y.js +269 -0
  103. package/dist/chunk-MJQ55WZZ.js +65 -0
  104. package/dist/chunk-ML3UKMH3.js +40 -0
  105. package/dist/chunk-MOZPLHSU.js +5526 -0
  106. package/dist/chunk-NGEWJ4DF.js +89 -0
  107. package/dist/chunk-NH6VIOAR.js +211 -0
  108. package/dist/chunk-O5VZQKCB.js +26106 -0
  109. package/dist/chunk-OHSV42QB.js +748 -0
  110. package/dist/chunk-OO3TAHO5.js +4070 -0
  111. package/dist/chunk-OTNHSAGN.js +38282 -0
  112. package/dist/chunk-OUWAC7AQ.js +442 -0
  113. package/dist/chunk-OXMIV4WE.js +270 -0
  114. package/dist/chunk-P5KJURUR.js +229 -0
  115. package/dist/chunk-P6GJOVO6.js +307 -0
  116. package/dist/chunk-QARAGUXX.js +23 -0
  117. package/dist/chunk-R43HJCDX.js +2711 -0
  118. package/dist/chunk-RAXBAWRN.js +210 -0
  119. package/dist/chunk-RK3JHWPT.js +39 -0
  120. package/dist/chunk-RWA23GMM.js +24 -0
  121. package/dist/chunk-S4K4L6IM.js +179 -0
  122. package/dist/chunk-SAXK6LVF.js +124 -0
  123. package/dist/chunk-SEPGEJNK.js +1607 -0
  124. package/dist/chunk-SUJERP5I.js +285 -0
  125. package/dist/chunk-T3ED6BY2.js +913 -0
  126. package/dist/chunk-THKDVSEC.js +90 -0
  127. package/dist/chunk-TI4GSEL5.js +52 -0
  128. package/dist/chunk-TPI73L57.js +164 -0
  129. package/dist/chunk-TVHW77C7.js +338 -0
  130. package/dist/chunk-TWNHOSDT.js +1073 -0
  131. package/dist/chunk-UFMN76QD.js +347 -0
  132. package/dist/chunk-UHGLFRZE.js +205 -0
  133. package/dist/chunk-UTZYPKBF.js +15231 -0
  134. package/dist/chunk-UXNLELW6.js +25 -0
  135. package/dist/chunk-UXXS53EA.js +166 -0
  136. package/dist/chunk-V5DHFGI7.js +171 -0
  137. package/dist/chunk-V5YIPMHV.js +2353 -0
  138. package/dist/chunk-WMUWEO2C.js +754 -0
  139. package/dist/chunk-WOE3D7JM.js +945 -0
  140. package/dist/chunk-X2IK5LVR.js +2592 -0
  141. package/dist/chunk-XU2YXCDL.js +459 -0
  142. package/dist/chunk-YJG5HIX5.js +203 -0
  143. package/dist/chunk-Z3QE7TZM.js +72 -0
  144. package/dist/chunk-Z5Z3JHJY.js +64 -0
  145. package/dist/chunk-ZDIFBJFT.js +168 -0
  146. package/dist/chunk-ZGGCEXDC.js +176 -0
  147. package/dist/chunk-ZMZCZNKU.js +263 -0
  148. package/dist/chunk-ZNC5T65N.js +17 -0
  149. package/dist/chunk-ZO3I3A7O.js +308 -0
  150. package/dist/chunk-ZQKXBUB7.js +217 -0
  151. package/dist/chunk-ZTRINFM2.js +78 -0
  152. package/dist/chunk-ZU7JZCVP.js +193 -0
  153. package/dist/chunk-ZZAWNALS.js +350 -0
  154. package/dist/cli/entry.js +54 -0
  155. package/dist/connector-L5QQTR6Y.js +26 -0
  156. package/dist/context-62DC6EC3.js +31 -0
  157. package/dist/council-convene-NJWPNBDU.js +46 -0
  158. package/dist/create-agent-config-ODSQWNVC.js +55 -0
  159. package/dist/default-registry-2PU3BTAU.js +68 -0
  160. package/dist/deliver-task-KONV4C4M.js +57 -0
  161. package/dist/delivery-gate-v2-F2YVMVGK.js +27 -0
  162. package/dist/domain-knowledge-store-DDZ3F4J7.js +11 -0
  163. package/dist/fetch-core-ZPPYDSZH.js +14 -0
  164. package/dist/galaxy-K72BBDQN.js +91 -0
  165. package/dist/goal-criteria-SCALAU2D.js +141 -0
  166. package/dist/goal-persist-XWY2GDQX.js +19 -0
  167. package/dist/goal-tracker-FXMJ2GVK.js +11 -0
  168. package/dist/headless-LSB6I7PT.js +162 -0
  169. package/dist/headless-coordinator-VDPDMVUT.js +90 -0
  170. package/dist/inject-U55SMB6A.js +39 -0
  171. package/dist/login-flow-CD4ZGXOC.js +59 -0
  172. package/dist/logs-cli-SQVYFMMN.js +283 -0
  173. package/dist/loop-ULZYBDA3.js +76 -0
  174. package/dist/main.js +4064 -0
  175. package/dist/manager-ROZBCWPA.js +171 -0
  176. package/dist/manager-Z7XFFUOM.js +10670 -0
  177. package/dist/meridian-indexer-ZIW363L7.js +21 -0
  178. package/dist/native-resolver-PN34UIVV.js +13 -0
  179. package/dist/node_modules/better-sqlite3/lib/database.js +90 -0
  180. package/dist/node_modules/better-sqlite3/lib/index.js +3 -0
  181. package/dist/node_modules/better-sqlite3/lib/methods/aggregate.js +43 -0
  182. package/dist/node_modules/better-sqlite3/lib/methods/backup.js +67 -0
  183. package/dist/node_modules/better-sqlite3/lib/methods/function.js +31 -0
  184. package/dist/node_modules/better-sqlite3/lib/methods/inspect.js +7 -0
  185. package/dist/node_modules/better-sqlite3/lib/methods/pragma.js +12 -0
  186. package/dist/node_modules/better-sqlite3/lib/methods/serialize.js +16 -0
  187. package/dist/node_modules/better-sqlite3/lib/methods/table.js +189 -0
  188. package/dist/node_modules/better-sqlite3/lib/methods/transaction.js +78 -0
  189. package/dist/node_modules/better-sqlite3/lib/methods/wrappers.js +54 -0
  190. package/dist/node_modules/better-sqlite3/lib/sqlite-error.js +20 -0
  191. package/dist/node_modules/better-sqlite3/lib/util.js +12 -0
  192. package/dist/node_modules/better-sqlite3/package.json +59 -0
  193. package/dist/ownership-ledger-E3WIEKPL.js +9 -0
  194. package/dist/package.json +3 -0
  195. package/dist/path-grants-XRRXHXG7.js +40 -0
  196. package/dist/paths-LVV5IMQN.js +49 -0
  197. package/dist/plan-fact-anchors-LMVQZCW5.js +13 -0
  198. package/dist/plan-store-ODLTTPFB.js +17 -0
  199. package/dist/plan-templates-5FPFBOL6.js +109 -0
  200. package/dist/plugin-loader-ZZKX7ZE6.js +22 -0
  201. package/dist/prefix-budget-62OV5HQC.js +13 -0
  202. package/dist/project-trust-5WS5MOMU.js +31 -0
  203. package/dist/provider-cli-ALDQXIT7.js +37 -0
  204. package/dist/providers-XQYTAVPK.js +11 -0
  205. package/dist/proxy-resolver-V2Y6534D.js +15 -0
  206. package/dist/recovery-cli-KPGY6FY3.js +106 -0
  207. package/dist/review-discipline-MNSYCST5.js +55 -0
  208. package/dist/review-router-BVQZW3MX.js +18 -0
  209. package/dist/seed-capsules/seed-capsule-attack-methodology.md +62 -0
  210. package/dist/seed-capsules/seed-capsule-changgeng.md +31 -0
  211. package/dist/seed-capsules/seed-capsule-design-aesthetics.md +38 -0
  212. package/dist/seed-capsules/seed-capsule-diagnostic-ladder.md +54 -0
  213. package/dist/seed-capsules/seed-capsule-fu.md +39 -0
  214. package/dist/seed-capsules/seed-capsule-huagai.md +73 -0
  215. package/dist/seed-capsules/seed-capsule-kaiyang.md +65 -0
  216. package/dist/seed-capsules/seed-capsule-knowledge-work.md +40 -0
  217. package/dist/seed-capsules/seed-capsule-tanlang.md +47 -0
  218. package/dist/seed-capsules/seed-capsule-tianfu.md +23 -0
  219. package/dist/seed-capsules/seed-capsule-tianliang.md +63 -0
  220. package/dist/seed-capsules/seed-capsule-tianquan.md +32 -0
  221. package/dist/seed-capsules/seed-capsule-tianxuan-ccr.md +22 -0
  222. package/dist/seed-capsules/seed-capsule-tianxuan.md +64 -0
  223. package/dist/seed-capsules/seed-capsule-yaoguang.md +87 -0
  224. package/dist/serve-X2R2UTLF.js +170 -0
  225. package/dist/serve-agent-KHSU6RVV.js +965 -0
  226. package/dist/session-persist-HUUC772H.js +37 -0
  227. package/dist/session-registry-6XUD5C4Y.js +15 -0
  228. package/dist/skill-loader-CK3RMO47.js +48 -0
  229. package/dist/skills/cli-harness-sop.md +207 -0
  230. package/dist/spawn-git-4HFU65AH.js +29 -0
  231. package/dist/sse-EYUTBWUK.js +512 -0
  232. package/dist/starflow-NOFBMPFU.js +30 -0
  233. package/dist/streamableHttp-V67CNJMO.js +447 -0
  234. package/dist/task-contract-6XHBNJQK.js +32 -0
  235. package/dist/task-ledger-3K4TJIER.js +9 -0
  236. package/dist/team-orchestrate-S6NHZP7X.js +64 -0
  237. package/dist/title-extract-QX4XGCYN.js +50 -0
  238. package/dist/todo-QDXGRZZP.js +31 -0
  239. package/dist/todo-store-V3TTO5AH.js +12 -0
  240. package/dist/tool-preset-LU5BYEDZ.js +23 -0
  241. package/dist/tool-tiers-GD742YBN.js +23 -0
  242. package/dist/turndown.es-OI55K6C3.js +17373 -0
  243. package/dist/verification-attribution-EKG4CNUV.js +13 -0
  244. package/dist/volatile-git-MBTST7RA.js +15 -0
  245. package/dist/wave-checkpoint-XLIIHXQA.js +30 -0
  246. package/dist/web-cli-DUG4I35O.js +233 -0
  247. package/dist/web-search-3IK542AA.js +34 -0
  248. package/dist/workers/cpu-worker.js +45 -0
  249. package/dist/workflow-runner-HX7UBAYG.js +297 -0
  250. package/dist/worktree-baseline-O5NCU6M2.js +9 -0
  251. package/package.json +122 -0
  252. package/scripts/check-mobile-env.mjs +76 -0
  253. package/scripts/fetch-native-sqlite.js +392 -0
  254. package/scripts/prune-installed-wasms.js +45 -0
  255. package/scripts/tree-sitter-wasm-keep.js +40 -0
@@ -0,0 +1,581 @@
1
+ #!/usr/bin/env -S node --expose-gc --max-old-space-size=4096
2
+ import { createRequire as __rivetCreateRequire } from 'node:module'; const require = __rivetCreateRequire(import.meta.url);
3
+ import {
4
+ init_esm_shims
5
+ } from "./chunk-5GGOGLSN.js";
6
+
7
+ // src/agent/session-registry.ts
8
+ init_esm_shims();
9
+ import { join } from "path";
10
+ import { createHash } from "crypto";
11
+ import { existsSync, mkdirSync } from "fs";
12
+
13
+ // src/agent/retrospect-fingerprint.ts
14
+ init_esm_shims();
15
+ var SECTION_HEADINGS = {
16
+ rootCause: /^##\s+3\.\s+根因判定/,
17
+ recommendation: /^##\s+4\.\s+寻址建议/
18
+ };
19
+ var STOP_WORDS = /* @__PURE__ */ new Set([
20
+ "the",
21
+ "and",
22
+ "for",
23
+ "with",
24
+ "this",
25
+ "that",
26
+ "from",
27
+ "into",
28
+ "after",
29
+ "before",
30
+ "\u672C\u6B21",
31
+ "\u4F1A\u8BDD",
32
+ "\u5EFA\u8BAE",
33
+ "\u8003\u8651",
34
+ "\u7CFB\u7EDF",
35
+ "\u7528\u6237",
36
+ "\u53EF\u80FD",
37
+ "\u95EE\u9898"
38
+ ]);
39
+ function extractSectionText(report, heading) {
40
+ const lines = report.split("\n");
41
+ const start = lines.findIndex((line) => heading.test(line.trim()));
42
+ if (start === -1) return "";
43
+ const section = [];
44
+ for (const line of lines.slice(start + 1)) {
45
+ if (/^##\s+/.test(line.trim())) break;
46
+ section.push(line);
47
+ }
48
+ return section.join("\n");
49
+ }
50
+ function isCJKChar(ch) {
51
+ const cp = ch.codePointAt(0);
52
+ return cp >= 19968 && cp <= 40959 || cp >= 13312 && cp <= 19903 || cp >= 12288 && cp <= 12351 || cp >= 65280 && cp <= 65519;
53
+ }
54
+ function extractKeywords(text, max = 12) {
55
+ if (!text) return [];
56
+ const rawTokens = text.split(/[^\p{L}\p{N}_./-]+/u).map((t) => t.trim()).filter((t) => t.length >= 2).filter((t) => !STOP_WORDS.has(t.toLowerCase()));
57
+ const expanded = [];
58
+ for (const token of rawTokens) {
59
+ const chars = [...token];
60
+ const allCJK = chars.every((ch) => isCJKChar(ch));
61
+ if (allCJK && chars.length > 2) {
62
+ for (let i = 0; i < chars.length - 1; i++) {
63
+ expanded.push(chars[i] + chars[i + 1]);
64
+ }
65
+ } else {
66
+ expanded.push(token.toLowerCase());
67
+ }
68
+ }
69
+ return [...new Set(expanded)].slice(0, max);
70
+ }
71
+ function buildRetrospectFingerprint(sessionId, report, bullets, options) {
72
+ const now = options?.now ?? Date.now();
73
+ const rootCauseText = extractSectionText(report, SECTION_HEADINGS.rootCause);
74
+ const recommendationText = extractSectionText(report, SECTION_HEADINGS.recommendation);
75
+ return {
76
+ sessionId,
77
+ createdAt: now,
78
+ rootCauseKeywords: extractKeywords(rootCauseText),
79
+ recommendationKeywords: extractKeywords(recommendationText),
80
+ stabilityTrend: options?.stabilityTrend ?? "stable",
81
+ confidenceTrend: options?.confidenceTrend ?? "stable",
82
+ maxPressure: options?.maxPressure ?? 0.5,
83
+ toolFailureRate: options?.toolFailureRate ?? 0,
84
+ bulletIds: bullets.map((b) => b.id)
85
+ };
86
+ }
87
+ function fingerprintSimilarity(a, b) {
88
+ const rootOverlap = keywordOverlap(a.rootCauseKeywords, b.rootCauseKeywords);
89
+ const recoOverlap = keywordOverlap(a.recommendationKeywords, b.recommendationKeywords);
90
+ let similarity = rootOverlap * 0.7 + recoOverlap * 0.3;
91
+ if (a.stabilityTrend !== b.stabilityTrend) similarity *= 0.9;
92
+ if (a.confidenceTrend !== b.confidenceTrend) similarity *= 0.9;
93
+ return similarity;
94
+ }
95
+ function keywordOverlap(a, b) {
96
+ const left = new Set(a);
97
+ const right = new Set(b);
98
+ if (left.size === 0 && right.size === 0) return 0;
99
+ const intersection = [...left].filter((k) => right.has(k)).length;
100
+ const denominator = Math.max(1, Math.min(left.size, right.size));
101
+ return intersection / denominator;
102
+ }
103
+ var VALID_TRENDS = /* @__PURE__ */ new Set(["stable", "falling", "rising"]);
104
+ function validateTrend(value, fallback) {
105
+ if (VALID_TRENDS.has(value)) return value;
106
+ return fallback;
107
+ }
108
+
109
+ // src/agent/session-registry.ts
110
+ function projectHash(cwd) {
111
+ return createHash("sha256").update(cwd).digest("hex").slice(0, 12);
112
+ }
113
+ var SCHEMA = `
114
+ CREATE TABLE IF NOT EXISTS sessions (
115
+ id TEXT PRIMARY KEY,
116
+ pid INTEGER NOT NULL,
117
+ cwd TEXT NOT NULL,
118
+ started_at TEXT NOT NULL,
119
+ heartbeat_at TEXT NOT NULL,
120
+ role TEXT NOT NULL CHECK(role IN ('coordinator','worker','standalone')),
121
+ task_description TEXT
122
+ );
123
+
124
+ CREATE TABLE IF NOT EXISTS claims (
125
+ session_id TEXT NOT NULL,
126
+ file_path TEXT NOT NULL,
127
+ claim_type TEXT NOT NULL CHECK(claim_type IN ('exclusive','shared_read')),
128
+ acquired_at TEXT NOT NULL,
129
+ confidence_trend TEXT,
130
+ detail TEXT,
131
+ priority INTEGER DEFAULT 0,
132
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
133
+ );
134
+ CREATE INDEX IF NOT EXISTS idx_claims_session ON claims(session_id);
135
+ CREATE INDEX IF NOT EXISTS idx_claims_file ON claims(file_path);
136
+
137
+ CREATE TABLE IF NOT EXISTS events (
138
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
139
+ session_id TEXT NOT NULL,
140
+ event_type TEXT NOT NULL,
141
+ file_path TEXT,
142
+ detail TEXT,
143
+ priority INTEGER NOT NULL DEFAULT 0,
144
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
145
+ );
146
+ CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);
147
+ CREATE INDEX IF NOT EXISTS idx_events_created ON events(created_at);
148
+
149
+ CREATE TABLE IF NOT EXISTS cycle_relay (
150
+ session_id TEXT PRIMARY KEY,
151
+ cycle_open TEXT NOT NULL,
152
+ cycle_close TEXT,
153
+ generation INTEGER NOT NULL DEFAULT 0,
154
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
155
+ closed_at TEXT
156
+ );
157
+ CREATE INDEX IF NOT EXISTS idx_cycle_relay_closed ON cycle_relay(closed_at);
158
+
159
+ CREATE TABLE IF NOT EXISTS retrospect_fingerprints (
160
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
161
+ session_id TEXT NOT NULL,
162
+ created_at INTEGER NOT NULL,
163
+ root_cause_keywords TEXT NOT NULL,
164
+ recommendation_keywords TEXT NOT NULL,
165
+ stability_trend TEXT NOT NULL CHECK(stability_trend IN ('stable','falling','rising')),
166
+ confidence_trend TEXT NOT NULL CHECK(confidence_trend IN ('stable','falling','rising')),
167
+ max_pressure REAL NOT NULL,
168
+ tool_failure_rate REAL NOT NULL,
169
+ bullet_ids TEXT NOT NULL DEFAULT '[]',
170
+ project_hash TEXT,
171
+ UNIQUE(session_id)
172
+ );
173
+ CREATE INDEX IF NOT EXISTS idx_fingerprints_created ON retrospect_fingerprints(created_at);
174
+ CREATE INDEX IF NOT EXISTS idx_fingerprints_project ON retrospect_fingerprints(project_hash);
175
+ `;
176
+ var _forceNullDbForTest = false;
177
+ function _setBackendForTest(kind) {
178
+ _forceNullDbForTest = kind === "null";
179
+ }
180
+ function _resetBackendForTest() {
181
+ _forceNullDbForTest = false;
182
+ }
183
+ var SessionRegistry = class _SessionRegistry {
184
+ db;
185
+ static async create(stateDir) {
186
+ if (_forceNullDbForTest) return new _SessionRegistry(createNullDb());
187
+ if (!existsSync(stateDir)) mkdirSync(stateDir, { recursive: true });
188
+ let db;
189
+ try {
190
+ const { resolveBetterSqlite3 } = await import("./native-resolver-PN34UIVV.js");
191
+ const Database = resolveBetterSqlite3(import.meta.url);
192
+ if (!Database) throw new Error("better-sqlite3 not installed");
193
+ const dbPath = join(stateDir, "registry.db");
194
+ db = new Database(dbPath);
195
+ db.pragma("journal_mode = WAL");
196
+ db.pragma("busy_timeout = 3000");
197
+ db.pragma("foreign_keys = ON");
198
+ try {
199
+ db.exec("ALTER TABLE retrospect_fingerprints ADD COLUMN project_hash TEXT");
200
+ } catch {
201
+ }
202
+ db.exec(SCHEMA);
203
+ } catch (err) {
204
+ if (err?.code === "ESQLITE_BUNDLE_BROKEN") throw err;
205
+ if (err instanceof Error && err.message?.includes("better-sqlite3")) {
206
+ const hint = process.platform === "win32" ? 'Run: cd "$(npm root -g)\\tianshu-harness" && node scripts\\fetch-native-sqlite.js' : 'Run: cd "$(npm root -g)/tianshu-harness" && node scripts/fetch-native-sqlite.js';
207
+ console.warn(`\u26A0 better-sqlite3 not available. Session history & cross-session memory will NOT persist (in-memory only, lost on exit). Reason: ${err.message}
208
+ Fix: ${hint}`);
209
+ } else {
210
+ console.error("Session registry schema failed:", err);
211
+ }
212
+ db = createNullDb();
213
+ }
214
+ return new _SessionRegistry(db);
215
+ }
216
+ /**
217
+ * Create and immediately reap crashed sessions' rows/claims (pid-liveness
218
+ * sweep). A hard-killed process (kill -9 / power loss) leaves its exclusive
219
+ * claims behind in the shared db; without the sweep the R2 pre-write guard
220
+ * permanently rejects writes to those files, pointing at a dead session id.
221
+ * Single home for the TUI bootstrap and serve sidecar startup sweep — no
222
+ * auto-resume: crashed sessions stay recoverable via --continue / --resume.
223
+ */
224
+ static async createWithReap(stateDir, onReaped) {
225
+ const registry = await _SessionRegistry.create(stateDir);
226
+ const crashed = registry.detectCrashedSessions();
227
+ if (crashed.length > 0) onReaped?.(crashed);
228
+ return registry;
229
+ }
230
+ constructor(db) {
231
+ this.db = db;
232
+ }
233
+ // ── Safe DB helpers ──
234
+ safeRun(sql, ...params) {
235
+ try {
236
+ return this.db.prepare(sql).run(...params).changes ?? 0;
237
+ } catch (err) {
238
+ console.error("SessionRegistry write error:", err.message);
239
+ return 0;
240
+ }
241
+ }
242
+ safeGet(sql, ...params) {
243
+ try {
244
+ return this.db.prepare(sql).get(...params);
245
+ } catch (err) {
246
+ console.error("SessionRegistry read error:", err.message);
247
+ return void 0;
248
+ }
249
+ }
250
+ safeAll(sql, ...params) {
251
+ try {
252
+ return this.db.prepare(sql).all(...params);
253
+ } catch (err) {
254
+ console.error("SessionRegistry read error:", err.message);
255
+ return [];
256
+ }
257
+ }
258
+ register(sessionId, cwd, role = "standalone") {
259
+ const now = (/* @__PURE__ */ new Date()).toISOString();
260
+ this.safeRun(`
261
+ INSERT INTO sessions (id, pid, cwd, started_at, heartbeat_at, role)
262
+ VALUES (?, ?, ?, ?, ?, ?)
263
+ ON CONFLICT(id) DO UPDATE SET
264
+ pid = excluded.pid,
265
+ cwd = excluded.cwd,
266
+ heartbeat_at = excluded.heartbeat_at,
267
+ role = excluded.role
268
+ `, sessionId, process.pid, cwd, now, now, role);
269
+ }
270
+ heartbeat(sessionId) {
271
+ const now = (/* @__PURE__ */ new Date()).toISOString();
272
+ this.safeRun("UPDATE sessions SET heartbeat_at = ? WHERE id = ?", now, sessionId);
273
+ }
274
+ unregister(sessionId) {
275
+ this.releaseAllClaims(sessionId);
276
+ this.safeRun("DELETE FROM sessions WHERE id = ?", sessionId);
277
+ }
278
+ updatePid(sessionId, pid) {
279
+ this.safeRun("UPDATE sessions SET pid = ? WHERE id = ?", pid, sessionId);
280
+ }
281
+ listActive() {
282
+ return this.safeAll("SELECT id, pid, role, task_description AS taskDescription, heartbeat_at AS heartbeatAt FROM sessions");
283
+ }
284
+ /**
285
+ * Count other sessions running on the same cwd with a live pid and recent
286
+ * heartbeat. Used by VSW snapshot-policy to decide whether to isolate (C2).
287
+ *
288
+ * @param cwd project root to match against
289
+ * @param mySessionId exclude self
290
+ * @param maxHeartbeatAgeMs sessions with heartbeat older than this are considered stale (default 120s)
291
+ */
292
+ countSameCwdActive(cwd, mySessionId, maxHeartbeatAgeMs = 12e4) {
293
+ const cutoff = new Date(Date.now() - maxHeartbeatAgeMs).toISOString();
294
+ const rows = this.safeAll(
295
+ `SELECT id, pid FROM sessions WHERE cwd = ? AND id != ? AND heartbeat_at > ?`,
296
+ cwd,
297
+ mySessionId,
298
+ cutoff
299
+ );
300
+ return rows.filter((r) => this.isProcessRunning(r.pid)).length;
301
+ }
302
+ detectCrashedSessions() {
303
+ const sessions = this.listActive();
304
+ const crashed = [];
305
+ for (const s of sessions) {
306
+ if (!this.isProcessRunning(s.pid)) {
307
+ crashed.push(s);
308
+ }
309
+ }
310
+ if (crashed.length > 0) {
311
+ const ids = crashed.map((s) => s.id);
312
+ const placeholders = ids.map(() => "?").join(",");
313
+ this.safeRun(`DELETE FROM claims WHERE session_id IN (${placeholders})`, ...ids);
314
+ this.safeRun(`DELETE FROM sessions WHERE id IN (${placeholders})`, ...ids);
315
+ }
316
+ return crashed;
317
+ }
318
+ acquireClaim(sessionId, filePath, claimType) {
319
+ const existing = this.safeAll(
320
+ "SELECT session_id, claim_type FROM claims WHERE file_path = ?",
321
+ filePath
322
+ );
323
+ for (const c of existing) {
324
+ if (c.session_id === sessionId) return true;
325
+ if (c.claim_type === "exclusive") return false;
326
+ if (claimType === "exclusive") return false;
327
+ }
328
+ const now = (/* @__PURE__ */ new Date()).toISOString();
329
+ const changes = this.safeRun(
330
+ "INSERT OR REPLACE INTO claims (session_id, file_path, claim_type, acquired_at) VALUES (?, ?, ?, ?)",
331
+ sessionId,
332
+ filePath,
333
+ claimType,
334
+ now
335
+ );
336
+ return changes > 0;
337
+ }
338
+ releaseClaim(sessionId, filePath) {
339
+ this.safeRun("DELETE FROM claims WHERE session_id = ? AND file_path = ?", sessionId, filePath);
340
+ }
341
+ releaseAllClaims(sessionId) {
342
+ this.safeRun("DELETE FROM claims WHERE session_id = ?", sessionId);
343
+ }
344
+ checkClaim(filePath) {
345
+ const row = this.safeGet(
346
+ "SELECT session_id AS sessionId, claim_type AS claimType, file_path AS filePath FROM claims WHERE file_path = ? LIMIT 1",
347
+ filePath
348
+ );
349
+ return row ?? null;
350
+ }
351
+ reapStaleClaims() {
352
+ const sessions = this.listActive();
353
+ const deadIds = [];
354
+ for (const s of sessions) {
355
+ if (!this.isProcessRunning(s.pid)) {
356
+ deadIds.push(s.id);
357
+ }
358
+ }
359
+ if (deadIds.length === 0) return [];
360
+ const placeholders = deadIds.map(() => "?").join(",");
361
+ const rows = this.safeAll(
362
+ `SELECT DISTINCT file_path FROM claims WHERE session_id IN (${placeholders})`,
363
+ ...deadIds
364
+ );
365
+ this.safeRun(`DELETE FROM claims WHERE session_id IN (${placeholders})`, ...deadIds);
366
+ this.safeRun(`DELETE FROM sessions WHERE id IN (${placeholders})`, ...deadIds);
367
+ return rows.map((r) => r.file_path);
368
+ }
369
+ close() {
370
+ this.db.close();
371
+ }
372
+ // ── Events (cross-session communication) ──────────────────
373
+ publishEvent(sessionId, input) {
374
+ this.safeRun(`
375
+ INSERT INTO events (session_id, event_type, file_path, detail, priority)
376
+ VALUES (?, ?, ?, ?, ?)
377
+ `, sessionId, input.eventType, input.filePath ?? null, input.detail ?? null, input.priority ?? 0);
378
+ }
379
+ consumeEvents(mySessionId, lastSeenId, limit = 50) {
380
+ return this.safeAll(`
381
+ SELECT id, session_id AS sessionId, event_type AS eventType,
382
+ file_path AS filePath, detail, priority, created_at AS createdAt
383
+ FROM events
384
+ WHERE id > ? AND session_id != ?
385
+ ORDER BY id ASC
386
+ LIMIT ?
387
+ `, lastSeenId, mySessionId, limit);
388
+ }
389
+ cleanupOldEvents(maxAgeMs) {
390
+ const cutoff = new Date(Date.now() - maxAgeMs).toISOString();
391
+ return this.safeRun("DELETE FROM events WHERE created_at < ?", cutoff);
392
+ }
393
+ // ── Cycle relay (Songline substrate) ─────────────────────
394
+ setCycleOpen(sessionId, cycleOpen, generation = 0) {
395
+ this.safeRun(`
396
+ INSERT INTO cycle_relay (session_id, cycle_open, generation)
397
+ VALUES (?, ?, ?)
398
+ ON CONFLICT(session_id) DO UPDATE SET
399
+ cycle_open = excluded.cycle_open,
400
+ generation = excluded.generation
401
+ `, sessionId, cycleOpen, generation);
402
+ }
403
+ setCycleClose(sessionId, cycleClose) {
404
+ this.safeRun(`
405
+ INSERT INTO cycle_relay (session_id, cycle_open, cycle_close, generation, closed_at)
406
+ VALUES (?, ?, ?, 0, datetime('now'))
407
+ ON CONFLICT(session_id) DO UPDATE SET
408
+ cycle_close = excluded.cycle_close,
409
+ generation = cycle_relay.generation + 1,
410
+ closed_at = excluded.closed_at
411
+ `, sessionId, cycleClose, cycleClose);
412
+ }
413
+ getCycleOpen(sessionId) {
414
+ const row = this.safeGet(
415
+ "SELECT cycle_open AS cycleOpen FROM cycle_relay WHERE session_id = ?",
416
+ sessionId
417
+ );
418
+ return row?.cycleOpen ?? null;
419
+ }
420
+ getCycleClose(sessionId) {
421
+ const row = this.safeGet(
422
+ "SELECT cycle_close AS cycleClose FROM cycle_relay WHERE session_id = ?",
423
+ sessionId
424
+ );
425
+ return row?.cycleClose ?? null;
426
+ }
427
+ getLastCycleClose() {
428
+ const row = this.safeGet(`
429
+ SELECT cycle_close AS cycleClose
430
+ FROM cycle_relay
431
+ WHERE cycle_close IS NOT NULL
432
+ ORDER BY closed_at DESC, created_at DESC
433
+ LIMIT 1
434
+ `);
435
+ return row?.cycleClose ?? null;
436
+ }
437
+ getCycleRelay(sessionId) {
438
+ const row = this.safeGet(`
439
+ SELECT session_id AS sessionId,
440
+ cycle_open AS cycleOpen,
441
+ cycle_close AS cycleClose,
442
+ generation,
443
+ created_at AS createdAt,
444
+ closed_at AS closedAt
445
+ FROM cycle_relay
446
+ WHERE session_id = ?
447
+ `, sessionId);
448
+ return row ?? null;
449
+ }
450
+ // ── Cross-session claims snapshot for prompt injection ──
451
+ /**
452
+ * Return all active claims grouped by file, excluding claims held by `mySessionId`.
453
+ * Used for prompt injection so the LLM can see which files other sessions are working on.
454
+ */
455
+ getActiveClaims(excludeSessionId) {
456
+ return this.safeAll(`
457
+ SELECT c.session_id AS sessionId, c.file_path AS filePath, c.claim_type AS claimType
458
+ FROM claims c
459
+ JOIN sessions s ON s.id = c.session_id
460
+ WHERE c.session_id != ?
461
+ `, excludeSessionId);
462
+ }
463
+ isProcessRunning(pid) {
464
+ try {
465
+ process.kill(pid, 0);
466
+ return true;
467
+ } catch {
468
+ return false;
469
+ }
470
+ }
471
+ // ── Retrospect Fingerprints (REM pattern detection) ──────
472
+ /**
473
+ * 存储 session 的 retrospect 指纹。
474
+ * 如果该 session 已有指纹,会被覆盖(UPSERT)。
475
+ */
476
+ storeFingerprint(fp) {
477
+ this.safeRun(
478
+ `
479
+ INSERT INTO retrospect_fingerprints
480
+ (session_id, created_at, root_cause_keywords, recommendation_keywords,
481
+ stability_trend, confidence_trend, max_pressure, tool_failure_rate, bullet_ids, project_hash)
482
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
483
+ ON CONFLICT(session_id) DO UPDATE SET
484
+ created_at = excluded.created_at,
485
+ root_cause_keywords = excluded.root_cause_keywords,
486
+ recommendation_keywords = excluded.recommendation_keywords,
487
+ stability_trend = excluded.stability_trend,
488
+ confidence_trend = excluded.confidence_trend,
489
+ max_pressure = excluded.max_pressure,
490
+ tool_failure_rate = excluded.tool_failure_rate,
491
+ bullet_ids = excluded.bullet_ids,
492
+ project_hash = excluded.project_hash
493
+ `,
494
+ fp.sessionId,
495
+ fp.createdAt,
496
+ JSON.stringify(fp.rootCauseKeywords),
497
+ JSON.stringify(fp.recommendationKeywords),
498
+ fp.stabilityTrend,
499
+ fp.confidenceTrend,
500
+ fp.maxPressure,
501
+ fp.toolFailureRate,
502
+ JSON.stringify(fp.bulletIds),
503
+ fp.projectHash ?? null
504
+ );
505
+ }
506
+ /**
507
+ * 加载历史指纹,按时间倒序。
508
+ * @param limit 最多返回的指纹数量(默认 10)
509
+ * @param excludeSessionId 排除的 session ID(通常是当前 session)
510
+ */
511
+ loadFingerprints(limit = 10, excludeSessionId, projectHash2) {
512
+ const conditions = [];
513
+ const params = [];
514
+ if (excludeSessionId) {
515
+ conditions.push("session_id != ?");
516
+ params.push(excludeSessionId);
517
+ }
518
+ if (projectHash2) {
519
+ conditions.push("project_hash = ?");
520
+ params.push(projectHash2);
521
+ }
522
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
523
+ params.push(limit);
524
+ const query = `SELECT session_id, created_at, root_cause_keywords, recommendation_keywords,
525
+ stability_trend, confidence_trend, max_pressure, tool_failure_rate, bullet_ids
526
+ FROM retrospect_fingerprints
527
+ ${where}
528
+ ORDER BY created_at DESC
529
+ LIMIT ?`;
530
+ const rows = this.safeAll(query, ...params);
531
+ return rows.map((row) => ({
532
+ sessionId: row.session_id,
533
+ createdAt: row.created_at,
534
+ rootCauseKeywords: JSON.parse(row.root_cause_keywords),
535
+ recommendationKeywords: JSON.parse(row.recommendation_keywords),
536
+ stabilityTrend: validateTrend(row.stability_trend, "stable"),
537
+ confidenceTrend: validateTrend(row.confidence_trend, "stable"),
538
+ maxPressure: row.max_pressure,
539
+ toolFailureRate: row.tool_failure_rate,
540
+ bulletIds: JSON.parse(row.bullet_ids)
541
+ }));
542
+ }
543
+ /**
544
+ * 清理旧指纹。
545
+ * @param maxAgeMs 最大保留时间(毫秒)
546
+ * @returns 删除的指纹数量
547
+ */
548
+ cleanupOldFingerprints(maxAgeMs) {
549
+ const cutoff = Date.now() - maxAgeMs;
550
+ return this.safeRun("DELETE FROM retrospect_fingerprints WHERE created_at < ?", cutoff);
551
+ }
552
+ };
553
+ function createNullDb() {
554
+ const noopStmt = {
555
+ run: () => ({ changes: 1, lastInsertRowid: 0 }),
556
+ all: () => [],
557
+ get: () => void 0
558
+ };
559
+ return new Proxy(/* @__PURE__ */ Object.create(null), {
560
+ get: (_target, prop) => {
561
+ if (prop === "prepare") return () => noopStmt;
562
+ if (prop === "exec") return () => {
563
+ };
564
+ if (prop === "pragma") return () => {
565
+ };
566
+ if (prop === "close") return () => {
567
+ };
568
+ return () => {
569
+ };
570
+ }
571
+ });
572
+ }
573
+
574
+ export {
575
+ buildRetrospectFingerprint,
576
+ fingerprintSimilarity,
577
+ projectHash,
578
+ _setBackendForTest,
579
+ _resetBackendForTest,
580
+ SessionRegistry
581
+ };