draken 0.1.5 → 0.2.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 (301) hide show
  1. package/ACCEPTANCE.md +31 -0
  2. package/CHANGELOG.md +33 -0
  3. package/CONTRIBUTING.md +129 -0
  4. package/MEMORY.md +98 -0
  5. package/PERSONA.md +67 -0
  6. package/README.md +156 -78
  7. package/SETUP.md +111 -0
  8. package/SOURCES.md +75 -0
  9. package/TRAY.md +54 -0
  10. package/VOICE.md +118 -0
  11. package/dist/auth/middleware.d.ts +3 -1
  12. package/dist/auth/middleware.d.ts.map +1 -1
  13. package/dist/auth/middleware.js +20 -15
  14. package/dist/auth/middleware.js.map +1 -1
  15. package/dist/cli.js +77 -58
  16. package/dist/cli.js.map +1 -1
  17. package/dist/db/agent-migration.d.ts +7 -0
  18. package/dist/db/agent-migration.d.ts.map +1 -0
  19. package/dist/db/agent-migration.js +36 -0
  20. package/dist/db/agent-migration.js.map +1 -0
  21. package/dist/db/evidence.d.ts +37 -0
  22. package/dist/db/evidence.d.ts.map +1 -0
  23. package/dist/db/evidence.js +110 -0
  24. package/dist/db/evidence.js.map +1 -0
  25. package/dist/db/pointer-migration.d.ts +7 -0
  26. package/dist/db/pointer-migration.d.ts.map +1 -0
  27. package/dist/db/pointer-migration.js +117 -0
  28. package/dist/db/pointer-migration.js.map +1 -0
  29. package/dist/db/retained-text.d.ts +8 -0
  30. package/dist/db/retained-text.d.ts.map +1 -0
  31. package/dist/db/retained-text.js +36 -0
  32. package/dist/db/retained-text.js.map +1 -0
  33. package/dist/db/schema.d.ts +1 -5
  34. package/dist/db/schema.d.ts.map +1 -1
  35. package/dist/db/schema.js +200 -55
  36. package/dist/db/schema.js.map +1 -1
  37. package/dist/db/snapshot-pointers.d.ts +5 -0
  38. package/dist/db/snapshot-pointers.d.ts.map +1 -0
  39. package/dist/db/snapshot-pointers.js +174 -0
  40. package/dist/db/snapshot-pointers.js.map +1 -0
  41. package/dist/memory/automatic.d.ts +38 -0
  42. package/dist/memory/automatic.d.ts.map +1 -0
  43. package/dist/memory/automatic.js +155 -0
  44. package/dist/memory/automatic.js.map +1 -0
  45. package/dist/memory/context.d.ts +12 -0
  46. package/dist/memory/context.d.ts.map +1 -0
  47. package/dist/memory/context.js +59 -0
  48. package/dist/memory/context.js.map +1 -0
  49. package/dist/memory/embedding-pool.d.ts +28 -0
  50. package/dist/memory/embedding-pool.d.ts.map +1 -0
  51. package/dist/memory/embedding-pool.js +135 -0
  52. package/dist/memory/embedding-pool.js.map +1 -0
  53. package/dist/memory/embedding-worker.d.ts +2 -0
  54. package/dist/memory/embedding-worker.d.ts.map +1 -0
  55. package/dist/memory/embedding-worker.js +32 -0
  56. package/dist/memory/embedding-worker.js.map +1 -0
  57. package/dist/memory/index.d.ts +26 -0
  58. package/dist/memory/index.d.ts.map +1 -0
  59. package/dist/memory/index.js +283 -0
  60. package/dist/memory/index.js.map +1 -0
  61. package/dist/memory/model.d.ts +15 -0
  62. package/dist/memory/model.d.ts.map +1 -0
  63. package/dist/memory/model.js +152 -0
  64. package/dist/memory/model.js.map +1 -0
  65. package/dist/memory/service.d.ts +8 -0
  66. package/dist/memory/service.d.ts.map +1 -0
  67. package/dist/memory/service.js +217 -0
  68. package/dist/memory/service.js.map +1 -0
  69. package/dist/memory/sources.d.ts +15 -0
  70. package/dist/memory/sources.d.ts.map +1 -0
  71. package/dist/memory/sources.js +87 -0
  72. package/dist/memory/sources.js.map +1 -0
  73. package/dist/memory/stamps.d.ts +13 -0
  74. package/dist/memory/stamps.d.ts.map +1 -0
  75. package/dist/memory/stamps.js +101 -0
  76. package/dist/memory/stamps.js.map +1 -0
  77. package/dist/memory/storage.d.ts +5 -0
  78. package/dist/memory/storage.d.ts.map +1 -0
  79. package/dist/memory/storage.js +107 -0
  80. package/dist/memory/storage.js.map +1 -0
  81. package/dist/memory/types.d.ts +68 -0
  82. package/dist/memory/types.d.ts.map +1 -0
  83. package/dist/memory/types.js +8 -0
  84. package/dist/memory/types.js.map +1 -0
  85. package/dist/memory/worker.d.ts +2 -0
  86. package/dist/memory/worker.d.ts.map +1 -0
  87. package/dist/memory/worker.js +82 -0
  88. package/dist/memory/worker.js.map +1 -0
  89. package/dist/npm-cli.d.ts +4 -0
  90. package/dist/npm-cli.d.ts.map +1 -0
  91. package/dist/npm-cli.js +36 -0
  92. package/dist/npm-cli.js.map +1 -0
  93. package/dist/pi/runtime.d.ts +34 -0
  94. package/dist/pi/runtime.d.ts.map +1 -0
  95. package/dist/pi/runtime.js +210 -0
  96. package/dist/pi/runtime.js.map +1 -0
  97. package/dist/pi/setup.d.ts +5 -0
  98. package/dist/pi/setup.d.ts.map +1 -0
  99. package/dist/pi/setup.js +147 -0
  100. package/dist/pi/setup.js.map +1 -0
  101. package/dist/routes/auth.d.ts.map +1 -1
  102. package/dist/routes/auth.js +3 -1
  103. package/dist/routes/auth.js.map +1 -1
  104. package/dist/routes/live-voice.d.ts +3 -0
  105. package/dist/routes/live-voice.d.ts.map +1 -0
  106. package/dist/routes/live-voice.js +49 -0
  107. package/dist/routes/live-voice.js.map +1 -0
  108. package/dist/routes/{browse.d.ts → memory.d.ts} +1 -1
  109. package/dist/routes/memory.d.ts.map +1 -0
  110. package/dist/routes/memory.js +18 -0
  111. package/dist/routes/memory.js.map +1 -0
  112. package/dist/routes/{tasks.d.ts → sync.d.ts} +1 -1
  113. package/dist/routes/sync.d.ts.map +1 -0
  114. package/dist/routes/sync.js +127 -0
  115. package/dist/routes/sync.js.map +1 -0
  116. package/dist/routes/{projects.d.ts → voice.d.ts} +1 -1
  117. package/dist/routes/voice.d.ts.map +1 -0
  118. package/dist/routes/voice.js +35 -0
  119. package/dist/routes/voice.js.map +1 -0
  120. package/dist/server-state.d.ts +30 -0
  121. package/dist/server-state.d.ts.map +1 -0
  122. package/dist/server-state.js +113 -0
  123. package/dist/server-state.js.map +1 -0
  124. package/dist/server-status.d.ts +7 -0
  125. package/dist/server-status.d.ts.map +1 -0
  126. package/dist/server-status.js +34 -0
  127. package/dist/server-status.js.map +1 -0
  128. package/dist/server.d.ts +5 -1
  129. package/dist/server.d.ts.map +1 -1
  130. package/dist/server.js +148 -46
  131. package/dist/server.js.map +1 -1
  132. package/dist/sources/adapters.d.ts +15 -0
  133. package/dist/sources/adapters.d.ts.map +1 -0
  134. package/dist/sources/adapters.js +159 -0
  135. package/dist/sources/adapters.js.map +1 -0
  136. package/dist/sources/registry.d.ts +17 -0
  137. package/dist/sources/registry.d.ts.map +1 -0
  138. package/dist/sources/registry.js +56 -0
  139. package/dist/sources/registry.js.map +1 -0
  140. package/dist/sync/collector.d.ts +3 -0
  141. package/dist/sync/collector.d.ts.map +1 -0
  142. package/dist/sync/collector.js +284 -0
  143. package/dist/sync/collector.js.map +1 -0
  144. package/dist/sync/cost.d.ts +7 -0
  145. package/dist/sync/cost.d.ts.map +1 -0
  146. package/dist/sync/cost.js +31 -0
  147. package/dist/sync/cost.js.map +1 -0
  148. package/dist/sync/jsonl.d.ts +34 -0
  149. package/dist/sync/jsonl.d.ts.map +1 -0
  150. package/dist/sync/jsonl.js +161 -0
  151. package/dist/sync/jsonl.js.map +1 -0
  152. package/dist/sync/manager.d.ts +12 -0
  153. package/dist/sync/manager.d.ts.map +1 -0
  154. package/dist/sync/manager.js +211 -0
  155. package/dist/sync/manager.js.map +1 -0
  156. package/dist/sync/normalize.d.ts +22 -0
  157. package/dist/sync/normalize.d.ts.map +1 -0
  158. package/dist/sync/normalize.js +107 -0
  159. package/dist/sync/normalize.js.map +1 -0
  160. package/dist/sync/pi-output.d.ts +33 -0
  161. package/dist/sync/pi-output.d.ts.map +1 -0
  162. package/dist/sync/pi-output.js +186 -0
  163. package/dist/sync/pi-output.js.map +1 -0
  164. package/dist/sync/pi.d.ts +15 -0
  165. package/dist/sync/pi.d.ts.map +1 -0
  166. package/dist/sync/pi.js +149 -0
  167. package/dist/sync/pi.js.map +1 -0
  168. package/dist/sync/recap.d.ts +25 -0
  169. package/dist/sync/recap.d.ts.map +1 -0
  170. package/dist/sync/recap.js +118 -0
  171. package/dist/sync/recap.js.map +1 -0
  172. package/dist/sync/references.d.ts +10 -0
  173. package/dist/sync/references.d.ts.map +1 -0
  174. package/dist/sync/references.js +50 -0
  175. package/dist/sync/references.js.map +1 -0
  176. package/dist/sync/source-files.d.ts +16 -0
  177. package/dist/sync/source-files.d.ts.map +1 -0
  178. package/dist/sync/source-files.js +56 -0
  179. package/dist/sync/source-files.js.map +1 -0
  180. package/dist/sync/store.d.ts +36 -0
  181. package/dist/sync/store.d.ts.map +1 -0
  182. package/dist/sync/store.js +359 -0
  183. package/dist/sync/store.js.map +1 -0
  184. package/dist/sync/summary-budget.d.ts +19 -0
  185. package/dist/sync/summary-budget.d.ts.map +1 -0
  186. package/dist/sync/summary-budget.js +39 -0
  187. package/dist/sync/summary-budget.js.map +1 -0
  188. package/dist/sync/tree.d.ts +69 -0
  189. package/dist/sync/tree.d.ts.map +1 -0
  190. package/dist/sync/tree.js +599 -0
  191. package/dist/sync/tree.js.map +1 -0
  192. package/dist/tray/controller.d.ts +51 -0
  193. package/dist/tray/controller.d.ts.map +1 -0
  194. package/dist/tray/controller.js +178 -0
  195. package/dist/tray/controller.js.map +1 -0
  196. package/dist/tray/launcher.d.ts +3 -0
  197. package/dist/tray/launcher.d.ts.map +1 -0
  198. package/dist/tray/launcher.js +47 -0
  199. package/dist/tray/launcher.js.map +1 -0
  200. package/dist/tray/native.d.ts +12 -0
  201. package/dist/tray/native.d.ts.map +1 -0
  202. package/dist/tray/native.js +93 -0
  203. package/dist/tray/native.js.map +1 -0
  204. package/dist/tray/processes.d.ts +5 -0
  205. package/dist/tray/processes.d.ts.map +1 -0
  206. package/dist/tray/processes.js +99 -0
  207. package/dist/tray/processes.js.map +1 -0
  208. package/dist/tray/worker.d.ts +4 -0
  209. package/dist/tray/worker.d.ts.map +1 -0
  210. package/dist/tray/worker.js +150 -0
  211. package/dist/tray/worker.js.map +1 -0
  212. package/dist/types.d.ts +212 -20
  213. package/dist/types.d.ts.map +1 -1
  214. package/dist/voice/continuation.d.ts +25 -0
  215. package/dist/voice/continuation.d.ts.map +1 -0
  216. package/dist/voice/continuation.js +94 -0
  217. package/dist/voice/continuation.js.map +1 -0
  218. package/dist/voice/legacy-history.d.ts +5 -0
  219. package/dist/voice/legacy-history.d.ts.map +1 -0
  220. package/dist/voice/legacy-history.js +34 -0
  221. package/dist/voice/legacy-history.js.map +1 -0
  222. package/dist/voice/live-context.d.ts +14 -0
  223. package/dist/voice/live-context.d.ts.map +1 -0
  224. package/dist/voice/live-context.js +137 -0
  225. package/dist/voice/live-context.js.map +1 -0
  226. package/dist/voice/live.d.ts +37 -0
  227. package/dist/voice/live.d.ts.map +1 -0
  228. package/dist/voice/live.js +448 -0
  229. package/dist/voice/live.js.map +1 -0
  230. package/package.json +49 -16
  231. package/public/assets/index-CYz_kpAp.js +126 -0
  232. package/public/assets/index-DmRid5xE.css +1 -0
  233. package/public/assets/index-Ymd62i3g.js +40 -0
  234. package/public/assets/persona-C_m1CVKe.css +1 -0
  235. package/public/assets/persona-XbLvCqRT.js +1 -0
  236. package/public/assets/scene-CBV2oPhA.js +3854 -0
  237. package/public/index.html +6 -8
  238. package/public/persona/THREE-LICENSE.txt +21 -0
  239. package/public/persona/call-poster.webp +0 -0
  240. package/public/persona/reference.jpg +0 -0
  241. package/public/persona/sample.wav +0 -0
  242. package/public/persona.html +7 -0
  243. package/runtime/pi/package-lock.json +1549 -0
  244. package/runtime/pi/package.json +12 -0
  245. package/tests/fixtures/voice-question.wav +0 -0
  246. package/tray/assets/error.ico +0 -0
  247. package/tray/assets/error.png +0 -0
  248. package/tray/assets/running.ico +0 -0
  249. package/tray/assets/running.png +0 -0
  250. package/tray/assets/starting.ico +0 -0
  251. package/tray/assets/starting.png +0 -0
  252. package/tray/assets/stopped.ico +0 -0
  253. package/tray/assets/stopped.png +0 -0
  254. package/tray/assets/stopping.ico +0 -0
  255. package/tray/assets/stopping.png +0 -0
  256. package/tray/macos.js +72 -0
  257. package/tray/windows.ps1 +104 -0
  258. package/voice/realtime/README.md +84 -0
  259. package/voice/realtime/backend.mjs +81 -0
  260. package/voice/realtime/native.mjs +210 -0
  261. package/voice/realtime/peer.d.mts +15 -0
  262. package/voice/realtime/peer.mjs +116 -0
  263. package/voice/realtime/probe-browser.mjs +81 -0
  264. package/voice/realtime/probe-server.mjs +86 -0
  265. package/voice/realtime/probe.html +11 -0
  266. package/voice/realtime/probe.mjs +42 -0
  267. package/voice/realtime/rpc.mjs +118 -0
  268. package/voice/realtime/setup.mjs +32 -0
  269. package/voice/tls.mjs +49 -0
  270. package/dist/db/queries.d.ts +0 -16
  271. package/dist/db/queries.d.ts.map +0 -1
  272. package/dist/db/queries.js +0 -91
  273. package/dist/db/queries.js.map +0 -1
  274. package/dist/docker/dockerfile.d.ts +0 -50
  275. package/dist/docker/dockerfile.d.ts.map +0 -1
  276. package/dist/docker/dockerfile.js +0 -160
  277. package/dist/docker/dockerfile.js.map +0 -1
  278. package/dist/docker/manager.d.ts +0 -42
  279. package/dist/docker/manager.d.ts.map +0 -1
  280. package/dist/docker/manager.js +0 -421
  281. package/dist/docker/manager.js.map +0 -1
  282. package/dist/git/status.d.ts +0 -76
  283. package/dist/git/status.d.ts.map +0 -1
  284. package/dist/git/status.js +0 -332
  285. package/dist/git/status.js.map +0 -1
  286. package/dist/routes/browse.d.ts.map +0 -1
  287. package/dist/routes/browse.js +0 -49
  288. package/dist/routes/browse.js.map +0 -1
  289. package/dist/routes/git.d.ts +0 -9
  290. package/dist/routes/git.d.ts.map +0 -1
  291. package/dist/routes/git.js +0 -338
  292. package/dist/routes/git.js.map +0 -1
  293. package/dist/routes/projects.d.ts.map +0 -1
  294. package/dist/routes/projects.js +0 -220
  295. package/dist/routes/projects.js.map +0 -1
  296. package/dist/routes/tasks.d.ts.map +0 -1
  297. package/dist/routes/tasks.js +0 -313
  298. package/dist/routes/tasks.js.map +0 -1
  299. package/draken.dockerfile.template +0 -18
  300. package/public/assets/index-3ngsG5kF.css +0 -1
  301. package/public/assets/index-B3YSD0ee.js +0 -252
package/ACCEPTANCE.md ADDED
@@ -0,0 +1,31 @@
1
+ # Before approving or releasing this rewrite
2
+
3
+ This is a read-only engineering-sync application, not the old Docker/task dashboard. A draft PR, passing CI, configuration check, or quick code skim is not a release qualification. Public npm/website content changes only through separately authorized publication/deployment.
4
+
5
+ ## Automated gates
6
+
7
+ `Application qualification` runs strict typechecks/unused-code analysis, production builds, backend and browser-controller tests, Chromium UI regressions, root/client/private-pi-lock audits, a fresh production-only npm package/lifecycle smoke, and private runtime installation/protocol checks. `Tray platform checks` runs controller/executable tests, native helper startup/update/shutdown, and private pi/Codex installation on hosted macOS and Windows.
8
+
9
+ Runtime qualification uses a private HOME and synthetic loopback responses: it does **not** use real provider accounts, sign in, refresh credentials, acquire a microphone, or claim physical audio/menu usability. Package installation downloads npm dependencies. UI screenshots on failure contain synthetic fixtures only. CI dependency audits are time-specific advisory checks, not exploitability analysis or a security certification.
10
+
11
+ The release workflow requires a strict stable version already committed consistently in the manifest/lock, source on `master`, and both qualification workflows. Manual versions are passed as data, not interpolated into shell programs. Release uses pinned Node/npm versions and trusted-publishing provenance. No release is triggered by an ordinary PR push. Workflow checks are not a claim that repository branch protection has been configured.
12
+
13
+ ## Short product acceptance checklist
14
+
15
+ Use disposable histories first. Real-provider checks below require the operator's explicit sign-in and consent.
16
+
17
+ - [ ] **Useful, qualified briefing:** distinguish what was proposed, attempted, tested, deployed, and still unresolved. Inspect original evidence and a late correction; verify the summary preserves chronology and qualifications rather than sounding more certain.
18
+ - [ ] **Explicit permissions:** Codex/Claude imports stay off until independently enabled. Opening Settings/setup/status does not scan transcript bodies, prepare a snapshot, download an embedding model, call a provider, or request a microphone. Background indexing permission is separate from source permission.
19
+ - [ ] **Small tails and restart:** fewer than 64 distinct changed source records wait indefinitely in the background. Session bookkeeping and frozen aliases do not multiply the count. Foreground preparation handles smaller tails. Restart does not make an unchanged archive new again.
20
+ - [ ] **Evidence availability:** removing/changing a frozen original fails visibly; no cached-source fallback invents availability. Notes/discussion remain inspectable. Restore the exact original and verify recovery without regenerated evidence or review.
21
+ - [ ] **Call lifecycle:** cancel while history is preparing, including before the create response arrives; reload and start again. Microphone acquisition stays after preparation and explicit intent. End/Stop/mute/hidden-page behavior is understandable; no call restarts itself.
22
+ - [ ] **Review is yours:** included/retrieved/summarized/generated/heard/agreed/reviewed are not interchangeable. Only explicit review advances coverage. Discard, failure, cancellation and app restart do not mark work reviewed.
23
+ - [ ] **Real account and audio, separately:** configuration metadata alone does not prove provider/model access, token refresh, quota, billing or audible playback. If desired, test each explicitly with the intended account/device/browser; do not infer them from CI.
24
+ - [ ] **Physical desktop tray:** on actual macOS and Windows desktops, check icon visibility, all menu actions, browser opening, close-terminal survival, external-server observation, owned Stop/Quit and helper loss. No saved-PID adoption, external-server termination, silent settings changes or second same-data listener. Hosted helper smoke is not this test.
25
+ - [ ] **Overall value:** does the briefing and discussion help you resume engineering work better than reading the original sessions directly?
26
+
27
+ ## Focused independent review
28
+
29
+ Review authentication/origin boundaries, original-pointer/storage migrations, and child-process ownership/cancellation separately. Existing synthetic and private migration proofs are scoped evidence, not proof of every source format or deployment. Back up original JSONL histories **and** Draken private state before a real migration. Preserve exact compatible vectors, identities, references, credentials, TLS configuration and original launch environment; never deploy proof databases.
30
+
31
+ Keep the PR draft until the remaining risks are accepted. Version bump, merge, npm publication, website update and live deployment are distinct decisions. Older selected pi runtimes are not silently upgraded; the operator must explicitly rerun setup or update their external pi installation.
package/CHANGELOG.md ADDED
@@ -0,0 +1,33 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0
4
+
5
+ Draken now helps you catch up on what your coding agents did. It reads saved sessions, prepares summaries with links to the original messages, and lets you ask questions and save decisions. It does not run project commands or change your code.
6
+
7
+ ### What changed
8
+
9
+ - Read pi sessions, with separate opt-in imports for supported Codex CLI and Claude Code logs.
10
+ - Prepare summaries of the selected history, including large histories, and reuse completed work when possible.
11
+ - Search older sessions locally, ask questions in text, and keep notes about what to do next.
12
+ - Follow source links back to the original logs. Only explicitly marking a sync reviewed updates review progress.
13
+ - Use optional ChatGPT live voice with its own sign-in and an animated dragon. The microphone stays off until preparation is done and you choose to call.
14
+ - Install a private pi runtime with `draken setup`; pi handles provider sign-in and refresh.
15
+ - Find a running server with `draken status`. Try the optional macOS/Windows tray icon with `draken tray`.
16
+
17
+ ### Before upgrading from 0.1.6 or earlier
18
+
19
+ This replaces the Docker/Claude task dashboard. The old task execution, terminal, scheduling and Git-control features and APIs are removed. This is not a compatible upgrade for tools built around those APIs.
20
+
21
+ - **Node.js 22.19+ and npm are required.** Docker is not needed.
22
+ - Back up your original agent logs and private Draken data before upgrading. Original logs are needed to read source links, including those in older summaries.
23
+ - Applicable legacy tables and historical call records are retained, but old task conversations are not automatically imported.
24
+ - Do not use an old Draken binary to undo database changes.
25
+ - An update does not replace an already selected pi runtime. Run `draken setup` explicitly for the shipped private runtime, or manage your external pi installation yourself.
26
+
27
+ ### Limits to know
28
+
29
+ Summaries can miss details, and search can miss relevant messages. Source links are not independent proof that a claim is correct. Reading or listening does not mark work reviewed.
30
+
31
+ Reading and search run locally. Summaries and text questions send content to your selected model provider through pi. Voice sends audio and context to OpenAI, using a separate ChatGPT sign-in.
32
+
33
+ The desktop tray is a preview. Hosted tests check startup and shutdown, not physical menu usability. Automated tests also do not establish real-account access, quota, billing, microphone quality or speaker playback. See [ACCEPTANCE.md](ACCEPTANCE.md) for those checks, and [SETUP.md](SETUP.md) to get started.
@@ -0,0 +1,129 @@
1
+ # Contributing to Draken
2
+
3
+ For normal installation, use the npm instructions in [SETUP.md](SETUP.md). This guide is for people changing, building or qualifying Draken itself. Node.js **22.19+** and npm are required.
4
+
5
+ ## Build and run a checkout
6
+
7
+ ```sh
8
+ git clone https://github.com/mehanig/draken.git
9
+ cd draken
10
+ npm ci
11
+ npm --prefix client ci
12
+ npm run build
13
+ node dist/cli.js
14
+ ```
15
+
16
+ Use `node dist/cli.js` in place of `draken` when following the user guides from a checkout—for example, `node dist/cli.js status` or `node dist/cli.js tray`. No global npm installation is needed for development.
17
+
18
+ The foreground server defaults to `http://127.0.0.1:40333`; Ctrl+C stops it. Use a disposable `DRAKEN_DATA_DIR` and synthetic source roots when testing changes. A manually launched development server otherwise uses the normal private state/history locations. Do not build over or replace files used by a live deployment; use a separate checkout for staged work.
19
+
20
+ For frontend development, run the backend and `npm run dev:client` in separate terminals. Vite proxies `/api` to port 40333. Server changes need a rebuild/restart; `npm run dev` runs the TypeScript watcher.
21
+
22
+ ## Ordinary checks
23
+
24
+ ```sh
25
+ npm run check # Root/client typechecks and Knip
26
+ npm test # Build server, then backend tests
27
+ npm run build # Server and frontend production builds
28
+ npx --no-install playwright install chromium
29
+ node --test tests/live-browser.cjs tests/realtime-browser.cjs
30
+ npx --no-install playwright test
31
+ node --test tests/site-browser.cjs
32
+ ```
33
+
34
+ On a Linux CI image, Playwright may also need `--with-deps`. `npm run test:ui` combines the full build and application UI suite.
35
+
36
+ These tests use temporary synthetic records, fake providers and browser peers. They do not read your real source histories or call a real model provider. Website checks serve only the public static files and verify mobile layout, evidence-reference interaction and the absence of microphone/external-request activity.
37
+
38
+ ### Fresh package and runtime qualification
39
+
40
+ After a full build:
41
+
42
+ ```sh
43
+ node tests/package-smoke.cjs
44
+ node tests/runtime-smoke.cjs
45
+ npm audit
46
+ npm --prefix client audit
47
+ npm --prefix runtime/pi audit --omit=dev
48
+ ```
49
+
50
+ These smoke checks download npm dependencies into isolated temporary homes. The runtime check installs the pinned pi/Codex packages, checks missing-auth metadata, verifies pi text/usage behavior against synthetic loopback responses, and exercises native RPC denial/text boundaries and confirmed helper exit. It does not sign in, refresh real credentials, call real providers or acquire a microphone. Root/client audits do not cover the separately shipped pi lock; audit it explicitly.
51
+
52
+ ## Voice qualification
53
+
54
+ Ordinary voice tests cover native-only UI/control behavior, source lookup, interruption, no-capture-on-reload, absent retired endpoints and readable legacy records. Synthetic audio is not a human recording and is not proof of physical microphone, speaker, Bluetooth or mobile performance.
55
+
56
+ The following optional commands require their stated setup and, for real provider use, explicit consent:
57
+
58
+ ```sh
59
+ # Requires the pinned native helper; synthetic auth and loopback backends only:
60
+ npm run test:realtime:native
61
+
62
+ # Requires separate native ChatGPT sign-in; real provider, synthetic data/audio:
63
+ npm run voice:realtime:probe -- --allow-provider
64
+ DRAKEN_TEST_STATIC_DIR=/path/to/staged/frontend npm run test:live:real -- --allow-provider
65
+
66
+ # Real voice with a controlled delayed lookup:
67
+ DRAKEN_TEST_STATIC_DIR=/path/to/staged/frontend node tests/voice-continuation-real.cjs --allow-provider
68
+ ```
69
+
70
+ The environment-prefix examples above use Bash syntax. Real-provider probes are not included in ordinary tests or CI. Never add automatic credential copying, provider fallback, execution tools or approval grants to make a probe pass.
71
+
72
+ ## Local-model qualification
73
+
74
+ After building, opt into downloading the pinned embedding weights, or supply an existing private test copy:
75
+
76
+ ```sh
77
+ npm run test:memory:local -- --download-model
78
+ npm run test:memory:local -- --model-dir=/private/path/to/model
79
+ node tests/memory-performance.cjs --model-dir=/private/path/to/model
80
+ ```
81
+
82
+ The local-model check uses temporary synthetic records, real offline embeddings and the actual worker. It covers paraphrase/multilingual retrieval, abstention, native context, H citations, cancellation/resume and unchanged evidence/coverage. It does not open the production database or call OpenAI. The download flag controls this test harness, not ordinary product search.
83
+
84
+ The performance check compares the prior single-session runtime with the bounded pool on 48 synthetic passages plus long-window/query-prefix cases. It reports wall time, RSS, cosine similarity and byte equality. It does not download weights, inspect production history or guarantee whole-sync speed or retrieval quality.
85
+
86
+ ## Desktop tray qualification
87
+
88
+ On an interactive macOS/Windows desktop, after building:
89
+
90
+ ```sh
91
+ node --test tests/tray.test.cjs tests/native-launch.test.cjs
92
+ npm run test:tray:native
93
+ ```
94
+
95
+ The native smoke check opens a temporary icon, updates status and checks orderly exit. It does not start a Draken server, open a browser, scan history or access a provider. Hosted success does not verify physical menu clicks, desktop policies, logout behavior or every architecture. Complete the manual checks in [TRAY.md](TRAY.md) and [ACCEPTANCE.md](ACCEPTANCE.md).
96
+
97
+ ## Public website
98
+
99
+ `site/` is plain HTML/CSS with a small, synthetic evidence-reference interaction. It has no account/API integration, remote fonts, analytics or microphone access. Keep demo content labelled and never use private project screenshots or histories.
100
+
101
+ Merging `site/**` changes into `master` triggers `.github/workflows/web-deploy.yml`, which deploys to the existing Cloudflare R2 site and purges its cache. A feature-branch push does not deploy it. npm publication and application deployment are separate operations. Keep the release notice accurate: do not advertise the old npm package as the new application. Once the new version is published, update the site and user-doc release notices together.
102
+
103
+ ## Keep the code lean and the boundaries explicit
104
+
105
+ - Server and React UI code are TypeScript; native helpers/installers and some tests are JavaScript. The old Python speech worker is removed.
106
+ - Both TypeScript configurations reject unused locals/parameters. Pinned Knip models both packages, spawned fixtures, HTTP-served modules and generated-to-source mappings.
107
+ - Prefer individually inspectable client API exports. Preserve real test seams even where CommonJS namespace imports prevent exact unused-export attribution.
108
+ - Static reachability is not runtime proof. Trace HTTP routes, dynamic imports, generated assets, declarations and failure/cancellation/security paths. Do not remove safeguards to quiet an analyzer or test.
109
+ - Source-only pointers, exact compatible vectors, explicit source permissions, independent voice sign-in and review-only coverage are product boundaries—not optional implementation details.
110
+ - Keep credentials, private runtime data, databases, original histories and diagnostic artifacts out of git/npm. Never deploy a proof database.
111
+
112
+ ## Code map
113
+
114
+ | Area | Location |
115
+ | --- | --- |
116
+ | Runtime selection, private setup and pi-owned auth | `src/pi/`, `src/npm-cli.ts` |
117
+ | Agent registry and supported log readers | `src/sources/`, `src/sync/collector.ts` |
118
+ | Source-only evidence and migrations | `src/db/`, `src/sync/source-files.ts` |
119
+ | Frozen snapshots, notes and review | `src/sync/store.ts` |
120
+ | Summary trees, references and accounting | `src/sync/{tree,pi,pi-output,cost,references}.ts` |
121
+ | Recaps and conversation orchestration | `src/sync/{recap,manager}.ts` |
122
+ | Retrieval, embedding pool and batching/checkpoints | `src/memory/` |
123
+ | Authenticated application APIs | `src/routes/` |
124
+ | Native voice ownership and browser controls | `src/voice/`, `voice/realtime/`, `client/src/voice/` |
125
+ | Home, snapshots and history UI | `client/src/pages/`, `client/src/components/` |
126
+ | Tray ownership and OS helpers | `src/tray/`, `tray/` |
127
+ | Landing page | `site/` |
128
+
129
+ See [ARCHITECTURE.md](ARCHITECTURE.md) for design details and [ACCEPTANCE.md](ACCEPTANCE.md) for review/release gates. Passing CI is not permission to merge, publish, deploy, change real credentials or make provider calls.
package/MEMORY.md ADDED
@@ -0,0 +1,98 @@
1
+ # Historical search
2
+
3
+ **Just ask.** Draken automatically searches retained older sessions, sync conversations and saved notes when you ask a text question or talk to it. The separate **Search history** page (`/history`) lets you inspect the same records directly.
4
+
5
+ There is no memory-enable switch, index-update button, search-mode selector or wider-history checkbox. Historical retrieval does not replace the complete-snapshot summary tree or advance review coverage.
6
+
7
+ ## Automatic preparation
8
+
9
+ The first request with eligible retained history downloads approximately **136 MB** of pinned model/tokenizer files from Hugging Face, verifies their SHA-256 checksums and prepares local embeddings. Later requests reuse them and automatically embed new or changed records. Empty history does not require a model download.
10
+
11
+ Search combines meaning and exact keywords every time. It waits for both indexes rather than quietly returning keyword-only results while semantic work is incomplete. First use or a large amount of newly collected history can take minutes. Search, text-reply preparation and call setup show the current phase, model-download bytes or completed/total **new or changed unique passages**, unchanged cached work separately, elapsed time and an approximate remaining time once enough passages have been measured. The estimate can change with passage length. Progress is read-only metadata; polling it does not start indexing. Calls finish history preparation before the browser requests microphone permission or acquires a microphone track, and before connecting to ChatGPT. Cancelling or failing preparation leaves the microphone off. Ordinary concurrent requests queue behind one history process, within a fixed bound. Its embedding lanes run bounded batches in parallel; they do not run concurrent database writers. An unchanged index shows **History is up to date**, not a full-archive progress bar.
12
+
13
+ No session text or queries are uploaded for embedding. Text discussion and ChatGPT voice do receive the relevant historical excerpts, just as they receive frozen snapshot context. Those excerpts are dated background—not newly reviewed progress, agreement or permission to execute work.
14
+
15
+ Opening/reloading a page does not itself download the model, compute embeddings or start a microphone. Source collection happens through sync/scan actions or the optional server-side background schedule below. The search child itself follows only recorded, verified JSONL pointers beneath the configured per-agent source roots; it does not discover sessions or roam project files. There is no automatic decision saving, API-key fallback or second voice engine.
16
+
17
+ ### Optional background indexing
18
+
19
+ **Settings → Automatically index history**, available from the top bar on every page, is off by default. Its explicit, persistent opt-in authorizes local scanning and embedding (including the first-use model download) while the server runs, even with no browser open. It does not run provider summaries, create/extend frozen syncs, transcribe audio or advance review. Agent logs are already text; summary generation still happens on the explicit sync action and remains separately costed.
20
+
21
+ The app's single scheduler checks once a minute when no sync, call or foreground history work is active. A background batch requires at least 64 changed source records, regardless of how long they have waited. There is no timed flush. Smaller pending batches remain eligible for normal on-demand preparation when you sync, start a call or search; that foreground path has no minimum batch size. Metadata hashes detect additions, edits and removals once per original source entry (or discussion/note record). Session/file bookkeeping does not add records, and frozen aliases coalesce with the original rather than multiplying the count. Actual indexing still reconstructs and verifies admitted source bytes. Unchanged checks do not start an embedding request. Source records and embedding passages are different counts. New sessions from enabled agents retain the collector's included-by-default behavior; per-agent/per-session exclusions and the existing baseline remain respected. Codex and Claude sources require separate explicit opt-in in **Settings → Agents & sessions**, even if background indexing is already enabled. Summarization continues to use pi. See [SOURCES.md](SOURCES.md).
22
+
23
+ Foreground history requests preempt an active background request, waiting for the old child to exit before replacement. Disabling aborts only owned background embedding work; a scan already in progress may finish, but cannot start an embedding batch afterward. Completed vector commits remain reusable. Failures are visible and retry no sooner than five minutes; foreground preemption waits at least one minute. Shutdown awaits the owned scan/work within the existing bounded server shutdown. Reads of settings/progress start no jobs. The preference is a small `sync_settings` entry. A hash-only checkpoint in the derived search database survives restart and advances only after successful unscoped/current-call preparation; a historical or source-filtered search cannot replace it. It is scheduling metadata, not review coverage or a substitute for source verification. Progress remains process-local. An older index without a checkpoint waits for one foreground call preparation or unfiltered history search instead of treating its cached archive as newly changed. Unchanged preparations do not rewrite the checkpoint. There is no original-text copy, new provider, model substitution, vector conversion or timed small-batch flush.
24
+
25
+ A failed preparation/search reports an error. If a session file changed, moved or disappeared, scan sessions again before retrying; stale derived text is not a substitute for a verified source. Otherwise retry the ordinary question or call; completed embeddings are reused automatically. Cancelling a search, closing call setup or cancelling a text reply stops its owned work. There is no separate maintenance workflow to recover.
26
+
27
+ ## How it works
28
+
29
+ ```text
30
+ Canonical metadata in draken.db (read-only to search worker)
31
+ │
32
+ ├── selected session pointers → verified JSONL records
33
+ ├── frozen evidence pointers → exact recorded JSONL/edit selections
34
+ ├── preserved sync discussion (with playback qualifications)
35
+ └── explicitly saved notes
36
+ ↓
37
+ Automatic local preparation when needed
38
+ ├── SQLite FTS5 word index
39
+ └── sqlite-vec over local text embeddings
40
+ ↓
41
+ Combine matches → bounded source/context reads → dated H citations
42
+ ```
43
+
44
+ - **Model:** `Xenova/multilingual-e5-small`, pinned revision `761b726dd34fb83930e26aab4e9ac3899aa1fa78`, quantized ONNX. Uses query/passage prefixes, mean pooling and normalized 384-dimensional vectors. Long token sequences use bounded windows rather than silently embedding only a prefix.
45
+ - **Runtime:** CPU WASM through the same pinned `onnxruntime-web`, in one server-side history process. On machines with at least 12 available logical CPUs and 4 GiB of available memory, deltas of at least 32 passages use up to two isolated embedding worker threads, each with one model session and three kernel threads (six total). Smaller/constrained machines keep one lane, with at most four kernel threads and half the available CPUs (minimum one). Reads start no model; queries/small deltas use one lane. A second in-memory model copy costs additional RAM (about 1.7 GB process RSS for two lanes versus roughly 1 GB previously on the qualified host), but adds no on-disk model copies, GPU package or runtime dependency. Lanes retire with the history process, including cancellation; failures stop the request rather than switching runtimes. It is **not imported by the client** and is not transcription or speech synthesis. This avoids native CUDA-install downloads. The runtime package itself occupies additional disk space (about 133 MiB in the qualified Linux install), beyond model files.
46
+ - **Tokenizer:** pinned `@huggingface/tokenizers`; no downloaded executable model code.
47
+ - **Search:** FTS5/BM25 plus pinned `sqlite-vec` 0.1.9. Approximately 800-character overlapping passages (without redundant short tails already covered by the preceding passage), up to 80 candidate chunks per method, combined ranks and at most eight distinct source results. The minimum cosine-similarity threshold is a heuristic, not a correctness guarantee. Exact identifiers remain important keyword matches.
48
+ - **Identity:** content-addressed `H…` references retain source attribution; snapshot-local E/C labels are not global IDs. Duplicate attributions retain scope membership while sharing embeddings and collapsing in results. Changed/excluded records cannot silently replace the text behind an old link.
49
+ - **Preparation:** source admission and raw pointer integrity are still revalidated; delta indexing does not skip those checks. A bounded, process-local normalization cache avoids reparsing unchanged records, but is consulted only after rereading and verifying the complete raw-record hash. No cached text substitutes for missing or changed originals, and this cache is never written to disk. Only added/changed/deleted source records update postings and links. Only unique passages without a compatible vector enter the embedding queue. Unchanged refreshes perform no index writes, and a fully cached preparation does not load/download a model; an actual semantic query still ensures its model is ready. At most eight passage inputs are scheduled per batch, with no unbounded queue. Writes commit at batch boundaries after at most 64 passages or roughly a second of computed work. Progress counts only committed delta passages from zero; cached counts sit outside the denominator. A retry counts only work still uncommitted. ETA measures parallel wall-clock throughput and excludes the first cold batch. The existing model fingerprint/cache and original source references remain valid; obsolete fully covered tail chunks are removed only from the derived index.
50
+ - **Bounds:** one worker and at most eight queued requests; bounded queries, candidates, source pages and context packets. Preparation/search stops after ten minutes without a progress update, or two hours overall; read operations retain their 30-second limit. A healthy first index is no longer killed merely because it passes 30 minutes. The separate voice-call limit still applies. The worker retires after five idle minutes. Cancellation terminates the owned child when needed; queued work waits for its exit before starting a replacement. Search/read cancellation is isolated from other queued requests. Server shutdown cancels all owned work.
51
+ - **Storage:** `records` retains attribution metadata, not another full-text copy. Unique `passages` hold input hashes and embedding state, not text; `chunk_links` retain every source occurrence and offset. `passage_vectors` stores one vector per unique input. `chunks` and `embedding_cache` are join views, not duplicate physical stores. Contentless FTS5 keeps occurrence-level token postings and positions, not original passage strings, so scope membership and keyword document frequencies remain intact. Tokens and vectors are still private derived data, not an anonymization or secure-erasure mechanism. Text for reads and embedding is reconstructed from verified sources in memory. Semantic ranking selects scoped unique passages, expands their scoped occurrences, then bounds the candidate list; ties can rank differently from the former per-occurrence vector layout.
52
+ - **Rebuildability:** the separate passages, keyword index and vectors are derived. Fingerprints invalidate incompatible model data. The worker opens `draken.db` read-only and can read only recorded session files beneath the explicitly supplied session root. It has no source-file write, discovery, scan or application-write path.
53
+
54
+ ## Pointer storage and migration
55
+
56
+ Live `agent_entries` keep metadata, agent/session attribution, parser versions, byte offsets/lengths, raw-record SHA-256 hashes and branch/edit links—not `text`, `effective_text` and replacement copies. Reads verify the configured root, regular-file identity, session header, complete record hash and normalized entry identity. Context edits resolve both the original record and the selected edit. Append scans verify prior pointers when the file changes, including middle rewrites that leave its prefix and old tail intact.
57
+
58
+ **Pi JSONL files are the authoritative original-text archive, including for old snapshots.** `sync_items` and frozen background context pin the original record coordinates/hash, normalization version, exact selected context-edit pointer when present, any historical excerpt-clipping recipe, and a projected-text hash. Their membership, attribution and E/C identities remain frozen; later appends/edits/branch switches do not redirect them to the live projection. The current schema has no `retained_text` table. Versioned normalization reproduces older evidence exactly while current model-facing redaction remains in force.
59
+
60
+ Missing or altered originals are explicitly unavailable, not substituted with search text. Snapshot pages still show discussion, notes and stable citation identities. Generating replies, calling, expanding or marking the snapshot reviewed requires its evidence to be available. Restore the original files and rescan if live pointers moved or their file identity changed. Frozen reads can follow a newly registered path only after checking the session, record hashes and projection. An explicit scan updates current missing/admission state; it does not reconstruct lost snapshot bodies.
61
+
62
+ The one-way `pointer-v2` migration verifies every existing body against recorded original session prefixes before removing it. Unavailable, ambiguous or unmatchable originals stop and roll back conversion, preserving the old database rather than deleting or guessing evidence. Legacy edit matches must be unique, on a descendant branch and not dated after the snapshot; they carry `legacy_edit_match` because exact edit selection was not originally recorded. New snapshots record that selection directly. Entry IDs, projected evidence, notes, review coverage and summary caches are preserved. The `history-index-v3-contentless` search migration removes passage-body storage while retaining compatible vector bytes, occurrence/reference IDs and keyword frequencies without inference or an embedding-model change. Each database's conversion is transactional and can retry after failure. Unknown formats or conflicting vectors fail visibly instead of silently rebuilding.
63
+
64
+ Back up both current databases before deployment and qualify migration on private copies. Older binaries are not compatible with the pointer schema. SQLite can retain freed pages and WAL files: reclaiming them with an offline checkpoint/VACUUM is a separate deployment step, not automatic history deletion or page-open maintenance. The search worker closes SQLite on orderly retirement and requests a 4 MiB retained-journal limit after normal checkpoints; active transactions/readers can still require a larger WAL. Backups and pinned model files are additional disk use and are not included in compacted database-size claims. Pre-migration rollback backups still contain the old copied text; migration does not delete or securely erase those backups.
65
+
66
+ ## Scope and qualifications
67
+
68
+ Source inclusion, missing-session state, active branches for unsnapshotted entries and the existing history baseline are respected. Frozen evidence remains historical, including superseded branch material. Reviewed records remain searchable. Discarded sync discussion/notes are excluded, while discarded work can still be eligible through an active selected session.
69
+
70
+ Each lookup prepares the candidates observed at its start. Exclusions and edits are rechecked after inference; removed/changed candidates are pruned. Arrivals during a lookup wait for the next lookup, which prepares them automatically. This avoids mixing fresh keyword-only records into results described as fully hybrid.
71
+
72
+ Sync discussion is its own retained record: excluding a session does not erase text previously discussed in a sync. Recaps/current briefings are not separately indexed as primary evidence, though preserved discussion can contain historical assistant summaries; attribution remains visible.
73
+
74
+ Search covers admitted normalized source projections and retained Draken discussion—not every byte of the original JSONL files. Excerpt, ranking and packet limits can omit important information. No match is not proof something never happened. Similarity is not proof a source supports an answer. The local multilingual qualification is not a comprehensive relevance benchmark.
75
+
76
+ Open-sync notes have no retained edit timestamp and are not backdated. Date filters exclude undated notes. Calls use their start time as the historical cutoff so the active call does not retrieve its new questions as old history. A date filter does not reconstruct an earlier database state: context edits may change retained text without supplying a separate edit date.
77
+
78
+ Generated/interrupted speech stays qualified as generated/interrupted. Historical requests are data, not instructions. Prompts distinguish plans, reported work, observed validation and uncertainty. Models can still make semantic mistakes; inspect supporting records. Retrieval cannot save decisions, execute tools, complete syncs or advance coverage.
79
+
80
+ ## Private files and packaging
81
+
82
+ Under `DRAKEN_DATA_DIR` (default `~/.draken`):
83
+
84
+ - `draken.db`: canonical live/frozen pointers and metadata, Draken conversations, notes and generated summaries; not rewritten by retrieval.
85
+ - `memory/index.sqlite` and sidecars: passage hashes/unique vectors, source-occurrence mappings and contentless keyword postings.
86
+ - `memory/model/`: three pinned model/tokenizer files, downloaded automatically when first needed.
87
+
88
+ The directory is private (0700); database/model files are 0600. Embeddings are private data too. None belongs in a commit or npm tarball. Exclusions remove queryable derived records/cache on refresh; this is **not secure erasure** of filesystem blocks, WAL files, backups or canonical snapshots.
89
+
90
+ The npm package contains code/dependencies, not downloaded weights or private indexes. No database service, extra account, GPU package or user-selected search backend is required. Extension/model/runtime failures are visible; there is no alternate provider or keyword-only fallback.
91
+
92
+ ## Checks
93
+
94
+ Ordinary tests use synthetic records and deterministic or empty embedding fixtures, with real FTS5/vector tests. They do not download/run the real model. Browser fixtures exercise automatic search and source reading without setup controls, plus no capture on navigation. Existing native-only and review-boundary tests remain.
95
+
96
+ Optional real-model qualification and CPU comparison commands live in [CONTRIBUTING.md](CONTRIBUTING.md#local-model-qualification). Those are isolated test tools, not steps required to use search. Ordinary search/text/call requests prepare the index automatically; there is no separate model setup command for users.
97
+
98
+ Synthetic retrieval and throughput checks do not establish exhaustive recall, real-provider answer quality or performance on every machine.
package/PERSONA.md ADDED
@@ -0,0 +1,67 @@
1
+ # Draken’s 3D call persona
2
+
3
+ The call window now contains a local, animated mechanical dragon in a furnished workshop. This is an **initial procedural 3D interpretation**, not a photoreal reconstruction or camera/video feed. The reference is the actual illustration from [Draken’s homepage](https://draken.mehanig.com/): `site/img/scene1-web.jpg`, copied unchanged to `client/public/persona/reference.jpg`.
4
+
5
+ The character keeps its blue-gray mechanical shell, red wooden jaw/wing struts, amber eyes, ivory horns and mustard knitwear, with a knitted beanie. The call uses a closer, nearly frontal portrait with an uncluttered oak desk and chair. A dusk window, warm floor lamp and single plant sit in a softly blurred room behind the sharp character. Busy slats, shelves, clock, print and desktop accessories have been removed; lower-contrast wood/knit textures and matte wood reduce visual noise. Materials and lighting aim for a warm, tangible setting; the geometry remains deliberately stylized. A professionally sculpted/textured character would be a separate art pass.
6
+
7
+ ## Try it separately
8
+
9
+ Open **`/persona.html`** on your Draken server. In development:
10
+
11
+ ```sh
12
+ npm --prefix client ci
13
+ npm run dev:client
14
+ # Open http://localhost:3000/persona.html
15
+ ```
16
+
17
+ The preview offers listening/thinking poses, a generated speech sample, pause and the original reference. It does not request a microphone, authenticate, read sessions, or invoke a model. Everything is served locally. The call integration mounts the same component only while its dialog is active. Calls keep the dragon prominent, with a lightweight DOM subtitle layer at the bottom of the scene. It shows live transcript excerpts or bracketed waiting cues; the full transcript/details block is collapsed by default. There is no workshop label or animation-button overlay. Preview chrome is an explicit `studioControls` option; the preview keeps its pause/retry controls.
18
+
19
+ ## Boundaries
20
+
21
+ ```text
22
+ voice/live-session.ts — owns the ChatGPT call and audio
23
+ └─ optional onPlayback(PlaybackCue | null)
24
+ └─ VoiceCall.tsx stores a ref and maps call phase to PersonaMode
25
+ └─ persona/PersonaStage.tsx — lifecycle, visibility, lazy loading
26
+ ├─ scene.ts — WebGL renderer, lighting, camera
27
+ ├─ dragon.ts — articulated character
28
+ ├─ studio.ts — separated background / foreground furniture
29
+ ├─ background.ts — cached, separable Gaussian room blur
30
+ ├─ geometry.ts — materials and disposable GPU resources
31
+ └─ motion.ts — pure motion / audio-envelope math
32
+ ```
33
+
34
+ `client/src/audio/playback.ts` is a presentation-only contract. Live calls supply a read-only energy meter and audio clock. The standalone preview supplies its stored sample's text, PCM samples, duration and playback clock. The renderer cannot start/stop speech, submit a turn, write notes, access auth or finish a sync. Observer exceptions are contained by the audio controller. Live voice optionally supplies only a read-only received-energy getter and audio clock, with no PCM retention or invented phoneme alignment. The persona itself requires no server/schema/model dependencies.
35
+
36
+ `types.ts` describes the small pose/scene interface. A future authored mesh or glTF rig can replace `dragon.ts` and map the existing pose values to bones/morph targets; call logic and the room do not need to change. No glTF loading or external avatar service is implemented in this version.
37
+
38
+ ## Animation and timing
39
+
40
+ - The standalone preview derives jaw energy from its **stored sample's decoded audio**. Its text supplies approximate English mouth shapes. This preview does not synthesize speech or provide another call engine.
41
+ - Native live voice follows actual received energy through the same presentation boundary. It does not estimate phonemes from a growing transcript; mute/stop, silence and suspended output produce a closed mouth.
42
+ - Timing uses the same `AudioContext.currentTime` as playback—not response arrival or a looping “talk” animation. Silence, chunk boundaries, interruption, suspended audio and hang-up close the mouth.
43
+ - Idle breathing/blinks, listening/thinking head poses, speaking gestures and question tilt are procedural. They are not model-generated emotions or evidence of attention/understanding.
44
+ - This is **heuristic lip synchronization**, not forced phoneme alignment. Accents, names and speech rate can misalign individual shapes.
45
+ - Pausing motion in the preview or an OS reduced-motion preference keeps a static pose without changing audio. Offscreen/hidden scenes stop drawing. Hang-up disposes textures, geometry, materials, shadows, environment maps, timers and the WebGL context.
46
+
47
+ ## Performance and failure behavior
48
+
49
+ The Three.js scene is loaded lazily (about 140 kB gzip for the scene/engine chunk), not with the ordinary sync page. No CDN, remote textures/fonts, webcam, extra voice API or rendering server is involved. The reference, a WebP poster matching the new framing/blur, generated demo WAV and Three.js MIT license are local assets.
50
+
51
+ Hardware rendering is capped at 30 fps while speaking / 18 fps otherwise, DPR 1.5 and 900,000 pixels. Known software renderers use an economy profile: 10 fps, at most 160,000 pixels, no dynamic shadows, clearcoat or cloth sheen. This is visibly less detailed, but keeps graphics from overwhelming audio/control responsiveness. A timer between animation frames avoids continuously asking the browser to composite at 60 fps. These are caps, not promised frame rates.
52
+
53
+ The room is rendered and blurred only on initialization/resize; each animation frame composites that cached texture behind the sharp dragon and desk. This is layered call-style background separation, not a full optical depth-of-field simulation. Both rendering profiles use it. Blur targets/materials are disposed with the renderer.
54
+
55
+ WebGL initialization/context loss switches to the static poster; voice continues independently. The call fallback retains the same live subtitle layer and an accessible scene description. Captions are presentation only and do not drive speech or write records. The standalone preview retains its retry button and **Pause animation** control. The preview and call were exercised in Chromium, including its software renderer. Physical mobile GPUs, Safari, Firefox and long-session battery behavior are not yet verified.
56
+
57
+ ## Verification
58
+
59
+ ```sh
60
+ npm test # Includes pure audio-clock/pose and module-boundary checks
61
+ npm run test:ui # Includes preview, reduced motion, context loss and call regressions
62
+
63
+ # Explicit opt-in: synthetic evidence/audio, real ChatGPT voice, staged frontend:
64
+ DRAKEN_TEST_STATIC_DIR=/path/to/stage node tests/live-voice-real.cjs --allow-provider
65
+ ```
66
+
67
+ Browser checks cover a chrome-free call image, cached blur reuse/resize, high-quality shader compilation (also exercised on software GL), audible sample mouth motion, stopping, desktop/mobile sizing, local-only requests, no preview microphone access, WebGL loss/retry, unavailable WebGL during a working call, and context disposal on hang-up. Native controller/peer tests cover speaker/headphone gating, interruption, mute, permission refusal and cleanup. Main UI tests cover supplied sources, generated/interrupted history, explicit retry and preservation of retired-engine records. No production snapshot is changed by these tests.