create-walle 0.9.21 → 0.9.23

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 (500) hide show
  1. package/README.md +27 -5
  2. package/package.json +2 -2
  3. package/template/CLAUDE.md +2 -2
  4. package/template/LICENSE +1 -1
  5. package/template/bin/ctm-dev-cleanup.js +24 -3
  6. package/template/bin/ctm-launch.sh +13 -0
  7. package/template/bin/dev.sh +156 -18
  8. package/template/bin/node-bin.sh +84 -0
  9. package/template/bin/pin-node.sh +51 -0
  10. package/template/claude-task-manager/api-prompts.js +1203 -182
  11. package/template/claude-task-manager/api-reviews.js +109 -15
  12. package/template/claude-task-manager/approval-agent.js +1360 -280
  13. package/template/claude-task-manager/bin/restart-ctm.sh +64 -23
  14. package/template/claude-task-manager/bin/storage-migration-supervisor.js +338 -0
  15. package/template/claude-task-manager/db.js +4417 -295
  16. package/template/claude-task-manager/docs/app-update-refresh-protocol.md +69 -0
  17. package/template/claude-task-manager/docs/approval-ai-refinement.md +138 -0
  18. package/template/claude-task-manager/docs/approval-rescue-loop.md +74 -0
  19. package/template/claude-task-manager/docs/codex-operational-warning-health.md +107 -0
  20. package/template/claude-task-manager/docs/codex-resume-state-guard-design.md +17 -12
  21. package/template/claude-task-manager/docs/codex-terminal-render-controller-handoff.md +311 -0
  22. package/template/claude-task-manager/docs/coding-agent-hooks-architecture.md +418 -0
  23. package/template/claude-task-manager/docs/conversation-import-freshness.md +20 -0
  24. package/template/claude-task-manager/docs/google-workspace-auth-health.md +77 -0
  25. package/template/claude-task-manager/docs/image-paste-ux.md +13 -0
  26. package/template/claude-task-manager/docs/ipad-web-preview.md +88 -0
  27. package/template/claude-task-manager/docs/main-loop-offload-architecture.md +66 -0
  28. package/template/claude-task-manager/docs/microsoft-dev-tunnel-phone-access-design.md +274 -519
  29. package/template/claude-task-manager/docs/mobile-live-streaming.md +27 -5
  30. package/template/claude-task-manager/docs/mobile-remote-submission-lifecycle.md +69 -0
  31. package/template/claude-task-manager/docs/phone-access-design.md +53 -15
  32. package/template/claude-task-manager/docs/phone-passkey-identity.md +122 -0
  33. package/template/claude-task-manager/docs/phone-setup.md +3 -0
  34. package/template/claude-task-manager/docs/prompt-editing-tree-design.md +25 -1
  35. package/template/claude-task-manager/docs/remote-desktop-access-design.md +268 -0
  36. package/template/claude-task-manager/docs/restart-lifecycle-architecture.md +95 -0
  37. package/template/claude-task-manager/docs/runtime-work-control-plane.md +53 -0
  38. package/template/claude-task-manager/docs/session-interactive-wait-surfaces.md +38 -0
  39. package/template/claude-task-manager/docs/session-needs-you-dismissal.md +84 -0
  40. package/template/claude-task-manager/docs/session-render-state-management-design.md +91 -3
  41. package/template/claude-task-manager/docs/session-standup-command-center-design.md +25 -1
  42. package/template/claude-task-manager/docs/session-title-authority.md +32 -0
  43. package/template/claude-task-manager/docs/session-workspace-binding.md +33 -0
  44. package/template/claude-task-manager/docs/skill-intent-resolution-design.md +72 -0
  45. package/template/claude-task-manager/docs/walle-mcp-supervisor-health.md +86 -0
  46. package/template/claude-task-manager/docs/walle-relay-phone-access-design.md +24 -15
  47. package/template/claude-task-manager/docs/walle-session-history-hydration.md +114 -0
  48. package/template/claude-task-manager/docs/walle-session-input-queue.md +104 -0
  49. package/template/claude-task-manager/docs/walle-session-model-catalog.md +90 -0
  50. package/template/claude-task-manager/docs/walle-session-model-preferences.md +15 -6
  51. package/template/claude-task-manager/git-utils.js +897 -27
  52. package/template/claude-task-manager/lib/agent-capabilities.js +33 -0
  53. package/template/claude-task-manager/lib/agent-cli-cache.js +37 -7
  54. package/template/claude-task-manager/lib/agent-hooks-installer.js +26 -2
  55. package/template/claude-task-manager/lib/agent-presets.js +17 -1
  56. package/template/claude-task-manager/lib/all-sessions-query.js +108 -0
  57. package/template/claude-task-manager/lib/approval-ai-refinement.js +488 -0
  58. package/template/claude-task-manager/lib/approval-self-adapt.js +168 -0
  59. package/template/claude-task-manager/lib/async-semaphore.js +44 -0
  60. package/template/claude-task-manager/lib/auth-context.js +5 -0
  61. package/template/claude-task-manager/lib/auth-rate-limit.js +47 -4
  62. package/template/claude-task-manager/lib/auth-rules.js +29 -2
  63. package/template/claude-task-manager/lib/auto-approval-verifier.js +129 -16
  64. package/template/claude-task-manager/lib/background-llm.js +144 -17
  65. package/template/claude-task-manager/lib/branch-inventory.js +212 -0
  66. package/template/claude-task-manager/lib/claude-desktop-sessions.js +15 -3
  67. package/template/claude-task-manager/lib/coalesce-sync-frames.js +151 -0
  68. package/template/claude-task-manager/lib/codex-launch-health.js +762 -0
  69. package/template/claude-task-manager/lib/codex-transcript-pager.js +51 -0
  70. package/template/claude-task-manager/lib/codex-zst.js +124 -0
  71. package/template/claude-task-manager/lib/coding-agent-models.js +233 -30
  72. package/template/claude-task-manager/lib/connection-health.js +232 -0
  73. package/template/claude-task-manager/lib/conversation-blob-parser.js +42 -0
  74. package/template/claude-task-manager/lib/conversation-tail-merge.js +89 -26
  75. package/template/claude-task-manager/lib/ctm-session-context-api.js +39 -10
  76. package/template/claude-task-manager/lib/cursor-conversation-store.js +354 -0
  77. package/template/claude-task-manager/lib/db-owner-worker-client.js +315 -0
  78. package/template/claude-task-manager/lib/document-review.js +141 -6
  79. package/template/claude-task-manager/lib/escalation-review.js +152 -0
  80. package/template/claude-task-manager/lib/graceful-shutdown.js +159 -0
  81. package/template/claude-task-manager/lib/headless-term-service.js +678 -0
  82. package/template/claude-task-manager/lib/heavy-worker-fallback.js +38 -0
  83. package/template/claude-task-manager/lib/jsonl-conversation-parser.js +542 -0
  84. package/template/claude-task-manager/lib/jsonl-range-reader.js +112 -0
  85. package/template/claude-task-manager/lib/main-db-census.js +216 -0
  86. package/template/claude-task-manager/lib/message-pagination.js +106 -4
  87. package/template/claude-task-manager/lib/microsoft-dev-tunnel-setup.js +750 -26
  88. package/template/claude-task-manager/lib/mobile-auth-api.js +274 -7
  89. package/template/claude-task-manager/lib/mobile-auth-store.js +592 -10
  90. package/template/claude-task-manager/lib/mobile-notification-dispatcher.js +15 -0
  91. package/template/claude-task-manager/lib/model-overview-brain-fallback.js +311 -0
  92. package/template/claude-task-manager/lib/model-overview-cache.js +141 -0
  93. package/template/claude-task-manager/lib/models-health-routing-notice.js +126 -0
  94. package/template/claude-task-manager/lib/node-pin-guard.js +93 -0
  95. package/template/claude-task-manager/lib/perf-tracker.js +242 -6
  96. package/template/claude-task-manager/lib/permission-match.js +76 -0
  97. package/template/claude-task-manager/lib/permission-sync.js +133 -20
  98. package/template/claude-task-manager/lib/process-title.js +35 -0
  99. package/template/claude-task-manager/lib/prompt-executions-query.js +25 -0
  100. package/template/claude-task-manager/lib/prompt-index-disk-cache.js +44 -0
  101. package/template/claude-task-manager/lib/prompt-intent.js +132 -0
  102. package/template/claude-task-manager/lib/provider-user-context.js +34 -0
  103. package/template/claude-task-manager/lib/read-pool-client.js +313 -0
  104. package/template/claude-task-manager/lib/readpool-breaker.js +31 -0
  105. package/template/claude-task-manager/lib/recent-sessions-breaker.js +12 -0
  106. package/template/claude-task-manager/lib/remote-feedback-client.js +72 -0
  107. package/template/claude-task-manager/lib/remote-relay-protocol.js +37 -4
  108. package/template/claude-task-manager/lib/remote-relay-store.js +159 -0
  109. package/template/claude-task-manager/lib/remote-submission-observer.js +278 -0
  110. package/template/claude-task-manager/lib/restart-guard.js +109 -0
  111. package/template/claude-task-manager/lib/restore-interruption-detector.js +439 -0
  112. package/template/claude-task-manager/lib/restore-policy.js +13 -0
  113. package/template/claude-task-manager/lib/restore-resume-batch.js +74 -0
  114. package/template/claude-task-manager/lib/restore-runtime.js +68 -0
  115. package/template/claude-task-manager/lib/restore-storm.js +34 -0
  116. package/template/claude-task-manager/lib/resume-cwd.js +36 -0
  117. package/template/claude-task-manager/lib/resume-preflight.js +313 -0
  118. package/template/claude-task-manager/lib/runtime-work-registry.js +444 -0
  119. package/template/claude-task-manager/lib/sanitize-openai-auth.js +31 -0
  120. package/template/claude-task-manager/lib/scheduler.js +21 -1
  121. package/template/claude-task-manager/lib/scrollback-snapshot-store.js +159 -0
  122. package/template/claude-task-manager/lib/serial-task-queue.js +64 -0
  123. package/template/claude-task-manager/lib/server-listeners.js +239 -0
  124. package/template/claude-task-manager/lib/session-capture.js +42 -7
  125. package/template/claude-task-manager/lib/session-content-backfill.js +131 -0
  126. package/template/claude-task-manager/lib/session-history.js +388 -43
  127. package/template/claude-task-manager/lib/session-host-manager.js +287 -0
  128. package/template/claude-task-manager/lib/session-image-refs.js +209 -0
  129. package/template/claude-task-manager/lib/session-jobs.js +399 -59
  130. package/template/claude-task-manager/lib/session-prompt-index.js +137 -0
  131. package/template/claude-task-manager/lib/session-restore.js +53 -0
  132. package/template/claude-task-manager/lib/session-standup.js +123 -23
  133. package/template/claude-task-manager/lib/session-state-bus.js +14 -0
  134. package/template/claude-task-manager/lib/session-stream.js +64 -16
  135. package/template/claude-task-manager/lib/session-timeline-summary.js +260 -0
  136. package/template/claude-task-manager/lib/session-token-usage.js +494 -0
  137. package/template/claude-task-manager/lib/session-workspace-binding.js +356 -0
  138. package/template/claude-task-manager/lib/setup-network-config.js +9 -0
  139. package/template/claude-task-manager/lib/size-cap.js +45 -0
  140. package/template/claude-task-manager/lib/size-cap.test.js +62 -0
  141. package/template/claude-task-manager/lib/skill-autocomplete.js +180 -1
  142. package/template/claude-task-manager/lib/skill-intent-resolver.js +304 -0
  143. package/template/claude-task-manager/lib/sqlite-driver.js +19 -3
  144. package/template/claude-task-manager/lib/standup-attention.js +7 -3
  145. package/template/claude-task-manager/lib/status-authority.js +39 -0
  146. package/template/claude-task-manager/lib/status-hooks.js +4 -0
  147. package/template/claude-task-manager/lib/storage-migration.js +235 -0
  148. package/template/claude-task-manager/lib/structured-capture.js +298 -0
  149. package/template/claude-task-manager/lib/sync-io-census.js +163 -0
  150. package/template/claude-task-manager/lib/tailscale-setup.js +6 -0
  151. package/template/claude-task-manager/lib/terminal-activity-evidence.js +33 -0
  152. package/template/claude-task-manager/lib/terminal-choice.js +364 -0
  153. package/template/claude-task-manager/lib/terminal-control-sanitize.js +17 -0
  154. package/template/claude-task-manager/lib/terminal-fingerprint.js +48 -0
  155. package/template/claude-task-manager/lib/terminal-output-flush.js +84 -0
  156. package/template/claude-task-manager/lib/timeline-order.js +122 -0
  157. package/template/claude-task-manager/lib/transcript-store.js +348 -43
  158. package/template/claude-task-manager/lib/transport-security.js +84 -1
  159. package/template/claude-task-manager/lib/wait-state.js +184 -0
  160. package/template/claude-task-manager/lib/walle-client.js +47 -5
  161. package/template/claude-task-manager/lib/walle-ctm-history.js +564 -4
  162. package/template/claude-task-manager/lib/walle-external-actions.js +135 -16
  163. package/template/claude-task-manager/lib/walle-history-hydration.js +46 -0
  164. package/template/claude-task-manager/lib/walle-native-health.js +403 -0
  165. package/template/claude-task-manager/lib/walle-repair.js +701 -0
  166. package/template/claude-task-manager/lib/walle-session-cache.js +109 -0
  167. package/template/claude-task-manager/lib/walle-session-context.js +57 -21
  168. package/template/claude-task-manager/lib/walle-session-model-catalog.js +34 -0
  169. package/template/claude-task-manager/lib/walle-supervisor.js +539 -63
  170. package/template/claude-task-manager/lib/walle-transcript.js +52 -0
  171. package/template/claude-task-manager/lib/worktree-active-sync.js +11 -7
  172. package/template/claude-task-manager/lib/worktree-cwd.js +32 -1
  173. package/template/claude-task-manager/package.json +1 -1
  174. package/template/claude-task-manager/prompt-harvest.js +89 -66
  175. package/template/claude-task-manager/providers/claude-code.js +51 -3
  176. package/template/claude-task-manager/providers/cursor.js +140 -45
  177. package/template/claude-task-manager/public/css/reviews.css +551 -61
  178. package/template/claude-task-manager/public/css/setup.css +191 -0
  179. package/template/claude-task-manager/public/css/walle-session.css +865 -10
  180. package/template/claude-task-manager/public/css/walle.css +154 -0
  181. package/template/claude-task-manager/public/designs/ai-providers-consolidation-v2.html +830 -0
  182. package/template/claude-task-manager/public/index.html +18516 -2058
  183. package/template/claude-task-manager/public/ipad.html +363 -0
  184. package/template/claude-task-manager/public/js/document-review-links.js +301 -0
  185. package/template/claude-task-manager/public/js/image-normalize.js +69 -36
  186. package/template/claude-task-manager/public/js/message-renderer.js +1265 -77
  187. package/template/claude-task-manager/public/js/prompts.js +66 -29
  188. package/template/claude-task-manager/public/js/reviews.js +901 -133
  189. package/template/claude-task-manager/public/js/session-activity-utils.js +11 -1
  190. package/template/claude-task-manager/public/js/session-search-utils.js +94 -10
  191. package/template/claude-task-manager/public/js/session-status-precedence.js +23 -5
  192. package/template/claude-task-manager/public/js/setup.js +1273 -176
  193. package/template/claude-task-manager/public/js/stream-view.js +691 -73
  194. package/template/claude-task-manager/public/js/terminal-reconciler.js +210 -0
  195. package/template/claude-task-manager/public/js/walle-session.js +2455 -158
  196. package/template/claude-task-manager/public/js/walle.js +455 -28
  197. package/template/claude-task-manager/public/m/app.css +2909 -262
  198. package/template/claude-task-manager/public/m/app.js +6601 -398
  199. package/template/claude-task-manager/public/m/claim.html +224 -17
  200. package/template/claude-task-manager/public/m/index.html +117 -21
  201. package/template/claude-task-manager/public/m/sw.js +3 -1
  202. package/template/claude-task-manager/public/manifest.json +2 -2
  203. package/template/claude-task-manager/public/prompts.html +30 -14
  204. package/template/claude-task-manager/queue-engine.js +507 -28
  205. package/template/claude-task-manager/scripts/repair-claude-session-images.js +27 -8
  206. package/template/claude-task-manager/server.js +14341 -2197
  207. package/template/claude-task-manager/session-integrity.js +160 -18
  208. package/template/claude-task-manager/session-search-ranking.js +1 -0
  209. package/template/claude-task-manager/session-utils.js +25 -5
  210. package/template/claude-task-manager/workers/approval-blocklist.js +96 -6
  211. package/template/claude-task-manager/workers/approval-widget-validator.js +14 -8
  212. package/template/claude-task-manager/workers/conversation-import-worker.js +11 -50
  213. package/template/claude-task-manager/workers/db-owner-worker.js +386 -0
  214. package/template/claude-task-manager/workers/harvest-worker.js +9 -55
  215. package/template/claude-task-manager/workers/headless-term-worker.js +9 -530
  216. package/template/claude-task-manager/workers/read-pool-worker.js +387 -0
  217. package/template/claude-task-manager/workers/scrollback-worker.js +11 -72
  218. package/template/claude-task-manager/workers/session-host-process.js +146 -0
  219. package/template/claude-task-manager/workers/session-integrity-worker.js +10 -54
  220. package/template/claude-task-manager/workers/state-detectors/base.js +18 -1
  221. package/template/claude-task-manager/workers/state-detectors/claude-code.js +182 -9
  222. package/template/claude-task-manager/workers/state-detectors/codex.js +150 -2
  223. package/template/claude-task-manager/workers/state-detectors/cursor.js +127 -0
  224. package/template/claude-task-manager/workers/state-detectors/gemini.js +21 -0
  225. package/template/claude-task-manager/workers/state-detectors/index.js +29 -0
  226. package/template/claude-task-manager/workers/state-detectors/opencode.js +103 -0
  227. package/template/docs/design/markdown-review-pane.md +206 -0
  228. package/template/docs/designs/2026-05-17-portkey-gateway-provider-ux.md +129 -38
  229. package/template/docs/designs/2026-05-20-mobile-worktree-finish-command.md +27 -0
  230. package/template/docs/designs/2026-05-22-ai-configuration-consolidation.md +248 -0
  231. package/template/docs/designs/ai-configuration-consolidation-mock.html +812 -0
  232. package/template/docs/private-memory-and-pii-policy.md +69 -0
  233. package/template/package.json +2 -1
  234. package/template/scripts/check-private-data.js +201 -0
  235. package/template/shared/sqlite-owner-guard.js +30 -0
  236. package/template/shared/sqlite-owner-write-queue.js +225 -0
  237. package/template/shared/sqlite-storage-policy.js +111 -0
  238. package/template/shared/sqlite-write-lock.js +428 -0
  239. package/template/wall-e/agent-runners/claude-code.js +5 -0
  240. package/template/wall-e/agent.js +166 -22
  241. package/template/wall-e/api-walle.js +524 -70
  242. package/template/wall-e/auth/provider-flows.js +11 -1
  243. package/template/wall-e/bin/walle-mcp-stdio.js +341 -17
  244. package/template/wall-e/brain.js +1614 -141
  245. package/template/wall-e/chat/attachment-blocks.js +96 -0
  246. package/template/wall-e/chat/attachments.js +2 -1
  247. package/template/wall-e/chat/capability-resolver.js +7 -7
  248. package/template/wall-e/chat/context-messages.js +28 -0
  249. package/template/wall-e/chat/conversation-frame.js +630 -0
  250. package/template/wall-e/chat/provider-messages.js +125 -0
  251. package/template/wall-e/chat.js +1002 -233
  252. package/template/wall-e/coding/acceptance-contract.js +170 -0
  253. package/template/wall-e/coding/acp-adapter.js +1 -1
  254. package/template/wall-e/coding/agent-catalog.js +3 -0
  255. package/template/wall-e/coding/artifact-store.js +93 -0
  256. package/template/wall-e/coding/capability-router.js +120 -0
  257. package/template/wall-e/coding/coding-run-controller.js +423 -0
  258. package/template/wall-e/coding/compaction-service.js +157 -12
  259. package/template/wall-e/coding/frontend-verification.js +258 -0
  260. package/template/wall-e/coding/lifecycle-hooks.js +75 -0
  261. package/template/wall-e/coding/local-preview-contract.js +157 -0
  262. package/template/wall-e/coding/permission-service.js +57 -13
  263. package/template/wall-e/coding/prompt-bundle.js +19 -1
  264. package/template/wall-e/coding/prompt-section-registry.js +227 -0
  265. package/template/wall-e/coding/provider-compat.js +15 -0
  266. package/template/wall-e/coding/runtime-events.js +224 -0
  267. package/template/wall-e/coding/runtime-mode.js +3 -0
  268. package/template/wall-e/coding/side-git-snapshot.js +160 -4
  269. package/template/wall-e/coding/snapshot-service.js +143 -1
  270. package/template/wall-e/coding/stream-processor.js +388 -34
  271. package/template/wall-e/coding/task-tool.js +141 -4
  272. package/template/wall-e/coding/tool-execution-controller.js +365 -0
  273. package/template/wall-e/coding/tool-registry.js +43 -5
  274. package/template/wall-e/coding/user-hooks.js +217 -0
  275. package/template/wall-e/coding-orchestrator.js +1330 -221
  276. package/template/wall-e/coding-prompts.js +20 -4
  277. package/template/wall-e/context/context-builder.js +15 -2
  278. package/template/wall-e/decision/confidence.js +1 -1
  279. package/template/wall-e/docs/coding-acceptance-contract.md +41 -0
  280. package/template/wall-e/docs/external-action-controller.md +26 -6
  281. package/template/wall-e/docs/telemetry-lifecycle.md +8 -2
  282. package/template/wall-e/embeddings.js +591 -53
  283. package/template/wall-e/external-action-controller.js +12 -0
  284. package/template/wall-e/http/auth.js +1 -0
  285. package/template/wall-e/http/chat-api.js +46 -11
  286. package/template/wall-e/http/model-admin.js +836 -34
  287. package/template/wall-e/lib/boot-profile.js +88 -0
  288. package/template/wall-e/lib/event-loop-monitor.js +93 -0
  289. package/template/wall-e/lib/service-health.js +194 -0
  290. package/template/wall-e/llm/anthropic.js +130 -5
  291. package/template/wall-e/llm/client.js +266 -63
  292. package/template/wall-e/llm/default-fallback.js +382 -0
  293. package/template/wall-e/llm/health.js +19 -0
  294. package/template/wall-e/llm/message-guard.js +78 -0
  295. package/template/wall-e/llm/model-catalog.js +252 -1
  296. package/template/wall-e/llm/openai.js +26 -4
  297. package/template/wall-e/llm/portkey-sync.js +654 -0
  298. package/template/wall-e/llm/provider-error.js +30 -2
  299. package/template/wall-e/llm/registry.js +5 -1
  300. package/template/wall-e/llm/request-compat.js +67 -0
  301. package/template/wall-e/loops/backfill.js +79 -23
  302. package/template/wall-e/loops/brain-optimize.js +67 -0
  303. package/template/wall-e/loops/ingest.js +25 -10
  304. package/template/wall-e/loops/question-digest.js +160 -0
  305. package/template/wall-e/loops/reflect.js +6 -4
  306. package/template/wall-e/loops/think.js +39 -12
  307. package/template/wall-e/mcp-server.js +318 -36
  308. package/template/wall-e/memory/ctm-context-client.js +52 -14
  309. package/template/wall-e/memory/ctm-operational-context.js +237 -0
  310. package/template/wall-e/memory/ctm-prompt-executions-client.js +128 -0
  311. package/template/wall-e/memory/ctm-session-context.js +111 -63
  312. package/template/wall-e/prompts/coding/deepseek.txt +3 -0
  313. package/template/wall-e/prompts/coding/gemini.txt +6 -0
  314. package/template/wall-e/prompts/coding/gpt.txt +6 -0
  315. package/template/wall-e/prompts/coding/local.txt +7 -0
  316. package/template/wall-e/runtime/decision-hooks.js +115 -0
  317. package/template/wall-e/runtime/devbox-gateway.js +82 -8
  318. package/template/wall-e/runtime/prompt-manifest.js +86 -0
  319. package/template/wall-e/runtime/tool-executor.js +269 -0
  320. package/template/wall-e/runtime/tool-result-envelope.js +138 -0
  321. package/template/wall-e/runtime/transcript-projection.js +60 -0
  322. package/template/wall-e/runtime/walle-runtime.js +224 -0
  323. package/template/wall-e/scripts/db-optimize/migrate.js +162 -0
  324. package/template/wall-e/scripts/db-optimize/recall-eval.js +117 -0
  325. package/template/wall-e/server.js +15 -0
  326. package/template/wall-e/session-files.js +9 -0
  327. package/template/wall-e/skills/_bundled/google-calendar/run.js +1 -1
  328. package/template/wall-e/skills/_bundled/gws-workspace/run.js +1 -1
  329. package/template/wall-e/skills/_bundled/slack-mentions/run.js +76 -6
  330. package/template/wall-e/skills/claude-code-reader.js +7 -3
  331. package/template/wall-e/skills/script-skill-runner.js +10 -0
  332. package/template/wall-e/skills/skill-planner.js +38 -0
  333. package/template/wall-e/tools/builtin-middleware.js +19 -9
  334. package/template/wall-e/tools/local-tools.js +1428 -16
  335. package/template/wall-e/tools/permission-checker.js +73 -5
  336. package/template/wall-e/tools/question-manager.js +117 -7
  337. package/template/wall-e/training/harvester.js +12 -28
  338. package/template/wall-e/training/replay.js +25 -80
  339. package/template/website/index.html +10 -10
  340. package/template/wall-e/eval/ab-test.js +0 -203
  341. package/template/wall-e/eval/agent-runner.js +0 -772
  342. package/template/wall-e/eval/agent-scorer.js +0 -461
  343. package/template/wall-e/eval/aggregator.js +0 -414
  344. package/template/wall-e/eval/allowed-test-commands.js +0 -34
  345. package/template/wall-e/eval/benchmark-generator.js +0 -113
  346. package/template/wall-e/eval/benchmarks/chat-eval.json +0 -1662
  347. package/template/wall-e/eval/benchmarks/chat.json +0 -82
  348. package/template/wall-e/eval/benchmarks/coding-agent-real.json +0 -1
  349. package/template/wall-e/eval/benchmarks/coding-agent.json +0 -1581
  350. package/template/wall-e/eval/benchmarks/coding.json +0 -122
  351. package/template/wall-e/eval/benchmarks/memory-retrieval.json +0 -234
  352. package/template/wall-e/eval/benchmarks/reasoning.json +0 -82
  353. package/template/wall-e/eval/benchmarks/swebench-lite-30.json +0 -212
  354. package/template/wall-e/eval/benchmarks.js +0 -669
  355. package/template/wall-e/eval/cc-replay.js +0 -719
  356. package/template/wall-e/eval/chat-eval.js +0 -525
  357. package/template/wall-e/eval/check-keys.js +0 -15
  358. package/template/wall-e/eval/check-providers.js +0 -42
  359. package/template/wall-e/eval/codex-cli-baseline.js +0 -669
  360. package/template/wall-e/eval/coding-agent-real.js +0 -570
  361. package/template/wall-e/eval/context-compactor.js +0 -251
  362. package/template/wall-e/eval/debug-agent003.js +0 -68
  363. package/template/wall-e/eval/diagnostics.js +0 -216
  364. package/template/wall-e/eval/eval-orchestrator.js +0 -642
  365. package/template/wall-e/eval/evaluate.js +0 -202
  366. package/template/wall-e/eval/evaluator.js +0 -373
  367. package/template/wall-e/eval/exporter.js +0 -212
  368. package/template/wall-e/eval/fixtures/express-basic/package.json +0 -9
  369. package/template/wall-e/eval/fixtures/express-basic/server.js +0 -115
  370. package/template/wall-e/eval/fixtures/express-basic/test.js +0 -83
  371. package/template/wall-e/eval/fixtures/express-buggy/package.json +0 -9
  372. package/template/wall-e/eval/fixtures/express-buggy/server.js +0 -113
  373. package/template/wall-e/eval/fixtures/express-buggy/test.js +0 -83
  374. package/template/wall-e/eval/fixtures/express-buggy-items/package.json +0 -9
  375. package/template/wall-e/eval/fixtures/express-buggy-items/server.js +0 -112
  376. package/template/wall-e/eval/fixtures/express-buggy-items/test.js +0 -83
  377. package/template/wall-e/eval/fixtures/express-buggy-search/package.json +0 -9
  378. package/template/wall-e/eval/fixtures/express-buggy-search/server.js +0 -121
  379. package/template/wall-e/eval/fixtures/express-buggy-search/test.js +0 -83
  380. package/template/wall-e/eval/fixtures/express-rename-data/data.js +0 -34
  381. package/template/wall-e/eval/fixtures/express-rename-data/package.json +0 -9
  382. package/template/wall-e/eval/fixtures/express-rename-data/server.js +0 -97
  383. package/template/wall-e/eval/fixtures/express-rename-data/test.js +0 -88
  384. package/template/wall-e/eval/fixtures/express-xss/package.json +0 -12
  385. package/template/wall-e/eval/fixtures/express-xss/server.js +0 -90
  386. package/template/wall-e/eval/fixtures/express-xss/test.js +0 -67
  387. package/template/wall-e/eval/fixtures/express-xss/views/profile.ejs +0 -9
  388. package/template/wall-e/eval/fixtures/fullstack-app/config/default.js +0 -9
  389. package/template/wall-e/eval/fixtures/fullstack-app/config/test.js +0 -13
  390. package/template/wall-e/eval/fixtures/fullstack-app/package.json +0 -11
  391. package/template/wall-e/eval/fixtures/fullstack-app/public/css/style.css +0 -137
  392. package/template/wall-e/eval/fixtures/fullstack-app/public/index.html +0 -46
  393. package/template/wall-e/eval/fixtures/fullstack-app/public/js/app.js +0 -121
  394. package/template/wall-e/eval/fixtures/fullstack-app/public/js/auth.js +0 -71
  395. package/template/wall-e/eval/fixtures/fullstack-app/public/js/items.js +0 -80
  396. package/template/wall-e/eval/fixtures/fullstack-app/public/js/users.js +0 -46
  397. package/template/wall-e/eval/fixtures/fullstack-app/public/login.html +0 -45
  398. package/template/wall-e/eval/fixtures/fullstack-app/public/register.html +0 -38
  399. package/template/wall-e/eval/fixtures/fullstack-app/scripts/migrate.js +0 -23
  400. package/template/wall-e/eval/fixtures/fullstack-app/scripts/seed.js +0 -46
  401. package/template/wall-e/eval/fixtures/fullstack-app/server/db.js +0 -99
  402. package/template/wall-e/eval/fixtures/fullstack-app/server/index.js +0 -94
  403. package/template/wall-e/eval/fixtures/fullstack-app/server/middleware/auth.js +0 -19
  404. package/template/wall-e/eval/fixtures/fullstack-app/server/middleware/logger.js +0 -19
  405. package/template/wall-e/eval/fixtures/fullstack-app/server/router.js +0 -50
  406. package/template/wall-e/eval/fixtures/fullstack-app/server/routes/auth.js +0 -69
  407. package/template/wall-e/eval/fixtures/fullstack-app/server/routes/health.js +0 -23
  408. package/template/wall-e/eval/fixtures/fullstack-app/server/routes/items.js +0 -88
  409. package/template/wall-e/eval/fixtures/fullstack-app/server/routes/users.js +0 -75
  410. package/template/wall-e/eval/fixtures/fullstack-app/server/test.js +0 -198
  411. package/template/wall-e/eval/fixtures/fullstack-app/server/utils/response.js +0 -34
  412. package/template/wall-e/eval/fixtures/fullstack-app/server/utils/validate.js +0 -26
  413. package/template/wall-e/eval/fixtures/fullstack-app/server.js +0 -8
  414. package/template/wall-e/eval/fixtures/fullstack-app/test.js +0 -12
  415. package/template/wall-e/eval/fixtures/monorepo-basic/package.json +0 -8
  416. package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/data.js +0 -58
  417. package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/middleware.js +0 -46
  418. package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/package.json +0 -8
  419. package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/routes.js +0 -64
  420. package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/server.js +0 -56
  421. package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/test.js +0 -116
  422. package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/commands.js +0 -61
  423. package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/index.js +0 -62
  424. package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/output.js +0 -43
  425. package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/package.json +0 -11
  426. package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/test.js +0 -44
  427. package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/formatters.js +0 -43
  428. package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/index.js +0 -12
  429. package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/package.json +0 -5
  430. package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/test.js +0 -55
  431. package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/validators.js +0 -29
  432. package/template/wall-e/eval/fixtures/monorepo-basic/test.js +0 -46
  433. package/template/wall-e/eval/fixtures/node-cli/index.js +0 -78
  434. package/template/wall-e/eval/fixtures/node-cli/package.json +0 -10
  435. package/template/wall-e/eval/fixtures/node-cli/test.js +0 -57
  436. package/template/wall-e/eval/fixtures/node-typed/package.json +0 -8
  437. package/template/wall-e/eval/fixtures/node-typed/src/handlers.js +0 -31
  438. package/template/wall-e/eval/fixtures/node-typed/src/utils.js +0 -33
  439. package/template/wall-e/eval/fixtures/node-typed/test.js +0 -36
  440. package/template/wall-e/eval/fixtures/python-flask/app.py +0 -14
  441. package/template/wall-e/eval/fixtures/python-flask/requirements.txt +0 -2
  442. package/template/wall-e/eval/fixtures/python-flask/test_app.py +0 -25
  443. package/template/wall-e/eval/fixtures/wall-e-subset/brain.js +0 -105
  444. package/template/wall-e/eval/fixtures/wall-e-subset/eval/aggregator.js +0 -101
  445. package/template/wall-e/eval/fixtures/wall-e-subset/eval/benchmarks/chat.json +0 -20
  446. package/template/wall-e/eval/fixtures/wall-e-subset/eval/benchmarks/coding.json +0 -32
  447. package/template/wall-e/eval/fixtures/wall-e-subset/eval/benchmarks.js +0 -64
  448. package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/package.json +0 -6
  449. package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/server.js +0 -31
  450. package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/test.js +0 -18
  451. package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/utils.js +0 -34
  452. package/template/wall-e/eval/fixtures/wall-e-subset/eval/runner.js +0 -104
  453. package/template/wall-e/eval/fixtures/wall-e-subset/eval/scorer.js +0 -73
  454. package/template/wall-e/eval/fixtures/wall-e-subset/eval/test.js +0 -134
  455. package/template/wall-e/eval/fixtures/wall-e-subset/llm/client.js +0 -99
  456. package/template/wall-e/eval/fixtures/wall-e-subset/llm/providers.js +0 -63
  457. package/template/wall-e/eval/fixtures/wall-e-subset/llm/test.js +0 -70
  458. package/template/wall-e/eval/fixtures/wall-e-subset/package.json +0 -10
  459. package/template/wall-e/eval/fixtures/wall-e-subset/test.js +0 -86
  460. package/template/wall-e/eval/harvester.js +0 -685
  461. package/template/wall-e/eval/head-to-head.js +0 -388
  462. package/template/wall-e/eval/humaneval-adapter.js +0 -321
  463. package/template/wall-e/eval/list-models.js +0 -31
  464. package/template/wall-e/eval/livecodebench-adapter.js +0 -291
  465. package/template/wall-e/eval/mail-integration.js +0 -443
  466. package/template/wall-e/eval/manifest.js +0 -186
  467. package/template/wall-e/eval/meta-harness/adapters/coding-agent.js +0 -57
  468. package/template/wall-e/eval/meta-harness/bootstrap-snapshot.js +0 -149
  469. package/template/wall-e/eval/meta-harness/candidate-store.js +0 -117
  470. package/template/wall-e/eval/meta-harness/cli.js +0 -86
  471. package/template/wall-e/eval/meta-harness/domain-spec.js +0 -154
  472. package/template/wall-e/eval/meta-harness/domains/coding-agent.domain.json +0 -84
  473. package/template/wall-e/eval/meta-harness/examples/env-bootstrap-candidate.js +0 -29
  474. package/template/wall-e/eval/meta-harness/experience-store.js +0 -174
  475. package/template/wall-e/eval/meta-harness/frontier.js +0 -96
  476. package/template/wall-e/eval/meta-harness/harness-interface.js +0 -90
  477. package/template/wall-e/eval/meta-harness/leakage-guard.js +0 -80
  478. package/template/wall-e/eval/meta-harness/optimizer.js +0 -207
  479. package/template/wall-e/eval/meta-harness/proposer-runner.js +0 -110
  480. package/template/wall-e/eval/meta-harness/reporting.js +0 -58
  481. package/template/wall-e/eval/meta-harness/telemetry.js +0 -27
  482. package/template/wall-e/eval/meta-harness/validation.js +0 -81
  483. package/template/wall-e/eval/promoter.js +0 -228
  484. package/template/wall-e/eval/provider-normalizer.js +0 -33
  485. package/template/wall-e/eval/replay.js +0 -395
  486. package/template/wall-e/eval/run-agent-benchmarks.js +0 -386
  487. package/template/wall-e/eval/run-codex-cli-baseline.js +0 -177
  488. package/template/wall-e/eval/run-coding-agent-real.js +0 -187
  489. package/template/wall-e/eval/run-eval.js +0 -435
  490. package/template/wall-e/eval/run-model-comparison.js +0 -142
  491. package/template/wall-e/eval/session-evaluator.js +0 -187
  492. package/template/wall-e/eval/session-miner.js +0 -207
  493. package/template/wall-e/eval/session-retrieval-benchmark.js +0 -150
  494. package/template/wall-e/eval/session-transcripts.js +0 -509
  495. package/template/wall-e/eval/shadow.js +0 -161
  496. package/template/wall-e/eval/swebench-adapter.js +0 -345
  497. package/template/wall-e/eval/swebench-docker.js +0 -192
  498. package/template/wall-e/eval/train.py +0 -320
  499. package/template/wall-e/eval/trainer.js +0 -232
  500. package/template/wall-e/eval/weekly-eval-loop.js +0 -241
@@ -12,6 +12,7 @@ const {
12
12
  parseLimit,
13
13
  parseOffset,
14
14
  readBody,
15
+ BODY_LIMIT_LARGE,
15
16
  } = require('./http/api-utils');
16
17
  const authFlows = require('./auth/flow-manager');
17
18
  const {
@@ -20,7 +21,6 @@ const {
20
21
  } = require('./auth/provider-flows');
21
22
 
22
23
  const DATA_DIR = process.env.WALL_E_DATA_DIR || path.join(process.env.HOME, '.walle', 'data');
23
- const BRAIN_DB_PATH = path.join(DATA_DIR, 'wall-e-brain.db');
24
24
  const TELEMETRY_DB_PATH = path.join(DATA_DIR, 'telemetry.db');
25
25
  const FEEDBACK_RATE_LIMIT_PER_MINUTE = 2;
26
26
  const FEEDBACK_RATE_LIMIT_WINDOW_MS = 60 * 1000;
@@ -49,6 +49,8 @@ const TELEMETRY_DIAGNOSTIC_EVENTS = new Set([
49
49
  'upgrade',
50
50
  'upgrade_prompt',
51
51
  'funnel',
52
+ 'session_integrity_issue',
53
+ 'session_integrity_issue_summary',
52
54
  ]);
53
55
  const TELEMETRY_DIAGNOSTIC_PREFIXES = ['ctm_update_'];
54
56
  const TELEMETRY_REDACTED_TEXT = '[redacted after retention]';
@@ -556,6 +558,11 @@ function telemetryCleanupCandidateStats(tdb, policy = telemetryCleanupPolicy(),
556
558
  };
557
559
  }
558
560
 
561
+ function telemetryCleanupCheckpointMode() {
562
+ const raw = String(process.env.WALLE_TELEMETRY_CLEANUP_CHECKPOINT_MODE || 'PASSIVE').toUpperCase();
563
+ return new Set(['PASSIVE', 'FULL', 'RESTART', 'TRUNCATE']).has(raw) ? raw : 'PASSIVE';
564
+ }
565
+
559
566
  function telemetryAggregateArchivedEvents(tdb, since) {
560
567
  return telemetrySafeCount(
561
568
  tdb,
@@ -584,6 +591,103 @@ function telemetryEventsByTypeSince(tdb, since) {
584
591
  `).all(since, String(since || '').slice(0, 10));
585
592
  }
586
593
 
594
+ function telemetrySetIntersectionSize(left, right) {
595
+ let count = 0;
596
+ for (const value of left || []) {
597
+ if (right && right.has(value)) count += 1;
598
+ }
599
+ return count;
600
+ }
601
+
602
+ function telemetrySetDifferenceSize(left, right) {
603
+ let count = 0;
604
+ for (const value of left || []) {
605
+ if (!right || !right.has(value)) count += 1;
606
+ }
607
+ return count;
608
+ }
609
+
610
+ function telemetryStableIdentityFromRow(row) {
611
+ return row && row.machine_bucket ? `m:${row.machine_bucket}` : `i:${row?.install_id || ''}`;
612
+ }
613
+
614
+ function telemetryMapCountsToRows(map, countKey = 'install_count') {
615
+ return Array.from(map.entries())
616
+ .map(([key, value]) => ({ key, [countKey]: value.size || value }))
617
+ .sort((a, b) => b[countKey] - a[countKey] || String(a.key).localeCompare(String(b.key)));
618
+ }
619
+
620
+ function telemetryEmbeddingFeatureAdoption(tdb, since, activeInstalls) {
621
+ const rows = tdb.prepare(`
622
+ SELECT te.event, te.install_id, ti.machine_bucket, te.meta, te.received_at
623
+ FROM telemetry_events te
624
+ LEFT JOIN telemetry_installs ti ON ti.install_id = te.install_id
625
+ WHERE te.event IN ('embedding_status', 'embedding_provider_switch', 'embedding_usage')
626
+ AND te.received_at >= ?
627
+ ORDER BY te.received_at ASC, te.id ASC
628
+ `).all(since);
629
+ const latestStatusByIdentity = new Map();
630
+ const setupView = new Set();
631
+ const switchSuccess = new Set();
632
+ const switchFailed = new Set();
633
+ const usage = new Set();
634
+ const usageByOperation = new Map();
635
+
636
+ for (const row of rows) {
637
+ const identity = telemetryStableIdentityFromRow(row);
638
+ if (!identity || identity === 'i:') continue;
639
+ let meta = {};
640
+ try { meta = row.meta ? JSON.parse(row.meta) : {}; } catch {}
641
+
642
+ if (row.event === 'embedding_status') {
643
+ latestStatusByIdentity.set(identity, meta);
644
+ if (meta.reason === 'setup_view') setupView.add(identity);
645
+ continue;
646
+ }
647
+ if (row.event === 'embedding_provider_switch') {
648
+ if (meta.ok) switchSuccess.add(identity);
649
+ else switchFailed.add(identity);
650
+ continue;
651
+ }
652
+ if (row.event === 'embedding_usage') {
653
+ usage.add(identity);
654
+ const operation = String(meta.operation || 'unknown').slice(0, 80) || 'unknown';
655
+ if (!usageByOperation.has(operation)) usageByOperation.set(operation, new Set());
656
+ usageByOperation.get(operation).add(identity);
657
+ }
658
+ }
659
+
660
+ const enabled = new Set();
661
+ const enabledByProvider = new Map();
662
+ const latestStateCounts = new Map();
663
+ for (const [identity, meta] of latestStatusByIdentity.entries()) {
664
+ const state = String(meta.state || 'unknown').slice(0, 80) || 'unknown';
665
+ latestStateCounts.set(state, (latestStateCounts.get(state) || 0) + 1);
666
+ if (meta.enabled || meta.state === 'ready') {
667
+ enabled.add(identity);
668
+ const provider = String(meta.provider || 'unknown').slice(0, 80) || 'unknown';
669
+ if (!enabledByProvider.has(provider)) enabledByProvider.set(provider, new Set());
670
+ enabledByProvider.get(provider).add(identity);
671
+ }
672
+ }
673
+
674
+ const denominator = activeInstalls || 0;
675
+ const pct = (num) => denominator > 0 ? Math.round(num / denominator * 100) / 100 : null;
676
+ return {
677
+ reporting_installs: latestStatusByIdentity.size,
678
+ enabled_installs: enabled.size,
679
+ usage_installs: usage.size,
680
+ setup_view_installs: setupView.size,
681
+ switch_success_installs: switchSuccess.size,
682
+ switch_failed_installs: switchFailed.size,
683
+ enabled_pct_active: pct(enabled.size),
684
+ usage_pct_active: pct(usage.size),
685
+ latest_state: telemetryMapCountsToRows(latestStateCounts, 'install_count'),
686
+ enabled_by_provider: telemetryMapCountsToRows(enabledByProvider),
687
+ usage_by_operation: telemetryMapCountsToRows(usageByOperation),
688
+ };
689
+ }
690
+
587
691
  function archiveAndDeleteTelemetryEvents(tdb, bucket, batchSize) {
588
692
  const selectRows = tdb.prepare(`
589
693
  SELECT id, date(received_at) as day, event, COALESCE(version, '') as version,
@@ -803,8 +907,9 @@ function runTelemetryCleanup(options = {}) {
803
907
 
804
908
  let checkpoint = null;
805
909
  try {
806
- const rows = tdb.pragma('wal_checkpoint(TRUNCATE)');
807
- checkpoint = { ok: true, rows };
910
+ const mode = telemetryCleanupCheckpointMode();
911
+ const rows = tdb.pragma(`wal_checkpoint(${mode})`);
912
+ checkpoint = { ok: true, mode, rows };
808
913
  } catch (e) {
809
914
  checkpoint = { ok: false, error: e.message };
810
915
  }
@@ -1064,18 +1169,16 @@ function buildSkillMd(body) {
1064
1169
  return lines.join('\n');
1065
1170
  }
1066
1171
 
1067
- let readDb = null;
1172
+ let readDbOverride = null;
1068
1173
 
1069
1174
  function getReadDb() {
1070
- if (!readDb) {
1071
- try {
1072
- readDb = new Database(BRAIN_DB_PATH, { readonly: true, fileMustExist: true });
1073
- readDb.pragma('busy_timeout = 5000');
1074
- } catch (err) {
1075
- return null;
1076
- }
1175
+ if (readDbOverride) return readDbOverride;
1176
+ if (!brain || !ensureBrainInit()) return null;
1177
+ try {
1178
+ return brain.getDb();
1179
+ } catch {
1180
+ return null;
1077
1181
  }
1078
- return readDb;
1079
1182
  }
1080
1183
 
1081
1184
  let brain = null;
@@ -1380,7 +1483,14 @@ function getQuestionsList(params) {
1380
1483
  if (!db) return [];
1381
1484
 
1382
1485
  const status = params.get('status') || 'pending';
1383
- return db.prepare('SELECT * FROM pending_questions WHERE status = ? ORDER BY created_at DESC').all(status);
1486
+ // Paginate: this endpoint had no LIMIT, so it returned every pending question (15k+ on
1487
+ // a busy brain) as one unbounded blob. Default to a page of 100; callers can override
1488
+ // with ?limit= and page with ?offset=.
1489
+ const limit = parseLimit(params, 100);
1490
+ const offset = Math.max(0, parseInt(params.get('offset') || '0', 10) || 0);
1491
+ return db.prepare(
1492
+ 'SELECT * FROM pending_questions WHERE status = ? ORDER BY created_at DESC LIMIT ? OFFSET ?'
1493
+ ).all(status, limit, offset);
1384
1494
  }
1385
1495
 
1386
1496
  function getStats() {
@@ -1421,22 +1531,73 @@ function handleWalleApi(req, res, url) {
1421
1531
  const schedulerHandled = handleSchedulerApi(req, res, url);
1422
1532
  if (schedulerHandled !== false) return schedulerHandled;
1423
1533
 
1424
- // GET /api/wall-e/health — simple liveness check
1534
+ // GET /api/wall-e/health — liveness check. Includes event-loop delay stats: if this
1535
+ // endpoint responds at all the loop is not currently wedged, and `event_loop.peak_ms` /
1536
+ // `last_warning` show whether it has stalled recently (a synchronous DB query starving
1537
+ // the loop). A blocked loop can't answer this, so a tight-timeout probe of it is a
1538
+ // truer readiness signal than "the process is running".
1425
1539
  if (p === '/api/wall-e/health' && m === 'GET') {
1426
- return jsonResponse(res, { status: 'ok', uptime: process.uptime(), version: '0.1.0' }), true;
1540
+ let eventLoop = { enabled: false };
1541
+ try { eventLoop = require('./lib/event-loop-monitor').stats(); } catch {}
1542
+ return jsonResponse(res, { status: 'ok', uptime: process.uptime(), version: '0.1.0', event_loop: eventLoop }), true;
1427
1543
  }
1428
1544
 
1429
1545
  // GET /api/wall-e/alerts — service alerts (auth failures, skill disabled, update available)
1430
1546
  if (p === '/api/wall-e/alerts' && m === 'GET') {
1431
1547
  try {
1548
+ res.setHeader('Cache-Control', 'no-store, max-age=0');
1432
1549
  const { getServiceAlerts } = require('./skills/skill-planner');
1550
+ const { buildServiceHealth } = require('./lib/service-health');
1433
1551
  const alerts = getServiceAlerts({ includeVersionUpdate: true });
1434
- return jsonResponse(res, { alerts }), true;
1552
+ let activeProvider = process.env.WALLE_PROVIDER || '';
1553
+ let activeModel = process.env.WALLE_MODEL || '';
1554
+ try {
1555
+ activeProvider = brain?.getKv?.('walle_provider') || activeProvider;
1556
+ activeModel = brain?.getKv?.('walle_model') || activeModel;
1557
+ } catch {}
1558
+ const summary = buildServiceHealth(alerts, {
1559
+ activeProvider,
1560
+ activeModel,
1561
+ });
1562
+ return jsonResponse(res, { alerts, summary }), true;
1435
1563
  } catch (e) {
1436
1564
  return jsonResponse(res, { alerts: [], error: e.message }), true;
1437
1565
  }
1438
1566
  }
1439
1567
 
1568
+ // GET /api/wall-e/coding/boundaries?session_id=… — persisted snapshot
1569
+ // boundaries (newest first) for the rewind UI.
1570
+ if (p === '/api/wall-e/coding/boundaries' && m === 'GET') {
1571
+ try {
1572
+ const sessionId = String(url.searchParams.get('session_id') || '').slice(0, 200);
1573
+ if (!sessionId) return jsonResponse(res, { ok: false, error: 'session_id required' }, 400), true;
1574
+ const { listSessionBoundaries } = require('./coding/snapshot-service');
1575
+ return jsonResponse(res, { ok: true, boundaries: listSessionBoundaries(sessionId, { brain }) }), true;
1576
+ } catch (e) {
1577
+ return jsonResponse(res, { ok: false, error: e.message }, 500), true;
1578
+ }
1579
+ }
1580
+
1581
+ // POST /api/wall-e/coding/rewind — restore the worktree to a snapshot
1582
+ // boundary. Restores tracked snapshot content; never deletes files the
1583
+ // snapshot system has not seen.
1584
+ if (p === '/api/wall-e/coding/rewind' && m === 'POST') {
1585
+ return readBody(req, { maxBytes: 16 * 1024 }).then(body => {
1586
+ try {
1587
+ const sessionId = String(body.session_id || body.sessionId || '').slice(0, 200);
1588
+ const boundaryId = String(body.boundary_id || body.boundaryId || '').slice(0, 200);
1589
+ if (!sessionId || !boundaryId) {
1590
+ return jsonResponse(res, { ok: false, error: 'session_id and boundary_id required' }, 400);
1591
+ }
1592
+ const { revertSessionBoundary } = require('./coding/snapshot-service');
1593
+ const result = revertSessionBoundary({ sessionId, boundaryId, brain });
1594
+ return jsonResponse(res, { ok: true, ...result });
1595
+ } catch (e) {
1596
+ return jsonResponse(res, { ok: false, error: e.message }, 400);
1597
+ }
1598
+ }), true;
1599
+ }
1600
+
1440
1601
  // POST /api/wall-e/feedback/ingest — explicit CTM feedback handoff.
1441
1602
  // This route stores curated feedback as memory/knowledge. It does not ingest
1442
1603
  // screenshots or full transcripts unless CTM included consented previews.
@@ -1624,6 +1785,13 @@ function handleWalleApi(req, res, url) {
1624
1785
  }
1625
1786
  }
1626
1787
 
1788
+ // GET /api/wall-e/boot-profile — per-phase startup timing (instrumentation)
1789
+ if (p === '/api/wall-e/boot-profile' && m === 'GET') {
1790
+ let profile = null;
1791
+ try { profile = require('./lib/boot-profile').getProfile(); } catch (_) {}
1792
+ return jsonResponse(res, { profile }), true;
1793
+ }
1794
+
1627
1795
  // GET /api/wall-e/scheduler — scheduler observability
1628
1796
  if (p === '/api/wall-e/scheduler' && m === 'GET') {
1629
1797
  const { Scheduler } = require('./lib/scheduler');
@@ -1782,7 +1950,10 @@ function handleWalleApi(req, res, url) {
1782
1950
  if (p === '/api/wall-e/gws/accounts' && m === 'GET') {
1783
1951
  (async () => {
1784
1952
  try {
1953
+ res.setHeader('Cache-Control', 'no-store, max-age=0');
1785
1954
  const gwsSetup = require('./skills/_bundled/gws-workspace/setup');
1955
+ let skillPlanner = null;
1956
+ try { skillPlanner = require('./skills/skill-planner'); } catch {}
1786
1957
  const accounts = gwsSetup.listAccounts();
1787
1958
  const gwsProfiles = typeof gwsSetup.getGwsProfiles === 'function' ? gwsSetup.getGwsProfiles() : [];
1788
1959
  const hasClientSecret = gwsProfiles.length
@@ -1849,6 +2020,15 @@ function handleWalleApi(req, res, url) {
1849
2020
  detail.reauthRequired = false;
1850
2021
  detail.authIssue = null;
1851
2022
  detail.syncMode = 'auto';
2023
+ if (skillPlanner && typeof skillPlanner.clearResolvedGoogleWorkspaceHealthAlerts === 'function') {
2024
+ try {
2025
+ skillPlanner.clearResolvedGoogleWorkspaceHealthAlerts({
2026
+ email: acct.email,
2027
+ profileId: acct.profileId || null,
2028
+ authenticated: true,
2029
+ });
2030
+ } catch {}
2031
+ }
1852
2032
  const apiResults = await gwsSetup.testApis(creds.access_token);
1853
2033
  for (const r of apiResults) {
1854
2034
  detail.apis[r.name.toLowerCase()] = r.enabled;
@@ -2323,9 +2503,15 @@ function handleWalleApi(req, res, url) {
2323
2503
  if (!hasApiKey && (walleProvider === 'ollama' || walleProvider === 'mlx')) hasApiKey = true;
2324
2504
 
2325
2505
  let serviceAlerts = [];
2506
+ let serviceAlertSummary = null;
2326
2507
  try {
2327
2508
  const { getServiceAlerts } = require('./skills/skill-planner');
2509
+ const { buildServiceHealth } = require('./lib/service-health');
2328
2510
  serviceAlerts = getServiceAlerts({ includeVersionUpdate: true });
2511
+ serviceAlertSummary = buildServiceHealth(serviceAlerts, {
2512
+ activeProvider: walleProvider,
2513
+ activeModel: walleModel,
2514
+ });
2329
2515
  } catch {}
2330
2516
 
2331
2517
  let slackSocketMode = null;
@@ -2350,6 +2536,7 @@ function handleWalleApi(req, res, url) {
2350
2536
  walle_model: walleModel,
2351
2537
  has_api_key: hasApiKey,
2352
2538
  service_alerts: serviceAlerts,
2539
+ service_alert_summary: serviceAlertSummary,
2353
2540
  slack_socket_mode: slackSocketMode,
2354
2541
  }
2355
2542
  }), true;
@@ -2360,6 +2547,7 @@ function handleWalleApi(req, res, url) {
2360
2547
  try {
2361
2548
  const embeddingsModule = require('./embeddings');
2362
2549
  if (!brain || !ensureBrainInit()) return jsonResponse(res, { providers: [], stats: {}, totalMemories: 0, error: 'Brain module not available' }, 503), true;
2550
+ const auto = embeddingsModule.autoSelectProvider?.('setup_view') || null;
2363
2551
  const providers = embeddingsModule.getProviderStatus();
2364
2552
  const stats = embeddingsModule.getAllEmbeddingStats();
2365
2553
  const totalMemories = brain.getDb().prepare("SELECT count(*) as c FROM memories WHERE content IS NOT NULL AND length(content) > 10").get().c;
@@ -2394,13 +2582,28 @@ function handleWalleApi(req, res, url) {
2394
2582
  etaMinutes: recentRate > 0 ? Math.ceil(remaining / recentRate) : null,
2395
2583
  };
2396
2584
  }
2397
- jsonResponse(res, { providers, stats, totalMemories, backfillProgress });
2585
+ const setup = embeddingsModule.getEmbeddingSetupState?.() || {};
2586
+ jsonResponse(res, { providers, stats, totalMemories, backfillProgress, setup, auto });
2587
+ try { embeddingsModule.trackEmbeddingSetupView?.(); } catch {}
2398
2588
  } catch (e) {
2399
2589
  jsonResponse(res, { providers: [], stats: {}, totalMemories: 0, error: e.message });
2400
2590
  }
2401
2591
  return true;
2402
2592
  }
2403
2593
 
2594
+ if (p === '/api/wall-e/setup/embeddings/policy' && m === 'POST') {
2595
+ readBody(req).then((body) => {
2596
+ try {
2597
+ const embeddingsModule = require('./embeddings');
2598
+ const result = embeddingsModule.setEmbeddingPolicy(body.policy);
2599
+ jsonResponse(res, result, result.ok ? 200 : 400);
2600
+ } catch (e) {
2601
+ jsonResponse(res, { ok: false, message: e.message }, 400);
2602
+ }
2603
+ }).catch((e) => jsonResponse(res, { ok: false, message: e.message }, 400));
2604
+ return true;
2605
+ }
2606
+
2404
2607
  if (p === '/api/wall-e/setup/embeddings/switch' && m === 'POST') {
2405
2608
  readBody(req).then((body) => {
2406
2609
  try {
@@ -2496,13 +2699,17 @@ function handleWalleApi(req, res, url) {
2496
2699
  const type = sanitizeSetupProviderType(body.type);
2497
2700
  if (!type) return jsonResponse(res, { ok: false, error: 'Invalid provider type' }, 400);
2498
2701
  const model = sanitizeSetupModel(body.model);
2702
+ const hasBaseUrl = Object.prototype.hasOwnProperty.call(body, 'base_url')
2703
+ || Object.prototype.hasOwnProperty.call(body, 'baseUrl');
2704
+ const hasCustomHeaders = Object.prototype.hasOwnProperty.call(body, 'custom_headers')
2705
+ || Object.prototype.hasOwnProperty.call(body, 'customHeaders');
2499
2706
  const result = brain.saveSetupProvider({
2500
2707
  id: body.id || `${type}-default`,
2501
2708
  name: body.name || SETUP_PROVIDER_NAMES[type] || type,
2502
2709
  type,
2503
- baseUrl: body.base_url || body.baseUrl || null,
2710
+ baseUrl: hasBaseUrl ? (body.base_url || body.baseUrl || null) : undefined,
2504
2711
  apiKeyEncrypted: typeof body.api_key === 'string' ? body.api_key.replace(/[\r\n\s]/g, '').slice(0, 200) : (body.apiKeyEncrypted || null),
2505
- customHeaders: body.custom_headers || body.customHeaders || null,
2712
+ customHeaders: hasCustomHeaders ? (body.custom_headers || body.customHeaders || null) : undefined,
2506
2713
  enabled: body.enabled !== false,
2507
2714
  model,
2508
2715
  setDefault: Boolean(body.set_default),
@@ -2880,19 +3087,73 @@ function handleWalleApi(req, res, url) {
2880
3087
  try {
2881
3088
  const backups = brain.listBackups();
2882
3089
  const dbPath = brain.getDbPath();
2883
- const dbSize = require('fs').statSync(dbPath).size;
2884
- jsonResponse(res, { data: { backups, db_path: dbPath, backup_dir: brain.BACKUP_DIR, db_size: dbSize } });
3090
+ let dbSize = 0;
3091
+ try { dbSize = require('fs').statSync(dbPath).size; } catch {}
3092
+ const info = typeof brain.getBackupDirInfo === 'function'
3093
+ ? brain.getBackupDirInfo()
3094
+ : { backup_dir: brain.BACKUP_DIR };
3095
+ jsonResponse(res, {
3096
+ data: {
3097
+ backups,
3098
+ db_path: dbPath,
3099
+ data_dir: path.dirname(dbPath),
3100
+ backup_dir: info.backup_dir || brain.BACKUP_DIR,
3101
+ default_backup_dir: info.default_backup_dir || '',
3102
+ configured_backup_dir: info.configured_backup_dir || '',
3103
+ backup_dir_source: info.source || 'default',
3104
+ db_size: dbSize,
3105
+ },
3106
+ });
3107
+ } catch (e) { jsonResponse(res, { error: e.message }, 500); }
3108
+ return true;
3109
+ }
3110
+
3111
+ // GET /api/wall-e/storage — current Wall-E data and backup locations
3112
+ if (p === '/api/wall-e/storage' && m === 'GET') {
3113
+ if (!brain) return jsonResponse(res, { error: 'Brain not available' }, 503), true;
3114
+ try {
3115
+ if (ensureBrainInit) ensureBrainInit();
3116
+ const dbPath = brain.getDbPath();
3117
+ const info = typeof brain.getBackupDirInfo === 'function'
3118
+ ? brain.getBackupDirInfo()
3119
+ : { backup_dir: brain.BACKUP_DIR };
3120
+ jsonResponse(res, {
3121
+ data: {
3122
+ db_path: dbPath,
3123
+ data_dir: path.dirname(dbPath),
3124
+ backup_dir: info.backup_dir || brain.BACKUP_DIR,
3125
+ default_backup_dir: info.default_backup_dir || '',
3126
+ configured_backup_dir: info.configured_backup_dir || '',
3127
+ backup_dir_source: info.source || 'default',
3128
+ },
3129
+ });
2885
3130
  } catch (e) { jsonResponse(res, { error: e.message }, 500); }
2886
3131
  return true;
2887
3132
  }
2888
3133
 
3134
+ // PUT /api/wall-e/storage/backup-dir — update Wall-E-owned backup directory
3135
+ if (p === '/api/wall-e/storage/backup-dir' && m === 'PUT') {
3136
+ if (!brain || !ensureBrainInit()) return jsonResponse(res, { error: 'Brain not available' }, 503), true;
3137
+ readBody(req, 32 * 1024)
3138
+ .then((body) => {
3139
+ const result = brain.setBackupDir(body.backup_dir || '', {
3140
+ persist: true,
3141
+ moveExisting: !!body.move_existing,
3142
+ });
3143
+ jsonResponse(res, { data: result });
3144
+ })
3145
+ .catch((e) => jsonResponse(res, { error: e.message }, 400));
3146
+ return true;
3147
+ }
3148
+
2889
3149
  // POST /api/wall-e/backups — create brain backup
2890
3150
  if (p === '/api/wall-e/backups' && m === 'POST') {
2891
3151
  if (!brain || !ensureBrainInit()) return jsonResponse(res, { error: 'Brain not available' }, 503), true;
2892
- try {
2893
- const result = brain.createBackup('manual');
2894
- jsonResponse(res, { data: result });
2895
- } catch (e) { jsonResponse(res, { error: e.message }, 500); }
3152
+ // createBackup is async (non-blocking incremental backup) — resolve before responding.
3153
+ Promise.resolve()
3154
+ .then(() => brain.createBackup('manual'))
3155
+ .then((result) => jsonResponse(res, { data: result }))
3156
+ .catch((e) => jsonResponse(res, { error: e.message }, 500));
2896
3157
  return true;
2897
3158
  }
2898
3159
 
@@ -3349,6 +3610,67 @@ function handleWalleApi(req, res, url) {
3349
3610
  return true;
3350
3611
  }
3351
3612
 
3613
+ // GET /api/wall-e/diagnostics/write-lock — brain.db write-lock probes (mirrors CTM's
3614
+ // /api/diagnostics/write-lock). Reports THIS process's (the daemon's) acquire/busy/hold/
3615
+ // wait stats by label. Cross-process contention from skill subprocesses shows up in the
3616
+ // `[sqlite-write-lock] busy fail-fast … pid=… holder=… caller=…` log lines. ?reset=1
3617
+ // snapshots and clears (for windowed deltas).
3618
+ if (p === '/api/wall-e/diagnostics/write-lock' && m === 'GET') {
3619
+ try {
3620
+ const b = brain || require('./brain');
3621
+ const reset = params.get('reset') === '1' || params.get('reset') === 'true';
3622
+ const stats = reset ? b.resetWriteLockStats() : b.getWriteLockStats();
3623
+ jsonResponse(res, { ok: true, generatedAt: Date.now(), reset, stats });
3624
+ } catch (e) {
3625
+ jsonResponse(res, { ok: false, error: e.message }, 500);
3626
+ }
3627
+ return true;
3628
+ }
3629
+
3630
+ // GET/POST /api/wall-e/questions/digest/settings — opt-in digest push channels. The CTM
3631
+ // Wall-E tab is always on (no toggle); Slack DM + email are off by default and enabled
3632
+ // here. Stored as kv flags read by the daily digest job.
3633
+ if (p === '/api/wall-e/questions/digest/settings' && m === 'GET') {
3634
+ try {
3635
+ const b = brain || require('./brain');
3636
+ const { KV } = require('./loops/question-digest');
3637
+ jsonResponse(res, { ok: true, settings: {
3638
+ slack_enabled: String(b.getKv(KV.slackEnabled) || '').toLowerCase() === 'true',
3639
+ slack_channel: b.getKv(KV.slackChannel) || '',
3640
+ email_enabled: String(b.getKv(KV.emailEnabled) || '').toLowerCase() === 'true',
3641
+ email_to: b.getKv(KV.emailTo) || '',
3642
+ } });
3643
+ } catch (e) { jsonResponse(res, { ok: false, error: e.message }, 500); }
3644
+ return true;
3645
+ }
3646
+ if (p === '/api/wall-e/questions/digest/settings' && m === 'POST') {
3647
+ if (!brain) return jsonResponse(res, { ok: false, error: 'Brain not available' }, 503), true;
3648
+ readBody(req).then((body) => {
3649
+ body = body || {};
3650
+ const { KV } = require('./loops/question-digest');
3651
+ if ('slack_enabled' in body) brain.setKv(KV.slackEnabled, String(!!body.slack_enabled));
3652
+ if ('slack_channel' in body) brain.setKv(KV.slackChannel, String(body.slack_channel || ''));
3653
+ if ('email_enabled' in body) brain.setKv(KV.emailEnabled, String(!!body.email_enabled));
3654
+ if ('email_to' in body) brain.setKv(KV.emailTo, String(body.email_to || ''));
3655
+ jsonResponse(res, { ok: true });
3656
+ }).catch((e) => jsonResponse(res, { ok: false, error: e.message }, 400));
3657
+ return true;
3658
+ }
3659
+
3660
+ // GET /api/wall-e/questions/digest — current undelivered digest questions (inspection +
3661
+ // the daily digest job). The CTM Wall-E tab itself answers via the existing
3662
+ // GET /api/wall-e/questions + POST /api/wall-e/questions/:id/answer routes above.
3663
+ if (p === '/api/wall-e/questions/digest' && m === 'GET') {
3664
+ try {
3665
+ const { buildDigest } = require('./loops/question-digest');
3666
+ const digest = buildDigest({ max: parseInt(params.get('max') || '10', 10) || 10 });
3667
+ jsonResponse(res, { ok: true, ...digest });
3668
+ } catch (e) {
3669
+ jsonResponse(res, { ok: false, error: e.message }, 500);
3670
+ }
3671
+ return true;
3672
+ }
3673
+
3352
3674
  // GET /api/wall-e/workspace/status — Item H of prompt-catalog deep-dive.
3353
3675
  // Returns which workspace prompt files exist + which templates are
3354
3676
  // available to install. Used by the setup page to render the
@@ -4120,7 +4442,105 @@ function handleWalleApi(req, res, url) {
4120
4442
  return true;
4121
4443
  }
4122
4444
 
4123
- // POST /api/wall-e/coding — start a coding task
4445
+ // POST /api/wall-e/coding/turnexecute one Wall-E coding turn.
4446
+ //
4447
+ // This is the native coding-run boundary for CTM/phone sessions. It differs
4448
+ // from /api/wall-e/chat?stream=1 by running the coding orchestrator directly
4449
+ // and returning a typed completion outcome: changed, read-only, blocked, or
4450
+ // failed. Chat can still discuss code, but coding sessions need this stronger
4451
+ // contract so prose alone cannot masquerade as completed implementation work.
4452
+ if (p === '/api/wall-e/coding/turn' && m === 'POST') {
4453
+ const wantsStream = params.get('stream') === '1' || (req.headers.accept || '').includes('text/event-stream');
4454
+ // Coding messages can carry pasted image attachments (base64) — accept the
4455
+ // same 16MB cap as /api/wall-e/chat, not the 1MB text default (a >1MB image
4456
+ // otherwise fails with "Body too large (>1MB)").
4457
+ readBody(req, { maxBytes: BODY_LIMIT_LARGE }).then(async body => {
4458
+ let requestFinished = false;
4459
+ const abortController = new AbortController();
4460
+ if (typeof res.on === 'function') {
4461
+ res.on('close', () => {
4462
+ if (!requestFinished) abortController.abort();
4463
+ });
4464
+ }
4465
+ const sendJsonError = (err, status = 500) => {
4466
+ jsonResponse(res, { error: err.message || String(err), code: err.code || 'WALLE_CODING_TURN_ERROR' }, status);
4467
+ };
4468
+ const sendStreamEvent = wantsStream
4469
+ ? (event) => {
4470
+ try { res.write('data: ' + JSON.stringify(event) + '\n\n'); } catch {}
4471
+ }
4472
+ : () => {};
4473
+ try {
4474
+ if (!body.message && !body.request && !body.prompt) {
4475
+ if (wantsStream) {
4476
+ res.writeHead(400, {
4477
+ 'Content-Type': 'text/event-stream',
4478
+ 'Cache-Control': 'no-cache',
4479
+ 'Connection': 'keep-alive',
4480
+ });
4481
+ sendStreamEvent({ type: 'error', error: 'message is required', code: 'BAD_REQUEST' });
4482
+ requestFinished = true;
4483
+ res.end();
4484
+ } else {
4485
+ sendJsonError(new Error('message is required'), 400);
4486
+ }
4487
+ return;
4488
+ }
4489
+ if (wantsStream) {
4490
+ res.writeHead(200, {
4491
+ 'Content-Type': 'text/event-stream',
4492
+ 'Cache-Control': 'no-cache',
4493
+ 'Connection': 'keep-alive',
4494
+ });
4495
+ }
4496
+ const { CodingRunController } = require('./coding/coding-run-controller');
4497
+ const controller = new CodingRunController();
4498
+ const result = await controller.runTurn(body, {
4499
+ signal: abortController.signal,
4500
+ onEvent: sendStreamEvent,
4501
+ });
4502
+ requestFinished = true;
4503
+ if (wantsStream) {
4504
+ sendStreamEvent({ type: 'done', reply: result.reply, data: result });
4505
+ res.end();
4506
+ } else {
4507
+ jsonResponse(res, { data: result });
4508
+ }
4509
+ } catch (err) {
4510
+ requestFinished = true;
4511
+ const isUserCancelled = err?.code === 'WALLE_CANCELLED'
4512
+ || err?.message === 'Cancelled'
4513
+ || (err?.name === 'AbortError' && abortController.signal.aborted);
4514
+ if (wantsStream) {
4515
+ if (!res.headersSent) {
4516
+ res.writeHead(isUserCancelled ? 499 : 500, {
4517
+ 'Content-Type': 'text/event-stream',
4518
+ 'Cache-Control': 'no-cache',
4519
+ 'Connection': 'keep-alive',
4520
+ });
4521
+ }
4522
+ sendStreamEvent({
4523
+ type: isUserCancelled ? 'cancelled' : 'error',
4524
+ error: isUserCancelled ? 'Cancelled' : (err.message || String(err)),
4525
+ code: isUserCancelled ? 'WALLE_CANCELLED' : (err.code || 'WALLE_CODING_TURN_ERROR'),
4526
+ });
4527
+ try { res.end(); } catch {}
4528
+ } else {
4529
+ sendJsonError(err, isUserCancelled ? 499 : 500);
4530
+ }
4531
+ }
4532
+ }).catch(e => {
4533
+ if (wantsStream && res.headersSent) {
4534
+ try { res.write('data: ' + JSON.stringify({ type: 'error', error: e.message }) + '\n\n'); } catch {}
4535
+ try { res.end(); } catch {}
4536
+ } else {
4537
+ jsonResponse(res, { error: e.message }, 400);
4538
+ }
4539
+ });
4540
+ return true;
4541
+ }
4542
+
4543
+ // POST /api/wall-e/coding — start a queued coding task
4124
4544
  if (p === '/api/wall-e/coding' && m === 'POST') {
4125
4545
  if (!brain || !ensureBrainInit()) return jsonResponse(res, { error: 'Brain module not available' }, 503), true;
4126
4546
  readBody(req).then(body => {
@@ -4148,7 +4568,7 @@ function handleWalleApi(req, res, url) {
4148
4568
  } catch (e) {
4149
4569
  jsonResponse(res, { error: e.message }, 500);
4150
4570
  }
4151
- }).catch(e => jsonResponse(res, { error: e.message }, 500));
4571
+ }).catch(e => jsonResponse(res, { error: e.message, code: e.code || '' }, e.code === 'BODY_TOO_LARGE' ? 413 : 500));
4152
4572
  return true;
4153
4573
  }
4154
4574
 
@@ -4424,6 +4844,7 @@ function handleWalleApi(req, res, url) {
4424
4844
 
4425
4845
  // GET /api/wall-e/shadow/stats — shadow training pipeline stats
4426
4846
  if (p === '/api/wall-e/shadow/stats' && m === 'GET') {
4847
+ (async () => {
4427
4848
  try {
4428
4849
  const db = brain.getDb();
4429
4850
  const totalResults = db.prepare('SELECT COUNT(*) AS count FROM shadow_results').get().count;
@@ -4458,41 +4879,22 @@ function handleWalleApi(req, res, url) {
4458
4879
  // Harvest progress — total CTM sessions vs processed
4459
4880
  let harvestProgress = null;
4460
4881
  try {
4461
- const dataDir = process.env.WALL_E_DATA_DIR || require('path').join(process.env.HOME, '.walle', 'data');
4462
- const ctmDbPath = require('path').join(dataDir, 'task-manager.db');
4463
- const fs = require('fs');
4464
- if (fs.existsSync(ctmDbPath)) {
4465
- const Database = require('better-sqlite3');
4466
- const ctmDb = new Database(ctmDbPath, { readonly: true, fileMustExist: true });
4467
- const totalSessions = ctmDb.prepare('SELECT COUNT(DISTINCT session_id) AS count FROM prompt_executions').get().count;
4468
- const totalPairs = ctmDb.prepare(`
4469
- SELECT COUNT(*) AS count FROM prompt_executions u
4470
- JOIN prompt_executions a ON a.session_id = u.session_id AND a.message_index = u.message_index + 1
4471
- WHERE u.role = 'user' AND a.role = 'assistant'
4472
- AND length(u.message_text) >= 20 AND length(a.message_text) >= 20
4473
- `).get().count;
4474
- const multiTurnSessions = ctmDb.prepare(`
4475
- SELECT COUNT(*) AS count FROM (
4476
- SELECT session_id FROM prompt_executions WHERE role = 'user'
4477
- GROUP BY session_id HAVING COUNT(*) >= 2
4478
- )
4479
- `).get().count;
4480
- ctmDb.close();
4481
-
4482
- const singleReplayed = (replayStats['ctm-replay']?.totalProcessed) || 0;
4483
- const multiReplayed = (replayStats['ctm-replay-mt']?.totalProcessed) || 0;
4484
-
4485
- harvestProgress = {
4486
- totalSessions,
4487
- totalPairs,
4488
- multiTurnSessions,
4489
- singleTurnReplayed: singleReplayed,
4490
- multiTurnReplayed: multiReplayed,
4491
- totalReplayed: singleReplayed + multiReplayed,
4492
- };
4493
- }
4882
+ const { getPromptExecutionStats } = require('./memory/ctm-prompt-executions-client');
4883
+ const stats = await getPromptExecutionStats({ timeoutMs: 1000 });
4884
+ const singleReplayed = (replayStats['ctm-replay']?.totalProcessed) || 0;
4885
+ const multiReplayed = (replayStats['ctm-replay-mt']?.totalProcessed) || 0;
4886
+
4887
+ harvestProgress = {
4888
+ totalSessions: stats.totalSessions || 0,
4889
+ totalPairs: stats.totalPairs || 0,
4890
+ multiTurnSessions: stats.multiTurnSessions || 0,
4891
+ singleTurnReplayed: singleReplayed,
4892
+ multiTurnReplayed: multiReplayed,
4893
+ totalReplayed: singleReplayed + multiReplayed,
4894
+ source: 'ctm-api',
4895
+ };
4494
4896
  } catch (e) {
4495
- // CTM DB not available skip
4897
+ harvestProgress = { source: 'ctm-api', unavailable: true, error: e.message };
4496
4898
  }
4497
4899
 
4498
4900
  // Recent results — last 5 shadow results for sample browser
@@ -4509,19 +4911,21 @@ function handleWalleApi(req, res, url) {
4509
4911
  LIMIT 5
4510
4912
  `).all();
4511
4913
 
4512
- return jsonResponse(res, { data: {
4914
+ jsonResponse(res, { data: {
4513
4915
  totalResults, evaluatedResults,
4514
4916
  avgScore: avgScore != null ? Math.round(avgScore * 1000) / 1000 : null,
4515
4917
  byModel, byTaskType, promotionCandidates, modelScores,
4516
4918
  replayStats, harvestProgress, recentResults,
4517
- } }), true;
4919
+ } });
4518
4920
  } catch (e) {
4519
- return jsonResponse(res, { data: {
4921
+ jsonResponse(res, { data: {
4520
4922
  totalResults: 0, evaluatedResults: 0, avgScore: null,
4521
4923
  byModel: [], byTaskType: [], promotionCandidates: [], modelScores: [],
4522
4924
  replayStats: {}, harvestProgress: null, recentResults: [],
4523
- } }), true;
4925
+ } });
4524
4926
  }
4927
+ })();
4928
+ return true;
4525
4929
  }
4526
4930
 
4527
4931
  // GET /api/wall-e/shadow/comparisons — paginated side-by-side comparison viewer
@@ -5578,6 +5982,28 @@ function handleWalleApi(req, res, url) {
5578
5982
  summary.recent_feedback = [];
5579
5983
  }
5580
5984
 
5985
+ try {
5986
+ summary.feature_adoption = {
5987
+ memory_search: telemetryEmbeddingFeatureAdoption(tdb, since, summary.active_installs || 0),
5988
+ };
5989
+ } catch {
5990
+ summary.feature_adoption = {
5991
+ memory_search: {
5992
+ reporting_installs: 0,
5993
+ enabled_installs: 0,
5994
+ usage_installs: 0,
5995
+ setup_view_installs: 0,
5996
+ switch_success_installs: 0,
5997
+ switch_failed_installs: 0,
5998
+ enabled_pct_active: null,
5999
+ usage_pct_active: null,
6000
+ latest_state: [],
6001
+ enabled_by_provider: [],
6002
+ usage_by_operation: [],
6003
+ },
6004
+ };
6005
+ }
6006
+
5581
6007
  // Compat lifecycle data (Phase 4)
5582
6008
  try {
5583
6009
  // Version distribution with last_seen
@@ -5612,8 +6038,28 @@ function handleWalleApi(req, res, url) {
5612
6038
  // Safe to remove: deprecated features with 0 usage
5613
6039
  try {
5614
6040
  const compat = require('./compat');
5615
- summary.safe_to_remove = compat.getRemovalCandidates(featureCounts)
5616
- .filter(c => c.safeToRemove)
6041
+ const registry = typeof compat.getRegistry === 'function' ? compat.getRegistry() : {};
6042
+ summary.compat_removal_status = Object.entries(registry).map(([key, info]) => {
6043
+ const telemetryKey = info.telemetryKey || key;
6044
+ const activeInstalls = featureCounts[telemetryKey] || 0;
6045
+ const deprecated = Boolean(info.deprecatedIn);
6046
+ const safeToRemove = deprecated && activeInstalls === 0;
6047
+ const blockedReason = safeToRemove
6048
+ ? ''
6049
+ : (!deprecated ? 'not_deprecated' : (activeInstalls > 0 ? 'active_usage' : 'not_scheduled'));
6050
+ return {
6051
+ key,
6052
+ telemetry_key: telemetryKey,
6053
+ deprecated,
6054
+ deprecated_in: info.deprecatedIn || null,
6055
+ remove_after: info.removeAfter || null,
6056
+ active_installs: activeInstalls,
6057
+ safe_to_remove: safeToRemove,
6058
+ blocked_reason: blockedReason,
6059
+ };
6060
+ });
6061
+ summary.safe_to_remove = summary.compat_removal_status
6062
+ .filter(c => c.safe_to_remove)
5617
6063
  .map(c => c.key);
5618
6064
  } catch {}
5619
6065
 
@@ -5650,6 +6096,7 @@ function handleWalleApi(req, res, url) {
5650
6096
  apply_clicked: new Set(),
5651
6097
  apply_started: new Set(),
5652
6098
  completed: new Set(),
6099
+ first_tracked_completed: new Set(),
5653
6100
  };
5654
6101
  for (const row of upgradeRows) {
5655
6102
  const identity = row.machine_bucket ? `m:${row.machine_bucket}` : `i:${row.install_id}`;
@@ -5657,6 +6104,7 @@ function handleWalleApi(req, res, url) {
5657
6104
  try { meta = row.meta ? JSON.parse(row.meta) : {}; } catch {}
5658
6105
  if (row.event === 'upgrade') {
5659
6106
  upgradeSets.completed.add(identity);
6107
+ if (meta.first_tracked) upgradeSets.first_tracked_completed.add(identity);
5660
6108
  continue;
5661
6109
  }
5662
6110
  if (row.event === 'funnel') {
@@ -5683,17 +6131,23 @@ function handleWalleApi(req, res, url) {
5683
6131
  }
5684
6132
  if (row.event === 'ctm_update_apply_started') upgradeSets.apply_started.add(identity);
5685
6133
  }
6134
+ const completedAfterApply = telemetrySetIntersectionSize(upgradeSets.completed, upgradeSets.apply_started);
6135
+ const externalCompleted = telemetrySetDifferenceSize(upgradeSets.completed, upgradeSets.apply_started);
5686
6136
  summary.upgrade_funnel = {
5687
6137
  available: upgradeSets.available.size,
5688
6138
  prompted: upgradeSets.prompted.size,
5689
6139
  dismissed: upgradeSets.dismissed.size,
5690
6140
  apply_clicked: upgradeSets.apply_clicked.size,
5691
6141
  apply_started: upgradeSets.apply_started.size,
5692
- completed: upgradeSets.completed.size,
6142
+ completed: completedAfterApply,
6143
+ completed_after_apply: completedAfterApply,
6144
+ completed_observed: upgradeSets.completed.size,
6145
+ external_completed: externalCompleted,
6146
+ first_tracked_completed: upgradeSets.first_tracked_completed.size,
5693
6147
  prompt_rate: pct(upgradeSets.prompted.size, upgradeSets.available.size),
5694
6148
  apply_rate: pct(upgradeSets.apply_clicked.size, upgradeSets.prompted.size),
5695
6149
  start_rate: pct(upgradeSets.apply_started.size, upgradeSets.apply_clicked.size),
5696
- completion_rate: pct(upgradeSets.completed.size, upgradeSets.apply_started.size),
6150
+ completion_rate: pct(completedAfterApply, upgradeSets.apply_started.size),
5697
6151
  };
5698
6152
  } catch {}
5699
6153
 
@@ -6230,7 +6684,7 @@ function handleWalleApi(req, res, url) {
6230
6684
 
6231
6685
  // Allow overriding the read DB (for testing)
6232
6686
  function _setReadDb(db) {
6233
- readDb = db;
6687
+ readDbOverride = db;
6234
6688
  }
6235
6689
 
6236
6690
  function _setBrain(b) {