@tt-a1i/hive 1.7.0 → 2.0.2

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 (251) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.en.md +73 -11
  3. package/README.md +41 -8
  4. package/dist/src/cli/hive-remote.d.ts +46 -0
  5. package/dist/src/cli/hive-remote.js +257 -0
  6. package/dist/src/cli/hive-update.js +7 -2
  7. package/dist/src/cli/hive.d.ts +6 -0
  8. package/dist/src/cli/hive.js +64 -0
  9. package/dist/src/cli/team.d.ts +22 -0
  10. package/dist/src/cli/team.js +255 -5
  11. package/dist/src/server/agent-command-resolver.js +10 -3
  12. package/dist/src/server/agent-exit-classification.d.ts +6 -0
  13. package/dist/src/server/agent-exit-classification.js +6 -0
  14. package/dist/src/server/agent-manager-support.d.ts +2 -1
  15. package/dist/src/server/agent-manager-support.js +59 -15
  16. package/dist/src/server/agent-manager.d.ts +3 -0
  17. package/dist/src/server/agent-manager.js +22 -7
  18. package/dist/src/server/agent-run-bootstrap.d.ts +14 -0
  19. package/dist/src/server/agent-run-bootstrap.js +11 -4
  20. package/dist/src/server/agent-run-exit-handler.js +14 -8
  21. package/dist/src/server/agent-run-starter.d.ts +3 -1
  22. package/dist/src/server/agent-run-starter.js +22 -5
  23. package/dist/src/server/agent-run-sync.js +13 -5
  24. package/dist/src/server/agent-runtime-types.d.ts +1 -0
  25. package/dist/src/server/agent-runtime.d.ts +2 -1
  26. package/dist/src/server/agent-runtime.js +9 -2
  27. package/dist/src/server/agent-startup-instructions.d.ts +2 -1
  28. package/dist/src/server/agent-startup-instructions.js +8 -4
  29. package/dist/src/server/agent-stdin-dispatcher.d.ts +4 -2
  30. package/dist/src/server/agent-stdin-dispatcher.js +35 -3
  31. package/dist/src/server/command-preset-defaults.d.ts +6 -1
  32. package/dist/src/server/command-preset-defaults.js +56 -0
  33. package/dist/src/server/fs-browse.d.ts +2 -0
  34. package/dist/src/server/fs-browse.js +165 -31
  35. package/dist/src/server/fs-pick-folder.js +6 -69
  36. package/dist/src/server/fs-sandbox.d.ts +5 -3
  37. package/dist/src/server/fs-sandbox.js +5 -3
  38. package/dist/src/server/hive-team-guidance.js +18 -6
  39. package/dist/src/server/machine-name.d.ts +2 -0
  40. package/dist/src/server/machine-name.js +13 -0
  41. package/dist/src/server/open-target-commands.d.ts +1 -0
  42. package/dist/src/server/open-target-commands.js +4 -1
  43. package/dist/src/server/orchestrator-autostart.js +1 -1
  44. package/dist/src/server/platform-path.d.ts +1 -0
  45. package/dist/src/server/platform-path.js +14 -1
  46. package/dist/src/server/post-start-input-writer.js +50 -13
  47. package/dist/src/server/preset-launch-support.js +1 -0
  48. package/dist/src/server/recovery-summary.d.ts +2 -1
  49. package/dist/src/server/recovery-summary.js +2 -1
  50. package/dist/src/server/remote-audit-store.d.ts +51 -0
  51. package/dist/src/server/remote-audit-store.js +108 -0
  52. package/dist/src/server/remote-config-keys.d.ts +17 -0
  53. package/dist/src/server/remote-config-keys.js +27 -0
  54. package/dist/src/server/remote-control-constants.d.ts +30 -0
  55. package/dist/src/server/remote-control-constants.js +29 -0
  56. package/dist/src/server/remote-device-session.d.ts +40 -0
  57. package/dist/src/server/remote-device-session.js +22 -0
  58. package/dist/src/server/remote-device-store.d.ts +36 -0
  59. package/dist/src/server/remote-device-store.js +67 -0
  60. package/dist/src/server/remote-frame-bridge.d.ts +102 -0
  61. package/dist/src/server/remote-frame-bridge.js +791 -0
  62. package/dist/src/server/remote-gateway-client.d.ts +14 -0
  63. package/dist/src/server/remote-gateway-client.js +36 -0
  64. package/dist/src/server/remote-loopback-auth.d.ts +6 -0
  65. package/dist/src/server/remote-loopback-auth.js +112 -0
  66. package/dist/src/server/remote-pairing-tunnel.d.ts +59 -0
  67. package/dist/src/server/remote-pairing-tunnel.js +146 -0
  68. package/dist/src/server/remote-pairing.d.ts +58 -0
  69. package/dist/src/server/remote-pairing.js +237 -0
  70. package/dist/src/server/remote-tunnel.d.ts +113 -0
  71. package/dist/src/server/remote-tunnel.js +514 -0
  72. package/dist/src/server/restart-policy-support.d.ts +4 -1
  73. package/dist/src/server/restart-policy-support.js +3 -1
  74. package/dist/src/server/restart-policy.d.ts +1 -1
  75. package/dist/src/server/restart-policy.js +19 -3
  76. package/dist/src/server/route-types.d.ts +1 -1
  77. package/dist/src/server/routes-dispatches.js +1 -1
  78. package/dist/src/server/routes-fs.js +3 -3
  79. package/dist/src/server/routes-marketplace.js +2 -2
  80. package/dist/src/server/routes-open-workspace.js +1 -1
  81. package/dist/src/server/routes-remote.d.ts +2 -0
  82. package/dist/src/server/routes-remote.js +166 -0
  83. package/dist/src/server/routes-runtime.js +6 -6
  84. package/dist/src/server/routes-settings.js +16 -16
  85. package/dist/src/server/routes-tasks.js +2 -2
  86. package/dist/src/server/routes-team-memory.d.ts +2 -0
  87. package/dist/src/server/routes-team-memory.js +154 -0
  88. package/dist/src/server/routes-team-recall.d.ts +2 -0
  89. package/dist/src/server/routes-team-recall.js +119 -0
  90. package/dist/src/server/routes-team.js +31 -9
  91. package/dist/src/server/routes-ui.js +11 -1
  92. package/dist/src/server/routes-workflow-schedules.js +3 -3
  93. package/dist/src/server/routes-workflows.js +5 -5
  94. package/dist/src/server/routes-workspace-memory-dreams.d.ts +2 -0
  95. package/dist/src/server/routes-workspace-memory-dreams.js +105 -0
  96. package/dist/src/server/routes-workspace-memory.d.ts +2 -0
  97. package/dist/src/server/routes-workspace-memory.js +215 -0
  98. package/dist/src/server/routes-workspaces.js +9 -9
  99. package/dist/src/server/routes.js +10 -0
  100. package/dist/src/server/runtime-database.d.ts +1 -0
  101. package/dist/src/server/runtime-database.js +27 -2
  102. package/dist/src/server/runtime-restart-policy.d.ts +3 -1
  103. package/dist/src/server/runtime-restart-policy.js +2 -1
  104. package/dist/src/server/runtime-store-contract.d.ts +37 -0
  105. package/dist/src/server/runtime-store-dream.d.ts +23 -0
  106. package/dist/src/server/runtime-store-dream.js +16 -0
  107. package/dist/src/server/runtime-store-helpers.d.ts +20 -0
  108. package/dist/src/server/runtime-store-helpers.js +81 -7
  109. package/dist/src/server/runtime-store-memory.d.ts +33 -0
  110. package/dist/src/server/runtime-store-memory.js +37 -0
  111. package/dist/src/server/runtime-store-remote.d.ts +5 -0
  112. package/dist/src/server/runtime-store-remote.js +45 -0
  113. package/dist/src/server/runtime-store-workflows.js +2 -0
  114. package/dist/src/server/runtime-store.js +14 -3
  115. package/dist/src/server/session-capture-claude.d.ts +1 -1
  116. package/dist/src/server/session-capture-claude.js +7 -4
  117. package/dist/src/server/session-capture-codex.js +4 -5
  118. package/dist/src/server/session-capture-gemini.js +4 -5
  119. package/dist/src/server/session-capture-opencode.d.ts +4 -4
  120. package/dist/src/server/session-capture-opencode.js +20 -12
  121. package/dist/src/server/session-capture-qwen.d.ts +5 -0
  122. package/dist/src/server/session-capture-qwen.js +104 -0
  123. package/dist/src/server/session-capture.d.ts +17 -0
  124. package/dist/src/server/session-capture.js +16 -0
  125. package/dist/src/server/sqlite-schema-v23.d.ts +2 -0
  126. package/dist/src/server/sqlite-schema-v23.js +43 -0
  127. package/dist/src/server/sqlite-schema-v24.d.ts +2 -0
  128. package/dist/src/server/sqlite-schema-v24.js +34 -0
  129. package/dist/src/server/sqlite-schema-v25.d.ts +2 -0
  130. package/dist/src/server/sqlite-schema-v25.js +127 -0
  131. package/dist/src/server/sqlite-schema-v26.d.ts +2 -0
  132. package/dist/src/server/sqlite-schema-v26.js +56 -0
  133. package/dist/src/server/sqlite-schema-v27.d.ts +6 -0
  134. package/dist/src/server/sqlite-schema-v27.js +92 -0
  135. package/dist/src/server/sqlite-schema-v28.d.ts +2 -0
  136. package/dist/src/server/sqlite-schema-v28.js +19 -0
  137. package/dist/src/server/sqlite-schema-v29.d.ts +2 -0
  138. package/dist/src/server/sqlite-schema-v29.js +27 -0
  139. package/dist/src/server/sqlite-schema-v30.d.ts +2 -0
  140. package/dist/src/server/sqlite-schema-v30.js +27 -0
  141. package/dist/src/server/sqlite-schema-v31.d.ts +2 -0
  142. package/dist/src/server/sqlite-schema-v31.js +30 -0
  143. package/dist/src/server/sqlite-schema.d.ts +1 -1
  144. package/dist/src/server/sqlite-schema.js +49 -1
  145. package/dist/src/server/startup-command-parser.js +5 -1
  146. package/dist/src/server/tasks-file-watcher.d.ts +2 -0
  147. package/dist/src/server/tasks-file-watcher.js +15 -6
  148. package/dist/src/server/tasks-file.js +30 -5
  149. package/dist/src/server/tasks-websocket-server.js +4 -0
  150. package/dist/src/server/team-authz.d.ts +1 -1
  151. package/dist/src/server/team-authz.js +13 -1
  152. package/dist/src/server/team-list-enrichment.js +3 -1
  153. package/dist/src/server/team-memory-digest.d.ts +52 -0
  154. package/dist/src/server/team-memory-digest.js +200 -0
  155. package/dist/src/server/team-memory-dream-applier.d.ts +5 -0
  156. package/dist/src/server/team-memory-dream-applier.js +234 -0
  157. package/dist/src/server/team-memory-dream-http-serializers.d.ts +13 -0
  158. package/dist/src/server/team-memory-dream-http-serializers.js +12 -0
  159. package/dist/src/server/team-memory-dream-ops.d.ts +40 -0
  160. package/dist/src/server/team-memory-dream-ops.js +153 -0
  161. package/dist/src/server/team-memory-dream-reverter.d.ts +22 -0
  162. package/dist/src/server/team-memory-dream-reverter.js +221 -0
  163. package/dist/src/server/team-memory-dream-run-store.d.ts +23 -0
  164. package/dist/src/server/team-memory-dream-run-store.js +211 -0
  165. package/dist/src/server/team-memory-dream-runner.d.ts +37 -0
  166. package/dist/src/server/team-memory-dream-runner.js +178 -0
  167. package/dist/src/server/team-memory-dream-scheduler.d.ts +32 -0
  168. package/dist/src/server/team-memory-dream-scheduler.js +115 -0
  169. package/dist/src/server/team-memory-dream-store.d.ts +19 -0
  170. package/dist/src/server/team-memory-dream-store.js +16 -0
  171. package/dist/src/server/team-memory-dream-types.d.ts +104 -0
  172. package/dist/src/server/team-memory-dream-types.js +23 -0
  173. package/dist/src/server/team-memory-export.d.ts +22 -0
  174. package/dist/src/server/team-memory-export.js +220 -0
  175. package/dist/src/server/team-memory-feature.d.ts +12 -0
  176. package/dist/src/server/team-memory-feature.js +12 -0
  177. package/dist/src/server/team-memory-http-serializers.d.ts +102 -0
  178. package/dist/src/server/team-memory-http-serializers.js +46 -0
  179. package/dist/src/server/team-memory-injection.d.ts +31 -0
  180. package/dist/src/server/team-memory-injection.js +49 -0
  181. package/dist/src/server/team-memory-store.d.ts +116 -0
  182. package/dist/src/server/team-memory-store.js +513 -0
  183. package/dist/src/server/team-operations.d.ts +5 -1
  184. package/dist/src/server/team-operations.js +46 -16
  185. package/dist/src/server/team-recall-store.d.ts +38 -0
  186. package/dist/src/server/team-recall-store.js +205 -0
  187. package/dist/src/server/terminal-input-profile.d.ts +1 -1
  188. package/dist/src/server/terminal-input-profile.js +18 -0
  189. package/dist/src/server/terminal-ws-server.js +6 -0
  190. package/dist/src/server/ui-auth-helpers.d.ts +1 -1
  191. package/dist/src/server/ui-auth-helpers.js +7 -1
  192. package/dist/src/server/ui-auth.d.ts +3 -0
  193. package/dist/src/server/ui-auth.js +21 -1
  194. package/dist/src/server/workflow-cli-policy.d.ts +2 -3
  195. package/dist/src/server/workflow-cli-policy.js +3 -3
  196. package/dist/src/server/workflow-runner.d.ts +1 -0
  197. package/dist/src/server/workflow-runner.js +9 -4
  198. package/dist/src/server/workspace-path-validation.js +6 -2
  199. package/dist/src/server/workspace-store.d.ts +1 -1
  200. package/dist/src/server/workspace-store.js +35 -9
  201. package/dist/src/shared/fs-browse.d.ts +1 -0
  202. package/dist/src/shared/fs-browse.js +1 -0
  203. package/dist/src/shared/path-input.d.ts +12 -0
  204. package/dist/src/shared/path-input.js +22 -0
  205. package/dist/src/shared/remote-bridge-routing.d.ts +19 -0
  206. package/dist/src/shared/remote-bridge-routing.js +141 -0
  207. package/dist/src/shared/remote-crypto.d.ts +138 -0
  208. package/dist/src/shared/remote-crypto.js +427 -0
  209. package/dist/src/shared/remote-pairing-code.d.ts +7 -0
  210. package/dist/src/shared/remote-pairing-code.js +47 -0
  211. package/dist/src/shared/remote-protocol.d.ts +160 -0
  212. package/dist/src/shared/remote-protocol.js +526 -0
  213. package/dist/src/shared/team-memory.d.ts +11 -0
  214. package/dist/src/shared/team-memory.js +10 -0
  215. package/dist/src/shared/team-recall.d.ts +1 -0
  216. package/dist/src/shared/team-recall.js +1 -0
  217. package/dist/src/shared/types.d.ts +4 -5
  218. package/package.json +12 -5
  219. package/scripts/postinstall-native-artifacts.mjs +113 -0
  220. package/web/dist/assets/AddWorkerDialog-CbV75qUX.js +2 -0
  221. package/web/dist/assets/AddWorkspaceFlow-CwV-7wPx.js +1 -0
  222. package/web/dist/assets/FirstRunWizard-a6PWIK3x.js +1 -0
  223. package/web/dist/assets/MarketplaceDrawer-Dd8WIA8T.js +67 -0
  224. package/web/dist/assets/TaskGraphDrawer-Bk5WFIk_.js +1 -0
  225. package/web/dist/assets/{WhatsNewDialog-CHkZeINH.js → WhatsNewDialog-C2VZaip0.js} +1 -1
  226. package/web/dist/assets/WorkerModal-DucW-9YT.js +1 -0
  227. package/web/dist/assets/WorkflowsDrawer-Bjf4olbR.js +1 -0
  228. package/web/dist/assets/WorkspaceMemoryDrawer-DglCy_5f.js +1 -0
  229. package/web/dist/assets/WorkspaceTaskDrawer-BIWwISvA.js +1 -0
  230. package/web/dist/assets/index-BAiLYajK.css +1 -0
  231. package/web/dist/assets/index-BV2k9Dts.js +73 -0
  232. package/web/dist/assets/search-Bk2HQvO7.js +1 -0
  233. package/web/dist/assets/square-terminal-D93m9hfY.js +1 -0
  234. package/web/dist/cli-icons/agy.png +0 -0
  235. package/web/dist/cli-icons/cursor.ico +0 -0
  236. package/web/dist/cli-icons/grok.ico +0 -0
  237. package/web/dist/cli-icons/qwen.png +0 -0
  238. package/web/dist/index.html +8 -3
  239. package/web/dist/sw.js +1 -1
  240. package/scripts/fix-runtime-artifacts.mjs +0 -33
  241. package/web/dist/assets/AddWorkerDialog-BRUxpa3f.js +0 -2
  242. package/web/dist/assets/AddWorkspaceDialog-D56x5JCb.js +0 -1
  243. package/web/dist/assets/FirstRunWizard-BFVaMIsE.js +0 -1
  244. package/web/dist/assets/MarketplaceDrawer-DeEZ35dN.js +0 -76
  245. package/web/dist/assets/WorkerModal-BBCuMLIa.js +0 -1
  246. package/web/dist/assets/WorkspaceTaskDrawer-CpZHAcj1.js +0 -1
  247. package/web/dist/assets/WorkspaceTerminalPanels-7If2mDyp.js +0 -1
  248. package/web/dist/assets/WorkspaceTerminalPanels-DDGTF8rc.css +0 -1
  249. package/web/dist/assets/index-5zh61jMg.css +0 -1
  250. package/web/dist/assets/index-CxNL0O-C.js +0 -73
  251. package/web/dist/assets/path-join-7MR1s7b1.js +0 -1
@@ -0,0 +1,127 @@
1
+ const tableExists = (db, table) => Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get(table));
2
+ const quoteFtsToken = (value) => `"${value.replaceAll('"', '""')}"`;
3
+ const firstUnicodeTerm = (value) => {
4
+ if (!value)
5
+ return null;
6
+ const token = value.trim().split(/\s+/).find(Boolean);
7
+ if (!token)
8
+ return null;
9
+ return token;
10
+ };
11
+ const firstTrigramTerm = (value) => {
12
+ const token = firstUnicodeTerm(value);
13
+ if (!token)
14
+ return null;
15
+ return [...token].slice(0, Math.max(1, Math.min(3, [...token].length))).join('');
16
+ };
17
+ const ftsHasSampleHit = (db, table, term) => db
18
+ .prepare(`SELECT COUNT(*) AS count FROM ${table} WHERE ${table} MATCH ?`)
19
+ .get(quoteFtsToken(term)).count > 0;
20
+ const shouldRebuildMessageIndexes = (db) => {
21
+ const sample = db
22
+ .prepare(`SELECT text
23
+ FROM messages
24
+ ORDER BY sequence ASC
25
+ LIMIT 1`)
26
+ .get();
27
+ if (!sample)
28
+ return false;
29
+ const unicodeTerm = firstUnicodeTerm(sample.text);
30
+ const trigramTerm = firstTrigramTerm(sample.text);
31
+ if (!unicodeTerm && !trigramTerm)
32
+ return false;
33
+ const unicodeHealthy = unicodeTerm ? ftsHasSampleHit(db, 'messages_fts', unicodeTerm) : true;
34
+ const trigramHealthy = trigramTerm
35
+ ? ftsHasSampleHit(db, 'messages_fts_trigram', trigramTerm)
36
+ : true;
37
+ return !(unicodeHealthy && trigramHealthy);
38
+ };
39
+ const shouldRebuildDispatchIndexes = (db) => {
40
+ const sample = db
41
+ .prepare(`SELECT text, report_text
42
+ FROM dispatches
43
+ ORDER BY sequence ASC
44
+ LIMIT 1`)
45
+ .get();
46
+ if (!sample)
47
+ return false;
48
+ const unicodeTerm = firstUnicodeTerm(sample.text) ?? firstUnicodeTerm(sample.report_text);
49
+ const trigramTerm = firstTrigramTerm(sample.text) ?? firstTrigramTerm(sample.report_text);
50
+ if (!unicodeTerm && !trigramTerm)
51
+ return false;
52
+ const unicodeHealthy = unicodeTerm ? ftsHasSampleHit(db, 'dispatches_fts', unicodeTerm) : true;
53
+ const trigramHealthy = trigramTerm
54
+ ? ftsHasSampleHit(db, 'dispatches_fts_trigram', trigramTerm)
55
+ : true;
56
+ return !(unicodeHealthy && trigramHealthy);
57
+ };
58
+ export const applySchemaVersion25 = (db) => {
59
+ const messagesFtsExists = tableExists(db, 'messages_fts');
60
+ const messagesFtsTrigramExists = tableExists(db, 'messages_fts_trigram');
61
+ const dispatchesFtsExists = tableExists(db, 'dispatches_fts');
62
+ const dispatchesFtsTrigramExists = tableExists(db, 'dispatches_fts_trigram');
63
+ db.exec(`
64
+ CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts
65
+ USING fts5(text, content='messages', content_rowid='sequence', tokenize='unicode61');
66
+ CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts_trigram
67
+ USING fts5(text, content='messages', content_rowid='sequence', tokenize='trigram');
68
+
69
+ CREATE VIRTUAL TABLE IF NOT EXISTS dispatches_fts
70
+ USING fts5(text, report_text, content='dispatches', content_rowid='sequence', tokenize='unicode61');
71
+ CREATE VIRTUAL TABLE IF NOT EXISTS dispatches_fts_trigram
72
+ USING fts5(text, report_text, content='dispatches', content_rowid='sequence', tokenize='trigram');
73
+
74
+ CREATE TRIGGER IF NOT EXISTS messages_fts_ai AFTER INSERT ON messages BEGIN
75
+ INSERT INTO messages_fts(rowid, text) VALUES (new.sequence, new.text);
76
+ INSERT INTO messages_fts_trigram(rowid, text) VALUES (new.sequence, new.text);
77
+ END;
78
+ CREATE TRIGGER IF NOT EXISTS messages_fts_ad AFTER DELETE ON messages BEGIN
79
+ INSERT INTO messages_fts(messages_fts, rowid, text) VALUES('delete', old.sequence, old.text);
80
+ INSERT INTO messages_fts_trigram(messages_fts_trigram, rowid, text)
81
+ VALUES('delete', old.sequence, old.text);
82
+ END;
83
+ CREATE TRIGGER IF NOT EXISTS messages_fts_au AFTER UPDATE ON messages BEGIN
84
+ INSERT INTO messages_fts(messages_fts, rowid, text) VALUES('delete', old.sequence, old.text);
85
+ INSERT INTO messages_fts(rowid, text) VALUES (new.sequence, new.text);
86
+ INSERT INTO messages_fts_trigram(messages_fts_trigram, rowid, text)
87
+ VALUES('delete', old.sequence, old.text);
88
+ INSERT INTO messages_fts_trigram(rowid, text) VALUES (new.sequence, new.text);
89
+ END;
90
+
91
+ CREATE TRIGGER IF NOT EXISTS dispatches_fts_ai AFTER INSERT ON dispatches BEGIN
92
+ INSERT INTO dispatches_fts(rowid, text, report_text)
93
+ VALUES (new.sequence, new.text, new.report_text);
94
+ INSERT INTO dispatches_fts_trigram(rowid, text, report_text)
95
+ VALUES (new.sequence, new.text, new.report_text);
96
+ END;
97
+ CREATE TRIGGER IF NOT EXISTS dispatches_fts_ad AFTER DELETE ON dispatches BEGIN
98
+ INSERT INTO dispatches_fts(dispatches_fts, rowid, text, report_text)
99
+ VALUES('delete', old.sequence, old.text, old.report_text);
100
+ INSERT INTO dispatches_fts_trigram(dispatches_fts_trigram, rowid, text, report_text)
101
+ VALUES('delete', old.sequence, old.text, old.report_text);
102
+ END;
103
+ CREATE TRIGGER IF NOT EXISTS dispatches_fts_au AFTER UPDATE ON dispatches BEGIN
104
+ INSERT INTO dispatches_fts(dispatches_fts, rowid, text, report_text)
105
+ VALUES('delete', old.sequence, old.text, old.report_text);
106
+ INSERT INTO dispatches_fts(rowid, text, report_text)
107
+ VALUES (new.sequence, new.text, new.report_text);
108
+ INSERT INTO dispatches_fts_trigram(dispatches_fts_trigram, rowid, text, report_text)
109
+ VALUES('delete', old.sequence, old.text, old.report_text);
110
+ INSERT INTO dispatches_fts_trigram(rowid, text, report_text)
111
+ VALUES (new.sequence, new.text, new.report_text);
112
+ END;
113
+ `);
114
+ if (!messagesFtsExists ||
115
+ !messagesFtsTrigramExists ||
116
+ !dispatchesFtsExists ||
117
+ !dispatchesFtsTrigramExists ||
118
+ shouldRebuildMessageIndexes(db) ||
119
+ shouldRebuildDispatchIndexes(db)) {
120
+ db.exec(`
121
+ INSERT INTO messages_fts(messages_fts) VALUES('rebuild');
122
+ INSERT INTO messages_fts_trigram(messages_fts_trigram) VALUES('rebuild');
123
+ INSERT INTO dispatches_fts(dispatches_fts) VALUES('rebuild');
124
+ INSERT INTO dispatches_fts_trigram(dispatches_fts_trigram) VALUES('rebuild');
125
+ `);
126
+ }
127
+ };
@@ -0,0 +1,2 @@
1
+ import type { Database } from 'better-sqlite3';
2
+ export declare const applySchemaVersion26: (db: Database) => void;
@@ -0,0 +1,56 @@
1
+ export const applySchemaVersion26 = (db) => {
2
+ db.exec(`
3
+ CREATE TABLE IF NOT EXISTS memory_entries (
4
+ id TEXT PRIMARY KEY,
5
+ workspace_id TEXT,
6
+ scope TEXT NOT NULL DEFAULT 'workspace',
7
+ fts_rowid INTEGER UNIQUE,
8
+ kind TEXT NOT NULL,
9
+ body TEXT NOT NULL,
10
+ tags TEXT,
11
+ status TEXT NOT NULL,
12
+ source TEXT NOT NULL DEFAULT 'manual',
13
+ confidence REAL,
14
+ pinned INTEGER NOT NULL DEFAULT 0,
15
+ disabled INTEGER NOT NULL DEFAULT 0,
16
+ created_at INTEGER NOT NULL,
17
+ updated_at INTEGER NOT NULL,
18
+ archived_at INTEGER,
19
+ last_injected_at INTEGER
20
+ );
21
+ CREATE INDEX IF NOT EXISTS idx_memory_entries_ws_status
22
+ ON memory_entries(workspace_id, status);
23
+ CREATE INDEX IF NOT EXISTS idx_memory_entries_ws_updated
24
+ ON memory_entries(workspace_id, updated_at);
25
+
26
+ CREATE TABLE IF NOT EXISTS memory_sources (
27
+ id TEXT PRIMARY KEY,
28
+ memory_id TEXT NOT NULL REFERENCES memory_entries(id) ON DELETE CASCADE,
29
+ source_type TEXT NOT NULL,
30
+ source_id TEXT,
31
+ source_sequence INTEGER,
32
+ excerpt TEXT,
33
+ text_hash TEXT,
34
+ actor_agent_id_snapshot TEXT,
35
+ actor_name_snapshot TEXT,
36
+ actor_role_snapshot TEXT,
37
+ created_at INTEGER NOT NULL
38
+ );
39
+ CREATE INDEX IF NOT EXISTS idx_memory_sources_memory
40
+ ON memory_sources(memory_id);
41
+
42
+ CREATE TABLE IF NOT EXISTS memory_injections (
43
+ id TEXT PRIMARY KEY,
44
+ memory_id TEXT NOT NULL REFERENCES memory_entries(id) ON DELETE CASCADE,
45
+ workspace_id TEXT,
46
+ target_agent_id_snapshot TEXT,
47
+ context_type TEXT NOT NULL,
48
+ dispatch_id TEXT,
49
+ injected_at INTEGER NOT NULL
50
+ );
51
+ CREATE INDEX IF NOT EXISTS idx_memory_injections_ws
52
+ ON memory_injections(workspace_id, injected_at);
53
+ CREATE INDEX IF NOT EXISTS idx_memory_injections_memory
54
+ ON memory_injections(memory_id, injected_at);
55
+ `);
56
+ };
@@ -0,0 +1,6 @@
1
+ import type { Database } from 'better-sqlite3';
2
+ interface ApplySchemaVersion27Options {
3
+ rebuild?: boolean;
4
+ }
5
+ export declare const applySchemaVersion27: (db: Database, options?: ApplySchemaVersion27Options) => void;
6
+ export {};
@@ -0,0 +1,92 @@
1
+ const tableExists = (db, table) => Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get(table));
2
+ const ensureFtsRowidColumn = (db) => {
3
+ const columns = new Set(db.prepare('PRAGMA table_info(memory_entries)').all().map((column) => column.name));
4
+ if (!columns.has('fts_rowid')) {
5
+ db.exec('ALTER TABLE memory_entries ADD COLUMN fts_rowid INTEGER');
6
+ }
7
+ db.exec(`
8
+ UPDATE memory_entries
9
+ SET fts_rowid = rowid
10
+ WHERE fts_rowid IS NULL;
11
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_entries_fts_rowid
12
+ ON memory_entries(fts_rowid)
13
+ WHERE fts_rowid IS NOT NULL;
14
+ `);
15
+ };
16
+ const quoteFtsToken = (value) => `"${value.replaceAll('"', '""')}"`;
17
+ const firstUnicodeTerm = (value) => {
18
+ if (!value)
19
+ return null;
20
+ const token = value.trim().split(/\s+/).find(Boolean);
21
+ if (!token)
22
+ return null;
23
+ return token;
24
+ };
25
+ const firstTrigramTerm = (value) => {
26
+ const token = firstUnicodeTerm(value);
27
+ if (!token)
28
+ return null;
29
+ return [...token].slice(0, Math.max(1, Math.min(3, [...token].length))).join('');
30
+ };
31
+ const ftsHasSampleHit = (db, table, term) => db
32
+ .prepare(`SELECT COUNT(*) AS count FROM ${table} WHERE ${table} MATCH ?`)
33
+ .get(quoteFtsToken(term)).count > 0;
34
+ const shouldRebuildExistingIndex = (db) => {
35
+ const sample = db
36
+ .prepare(`SELECT body, tags
37
+ FROM memory_entries
38
+ WHERE scope = 'workspace'
39
+ ORDER BY created_at ASC, id ASC
40
+ LIMIT 1`)
41
+ .get();
42
+ if (!sample)
43
+ return false;
44
+ const unicodeTerm = firstUnicodeTerm(sample.body) ?? firstUnicodeTerm(sample.tags);
45
+ const trigramTerm = firstTrigramTerm(sample.body) ?? firstTrigramTerm(sample.tags);
46
+ if (!unicodeTerm && !trigramTerm)
47
+ return false;
48
+ const unicodeHealthy = unicodeTerm ? ftsHasSampleHit(db, 'memory_fts', unicodeTerm) : true;
49
+ const trigramHealthy = trigramTerm ? ftsHasSampleHit(db, 'memory_fts_trigram', trigramTerm) : true;
50
+ return !(unicodeHealthy && trigramHealthy);
51
+ };
52
+ export const applySchemaVersion27 = (db, options = {}) => {
53
+ ensureFtsRowidColumn(db);
54
+ const memoryFtsExists = tableExists(db, 'memory_fts');
55
+ const memoryFtsTrigramExists = tableExists(db, 'memory_fts_trigram');
56
+ db.exec(`
57
+ CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts
58
+ USING fts5(body, tags, content='memory_entries', content_rowid='fts_rowid', tokenize='unicode61');
59
+ CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts_trigram
60
+ USING fts5(body, tags, content='memory_entries', content_rowid='fts_rowid', tokenize='trigram');
61
+
62
+ CREATE TRIGGER IF NOT EXISTS memory_fts_ai AFTER INSERT ON memory_entries BEGIN
63
+ INSERT INTO memory_fts(rowid, body, tags) VALUES (new.fts_rowid, new.body, new.tags);
64
+ INSERT INTO memory_fts_trigram(rowid, body, tags)
65
+ VALUES (new.fts_rowid, new.body, new.tags);
66
+ END;
67
+ CREATE TRIGGER IF NOT EXISTS memory_fts_ad AFTER DELETE ON memory_entries BEGIN
68
+ INSERT INTO memory_fts(memory_fts, rowid, body, tags)
69
+ VALUES('delete', old.fts_rowid, old.body, old.tags);
70
+ INSERT INTO memory_fts_trigram(memory_fts_trigram, rowid, body, tags)
71
+ VALUES('delete', old.fts_rowid, old.body, old.tags);
72
+ END;
73
+ CREATE TRIGGER IF NOT EXISTS memory_fts_au AFTER UPDATE ON memory_entries BEGIN
74
+ INSERT INTO memory_fts(memory_fts, rowid, body, tags)
75
+ VALUES('delete', old.fts_rowid, old.body, old.tags);
76
+ INSERT INTO memory_fts(rowid, body, tags) VALUES (new.fts_rowid, new.body, new.tags);
77
+ INSERT INTO memory_fts_trigram(memory_fts_trigram, rowid, body, tags)
78
+ VALUES('delete', old.fts_rowid, old.body, old.tags);
79
+ INSERT INTO memory_fts_trigram(rowid, body, tags)
80
+ VALUES (new.fts_rowid, new.body, new.tags);
81
+ END;
82
+ `);
83
+ if (options.rebuild === true ||
84
+ !memoryFtsExists ||
85
+ !memoryFtsTrigramExists ||
86
+ shouldRebuildExistingIndex(db)) {
87
+ db.exec(`
88
+ INSERT INTO memory_fts(memory_fts) VALUES('rebuild');
89
+ INSERT INTO memory_fts_trigram(memory_fts_trigram) VALUES('rebuild');
90
+ `);
91
+ }
92
+ };
@@ -0,0 +1,2 @@
1
+ import type { Database } from 'better-sqlite3';
2
+ export declare const applySchemaVersion28: (db: Database) => void;
@@ -0,0 +1,19 @@
1
+ export const applySchemaVersion28 = (db) => {
2
+ db.exec(`
3
+ CREATE TABLE IF NOT EXISTS dream_runs (
4
+ id TEXT PRIMARY KEY,
5
+ workspace_id TEXT NOT NULL,
6
+ trigger TEXT NOT NULL,
7
+ status TEXT NOT NULL,
8
+ started_at INTEGER NOT NULL,
9
+ finished_at INTEGER,
10
+ input_seq_from INTEGER,
11
+ input_seq_to INTEGER,
12
+ report TEXT,
13
+ revert_blob TEXT,
14
+ error TEXT
15
+ );
16
+ CREATE INDEX IF NOT EXISTS idx_dream_runs_ws
17
+ ON dream_runs(workspace_id, started_at);
18
+ `);
19
+ };
@@ -0,0 +1,2 @@
1
+ import type { Database } from 'better-sqlite3';
2
+ export declare const applySchemaVersion29: (db: Database) => void;
@@ -0,0 +1,27 @@
1
+ const QWEN_SESSION_ID_CAPTURE = {
2
+ pattern: '~/.qwen/sessions/**/*.json',
3
+ source: 'qwen_session_json_dir',
4
+ };
5
+ const QWEN_YOLO_ARGS = ['--approval-mode', 'yolo'];
6
+ export const applySchemaVersion29 = (db) => {
7
+ const hasCommandPresets = db
8
+ .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'command_presets'")
9
+ .get();
10
+ if (!hasCommandPresets)
11
+ return;
12
+ const now = Date.now();
13
+ db.prepare(`INSERT INTO command_presets (
14
+ id, display_name, command, args, env, resume_args_template, session_id_capture,
15
+ yolo_args_template, is_builtin, created_at, updated_at
16
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)
17
+ ON CONFLICT(id) DO UPDATE SET
18
+ display_name = excluded.display_name,
19
+ command = excluded.command,
20
+ args = excluded.args,
21
+ env = excluded.env,
22
+ resume_args_template = excluded.resume_args_template,
23
+ session_id_capture = excluded.session_id_capture,
24
+ yolo_args_template = excluded.yolo_args_template,
25
+ updated_at = excluded.updated_at
26
+ WHERE command_presets.is_builtin = 1`).run('qwen', 'Qwen Code', 'qwen', '[]', '{}', '--resume {session_id}', JSON.stringify(QWEN_SESSION_ID_CAPTURE), JSON.stringify(QWEN_YOLO_ARGS), now, now);
27
+ };
@@ -0,0 +1,2 @@
1
+ import type { Database } from 'better-sqlite3';
2
+ export declare const applySchemaVersion30: (db: Database) => void;
@@ -0,0 +1,27 @@
1
+ const AGY_SESSION_ID_CAPTURE = {
2
+ pattern: String.raw `(?:^|\s)(?:\S*[\\/])?agy(?:\.(?:cmd|exe))?\s+--conversation\s+([0-9a-fA-F-]{36})\b`,
3
+ source: 'stdout_regex',
4
+ };
5
+ const AGY_YOLO_ARGS = ['--dangerously-skip-permissions'];
6
+ export const applySchemaVersion30 = (db) => {
7
+ const hasCommandPresets = db
8
+ .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'command_presets'")
9
+ .get();
10
+ if (!hasCommandPresets)
11
+ return;
12
+ const now = Date.now();
13
+ db.prepare(`INSERT INTO command_presets (
14
+ id, display_name, command, args, env, resume_args_template, session_id_capture,
15
+ yolo_args_template, is_builtin, created_at, updated_at
16
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)
17
+ ON CONFLICT(id) DO UPDATE SET
18
+ display_name = excluded.display_name,
19
+ command = excluded.command,
20
+ args = excluded.args,
21
+ env = excluded.env,
22
+ resume_args_template = excluded.resume_args_template,
23
+ session_id_capture = excluded.session_id_capture,
24
+ yolo_args_template = excluded.yolo_args_template,
25
+ updated_at = excluded.updated_at
26
+ WHERE command_presets.is_builtin = 1`).run('agy', 'Antigravity CLI', 'agy', '[]', '{}', '--conversation {session_id}', JSON.stringify(AGY_SESSION_ID_CAPTURE), JSON.stringify(AGY_YOLO_ARGS), now, now);
27
+ };
@@ -0,0 +1,2 @@
1
+ import type { Database } from 'better-sqlite3';
2
+ export declare const applySchemaVersion31: (db: Database) => void;
@@ -0,0 +1,30 @@
1
+ import { getBuiltinCommandPreset } from './command-preset-defaults.js';
2
+ const upsertBuiltinCommandPreset = (db, id, now) => {
3
+ const preset = getBuiltinCommandPreset(id);
4
+ if (!preset)
5
+ return;
6
+ db.prepare(`INSERT INTO command_presets (
7
+ id, display_name, command, args, env, resume_args_template, session_id_capture,
8
+ yolo_args_template, is_builtin, created_at, updated_at
9
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)
10
+ ON CONFLICT(id) DO UPDATE SET
11
+ display_name = excluded.display_name,
12
+ command = excluded.command,
13
+ args = excluded.args,
14
+ env = excluded.env,
15
+ resume_args_template = excluded.resume_args_template,
16
+ session_id_capture = excluded.session_id_capture,
17
+ yolo_args_template = excluded.yolo_args_template,
18
+ updated_at = excluded.updated_at
19
+ WHERE command_presets.is_builtin = 1`).run(preset.id, preset.displayName, preset.command, '[]', '{}', preset.resumeArgsTemplate, preset.sessionIdCapture ? JSON.stringify(preset.sessionIdCapture) : null, preset.yoloArgsTemplate ? JSON.stringify(preset.yoloArgsTemplate) : null, now, now);
20
+ };
21
+ export const applySchemaVersion31 = (db) => {
22
+ const hasCommandPresets = db
23
+ .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'command_presets'")
24
+ .get();
25
+ if (!hasCommandPresets)
26
+ return;
27
+ const now = Date.now();
28
+ upsertBuiltinCommandPreset(db, 'cursor', now);
29
+ upsertBuiltinCommandPreset(db, 'grok', now);
30
+ };
@@ -1,3 +1,3 @@
1
1
  import type { Database } from 'better-sqlite3';
2
- export declare const CURRENT_SCHEMA_VERSION = 22;
2
+ export declare const CURRENT_SCHEMA_VERSION = 31;
3
3
  export declare const initializeRuntimeDatabase: (db: Database) => void;
@@ -15,7 +15,16 @@ import { applySchemaVersion19 } from './sqlite-schema-v19.js';
15
15
  import { applySchemaVersion20 } from './sqlite-schema-v20.js';
16
16
  import { applySchemaVersion21 } from './sqlite-schema-v21.js';
17
17
  import { applySchemaVersion22 } from './sqlite-schema-v22.js';
18
- export const CURRENT_SCHEMA_VERSION = 22;
18
+ import { applySchemaVersion23 } from './sqlite-schema-v23.js';
19
+ import { applySchemaVersion24 } from './sqlite-schema-v24.js';
20
+ import { applySchemaVersion25 } from './sqlite-schema-v25.js';
21
+ import { applySchemaVersion26 } from './sqlite-schema-v26.js';
22
+ import { applySchemaVersion27 } from './sqlite-schema-v27.js';
23
+ import { applySchemaVersion28 } from './sqlite-schema-v28.js';
24
+ import { applySchemaVersion29 } from './sqlite-schema-v29.js';
25
+ import { applySchemaVersion30 } from './sqlite-schema-v30.js';
26
+ import { applySchemaVersion31 } from './sqlite-schema-v31.js';
27
+ export const CURRENT_SCHEMA_VERSION = 31;
19
28
  // Idempotent column-add helper. SQLite doesn't have `ALTER TABLE … ADD COLUMN
20
29
  // IF NOT EXISTS`, so PRAGMA-check first. Safe to call on every init; required
21
30
  // for foreign-built DBs where the version-gated migration is skipped.
@@ -317,4 +326,43 @@ export const initializeRuntimeDatabase = (db) => {
317
326
  applySchemaVersion22(db);
318
327
  db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)').run(22, Date.now());
319
328
  }
329
+ if (!appliedVersions.has(23)) {
330
+ applySchemaVersion23(db);
331
+ db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)').run(23, Date.now());
332
+ }
333
+ if (!appliedVersions.has(24)) {
334
+ applySchemaVersion24(db);
335
+ db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)').run(24, Date.now());
336
+ }
337
+ applySchemaVersion25(db);
338
+ if (!appliedVersions.has(25)) {
339
+ db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)').run(25, Date.now());
340
+ }
341
+ applySchemaVersion26(db);
342
+ if (!appliedVersions.has(26)) {
343
+ db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)').run(26, Date.now());
344
+ }
345
+ if (!appliedVersions.has(27)) {
346
+ applySchemaVersion27(db, { rebuild: true });
347
+ db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)').run(27, Date.now());
348
+ }
349
+ else {
350
+ applySchemaVersion27(db, { rebuild: false });
351
+ }
352
+ applySchemaVersion28(db);
353
+ if (!appliedVersions.has(28)) {
354
+ db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)').run(28, Date.now());
355
+ }
356
+ applySchemaVersion29(db);
357
+ if (!appliedVersions.has(29)) {
358
+ db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)').run(29, Date.now());
359
+ }
360
+ applySchemaVersion30(db);
361
+ if (!appliedVersions.has(30)) {
362
+ db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)').run(30, Date.now());
363
+ }
364
+ applySchemaVersion31(db);
365
+ if (!appliedVersions.has(31)) {
366
+ db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)').run(31, Date.now());
367
+ }
320
368
  };
@@ -2,7 +2,11 @@ import { basename } from 'node:path';
2
2
  const getEnvValue = (env, key, platform = process.platform) => {
3
3
  if (platform !== 'win32')
4
4
  return env[key];
5
- const matchedKey = Object.keys(env).find((item) => item.toLowerCase() === key.toLowerCase());
5
+ if (Object.hasOwn(env, key))
6
+ return env[key];
7
+ const matchedKey = Object.keys(env)
8
+ .filter((item) => item.toLowerCase() === key.toLowerCase())
9
+ .at(-1);
6
10
  return matchedKey ? env[matchedKey] : undefined;
7
11
  };
8
12
  const createPosixShellArgs = (shell, command) => {
@@ -27,6 +27,8 @@ import type { WorkflowCliPolicy } from './workflow-cli-policy.js';
27
27
  */
28
28
  export declare const TASKS_WATCHER_OPTIONS: ChokidarOptions;
29
29
  export declare const buildTasksWatcherOptions: (workspacePath: string, platform?: NodeJS.Platform) => ChokidarOptions;
30
+ export declare const getTasksWatcherReadyTimeoutMs: (workspacePath: string, platform?: NodeJS.Platform) => number;
31
+ export declare const isTasksFileEvent: (tasksPath: string, changedPath: string | Buffer | undefined, platform?: NodeJS.Platform) => boolean;
30
32
  export interface TasksFileWatcher {
31
33
  close: () => Promise<void>;
32
34
  start: (workspaceId: string, workspacePath: string) => Promise<void>;
@@ -7,7 +7,8 @@ import { ensureProtocolFile, ensureTasksFile, getTasksFilePath, TASKS_FILE_NAME,
7
7
  const DEBOUNCE_MS = 100;
8
8
  const WATCHER_RETRY_MS = 5000;
9
9
  const WATCHER_CLOSE_TIMEOUT_MS = 2000;
10
- const WATCHER_READY_TIMEOUT_MS = 15000;
10
+ const DEFAULT_WATCHER_READY_TIMEOUT_MS = 15000;
11
+ const WINDOWS_WATCHER_READY_TIMEOUT_MS = 60000;
11
12
  /**
12
13
  * Watcher configuration. The atomic-save option matters on Windows: VS
13
14
  * Code, Cursor, Notepad++, and the editor inside Hive itself all save
@@ -43,6 +44,9 @@ export const buildTasksWatcherOptions = (workspacePath, platform = process.platf
43
44
  ? { interval: 500, usePolling: true }
44
45
  : {}),
45
46
  });
47
+ export const getTasksWatcherReadyTimeoutMs = (workspacePath, platform = process.platform) => platform === 'win32' || isWindowsUncPath(workspacePath, platform)
48
+ ? WINDOWS_WATCHER_READY_TIMEOUT_MS
49
+ : DEFAULT_WATCHER_READY_TIMEOUT_MS;
46
50
  const closeWatcherWithTimeout = async (watcher) => {
47
51
  if (!watcher)
48
52
  return;
@@ -61,10 +65,14 @@ const closeWatcherWithTimeout = async (watcher) => {
61
65
  clearTimeout(timer);
62
66
  }
63
67
  };
64
- const isTasksFileEvent = (tasksPath, changedPath) => {
68
+ export const isTasksFileEvent = (tasksPath, changedPath, platform = process.platform) => {
65
69
  if (!changedPath)
66
70
  return true;
67
71
  const text = Buffer.isBuffer(changedPath) ? changedPath.toString() : changedPath;
72
+ if (platform === 'win32') {
73
+ return (normalize(text).toLowerCase() === normalize(tasksPath).toLowerCase() ||
74
+ basename(text).toLowerCase() === TASKS_FILE_NAME);
75
+ }
68
76
  return normalize(text) === normalize(tasksPath) || basename(text) === TASKS_FILE_NAME;
69
77
  };
70
78
  export const createTasksFileWatcher = ({ onTasksUpdated, getWorkflowCliPolicy, getFlags, }) => {
@@ -120,7 +128,7 @@ export const createTasksFileWatcher = ({ onTasksUpdated, getWorkflowCliPolicy, g
120
128
  timer.unref?.();
121
129
  retryTimers.set(workspaceId, timer);
122
130
  };
123
- const waitForReady = async (watcher) => await new Promise((resolve, reject) => {
131
+ const waitForReady = async (watcher, timeoutMs) => await new Promise((resolve, reject) => {
124
132
  const cleanup = () => {
125
133
  watcher.off('ready', handleReady);
126
134
  watcher.off('error', handleError);
@@ -136,8 +144,8 @@ export const createTasksFileWatcher = ({ onTasksUpdated, getWorkflowCliPolicy, g
136
144
  };
137
145
  const timeout = setTimeout(() => {
138
146
  cleanup();
139
- reject(new Error(`Timed out waiting for tasks watcher ready after ${WATCHER_READY_TIMEOUT_MS}ms`));
140
- }, WATCHER_READY_TIMEOUT_MS);
147
+ reject(new Error(`Timed out waiting for tasks watcher ready after ${timeoutMs}ms`));
148
+ }, timeoutMs);
141
149
  timeout.unref?.();
142
150
  watcher.once('ready', handleReady);
143
151
  watcher.once('error', handleError);
@@ -149,6 +157,7 @@ export const createTasksFileWatcher = ({ onTasksUpdated, getWorkflowCliPolicy, g
149
157
  ensureProtocolFile(workspacePath, getWorkflowCliPolicy?.(), getFlags?.() ?? FEATURE_FLAGS_ALL_OFF);
150
158
  const tasksPath = getTasksFilePath(workspacePath);
151
159
  const watcher = chokidar.watch(dirname(tasksPath), buildTasksWatcherOptions(workspacePath));
160
+ const readyTimeoutMs = getTasksWatcherReadyTimeoutMs(workspacePath);
152
161
  const scheduleEmit = (changedPath) => {
153
162
  if (!isTasksFileEvent(tasksPath, changedPath))
154
163
  return;
@@ -169,7 +178,7 @@ export const createTasksFileWatcher = ({ onTasksUpdated, getWorkflowCliPolicy, g
169
178
  });
170
179
  watchers.set(workspaceId, watcher);
171
180
  try {
172
- await waitForReady(watcher);
181
+ await waitForReady(watcher, readyTimeoutMs);
173
182
  }
174
183
  catch (error) {
175
184
  watchers.delete(workspaceId);
@@ -1,5 +1,6 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
- import { dirname, join } from 'node:path';
1
+ import { randomUUID } from 'node:crypto';
2
+ import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
3
+ import { basename, dirname, join } from 'node:path';
3
4
  import { FEATURE_FLAGS_ALL_OFF } from './feature-flags.js';
4
5
  import { buildProtocolDoc } from './hive-team-guidance.js';
5
6
  export const HIVE_DIR_NAME = '.hive';
@@ -32,6 +33,30 @@ const runRetryableTasksFileOperation = (operation) => {
32
33
  const ensureTasksDir = (workspacePath) => {
33
34
  runRetryableTasksFileOperation(() => mkdirSync(dirname(getTasksFilePath(workspacePath)), { recursive: true }));
34
35
  };
36
+ const writeUtf8FileAtomically = (targetPath, content) => {
37
+ const tempPath = join(dirname(targetPath), `.${basename(targetPath)}.${randomUUID()}.tmp`);
38
+ let committed = false;
39
+ let fd = null;
40
+ try {
41
+ try {
42
+ fd = openSync(tempPath, 'w');
43
+ writeFileSync(fd, content, 'utf8');
44
+ fsyncSync(fd);
45
+ }
46
+ finally {
47
+ if (fd !== null) {
48
+ closeSync(fd);
49
+ fd = null;
50
+ }
51
+ }
52
+ renameSync(tempPath, targetPath);
53
+ committed = true;
54
+ }
55
+ finally {
56
+ if (!committed && existsSync(tempPath))
57
+ rmSync(tempPath, { force: true });
58
+ }
59
+ };
35
60
  export const ensureTasksFile = (workspacePath) => {
36
61
  ensureTasksDir(workspacePath);
37
62
  const tasksFilePath = getTasksFilePath(workspacePath);
@@ -42,7 +67,7 @@ export const ensureTasksFile = (workspacePath) => {
42
67
  const content = existsSync(legacyTasksFilePath)
43
68
  ? runRetryableTasksFileOperation(() => readFileSync(legacyTasksFilePath, 'utf8'))
44
69
  : '';
45
- runRetryableTasksFileOperation(() => writeFileSync(tasksFilePath, content, 'utf8'));
70
+ runRetryableTasksFileOperation(() => writeUtf8FileAtomically(tasksFilePath, content));
46
71
  return content;
47
72
  };
48
73
  /**
@@ -60,7 +85,7 @@ export const ensureProtocolFile = (workspacePath, cliPolicy, flags = FEATURE_FLA
60
85
  : null;
61
86
  if (current === desired)
62
87
  return desired;
63
- runRetryableTasksFileOperation(() => writeFileSync(protocolFilePath, desired, 'utf8'));
88
+ runRetryableTasksFileOperation(() => writeUtf8FileAtomically(protocolFilePath, desired));
64
89
  return desired;
65
90
  };
66
91
  export const createTasksFileService = () => {
@@ -70,7 +95,7 @@ export const createTasksFileService = () => {
70
95
  },
71
96
  writeTasks(workspacePath, content) {
72
97
  ensureTasksDir(workspacePath);
73
- runRetryableTasksFileOperation(() => writeFileSync(getTasksFilePath(workspacePath), content, 'utf8'));
98
+ runRetryableTasksFileOperation(() => writeUtf8FileAtomically(getTasksFilePath(workspacePath), content));
74
99
  },
75
100
  };
76
101
  };
@@ -12,6 +12,10 @@ export const createTasksWebSocketServer = (server, store, tasksFileService) => {
12
12
  attachWebSocketServerErrorHandler(wss, 'tasks');
13
13
  const socketsByWorkspaceId = new Map();
14
14
  const validateUpgradeSession = (request) => {
15
+ // Tunnel-originated upgrades carry the per-boot secret (invariant 2); a
16
+ // request with no secret header falls through to the unchanged cookie path.
17
+ if (store.authorizeRemoteTunnelRequest(request))
18
+ return true;
15
19
  const cookieHeader = Array.isArray(request.headers.cookie)
16
20
  ? request.headers.cookie.join('; ')
17
21
  : request.headers.cookie;