@qvac/core 0.0.0 → 0.1.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 (586) hide show
  1. package/LICENSE.md +198 -0
  2. package/NOTICE.md +13 -0
  3. package/README.md +694 -0
  4. package/SECURITY.md +6 -0
  5. package/dist/assistant.bundle +47256 -0
  6. package/dist/assistant.cjs +18 -0
  7. package/dist/assistant.d.ts +8 -0
  8. package/dist/prebuilds-manifest.json +380 -0
  9. package/dist-lib/client.d.ts +2 -0
  10. package/dist-lib/client.js +1 -0
  11. package/dist-lib/index.d.ts +13 -0
  12. package/dist-lib/index.js +10 -0
  13. package/dist-lib/lib/agent-icons.d.ts +3 -0
  14. package/dist-lib/lib/agent-icons.js +1 -0
  15. package/dist-lib/lib/api/agents.d.ts +5 -0
  16. package/dist-lib/lib/api/agents.js +38 -0
  17. package/dist-lib/lib/api/app-config.d.ts +5 -0
  18. package/dist-lib/lib/api/app-config.js +108 -0
  19. package/dist-lib/lib/api/approval-rules.d.ts +3 -0
  20. package/dist-lib/lib/api/approval-rules.js +9 -0
  21. package/dist-lib/lib/api/attachments.d.ts +8 -0
  22. package/dist-lib/lib/api/attachments.js +15 -0
  23. package/dist-lib/lib/api/chat-groups.d.ts +5 -0
  24. package/dist-lib/lib/api/chat-groups.js +12 -0
  25. package/dist-lib/lib/api/chat.d.ts +5 -0
  26. package/dist-lib/lib/api/chat.js +38 -0
  27. package/dist-lib/lib/api/chunks.d.ts +5 -0
  28. package/dist-lib/lib/api/chunks.js +61 -0
  29. package/dist-lib/lib/api/config.d.ts +5 -0
  30. package/dist-lib/lib/api/config.js +15 -0
  31. package/dist-lib/lib/api/current-mesh.d.ts +5 -0
  32. package/dist-lib/lib/api/current-mesh.js +12 -0
  33. package/dist-lib/lib/api/custom-models.d.ts +5 -0
  34. package/dist-lib/lib/api/custom-models.js +45 -0
  35. package/dist-lib/lib/api/devices.d.ts +8 -0
  36. package/dist-lib/lib/api/devices.js +125 -0
  37. package/dist-lib/lib/api/diagnostics.d.ts +7 -0
  38. package/dist-lib/lib/api/diagnostics.js +7 -0
  39. package/dist-lib/lib/api/effective-ctx-size.d.ts +2 -0
  40. package/dist-lib/lib/api/effective-ctx-size.js +10 -0
  41. package/dist-lib/lib/api/execution.d.ts +3 -0
  42. package/dist-lib/lib/api/execution.js +45 -0
  43. package/dist-lib/lib/api/files.d.ts +3 -0
  44. package/dist-lib/lib/api/files.js +48 -0
  45. package/dist-lib/lib/api/index.d.ts +62 -0
  46. package/dist-lib/lib/api/index.js +172 -0
  47. package/dist-lib/lib/api/instrument.d.ts +2 -0
  48. package/dist-lib/lib/api/instrument.js +86 -0
  49. package/dist-lib/lib/api/knowledge.d.ts +8 -0
  50. package/dist-lib/lib/api/knowledge.js +26 -0
  51. package/dist-lib/lib/api/logs.d.ts +14 -0
  52. package/dist-lib/lib/api/logs.js +116 -0
  53. package/dist-lib/lib/api/mesh.d.ts +13 -0
  54. package/dist-lib/lib/api/mesh.js +25 -0
  55. package/dist-lib/lib/api/model-download.d.ts +5 -0
  56. package/dist-lib/lib/api/model-download.js +47 -0
  57. package/dist-lib/lib/api/network.d.ts +8 -0
  58. package/dist-lib/lib/api/network.js +6 -0
  59. package/dist-lib/lib/api/notifications.d.ts +21 -0
  60. package/dist-lib/lib/api/notifications.js +108 -0
  61. package/dist-lib/lib/api/rag.d.ts +7 -0
  62. package/dist-lib/lib/api/rag.js +216 -0
  63. package/dist-lib/lib/api/sync.d.ts +3 -0
  64. package/dist-lib/lib/api/sync.js +6 -0
  65. package/dist-lib/lib/api/tool-activity.d.ts +5 -0
  66. package/dist-lib/lib/api/tool-activity.js +53 -0
  67. package/dist-lib/lib/api/tool-configs.d.ts +6 -0
  68. package/dist-lib/lib/api/tool-configs.js +27 -0
  69. package/dist-lib/lib/api/watchable.d.ts +12 -0
  70. package/dist-lib/lib/api/watchable.js +121 -0
  71. package/dist-lib/lib/assistant-client.d.ts +21 -0
  72. package/dist-lib/lib/assistant-client.js +52 -0
  73. package/dist-lib/lib/assistant-tree.d.ts +50 -0
  74. package/dist-lib/lib/assistant-tree.js +184 -0
  75. package/dist-lib/lib/assistant.d.ts +425 -0
  76. package/dist-lib/lib/assistant.js +2706 -0
  77. package/dist-lib/lib/audio-format.d.ts +4 -0
  78. package/dist-lib/lib/audio-format.js +4 -0
  79. package/dist-lib/lib/chat-title.d.ts +5 -0
  80. package/dist-lib/lib/chat-title.js +58 -0
  81. package/dist-lib/lib/client.d.ts +25 -0
  82. package/dist-lib/lib/client.js +53 -0
  83. package/dist-lib/lib/collabora/convert.d.ts +9 -0
  84. package/dist-lib/lib/collabora/convert.js +62 -0
  85. package/dist-lib/lib/collabora/entry.d.ts +1 -0
  86. package/dist-lib/lib/collabora/entry.js +44 -0
  87. package/dist-lib/lib/collabora/protocol.d.ts +15 -0
  88. package/dist-lib/lib/collabora/protocol.js +24 -0
  89. package/dist-lib/lib/collabora/worker.d.ts +5 -0
  90. package/dist-lib/lib/collabora/worker.js +111 -0
  91. package/dist-lib/lib/compaction.d.ts +30 -0
  92. package/dist-lib/lib/compaction.js +95 -0
  93. package/dist-lib/lib/constants.d.ts +4 -0
  94. package/dist-lib/lib/constants.js +4 -0
  95. package/dist-lib/lib/core/agent.d.ts +15 -0
  96. package/dist-lib/lib/core/agent.js +149 -0
  97. package/dist-lib/lib/core/app-build.d.ts +7 -0
  98. package/dist-lib/lib/core/app-build.js +1 -0
  99. package/dist-lib/lib/core/approval-rules.d.ts +15 -0
  100. package/dist-lib/lib/core/approval-rules.js +41 -0
  101. package/dist-lib/lib/core/async-iterable.d.ts +13 -0
  102. package/dist-lib/lib/core/async-iterable.js +65 -0
  103. package/dist-lib/lib/core/attachment-link.d.ts +7 -0
  104. package/dist-lib/lib/core/attachment-link.js +6 -0
  105. package/dist-lib/lib/core/audio-attachment.d.ts +1 -0
  106. package/dist-lib/lib/core/audio-attachment.js +4 -0
  107. package/dist-lib/lib/core/blind-peers.d.ts +6 -0
  108. package/dist-lib/lib/core/blind-peers.js +13 -0
  109. package/dist-lib/lib/core/chat-activity.d.ts +7 -0
  110. package/dist-lib/lib/core/chat-activity.js +244 -0
  111. package/dist-lib/lib/core/chat-group.d.ts +14 -0
  112. package/dist-lib/lib/core/chat-group.js +79 -0
  113. package/dist-lib/lib/core/chat-search.d.ts +17 -0
  114. package/dist-lib/lib/core/chat-search.js +199 -0
  115. package/dist-lib/lib/core/chat.d.ts +37 -0
  116. package/dist-lib/lib/core/chat.js +196 -0
  117. package/dist-lib/lib/core/chunk.d.ts +61 -0
  118. package/dist-lib/lib/core/chunk.js +212 -0
  119. package/dist-lib/lib/core/contract/activity.d.ts +11 -0
  120. package/dist-lib/lib/core/contract/activity.js +45 -0
  121. package/dist-lib/lib/core/contract/agents.d.ts +2 -0
  122. package/dist-lib/lib/core/contract/agents.js +29 -0
  123. package/dist-lib/lib/core/contract/approval-rules.d.ts +2 -0
  124. package/dist-lib/lib/core/contract/approval-rules.js +14 -0
  125. package/dist-lib/lib/core/contract/attestations.d.ts +2 -0
  126. package/dist-lib/lib/core/contract/attestations.js +14 -0
  127. package/dist-lib/lib/core/contract/chat-group.d.ts +2 -0
  128. package/dist-lib/lib/core/contract/chat-group.js +20 -0
  129. package/dist-lib/lib/core/contract/chat.d.ts +2 -0
  130. package/dist-lib/lib/core/contract/chat.js +18 -0
  131. package/dist-lib/lib/core/contract/chunks.d.ts +2 -0
  132. package/dist-lib/lib/core/contract/chunks.js +156 -0
  133. package/dist-lib/lib/core/contract/devices.d.ts +2 -0
  134. package/dist-lib/lib/core/contract/devices.js +110 -0
  135. package/dist-lib/lib/core/contract/index.d.ts +2 -0
  136. package/dist-lib/lib/core/contract/index.js +30 -0
  137. package/dist-lib/lib/core/contract/indexing.d.ts +4 -0
  138. package/dist-lib/lib/core/contract/indexing.js +82 -0
  139. package/dist-lib/lib/core/contract/knowledge.d.ts +2 -0
  140. package/dist-lib/lib/core/contract/knowledge.js +55 -0
  141. package/dist-lib/lib/core/contract/members.d.ts +2 -0
  142. package/dist-lib/lib/core/contract/members.js +96 -0
  143. package/dist-lib/lib/core/contract/seed-defaults.d.ts +2 -0
  144. package/dist-lib/lib/core/contract/seed-defaults.js +29 -0
  145. package/dist-lib/lib/core/contract/tool-activity.d.ts +2 -0
  146. package/dist-lib/lib/core/contract/tool-activity.js +25 -0
  147. package/dist-lib/lib/core/contract/tool-configs.d.ts +2 -0
  148. package/dist-lib/lib/core/contract/tool-configs.js +50 -0
  149. package/dist-lib/lib/core/contract/types.d.ts +11 -0
  150. package/dist-lib/lib/core/contract/types.js +1 -0
  151. package/dist-lib/lib/core/contract/writers.d.ts +2 -0
  152. package/dist-lib/lib/core/contract/writers.js +112 -0
  153. package/dist-lib/lib/core/cpu-info-node.d.ts +3 -0
  154. package/dist-lib/lib/core/cpu-info-node.js +2 -0
  155. package/dist-lib/lib/core/departures.d.ts +41 -0
  156. package/dist-lib/lib/core/departures.js +145 -0
  157. package/dist-lib/lib/core/device-attestation.d.ts +6 -0
  158. package/dist-lib/lib/core/device-attestation.js +12 -0
  159. package/dist-lib/lib/core/devices.d.ts +14 -0
  160. package/dist-lib/lib/core/devices.js +11 -0
  161. package/dist-lib/lib/core/document-attachment.d.ts +22 -0
  162. package/dist-lib/lib/core/document-attachment.js +35 -0
  163. package/dist-lib/lib/core/files/attachments.d.ts +8 -0
  164. package/dist-lib/lib/core/files/attachments.js +50 -0
  165. package/dist-lib/lib/core/files/index.d.ts +133 -0
  166. package/dist-lib/lib/core/files/index.js +585 -0
  167. package/dist-lib/lib/core/files/knowledge.d.ts +9 -0
  168. package/dist-lib/lib/core/files/knowledge.js +31 -0
  169. package/dist-lib/lib/core/files/preview/image.d.ts +4 -0
  170. package/dist-lib/lib/core/files/preview/image.js +42 -0
  171. package/dist-lib/lib/core/files/preview/index.d.ts +33 -0
  172. package/dist-lib/lib/core/files/preview/index.js +161 -0
  173. package/dist-lib/lib/core/files/processing/claims.d.ts +26 -0
  174. package/dist-lib/lib/core/files/processing/claims.js +89 -0
  175. package/dist-lib/lib/core/files/processing/document.d.ts +100 -0
  176. package/dist-lib/lib/core/files/processing/document.js +101 -0
  177. package/dist-lib/lib/core/files/processing/index.d.ts +75 -0
  178. package/dist-lib/lib/core/files/processing/index.js +295 -0
  179. package/dist-lib/lib/core/files/processing/ownership.d.ts +11 -0
  180. package/dist-lib/lib/core/files/processing/ownership.js +41 -0
  181. package/dist-lib/lib/core/files/processing/protocol.d.ts +29 -0
  182. package/dist-lib/lib/core/files/processing/protocol.js +70 -0
  183. package/dist-lib/lib/core/files/processing/settle.d.ts +10 -0
  184. package/dist-lib/lib/core/files/processing/settle.js +75 -0
  185. package/dist-lib/lib/core/files/processing/source.d.ts +36 -0
  186. package/dist-lib/lib/core/files/processing/source.js +96 -0
  187. package/dist-lib/lib/core/files/processing/state.d.ts +64 -0
  188. package/dist-lib/lib/core/files/processing/state.js +118 -0
  189. package/dist-lib/lib/core/files/rag/artifact/codec.d.ts +3 -0
  190. package/dist-lib/lib/core/files/rag/artifact/codec.js +172 -0
  191. package/dist-lib/lib/core/files/rag/artifact/id.d.ts +10 -0
  192. package/dist-lib/lib/core/files/rag/artifact/id.js +39 -0
  193. package/dist-lib/lib/core/files/rag/artifact/types.d.ts +24 -0
  194. package/dist-lib/lib/core/files/rag/artifact/types.js +41 -0
  195. package/dist-lib/lib/core/files/rag/build.d.ts +25 -0
  196. package/dist-lib/lib/core/files/rag/build.js +60 -0
  197. package/dist-lib/lib/core/files/rag/chunk/chunking.d.ts +12 -0
  198. package/dist-lib/lib/core/files/rag/chunk/chunking.js +117 -0
  199. package/dist-lib/lib/core/files/rag/chunk/metadata.d.ts +30 -0
  200. package/dist-lib/lib/core/files/rag/chunk/metadata.js +89 -0
  201. package/dist-lib/lib/core/files/rag/chunk/tokenizer.d.ts +7 -0
  202. package/dist-lib/lib/core/files/rag/chunk/tokenizer.js +18 -0
  203. package/dist-lib/lib/core/files/rag/document-types.d.ts +11 -0
  204. package/dist-lib/lib/core/files/rag/document-types.js +94 -0
  205. package/dist-lib/lib/core/files/rag/extract/csv-text.d.ts +1 -0
  206. package/dist-lib/lib/core/files/rag/extract/csv-text.js +117 -0
  207. package/dist-lib/lib/core/files/rag/extract/document-types.d.ts +22 -0
  208. package/dist-lib/lib/core/files/rag/extract/document-types.js +152 -0
  209. package/dist-lib/lib/core/files/rag/extract/extract.d.ts +17 -0
  210. package/dist-lib/lib/core/files/rag/extract/extract.js +81 -0
  211. package/dist-lib/lib/core/files/rag/extract/html-table-text.d.ts +1 -0
  212. package/dist-lib/lib/core/files/rag/extract/html-table-text.js +125 -0
  213. package/dist-lib/lib/core/files/rag/extract/html-text.d.ts +1 -0
  214. package/dist-lib/lib/core/files/rag/extract/html-text.js +132 -0
  215. package/dist-lib/lib/core/files/rag/extract/office-collabora.d.ts +2 -0
  216. package/dist-lib/lib/core/files/rag/extract/office-collabora.js +8 -0
  217. package/dist-lib/lib/core/files/rag/extract/pdf-platform.d.ts +17 -0
  218. package/dist-lib/lib/core/files/rag/extract/pdf-platform.js +141 -0
  219. package/dist-lib/lib/core/files/rag/extract/pdf-text.d.ts +12 -0
  220. package/dist-lib/lib/core/files/rag/extract/pdf-text.js +43 -0
  221. package/dist-lib/lib/core/files/rag/extract/rtf-text.d.ts +1 -0
  222. package/dist-lib/lib/core/files/rag/extract/rtf-text.js +15 -0
  223. package/dist-lib/lib/core/files/rag/extract/text-decoding.d.ts +1 -0
  224. package/dist-lib/lib/core/files/rag/extract/text-decoding.js +91 -0
  225. package/dist-lib/lib/core/files/rag/extract/types.d.ts +36 -0
  226. package/dist-lib/lib/core/files/rag/extract/types.js +12 -0
  227. package/dist-lib/lib/core/files/rag/extract/utf.d.ts +2 -0
  228. package/dist-lib/lib/core/files/rag/extract/utf.js +18 -0
  229. package/dist-lib/lib/core/files/rag/index.d.ts +54 -0
  230. package/dist-lib/lib/core/files/rag/index.js +79 -0
  231. package/dist-lib/lib/core/files/rag/limits.d.ts +2 -0
  232. package/dist-lib/lib/core/files/rag/limits.js +2 -0
  233. package/dist-lib/lib/core/files/rag/load.d.ts +28 -0
  234. package/dist-lib/lib/core/files/rag/load.js +250 -0
  235. package/dist-lib/lib/core/files/rag/models.d.ts +1 -0
  236. package/dist-lib/lib/core/files/rag/models.js +1 -0
  237. package/dist-lib/lib/core/files/rag/policy.d.ts +2 -0
  238. package/dist-lib/lib/core/files/rag/policy.js +23 -0
  239. package/dist-lib/lib/core/files/rag/produce.d.ts +21 -0
  240. package/dist-lib/lib/core/files/rag/produce.js +214 -0
  241. package/dist-lib/lib/core/files/rag/retrieval/citation-match.d.ts +2 -0
  242. package/dist-lib/lib/core/files/rag/retrieval/citation-match.js +16 -0
  243. package/dist-lib/lib/core/files/rag/retrieval/coverage.d.ts +4 -0
  244. package/dist-lib/lib/core/files/rag/retrieval/coverage.js +48 -0
  245. package/dist-lib/lib/core/files/rag/retrieval/diversity.d.ts +10 -0
  246. package/dist-lib/lib/core/files/rag/retrieval/diversity.js +37 -0
  247. package/dist-lib/lib/core/files/rag/retrieval/floor.d.ts +5 -0
  248. package/dist-lib/lib/core/files/rag/retrieval/floor.js +21 -0
  249. package/dist-lib/lib/core/files/rag/retrieval/format.d.ts +6 -0
  250. package/dist-lib/lib/core/files/rag/retrieval/format.js +21 -0
  251. package/dist-lib/lib/core/files/rag/retrieval/full-text.d.ts +22 -0
  252. package/dist-lib/lib/core/files/rag/retrieval/full-text.js +137 -0
  253. package/dist-lib/lib/core/files/rag/retrieval/knowledge.d.ts +2 -0
  254. package/dist-lib/lib/core/files/rag/retrieval/knowledge.js +53 -0
  255. package/dist-lib/lib/core/files/rag/retrieval/notices.d.ts +16 -0
  256. package/dist-lib/lib/core/files/rag/retrieval/notices.js +27 -0
  257. package/dist-lib/lib/core/files/rag/retrieval/scoring.d.ts +8 -0
  258. package/dist-lib/lib/core/files/rag/retrieval/scoring.js +82 -0
  259. package/dist-lib/lib/core/files/rag/retrieval/search.d.ts +3 -0
  260. package/dist-lib/lib/core/files/rag/retrieval/search.js +46 -0
  261. package/dist-lib/lib/core/files/rag/retrieval/selection.d.ts +10 -0
  262. package/dist-lib/lib/core/files/rag/retrieval/selection.js +63 -0
  263. package/dist-lib/lib/core/files/rag/retrieval/source-tag-filter.d.ts +6 -0
  264. package/dist-lib/lib/core/files/rag/retrieval/source-tag-filter.js +79 -0
  265. package/dist-lib/lib/core/files/rag/retrieval/sources.d.ts +13 -0
  266. package/dist-lib/lib/core/files/rag/retrieval/sources.js +20 -0
  267. package/dist-lib/lib/core/files/rag/retrieval/target.d.ts +13 -0
  268. package/dist-lib/lib/core/files/rag/retrieval/target.js +89 -0
  269. package/dist-lib/lib/core/files/rag/retrieval/terms.d.ts +8 -0
  270. package/dist-lib/lib/core/files/rag/retrieval/terms.js +70 -0
  271. package/dist-lib/lib/core/files/rag/retrieval/types.d.ts +28 -0
  272. package/dist-lib/lib/core/files/rag/retrieval/types.js +1 -0
  273. package/dist-lib/lib/core/files/rag/retrieval/web-sources.d.ts +8 -0
  274. package/dist-lib/lib/core/files/rag/retrieval/web-sources.js +41 -0
  275. package/dist-lib/lib/core/files/rag/retrieval/whole-document-query.d.ts +1 -0
  276. package/dist-lib/lib/core/files/rag/retrieval/whole-document-query.js +96 -0
  277. package/dist-lib/lib/core/files/rag/vector/memory.d.ts +15 -0
  278. package/dist-lib/lib/core/files/rag/vector/memory.js +135 -0
  279. package/dist-lib/lib/core/files/rag/vector/types.d.ts +28 -0
  280. package/dist-lib/lib/core/files/rag/vector/types.js +1 -0
  281. package/dist-lib/lib/core/files/records/chunks.d.ts +3 -0
  282. package/dist-lib/lib/core/files/records/chunks.js +12 -0
  283. package/dist-lib/lib/core/files/records/index.d.ts +10 -0
  284. package/dist-lib/lib/core/files/records/index.js +8 -0
  285. package/dist-lib/lib/core/files/records/knowledge.d.ts +22 -0
  286. package/dist-lib/lib/core/files/records/knowledge.js +105 -0
  287. package/dist-lib/lib/core/files/records/linkage.d.ts +11 -0
  288. package/dist-lib/lib/core/files/records/linkage.js +27 -0
  289. package/dist-lib/lib/core/files/records/output.d.ts +8 -0
  290. package/dist-lib/lib/core/files/records/output.js +38 -0
  291. package/dist-lib/lib/core/files/records/reclaim.d.ts +5 -0
  292. package/dist-lib/lib/core/files/records/reclaim.js +73 -0
  293. package/dist-lib/lib/core/files/records/routing.d.ts +23 -0
  294. package/dist-lib/lib/core/files/records/routing.js +62 -0
  295. package/dist-lib/lib/core/files/records/source.d.ts +16 -0
  296. package/dist-lib/lib/core/files/records/source.js +70 -0
  297. package/dist-lib/lib/core/files/records/sweep.d.ts +2 -0
  298. package/dist-lib/lib/core/files/records/sweep.js +30 -0
  299. package/dist-lib/lib/core/files/store/blob.d.ts +29 -0
  300. package/dist-lib/lib/core/files/store/blob.js +98 -0
  301. package/dist-lib/lib/core/files/store/index.d.ts +36 -0
  302. package/dist-lib/lib/core/files/store/index.js +89 -0
  303. package/dist-lib/lib/core/files/store/inline.d.ts +8 -0
  304. package/dist-lib/lib/core/files/store/inline.js +38 -0
  305. package/dist-lib/lib/core/files/store/reference.d.ts +27 -0
  306. package/dist-lib/lib/core/files/store/reference.js +168 -0
  307. package/dist-lib/lib/core/files/store/server.d.ts +27 -0
  308. package/dist-lib/lib/core/files/store/server.js +114 -0
  309. package/dist-lib/lib/core/files/store/sweep.d.ts +2 -0
  310. package/dist-lib/lib/core/files/store/sweep.js +45 -0
  311. package/dist-lib/lib/core/files/upload-input.d.ts +27 -0
  312. package/dist-lib/lib/core/files/upload-input.js +52 -0
  313. package/dist-lib/lib/core/files/upload.d.ts +32 -0
  314. package/dist-lib/lib/core/files/upload.js +201 -0
  315. package/dist-lib/lib/core/fs-drives.d.ts +18 -0
  316. package/dist-lib/lib/core/fs-drives.js +58 -0
  317. package/dist-lib/lib/core/gpu-info-node.d.ts +3 -0
  318. package/dist-lib/lib/core/gpu-info-node.js +2 -0
  319. package/dist-lib/lib/core/handoff-channel.d.ts +46 -0
  320. package/dist-lib/lib/core/handoff-channel.js +102 -0
  321. package/dist-lib/lib/core/health-assistant.d.ts +7 -0
  322. package/dist-lib/lib/core/health-assistant.js +24 -0
  323. package/dist-lib/lib/core/helpers/index.d.ts +5 -0
  324. package/dist-lib/lib/core/helpers/index.js +74 -0
  325. package/dist-lib/lib/core/hosted-agents.d.ts +36 -0
  326. package/dist-lib/lib/core/hosted-agents.js +127 -0
  327. package/dist-lib/lib/core/id.d.ts +1 -0
  328. package/dist-lib/lib/core/id.js +5 -0
  329. package/dist-lib/lib/core/identity.d.ts +14 -0
  330. package/dist-lib/lib/core/identity.js +45 -0
  331. package/dist-lib/lib/core/image/cache-sweep.d.ts +12 -0
  332. package/dist-lib/lib/core/image/cache-sweep.js +67 -0
  333. package/dist-lib/lib/core/image/normalize.d.ts +3 -0
  334. package/dist-lib/lib/core/image/normalize.js +49 -0
  335. package/dist-lib/lib/core/image/sniff.d.ts +3 -0
  336. package/dist-lib/lib/core/image/sniff.js +149 -0
  337. package/dist-lib/lib/core/invite.d.ts +14 -0
  338. package/dist-lib/lib/core/invite.js +36 -0
  339. package/dist-lib/lib/core/local/index.d.ts +71 -0
  340. package/dist-lib/lib/core/local/index.js +391 -0
  341. package/dist-lib/lib/core/membership.d.ts +15 -0
  342. package/dist-lib/lib/core/membership.js +40 -0
  343. package/dist-lib/lib/core/mesh.d.ts +153 -0
  344. package/dist-lib/lib/core/mesh.js +509 -0
  345. package/dist-lib/lib/core/migrations/agent-catalog.d.ts +6 -0
  346. package/dist-lib/lib/core/migrations/agent-catalog.js +17 -0
  347. package/dist-lib/lib/core/migrations/index.d.ts +5 -0
  348. package/dist-lib/lib/core/migrations/index.js +7 -0
  349. package/dist-lib/lib/core/migrations/membership.d.ts +11 -0
  350. package/dist-lib/lib/core/migrations/membership.js +26 -0
  351. package/dist-lib/lib/core/min-version.d.ts +6 -0
  352. package/dist-lib/lib/core/min-version.js +31 -0
  353. package/dist-lib/lib/core/next-frame.d.ts +6 -0
  354. package/dist-lib/lib/core/next-frame.js +27 -0
  355. package/dist-lib/lib/core/notification.d.ts +6 -0
  356. package/dist-lib/lib/core/notification.js +162 -0
  357. package/dist-lib/lib/core/ota-config.d.ts +19 -0
  358. package/dist-lib/lib/core/ota-config.js +44 -0
  359. package/dist-lib/lib/core/prompt-history.d.ts +12 -0
  360. package/dist-lib/lib/core/prompt-history.js +40 -0
  361. package/dist-lib/lib/core/search-fold.d.ts +1 -0
  362. package/dist-lib/lib/core/search-fold.js +4 -0
  363. package/dist-lib/lib/core/secret-handoff.d.ts +17 -0
  364. package/dist-lib/lib/core/secret-handoff.js +29 -0
  365. package/dist-lib/lib/core/send.d.ts +18 -0
  366. package/dist-lib/lib/core/send.js +46 -0
  367. package/dist-lib/lib/core/skill-overlay-handoff.d.ts +25 -0
  368. package/dist-lib/lib/core/skill-overlay-handoff.js +50 -0
  369. package/dist-lib/lib/core/skill-overlay-receiver.d.ts +8 -0
  370. package/dist-lib/lib/core/skill-overlay-receiver.js +20 -0
  371. package/dist-lib/lib/core/skill-secrets.d.ts +25 -0
  372. package/dist-lib/lib/core/skill-secrets.js +79 -0
  373. package/dist-lib/lib/core/sync/chats.d.ts +3 -0
  374. package/dist-lib/lib/core/sync/chats.js +5 -0
  375. package/dist-lib/lib/core/sync/chunks.d.ts +9 -0
  376. package/dist-lib/lib/core/sync/chunks.js +16 -0
  377. package/dist-lib/lib/core/sync/devices.d.ts +4 -0
  378. package/dist-lib/lib/core/sync/devices.js +12 -0
  379. package/dist-lib/lib/core/sync/files.d.ts +16 -0
  380. package/dist-lib/lib/core/sync/files.js +109 -0
  381. package/dist-lib/lib/core/sync/index.d.ts +5 -0
  382. package/dist-lib/lib/core/sync/index.js +63 -0
  383. package/dist-lib/lib/core/sync-status.d.ts +17 -0
  384. package/dist-lib/lib/core/sync-status.js +129 -0
  385. package/dist-lib/lib/core/system-info.d.ts +24 -0
  386. package/dist-lib/lib/core/system-info.js +66 -0
  387. package/dist-lib/lib/core/tool-activity.d.ts +22 -0
  388. package/dist-lib/lib/core/tool-activity.js +39 -0
  389. package/dist-lib/lib/core/tool-configs.d.ts +13 -0
  390. package/dist-lib/lib/core/tool-configs.js +141 -0
  391. package/dist-lib/lib/core.d.ts +291 -0
  392. package/dist-lib/lib/core.js +2126 -0
  393. package/dist-lib/lib/credential-pusher.d.ts +67 -0
  394. package/dist-lib/lib/credential-pusher.js +290 -0
  395. package/dist-lib/lib/custom-model-pusher.d.ts +9 -0
  396. package/dist-lib/lib/custom-model-pusher.js +42 -0
  397. package/dist-lib/lib/custom-model-source.d.ts +15 -0
  398. package/dist-lib/lib/custom-model-source.js +46 -0
  399. package/dist-lib/lib/deadline.d.ts +9 -0
  400. package/dist-lib/lib/deadline.js +31 -0
  401. package/dist-lib/lib/diagnostics.d.ts +35 -0
  402. package/dist-lib/lib/diagnostics.js +14 -0
  403. package/dist-lib/lib/fetch-node.d.ts +1 -0
  404. package/dist-lib/lib/fetch-node.js +1 -0
  405. package/dist-lib/lib/fs-drive-bridge.d.ts +12 -0
  406. package/dist-lib/lib/fs-drive-bridge.js +186 -0
  407. package/dist-lib/lib/fs-grant-keys.d.ts +2 -0
  408. package/dist-lib/lib/fs-grant-keys.js +6 -0
  409. package/dist-lib/lib/fs-grants.d.ts +10 -0
  410. package/dist-lib/lib/fs-grants.js +44 -0
  411. package/dist-lib/lib/image-cache-dir.d.ts +1 -0
  412. package/dist-lib/lib/image-cache-dir.js +5 -0
  413. package/dist-lib/lib/integrations/index.d.ts +4 -0
  414. package/dist-lib/lib/integrations/index.js +3 -0
  415. package/dist-lib/lib/integrations/mcp-providers.d.ts +12 -0
  416. package/dist-lib/lib/integrations/mcp-providers.js +22 -0
  417. package/dist-lib/lib/integrations/oauth-providers.d.ts +14 -0
  418. package/dist-lib/lib/integrations/oauth-providers.js +116 -0
  419. package/dist-lib/lib/integrations/setup-routes.d.ts +12 -0
  420. package/dist-lib/lib/integrations/setup-routes.js +114 -0
  421. package/dist-lib/lib/knowledge-search.d.ts +7 -0
  422. package/dist-lib/lib/knowledge-search.js +53 -0
  423. package/dist-lib/lib/kv-cache-reclaim.d.ts +5 -0
  424. package/dist-lib/lib/kv-cache-reclaim.js +29 -0
  425. package/dist-lib/lib/kv-cache.d.ts +18 -0
  426. package/dist-lib/lib/kv-cache.js +33 -0
  427. package/dist-lib/lib/legacy-core-domain.d.ts +258 -0
  428. package/dist-lib/lib/legacy-core-domain.js +1 -0
  429. package/dist-lib/lib/llm-model-names.d.ts +1 -0
  430. package/dist-lib/lib/llm-model-names.js +1 -0
  431. package/dist-lib/lib/log-buffer.d.ts +7 -0
  432. package/dist-lib/lib/log-buffer.js +16 -0
  433. package/dist-lib/lib/log-levels.d.ts +7 -0
  434. package/dist-lib/lib/log-levels.js +1 -0
  435. package/dist-lib/lib/log.d.ts +60 -0
  436. package/dist-lib/lib/log.js +149 -0
  437. package/dist-lib/lib/logger-type.d.ts +13 -0
  438. package/dist-lib/lib/logger-type.js +1 -0
  439. package/dist-lib/lib/logger.d.ts +5 -0
  440. package/dist-lib/lib/logger.js +18 -0
  441. package/dist-lib/lib/model-catalog-revision.d.ts +9 -0
  442. package/dist-lib/lib/model-catalog-revision.js +65 -0
  443. package/dist-lib/lib/model-clamp.d.ts +6 -0
  444. package/dist-lib/lib/model-clamp.js +20 -0
  445. package/dist-lib/lib/model-context.d.ts +13 -0
  446. package/dist-lib/lib/model-context.js +71 -0
  447. package/dist-lib/lib/model-download-status.d.ts +29 -0
  448. package/dist-lib/lib/model-download-status.js +56 -0
  449. package/dist-lib/lib/model-download.d.ts +191 -0
  450. package/dist-lib/lib/model-download.js +787 -0
  451. package/dist-lib/lib/oauth/asana.d.ts +35 -0
  452. package/dist-lib/lib/oauth/asana.js +71 -0
  453. package/dist-lib/lib/oauth/browser-connect.d.ts +22 -0
  454. package/dist-lib/lib/oauth/browser-connect.js +83 -0
  455. package/dist-lib/lib/oauth/client.d.ts +9 -0
  456. package/dist-lib/lib/oauth/client.js +9 -0
  457. package/dist-lib/lib/oauth/common.d.ts +38 -0
  458. package/dist-lib/lib/oauth/common.js +36 -0
  459. package/dist-lib/lib/oauth/crypto.d.ts +3 -0
  460. package/dist-lib/lib/oauth/crypto.js +25 -0
  461. package/dist-lib/lib/oauth/fetch-platform.d.ts +1 -0
  462. package/dist-lib/lib/oauth/fetch-platform.js +1 -0
  463. package/dist-lib/lib/oauth/flow-core.d.ts +6 -0
  464. package/dist-lib/lib/oauth/flow-core.js +37 -0
  465. package/dist-lib/lib/oauth/flow.d.ts +3 -0
  466. package/dist-lib/lib/oauth/flow.js +24 -0
  467. package/dist-lib/lib/oauth/google.d.ts +37 -0
  468. package/dist-lib/lib/oauth/google.js +77 -0
  469. package/dist-lib/lib/oauth/host.d.ts +5 -0
  470. package/dist-lib/lib/oauth/host.js +3 -0
  471. package/dist-lib/lib/oauth/keys.d.ts +25 -0
  472. package/dist-lib/lib/oauth/keys.js +93 -0
  473. package/dist-lib/lib/oauth/loopback.d.ts +3 -0
  474. package/dist-lib/lib/oauth/loopback.js +119 -0
  475. package/dist-lib/lib/oauth/loopback.stub.d.ts +3 -0
  476. package/dist-lib/lib/oauth/loopback.stub.js +6 -0
  477. package/dist-lib/lib/oauth/notion.d.ts +34 -0
  478. package/dist-lib/lib/oauth/notion.js +82 -0
  479. package/dist-lib/lib/oauth/pkce-runtime.d.ts +3 -0
  480. package/dist-lib/lib/oauth/pkce-runtime.js +5 -0
  481. package/dist-lib/lib/oauth/pkce.d.ts +8 -0
  482. package/dist-lib/lib/oauth/pkce.js +6 -0
  483. package/dist-lib/lib/oauth/providers.d.ts +5 -0
  484. package/dist-lib/lib/oauth/providers.js +73 -0
  485. package/dist-lib/lib/oauth/redirect-types.d.ts +19 -0
  486. package/dist-lib/lib/oauth/redirect-types.js +1 -0
  487. package/dist-lib/lib/oauth/register.d.ts +3 -0
  488. package/dist-lib/lib/oauth/register.js +34 -0
  489. package/dist-lib/lib/oauth/resolve.d.ts +17 -0
  490. package/dist-lib/lib/oauth/resolve.js +57 -0
  491. package/dist-lib/lib/oauth/spotify.d.ts +32 -0
  492. package/dist-lib/lib/oauth/spotify.js +73 -0
  493. package/dist-lib/lib/oauth/token-exchange-core.d.ts +6 -0
  494. package/dist-lib/lib/oauth/token-exchange-core.js +97 -0
  495. package/dist-lib/lib/oauth/token-exchange.d.ts +5 -0
  496. package/dist-lib/lib/oauth/token-exchange.js +9 -0
  497. package/dist-lib/lib/oauth/types.d.ts +54 -0
  498. package/dist-lib/lib/oauth/types.js +1 -0
  499. package/dist-lib/lib/oauth.client.d.ts +5 -0
  500. package/dist-lib/lib/oauth.client.js +2 -0
  501. package/dist-lib/lib/oauth.d.ts +14 -0
  502. package/dist-lib/lib/oauth.js +12 -0
  503. package/dist-lib/lib/obsidian-key.d.ts +1 -0
  504. package/dist-lib/lib/obsidian-key.js +1 -0
  505. package/dist-lib/lib/race-resource.d.ts +5 -0
  506. package/dist-lib/lib/race-resource.js +25 -0
  507. package/dist-lib/lib/reconnecting-watch.d.ts +2 -0
  508. package/dist-lib/lib/reconnecting-watch.js +78 -0
  509. package/dist-lib/lib/retry-budget.d.ts +14 -0
  510. package/dist-lib/lib/retry-budget.js +39 -0
  511. package/dist-lib/lib/run-notices.d.ts +23 -0
  512. package/dist-lib/lib/run-notices.js +33 -0
  513. package/dist-lib/lib/run-transcript.d.ts +35 -0
  514. package/dist-lib/lib/run-transcript.js +106 -0
  515. package/dist-lib/lib/skill-readiness.d.ts +2 -0
  516. package/dist-lib/lib/skill-readiness.js +21 -0
  517. package/dist-lib/lib/skill-types.d.ts +70 -0
  518. package/dist-lib/lib/skill-types.js +1 -0
  519. package/dist-lib/lib/spec-loader.d.ts +14 -0
  520. package/dist-lib/lib/spec-loader.js +9 -0
  521. package/dist-lib/lib/suspend-lifecycle.d.ts +17 -0
  522. package/dist-lib/lib/suspend-lifecycle.js +33 -0
  523. package/dist-lib/lib/task.d.ts +26 -0
  524. package/dist-lib/lib/task.js +69 -0
  525. package/dist-lib/lib/unref.d.ts +3 -0
  526. package/dist-lib/lib/unref.js +4 -0
  527. package/dist-lib/lib/wav-duration.d.ts +1 -0
  528. package/dist-lib/lib/wav-duration.js +53 -0
  529. package/dist-lib/lib/write-queue.d.ts +22 -0
  530. package/dist-lib/lib/write-queue.js +39 -0
  531. package/dist-lib/schema/build.d.ts +1 -0
  532. package/dist-lib/schema/build.js +120 -0
  533. package/dist-lib/schema/config.d.ts +4 -0
  534. package/dist-lib/schema/config.js +7 -0
  535. package/dist-lib/schema/content.d.ts +20 -0
  536. package/dist-lib/schema/content.js +122 -0
  537. package/dist-lib/schema/engine.d.ts +2 -0
  538. package/dist-lib/schema/engine.js +2013 -0
  539. package/dist-lib/schema/field-order.d.ts +1 -0
  540. package/dist-lib/schema/field-order.js +95 -0
  541. package/dist-lib/schema/local.d.ts +2 -0
  542. package/dist-lib/schema/local.js +133 -0
  543. package/dist-lib/schema/qvac.d.ts +22 -0
  544. package/dist-lib/schema/qvac.js +722 -0
  545. package/dist-lib/spec/bind.d.ts +2 -0
  546. package/dist-lib/spec/bind.js +520 -0
  547. package/dist-lib/spec/calls.d.ts +3 -0
  548. package/dist-lib/spec/calls.js +129 -0
  549. package/dist-lib/spec/capabilities.d.ts +263 -0
  550. package/dist-lib/spec/config/hyperconf/index.d.ts +15 -0
  551. package/dist-lib/spec/config/hyperconf/index.js +24 -0
  552. package/dist-lib/spec/config/hyperconf/package.json +3 -0
  553. package/dist-lib/spec/config/hyperschema/index.js +81 -0
  554. package/dist-lib/spec/config/hyperschema/schema.json +19 -0
  555. package/dist-lib/spec/hrpc/hrpc.json +1500 -0
  556. package/dist-lib/spec/hrpc/index.d.ts +3 -0
  557. package/dist-lib/spec/hrpc/index.js +1545 -0
  558. package/dist-lib/spec/hrpc/messages.d.ts +4 -0
  559. package/dist-lib/spec/hrpc/messages.js +8373 -0
  560. package/dist-lib/spec/hyperschema/index.d.ts +20 -0
  561. package/dist-lib/spec/hyperschema/index.js +8373 -0
  562. package/dist-lib/spec/hyperschema/schema.json +6202 -0
  563. package/dist-lib/spec/hyperschema/types.d.ts +1545 -0
  564. package/dist-lib/spec/index.d.ts +36 -0
  565. package/dist-lib/spec/index.js +6 -0
  566. package/dist-lib/spec/local/hyperdb/db.json +186 -0
  567. package/dist-lib/spec/local/hyperdb/index.d.ts +9 -0
  568. package/dist-lib/spec/local/hyperdb/index.js +855 -0
  569. package/dist-lib/spec/local/hyperdb/messages.js +2189 -0
  570. package/dist-lib/spec/local/hyperschema/index.js +1208 -0
  571. package/dist-lib/spec/local/hyperschema/schema.json +639 -0
  572. package/dist-lib/spec/local/hyperschema/types.d.ts +159 -0
  573. package/dist-lib/spec/mesh/hyperdb/db.json +702 -0
  574. package/dist-lib/spec/mesh/hyperdb/index.d.ts +9 -0
  575. package/dist-lib/spec/mesh/hyperdb/index.js +2260 -0
  576. package/dist-lib/spec/mesh/hyperdb/messages.js +7453 -0
  577. package/dist-lib/spec/mesh/hyperdispatch/dispatch.json +174 -0
  578. package/dist-lib/spec/mesh/hyperdispatch/index.d.ts +13 -0
  579. package/dist-lib/spec/mesh/hyperdispatch/index.js +566 -0
  580. package/dist-lib/spec/mesh/hyperdispatch/messages.js +5713 -0
  581. package/dist-lib/spec/mesh/hyperschema/index.d.ts +14 -0
  582. package/dist-lib/spec/mesh/hyperschema/index.js +5713 -0
  583. package/dist-lib/spec/mesh/hyperschema/schema.json +3142 -0
  584. package/dist-lib/spec/mesh/hyperschema/types.d.ts +754 -0
  585. package/ducks.png +0 -0
  586. package/package.json +332 -1
package/README.md ADDED
@@ -0,0 +1,694 @@
1
+ <p align="center">
2
+ <img src="ducks.png" alt="" width="200">
3
+ </p>
4
+
5
+ <h1 align="center">@qvac/core</h1>
6
+
7
+ <p align="center">
8
+ <b>Peer-to-peer AI across your devices</b>
9
+ <br>
10
+ Run on mobile, desktop or remotely via P2P magic
11
+ </p>
12
+
13
+ <p align="center">
14
+ delegated inference &middot; replicated agents, chats and skills &middot; approve or
15
+ stop from anywhere &middot; works offline &middot; no servers
16
+ </p>
17
+
18
+ <p align="center">
19
+ <a href="#integrating-a-harness">Harness</a> &middot;
20
+ <a href="#what-you-get-for-free">What you get</a> &middot;
21
+ <a href="#one-shot-tasks">Tasks</a> &middot;
22
+ <a href="#rendering-the-chunk-stream-any-ui">Chunk stream</a> &middot;
23
+ <a href="#harness-event-reference">Events</a> &middot;
24
+ <a href="#development">Development</a>
25
+ </p>
26
+
27
+ A P2P, multi-device AI workbench core. Three pieces, one duplex stream:
28
+
29
+ - **`Core`** — the state authority. Owns the replicated mesh (agents, devices,
30
+ the chunk stream) and the device-local store, serves every capability over
31
+ hrpc. It contains **no AI whatsoever**.
32
+ - **`Client`** — the client. A thin proxy whose methods are generated from the
33
+ schema; everything an app (or an assistant) does goes through it.
34
+ - **`QvacAssistant`** — the execution host. Owns _your_ AI harness and drives
35
+ it purely through the Client's capability surface, exactly like any other
36
+ client. This is the only place inference happens.
37
+
38
+ ```
39
+ ┌───────────── host app process ─────────────┐ ┌── sidecar / same process ──┐
40
+ │ UI ──► Client ◄── QvacAssistant ◄─ Harness│ ◄──► │ Core ◄──► mesh (other devices)
41
+ └────────────────────────────────────────────┘ └────────────────────────────┘
42
+ ```
43
+
44
+ ## Integrating a harness
45
+
46
+ A harness is one method. Implement `completion(context)` as an async iterable
47
+ of plain events and you are done — no knowledge of chunks, meshes, or RPC:
48
+
49
+ ```js
50
+ const harness = {
51
+ async *completion({ model, agent, messages, approve, signal }) {
52
+ // Call any model you like — llama.cpp, an HTTP API, a test fake…
53
+ const reply = await myModel.chat({ system: agent.bio, messages })
54
+ yield { type: 'content', text: reply }
55
+ }
56
+ }
57
+ ```
58
+
59
+ Wire it up — prod shape, Core in its own Bare sidecar process:
60
+
61
+ ```js
62
+ import { QvacAssistant, AssistantClient } from 'qvac-core'
63
+
64
+ // Spawns the bundled Core sidecar for free — ready() boots it, close()
65
+ // tears it down. Joining a mesh: pass meshSeed/meshKey from a decoded invite.
66
+ const engine = new AssistantClient({
67
+ storagePath: '/path/to/state',
68
+ device: { name: 'my-device', capabilityProfile: 'mid', recommendedModelName: 'Qwen3.5-9B' }
69
+ })
70
+ await engine.ready()
71
+
72
+ const assistant = new QvacAssistant(engine, harness)
73
+ await assistant.ready()
74
+ ```
75
+
76
+ No `Core` construction host-side at all — the sidecar entry boots it, your
77
+ process keeps only the Client, the assistant, and the model runtime. Pass
78
+ `entry` in the options to replace the bundled Core entry with your own.
79
+
80
+ For tests or a single-process host, `sidecar: false` boots the same Core
81
+ in-process from the same options — nothing else changes:
82
+
83
+ ```js
84
+ const engine = new AssistantClient({ sidecar: false, storagePath, device })
85
+ await engine.ready() // engine.core is the in-process Core (null in sidecar mode)
86
+ ```
87
+
88
+ (Lower still, `Core`/`Client` construct directly over any duplex pair —
89
+ that's all either mode does.)
90
+
91
+ Either way that's the whole integration — the assistant can't tell the
92
+ difference, because it only ever speaks capabilities. It is a
93
+ `ready-resource`: `ready()`, `close()`, `suspend()`/`resume()` (mobile
94
+ backgrounding — stops taking new work; in-flight runs finish).
95
+
96
+ The real llama.cpp harness lives in `../assistant-harness` and implements this
97
+ same contract — see `tui/index.mjs` for it running end to end.
98
+
99
+ ### The harness over a wire (desktop / mobile)
100
+
101
+ In-process is for tests and simple hosts. Real apps put the harness where
102
+ the model runtime belongs, behind the `qvac-harness` wire contract
103
+ — the same `Harness` object served on a pipe, and a `RemoteHarness` handle
104
+ the assistant takes unchanged. The rule: **colocate the harness with
105
+ wherever the SDK runs in-process.**
106
+
107
+ **Desktop (Electron)** — the harness gets its own OS process, so a llama.cpp
108
+ crash or OOM kills the AI, never the mesh or the app. The whole local stack
109
+ is three lines — `spawnHarness()` spawns the bundled llama.cpp harness
110
+ sidecar and hands back the handle the assistant takes unchanged:
111
+
112
+ ```js
113
+ import { QvacAssistant, AssistantClient } from 'qvac-core'
114
+ import { spawnHarness } from 'qvac-harness'
115
+
116
+ const engine = new AssistantClient({ storagePath, device })
117
+ const harness = spawnHarness({ entry, skillsDir, sdkStorage, logger })
118
+ const assistant = new QvacAssistant(engine, harness)
119
+ await assistant.ready()
120
+
121
+ harness.on('status', (s) => log.warn('harness', s)) // running / died, lives
122
+ // shutdown: harness.close() kills the spawned process
123
+ ```
124
+
125
+ Custom tool sets: pass your own `entry` to `spawnHarness` (the bundled entry
126
+ is `qvac-harness/worker/harness-sidecar.mjs` — two lines of
127
+ `serveHarness(Bare.IPC, new Harness({ tools }))` to copy).
128
+
129
+ **Mobile (React Native + bare-kit)** — worklets share the app process, so a
130
+ separate harness worklet buys no isolation and costs a hop; the harness
131
+ lives in the Core worklet (or speaks to the SDK's own worklet over its
132
+ pipe). Same contract, an in-worklet duplex instead of a spawn:
133
+
134
+ ```js
135
+ // inside the Core worklet: Core + harness + assistant, one process
136
+ import { QvacAssistant, duplexPair } from 'qvac-core'
137
+ import { Harness, serveHarness, connectHarness } from 'qvac-harness'
138
+
139
+ const [serverEnd, clientEnd] = duplexPair()
140
+ serveHarness(serverEnd, new Harness({ tools }))
141
+ new QvacAssistant(
142
+ client,
143
+ connectHarness(() => clientEnd)
144
+ )
145
+ ```
146
+
147
+ **Death is death** (day one, like v1): a harness death fails the in-flight
148
+ run with a `HARNESS_DIED` reason and fails every later call fast — never a
149
+ hang. The `'status'` events and `harness.lives` are in-memory observability;
150
+ restart/recovery policy lands on that plumbing later. The wire contract also
151
+ carries `agent.id`, so a harness scopes per-agent tool config/credentials
152
+ locally (a work agent's gmail never leaks to a personal agent) — that config
153
+ never crosses the wire or touches the mesh. See `../harness-rpc/README.md`
154
+ for the contract itself.
155
+
156
+ ## What you get for free
157
+
158
+ Because every part of a run is a replicated chunk, the assistant inherits the
159
+ whole multi-device story without any harness code:
160
+
161
+ - **Delegation** — agents live on the mesh with a `providerDeviceId`. Any
162
+ device asks (`engine.runAgent({ chatId, agentId })` appends a `run-request`
163
+ chunk); the device that _provides_ the agent executes; every chunk streams
164
+ back to everyone. A request made while the provider is offline runs when it
165
+ returns. Asking requires no harness at all.
166
+ - **Approval** — `await approve(call)` inside your harness surfaces as an
167
+ approval-request chunk on every device; ANY of them may answer
168
+ (`engine.resolveApproval`), first decision wins, and your promise resolves.
169
+ A run that dies with an unanswered request auto-denies it — no peer ever
170
+ sees a forever-pending ask. Tools marked `autoApprove` skip the gate.
171
+ - **Stop** — any device may stop any run: `engine.stopRun` writes the
172
+ terminal `canceled` run-status itself (no executor needed — it settles an
173
+ orphaned run too); an executing harness's `signal` fires when the status
174
+ replicates to it, and it writes nothing more.
175
+ - **Model progress** — emit `'model'` events (`loading`/`downloading`/`ready`)
176
+ from your harness (plain EventEmitter) and every device watching the run
177
+ sees load/download progress, coalesced.
178
+ - **Metrics** — yield one `{ type: 'metrics', ... }` event and it lands on the
179
+ completed run-status chunk, replicated.
180
+ - **Crash safety** — the first run-status chunk is the replicated claim
181
+ marker: a restarted assistant never re-executes a run that already started.
182
+
183
+ ## One-shot tasks
184
+
185
+ A chat is a conversation; a task is one question with one answer. `runTask`
186
+ composes the append/run/watch flow into a single await — no streaming, no chunk
187
+ bookkeeping — and resolves once the run settles:
188
+
189
+ ```js
190
+ import { runTask } from 'qvac-core/task'
191
+
192
+ const result = await runTask(engine, { agentId: agent.id, prompt: 'Say hi' })
193
+
194
+ result.state // 'completed' | 'failed' | 'canceled'
195
+ result.text // the answer: content after the last tool result, trimmed
196
+ result.thinking // kept apart from the answer
197
+ result.toolCalls // [{ name, arguments }]
198
+ result.attachments // what the run produced
199
+ result.sources // RAG hits the run cited
200
+ result.reason // why, when state is not 'completed'
201
+ result.chunks // the run's raw chunks, for anything the above flattened
202
+ ```
203
+
204
+ A run that calls a tool interleaves content around `tool-call` / `tool-result`,
205
+ so `text` is the content after the last tool result — the model narrating its
206
+ way there is in `chunks`, not in the answer.
207
+
208
+ It opens its own chat, titled from the prompt; pass `chatId` to continue an
209
+ existing one instead. A failed or canceled run resolves like any other — the
210
+ outcome is in `state`, so nothing hangs waiting for an answer that is not
211
+ coming.
212
+
213
+ `skill: 'presentations'` forces that skill into the turn. It rides the same
214
+ `@mention` channel a user types, so the harness inlines the skill's
215
+ instructions into the message rather than leaving the model to call the loader
216
+ — and it overrides the agent's `enabledSkills`, which readiness alone gates.
217
+ One skill per task: the mention is the first token of the message.
218
+
219
+ ## Rendering the chunk stream (any UI)
220
+
221
+ The engine exposes a snapshot chat catalog:
222
+
223
+ ```js
224
+ const { chats } = await engine.listChats()
225
+ ```
226
+
227
+ It returns complete, active mesh chat rows ordered by `createdAt`, then `id` —
228
+ pure metadata, so `chatsWatch` only re-emits on create, rename, delete, or flag
229
+ changes. The existing replicated chat encoding uses zero as the default for an
230
+ absent optional `deletedAt`, so `deletedAt: 0` is not a representable tombstone
231
+ and is treated as active. Positive tombstone timestamps are excluded.
232
+ `listChats()` itself is a snapshot query. Chat create, update, delete, and watch
233
+ capabilities are available through the typed chat API.
234
+
235
+ ### Chat activity
236
+
237
+ Derived per-chat run state rides its own lean stream so it never churns the
238
+ metadata list:
239
+
240
+ ```js
241
+ const { activities } = await engine.listChatActivity() // or engine.chatActivityWatch()
242
+ ```
243
+
244
+ Each row is just `{ id, activityState }` — a small state machine over the chat's
245
+ replicated runs:
246
+
247
+ - `queued` — a run requested but not yet executing.
248
+ - `running` — generic compute (thinking, responding, an ordinary tool call).
249
+ - `generating-image` / `generating-audio` / `transcribing-audio` — the active run
250
+ refined by the media it is producing.
251
+ - `awaiting-approval` — the active run paused on the user.
252
+
253
+ Every value is a mutually-exclusive state, not a parallel flag. Runs serialize
254
+ per chat, so at most one is executing. The list carries only active chats — a
255
+ chat absent from it is idle, so a busy list stays proportional to what's running
256
+ rather than to how many chats exist. Clients join these rows to `listChats` by
257
+ `id` and render the label per state, so the wire carries no words.
258
+
259
+ Pass `chatIds` to scope activity to a viewport: `chatActivityWatch({ chatIds })`
260
+ returns captions for only those chats, so a client watches what is on screen and
261
+ re-opens with a new window on scroll (chats it stops watching read as idle). An
262
+ empty or absent `chatIds` returns the whole active set.
263
+
264
+ The media states are the only phase detail on this stream, and they belong here
265
+ because they are coarse and boundary-driven — image generation is one long phase,
266
+ not a per-token flip. Core derives them from the run's `progress.phase`
267
+ (`generating` plus an unsettled `generate_music` → audio, otherwise
268
+ `generating` → image; `transcribing` → transcription) and its unsettled media
269
+ tool-calls (`generate_music` → audio before sampling starts).
270
+ Everything finer stays off the list: the moment-to-moment phase
271
+ (thinking/responding, which non-media tool, the live percentage) is on the
272
+ per-chat chunk stream the open chat subscribes to, and model download/load
273
+ progress stays in `modelDownloadsWatch` (it reads as generic `running` here). So
274
+ `chatActivityWatch` broadcasts only at these coarse boundaries, and `chatsWatch`
275
+ stays silent through a run entirely.
276
+
277
+ ### Notifications
278
+
279
+ `notificationsWatch()` merges device-local model completions with chat-derived
280
+ `assistant-reply`, `run-failed`, `approval-required`, and `user-message` events.
281
+ The newest 200 events are returned by default, with `truncated` when more remain.
282
+
283
+ Chat rows carry `chatId`, `agentId`, `chunkId`, `seq`, and `createdAt`, with an
284
+ optional 280-character preview and agent name. Human messages also carry
285
+ `senderDeviceId`, optional `senderMemberId`, `senderName`, and `chatTitle`.
286
+ Messages from this device or another device of the same member are suppressed;
287
+ without membership the sender falls back to device identity. Deleted chats and
288
+ automatic diagram repairs stay silent.
289
+
290
+ `ackNotification({ chatId, seq })` advances the local cursor monotonically.
291
+ Pending approvals stay visible until resolved, including when answered elsewhere.
292
+ Model rows carry `kind: 'model-downloaded'`, `notificationId`, `modelName`, and
293
+ `createdAt`; `ackNotification({ notificationId })` acknowledges that completion
294
+ without touching a chat cursor. One latest completion per model survives restart,
295
+ and an old acknowledgement cannot clear a newer download.
296
+
297
+ The Core-lifetime stream follows mesh transitions through lifecycle events and
298
+ keeps local model events available without a mesh. It ends on Core shutdown or
299
+ subscriber cancellation. Clients own notification copy, foreground suppression,
300
+ and operating-system delivery.
301
+
302
+ The assistant persists a logical transfer identity and completion separately from
303
+ download progress. Retries and suspension retain that identity. Completion waits
304
+ for required companion assets; cached discovery and 100% progress do not notify.
305
+ Core also emits `notification` wake events for terminal run-status,
306
+ approval-request, and user-message chunks.
307
+
308
+ A chat IS its chunk stream — one watch drives the whole view, and every
309
+ device renders the same thing because they all see the same chunks:
310
+
311
+ ```js
312
+ for await (const { chunks } of engine.chunksWatch({ chatId })) render(chunks)
313
+ ```
314
+
315
+ Each frame is the full chat in seq order. To render it, group chunks into
316
+ turns, then map each chunk type:
317
+
318
+ **Grouping.** A `user-message` (and an `attachment`) is its own user turn —
319
+ label it by `originDeviceId`, resolved to a name via `devicesWatch`. Every
320
+ chunk carrying a `runId` belongs to one assistant turn, anchored where its
321
+ `run-request` sits (that ordering matters: a message queued during a slow run
322
+ must sort before the reply that follows it).
323
+
324
+ **Per type:**
325
+
326
+ | chunk type | render as |
327
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
328
+ | `user-message` | the user's text (`chunk.userMessage.text`), labelled by device |
329
+ | `run-request` | nothing — it anchors the turn and marks "a run is in flight" before the provider even starts (`chunk.runRequest.agentId` names the agent) |
330
+ | `content` | assistant text (`chunk.content.text`); consecutive content chunks concatenate into one bubble |
331
+ | `thinking` | collapsed/dimmed reasoning segment (`chunk.thinking.text`) |
332
+ | `tool-call` | `→ name(arguments)` line from `chunk.toolCall` |
333
+ | `tool-result` | `← name: result` line from `chunk.toolResult` (collapse long results) |
334
+ | `attachment` | file card from `chunk.attachment` (`fileName`, `mimeType`, `byteLength`); fetch bytes with `engine.downloadAttachment({ blobId })` |
335
+ | `approval-request` | **the chunk IS the approval state**, in the typed `chunk.approvalRequest` struct. `status === 'pending'` → show the ask (prefer `resource` — e.g. `host wttr.in` — over raw arguments) with allow / always-allow / deny; any other status → a resolved badge |
336
+ | `run-status` | turn lifecycle, in the typed `chunk.runStatus` struct: `executing` with a `progress` sub-struct (`phase` `loading`/`downloading`, `model`, `percentage` — edited in place on the one executing chunk, labelled by the chunk's `originDeviceId`); `failed` → error with `reason`; `completed` → done, `metrics` (tokens, tok/s, ttft) |
337
+
338
+ **Approval answers** go through `engine.resolveApproval({ id, approved,
339
+ scope, expiresAt })` from ANY device — first decision wins, the flipped
340
+ chunk replicates to everyone. `scope: 'always'` also mints a standing
341
+ approval-rule from the request's tool + resource, so matching future calls
342
+ don't prompt anywhere until the rule expires — one hour by default; pass
343
+ `expiresAt` (ms since epoch) to grant longer or shorter
344
+ (`engine.listApprovalRules({})` shows what's remembered).
345
+
346
+ **Busy state** derives from the stream, not local flags: if the last
347
+ assistant turn has no terminal `run-status` (`completed`/`failed`/
348
+ `canceled`), a run is in flight somewhere — show the spinner on every device
349
+ and offer `engine.stopRun({ chatId, runId })`.
350
+
351
+ **Stopping** is a right of any device, not a request to the executor:
352
+ `stopRun` appends the terminal `canceled` run-status itself, so it settles a
353
+ run whether its provider is live, offline, or gone entirely (an orphaned
354
+ run's spinner). A live executor yields when the status replicates to it and
355
+ writes nothing more; if the run finished first, the stop is a no-op. The
356
+ first terminal status in seq order is always the run's one outcome.
357
+
358
+ **Orphaned agents.** Every agent from `listAgents`/`getAgent`/`agentsWatch`
359
+ carries a derived `orphaned` flag: its provider device row is gone or
360
+ revoked, so nobody can execute its runs until it's re-homed
361
+ (`updateAgent({ id, providerDeviceId })`). Render it as "waiting for
362
+ provider" — on the agent itself, and on a busy turn whose agent is orphaned
363
+ (that spinner will never resolve by itself). Device removal deliberately
364
+ does NOT delete the device's agents: their chats survive, and re-homing
365
+ brings them back.
366
+
367
+ `tui/app.mjs` (`turnsFromChunks`, `renderTurn`) is this table as
368
+ working code — the same model drives the TUI end to end.
369
+
370
+ ## File status
371
+
372
+ `Mesh.files`, implemented in `lib/core/files/index.ts`, owns file access, local
373
+ processing state, and cleanup. Its internal modules expose one `index.ts` each:
374
+ Store handles bytes and ranges, Records handles the existing attachment and
375
+ knowledge surfaces, Processing owns worker lifetime, and RAG owns extraction,
376
+ artifacts, vectors, and retrieval. Removing a Knowledge entry preserves its
377
+ source while a live chat attachment still references it.
378
+
379
+ Browser clients keep using the pure `@qvac/core/rag/*` package exports; these
380
+ do not import the backend Files module.
381
+
382
+ `fileStatusWatch({ chatId })` returns a full snapshot of the chat's files, including
383
+ uploads still in progress. Each file has `fileId`, `state`, `readyForChat`, optional
384
+ `uploadProgress`, and `processors`. Each registered processor has an independent
385
+ queue, state and output identity. A processor outside a file's scope reports
386
+ `not-applicable`. Replacing the embedding model stops only RAG; other processors
387
+ continue for the lifetime of the mesh.
388
+
389
+ Source and output blobs download independently into local storage. An unavailable
390
+ file starts a background range download and yields its processing slot, so ready
391
+ files can proceed. Byte arrival wakes that file immediately, without waiting for
392
+ the retry timer or other downloads. These transfers have no 15-second cutoff;
393
+ deletion, suspension and shutdown cancel them. Each processor still limits its
394
+ CPU work to one job, and RAG production and repair share one embedding slot.
395
+
396
+ `Mesh.files` owns local processing, recovery, and readiness. Retrieval prioritizes
397
+ the current turn's attachments or named files, otherwise the chat's knowledge,
398
+ and waits up to five seconds total for indexing. A timeout leaves processing
399
+ running in the background for API searches. When a chat request attaches documents,
400
+ names pending files, or asks for a document overview, the reply then watches those
401
+ files until they are ready, failed or removed. It shows “Preparing documents…” and
402
+ refreshes retrieval before answering automatically; Stop closes that subscription.
403
+ Documents whose full text already fits the prompt need no index wait.
404
+ See the [cache and processing decision](docs/decisions/2026-07-15-vector-index-session-cache.md).
405
+
406
+ ```ts
407
+ const statuses = engine.fileStatusWatch({ chatId })
408
+ statuses.on('data', ({ files }) => renderFileStates(files))
409
+
410
+ const upload = engine.uploadKnowledge()
411
+ upload.write({
412
+ chatId,
413
+ fileName: 'notes.txt',
414
+ mimeType: 'text/plain',
415
+ fileId,
416
+ totalBytes: bytes.length
417
+ })
418
+ upload.write({ data: bytes })
419
+ upload.end()
420
+ for await (const receipt of upload) {
421
+ if (receipt.knowledge) console.log(receipt.knowledge.id)
422
+ }
423
+ statuses.destroy()
424
+ ```
425
+
426
+ The optional `fileId` must be unique and becomes the stored file ID. Omitting it
427
+ keeps server-generated IDs. Cancelled or failed uploads can reuse that ID once
428
+ the previous upload closes, provided no file was stored. Upload session details
429
+ expire one minute after closing; stored files remain visible.
430
+ Expiry stays private to the upload session. A supplied ID is reserved before
431
+ persisted-record validation, so concurrent uploads cannot acquire the same ID.
432
+ Both upload endpoints retain their existing progress
433
+ frames and final receipt. `uploadProgress.completed` counts original source bytes,
434
+ even when an image is normalized before storage. A missing or zero total means
435
+ unknown; do not calculate a percentage without a positive total.
436
+
437
+ Both endpoints share one upload path. A new Knowledge-panel document creates a
438
+ chat attachment and a knowledge row with the same ID and blob. Its receipt follows
439
+ both records, without waiting for indexing. The attachment belongs to the next
440
+ user message, allowing that question to read the document immediately. Removing
441
+ knowledge leaves the chat attachment downloadable until it is deleted too.
442
+ Direct `mesh.files.knowledge({ deviceId })` callers supply the uploader's device
443
+ identity; the Engine API supplies it without changing upload request frames.
444
+
445
+ Legacy panel-only rows can also be staged by their explicit ID, within their
446
+ original chat. Metadata reads need no blob bytes; unavailable bytes produce a
447
+ read error. This compatibility path writes no backfill or migration.
448
+
449
+ `fileRead({ chatId, fileId, metadataOnly? })` resolves either surface in the named
450
+ chat. The stream starts with `metadata`, then yields original bytes and cumulative
451
+ `bytesRead` / `byteLength`. A missing or deleted file returns an empty stream.
452
+ Metadata-only reads never open blob storage; destroying the stream cancels an
453
+ outstanding remote read. Assistant file staging uses this same endpoint.
454
+
455
+ Processor outputs reuse the existing artifact operation. Inline outputs are capped
456
+ at 512 KiB and carried inside the reference string, alongside a zero-length legacy
457
+ blob range. Their processor version has no RAG model separator, so released RAG
458
+ readers skip them. Inline data never enters blob storage or reclamation. The
459
+ compatibility suite runs released apply and reclaim code, including late joins,
460
+ reopen and downgrade, without changing mesh or local database schemas.
461
+
462
+ New image uploads opt into the `preview` processor. It produces an upright JPEG
463
+ from the first frame, at most 768 pixels on either side, targeting 80 KiB with
464
+ the 512 KiB inline ceiling. The uploader gets the first 30 seconds to produce it;
465
+ another capable peer can then recover missing work. Images uploaded before this
466
+ feature remain `not-applicable` and use their existing rendering path.
467
+
468
+ `filePreview({ chatId, fileId })` returns the preview's `outputId`, MIME type and
469
+ bytes, or an empty result while it is unavailable. It never downloads the original.
470
+ `fileRetry({ chatId, fileId, processorId })` clears local failure and retry state
471
+ for that processor. Desktop and mobile consume the same runtime file-status actor;
472
+ opening or saving an image requests its original separately. A failed preview has
473
+ retry and open-original actions.
474
+
475
+ Preview identity hashes the document ID, its immutable source core/range and the
476
+ processor version. Unlike RAG's source-byte hash, that source address lets a peer
477
+ recognize the matching preview without downloading the original. The version is
478
+ stored in the existing `embedderVersion` string as `processor:preview:1`, with zero
479
+ dimensions. Released devices ignore this output and continue reading originals;
480
+ no roster gate or forced update is needed for this compatible representation.
481
+
482
+ HTTP blob links probe the first requested block with a 15-second bound before
483
+ returning success headers. Missing blocks produce a transient HTTP failure; the
484
+ same URL can succeed after replication. HEAD and empty responses need no probe,
485
+ and byte ranges fetch only the requested range. Suspend and close cancel pending
486
+ probes. A deadline for stalls later in the response body remains follow-up work.
487
+
488
+ Progress is local to the uploading or processing device. Remote processing exposes
489
+ its existing replicated claim and result without invented counts. `claimedBy` is
490
+ the writer key; `agentId` is included when the current agent host matches it.
491
+ `published` means an output record exists, while `readyForChat` requires the local
492
+ RAG index to be loaded. Neither field promises that another device is ready.
493
+
494
+ Uploads fail after 30 seconds without an incoming frame. Cancelling releases their
495
+ writer and removes uncommitted bytes. Committed files survive cancellation that
496
+ arrives after publication. Suspend cancels outstanding source/output reads; resume
497
+ and peer connection allow recovery. An unavailable output can be rebuilt into the
498
+ local index when its source and embedding model are available, without changing
499
+ the immutable shared artifact reference. This local cache is rebuilt after restart.
500
+
501
+ `Files` owns the status consumed by both this watch and retrieval. `ragSearch`
502
+ includes `pendingFiles`, also surfaced in the prompt and source notice. Existing
503
+ knowledge and indexing APIs remain supported. `listIndexing` and `indexingWatch`
504
+ also report repeated source misses and locally unavailable outputs using the
505
+ existing response shape. These diagnostics never write shared status rows and
506
+ clear when the file becomes usable locally. This feature adds no replicated file
507
+ fields, operations, data migrations, or changes to existing RAG artifact IDs.
508
+
509
+ The TUI uses this watch for its live progress strip. `/files` lists the current
510
+ snapshot, including failures and whether each file is ready for chat on this
511
+ device. Upload with `/attach <path>` or `/knowledge add <path>`; `/knowledge`
512
+ retains its detailed indexing records for diagnostics.
513
+
514
+ ## Watch streams: subscribe, unsubscribe, errors
515
+
516
+ Every `*Watch` capability (`chunksWatch`, `agentsWatch`, `devicesWatch`,
517
+ `configWatch`, `meshStatusWatch`) returns the same kind of stream, with one
518
+ contract:
519
+
520
+ - **The first frame is the current snapshot, immediately.** Subscribing IS
521
+ the fetch — there is no fetch-then-subscribe race to paper over, ever.
522
+ - **Later frames arrive only on real change** (identical snapshots are
523
+ deduped server-side), and each frame is the full current result, not a
524
+ delta.
525
+ - **`destroy()` is the unsubscribe** (ending a `for await` does the same
526
+ thing). Nothing else to call, no registry to clean up — in any process,
527
+ over any transport.
528
+ - **A dying stream can never crash the host.** Every stream comes pre-armed
529
+ with an error observer, so a consumer that only listens to `'data'` is
530
+ safe; `'close'` always fires, and a `for await` rejects with the reason.
531
+
532
+ The stream is typed end to end (`WatchStream<T>` — async-iterable AND
533
+ evented; `T` is the hyperschema frame type), so both idioms typecheck:
534
+
535
+ **Loop** — `break`/`return` unsubscribes:
536
+
537
+ ```js
538
+ for await (const { agents } of engine.agentsWatch({})) {
539
+ render(agents)
540
+ if (done) break // break IS the unsubscribe
541
+ }
542
+ ```
543
+
544
+ **Mount/unmount** — subscribe with `on('data')`, unsubscribe with
545
+ `destroy()`; this is plain streamx, nothing layered on top:
546
+
547
+ ```js
548
+ const stream = engine.chunksWatch({ chatId }) // opening the chat
549
+ stream.on('data', ({ chunks }) => render(chunks))
550
+ // ...closing the chat:
551
+ stream.destroy() // the unsubscribe
552
+ ```
553
+
554
+ **One value, not a stream** — take the first frame and leave; still no
555
+ race, because frame one is the snapshot:
556
+
557
+ ```js
558
+ async function once(stream) {
559
+ for await (const frame of stream) return frame // break-on-return unsubscribes
560
+ }
561
+ const { devices } = await once(engine.devicesWatch({}))
562
+ ```
563
+
564
+ **Errors end the stream, they never hang it.** A server-side query failure
565
+ destroys the stream with that error (`for await` rejects with it); transport
566
+ death settles every open stream and pending call. So a long-lived consumer
567
+ is a re-arm loop, not error plumbing:
568
+
569
+ ```js
570
+ while (!closing) {
571
+ try {
572
+ for await (const { chunks } of engine.chunksWatch({ chatId })) render(chunks)
573
+ break // server ended the stream deliberately
574
+ } catch {
575
+ await delay(1000) // transport blip — resubscribe (frame one re-syncs you)
576
+ }
577
+ }
578
+ ```
579
+
580
+ Under the hood: ending the iterator destroys the wire stream, a DESTROY
581
+ frame crosses the transport, and the Core releases the underlying store
582
+ watcher — verified by test. The relays a host puts between the Client and
583
+ the Core (Electron IPC, a worklet bridge) carry these as ordinary bytes and
584
+ hold no subscription state, so there is nothing to unsubscribe at any layer
585
+ in between. If several consumers want the same data, share ONE subscription
586
+ and ref-count the unsubscribe in your app layer — the stream is cheap, but
587
+ one per consumer is still one store watcher each on the Core.
588
+
589
+ ## Harness event reference
590
+
591
+ `completion()` may yield:
592
+
593
+ | event | shape | becomes |
594
+ | ------------- | -------------------------------------- | --------------------------------------------------------- |
595
+ | `content` | `{ type, text }` | `content` chunks (coalesced) |
596
+ | `thinking` | `{ type, text }` | `thinking` chunks |
597
+ | `tool-call` | `{ type, name, args }` | `tool-call` chunk (`toolCall.name`, `toolCall.arguments`) |
598
+ | `tool-result` | `{ type, name, result }` | `tool-result` chunk (`toolResult`) |
599
+ | `metrics` | `{ type, totalTokens, durationMs, … }` | stored on the completed run-status |
600
+ | `error` | `{ type, message }` | run-status `failed` |
601
+ | `aborted` | `{ type }` | run-status `canceled` |
602
+
603
+ Context you receive: `model` (the agent's model name), `agent`
604
+ (`{ name, bio }`), `messages` (`{ role, content }[]`), `approve(call)`,
605
+ `signal`.
606
+
607
+ ## Connecting a skill (Notion, GitHub, …)
608
+
609
+ A skill reaches an external service through a **credential key** its
610
+ `SKILL.md` declares. The harness never sees the raw token: the credential
611
+ store resolves the key at call time and injects it as a bearer, and the
612
+ skill's `allow_list` binds that key to the URL prefixes it may be sent to.
613
+ Credentials are **per agent** (stored at `skills.<agentId>.<key>`), so two
614
+ agents never share one connection.
615
+
616
+ Fill a key with a slash command in the running TUI (`duck` drives the
617
+ command set):
618
+
619
+ - `/connect google` · `/connect spotify` · `/connect asana` · `/connect notion` — browser OAuth;
620
+ the example stores the token and refreshes it on use.
621
+ - `/cred <key> <value>` — set any credential key by hand, for a service with
622
+ no built-in OAuth flow.
623
+
624
+ `/connect google` needs `GOOGLE_OAUTH_CLIENT_ID` + `GOOGLE_OAUTH_CLIENT_SECRET`
625
+ in env, `/connect spotify` needs `SPOTIFY_OAUTH_CLIENT_ID`, `/connect asana` needs
626
+ `ASANA_OAUTH_CLIENT_ID` + `ASANA_OAUTH_CLIENT_SECRET` (an MCP-type app registered
627
+ at app.asana.com); `/connect notion` needs nothing — its MCP server self-provisions
628
+ a public client via dynamic registration (RFC 7591) + PKCE.
629
+
630
+ ### Notion
631
+
632
+ Notion runs over its official MCP server (`https://mcp.notion.com/mcp`) via
633
+ the `mcp_call` tool, under the credential key `notion_mcp_access_token`:
634
+
635
+ ```
636
+ /connect notion # authorize in the browser, pick the pages to share
637
+ /skill on notion # if the agent's skill scope is 'selected'
638
+ ```
639
+
640
+ Then ask the assistant anything Notion — it loads the `notion` skill and calls
641
+ `mcp_call` itself; the session handshake and auth are handled for you. A `401`
642
+ in a tool result means the token expired — run `/connect notion` again.
643
+
644
+ ```sh
645
+ duck
646
+ ```
647
+
648
+ Office documents (`docx`, `doc`, `odt`) decode through an engine the host supplies rather
649
+ than a declared dependency: the package pulls all thirteen platform prebuilds at ~350MB
650
+ each, so declaring it would cost every install 4.3GB. Fetch the one for this machine before
651
+ exercising `/knowledge add report.docx`:
652
+
653
+ ```sh
654
+ bun run engine:collabora
655
+ ```
656
+
657
+ The TUI copies its mesh invite to the clipboard on ctrl+y. On a second machine
658
+ (or a second terminal):
659
+
660
+ ```sh
661
+ duck qvac://mesh/…
662
+ ```
663
+
664
+ A running TUI moves mesh with `/join qvac://mesh/…` — it retires the mesh it is
665
+ on, joins that one, and re-resolves the agent and chat from it.
666
+
667
+ `--headless` brings the same stack up with no TUI: it prints the invite and then
668
+ streams logs (including the sidecar child's own output, which the TUI drops) to
669
+ stdout until ctrl+C. That is the shape to run as a background mesh peer, and the
670
+ only way to read the engine's logs live — `QVAC_LOG_LEVEL` picks the level:
671
+
672
+ ```sh
673
+ duck --headless
674
+ ```
675
+
676
+ The joiner brings no models and no agents — it finds the mesh's seeded agent,
677
+ asks, and the _creator's_ device runs the model while both sides watch the
678
+ same replicated stream: load progress, thinking, tokens, tool approvals
679
+ (either side can answer), and final stats.
680
+
681
+ ## Development
682
+
683
+ ```sh
684
+ npm test # node + bare suites
685
+ npm run build:schema # regenerate spec/ from schema/ (bare)
686
+ bun run duck # build the TUI sidecar bundle, then run it
687
+ bun run duck:warm # run the TUI against the last-built bundle
688
+ ```
689
+
690
+ The TUI lives in [`tui/`](tui/README.md) and installs as the `duck` command.
691
+ Schema changes go in `schema/*.ts` (builders) — `spec/` is generated. The
692
+ mesh schema (`spec/mesh/*`) is the replicated protocol: every device in a
693
+ mesh must run the same generated spec. See `MIGRATION.md` for the audited
694
+ feature checklist vs core v1.