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
@@ -0,0 +1,232 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Unified phone/remote-access connection health.
5
+ *
6
+ * Produces ONE normalized health record per method (Microsoft Dev Tunnel, Walle Remote
7
+ * relay, Tailscale) so the Access page can show a single status truth, detect
8
+ * expiry/offline, fire an action-required notification on the TRANSITION to broken, and
9
+ * auto-recover when the user fixes it.
10
+ *
11
+ * Probes are READ-ONLY (no recovery side-effects) — the existing Microsoft watchdog owns
12
+ * actually fixing the tunnel; this module reports state and offers `action` endpoints the
13
+ * client triggers. Cheap enough to poll (~20s) — no deep public-reachability probe here.
14
+ *
15
+ * Record shape:
16
+ * { method, state, detail, origin, action, since, checked_at }
17
+ * state: 'ok' | 'warning' | 'expired' | 'offline' | 'error' | 'not_configured'
18
+ * action: null | { kind, endpoint, method } // a one-click fix the client can POST/GET
19
+ */
20
+
21
+ const fs = require('fs');
22
+ const { X509Certificate } = require('crypto');
23
+
24
+ const microsoft = require('./microsoft-dev-tunnel-setup');
25
+ const tailscale = require('./tailscale-setup');
26
+ const remoteRelay = require('./remote-relay-store');
27
+
28
+ // Walle relay heartbeat: if not connected and last handshake older than this, treat as offline.
29
+ const WALLE_STALE_MS = Number(process.env.CTM_WALLE_RELAY_STALE_MS) || 5 * 60 * 1000;
30
+ const CERT_SOON_MS = 24 * 60 * 60 * 1000;
31
+
32
+ // States that count as "broken" (drive the Reconnect badge + action-required notification).
33
+ const BROKEN = new Set(['expired', 'offline', 'error']);
34
+
35
+ // method -> { state, since } — transition cache (also keeps `since` accurate between polls).
36
+ const _stateCache = new Map();
37
+
38
+ function _rec(method, { state, detail, origin = '', action = null }) {
39
+ return { method, state, detail, origin, action };
40
+ }
41
+
42
+ // How far past its scheduled renewal the managed tunnel is (ms; 0 = on track).
43
+ // Considers both the tunnel renewal and the anonymous-access renewal.
44
+ function _renewalOverdueMs(managed, now = Date.now()) {
45
+ let worst = 0;
46
+ for (const key of ['next_tunnel_renewal_at', 'next_access_renewal_at']) {
47
+ const at = Date.parse(managed && managed[key] || '');
48
+ if (Number.isFinite(at) && now > at) worst = Math.max(worst, now - at);
49
+ }
50
+ return worst;
51
+ }
52
+
53
+ async function probeMicrosoftHealth(opts) {
54
+ try {
55
+ const d = await microsoft.detectMicrosoftDevTunnelSetup(opts);
56
+ const managed = d.managed_tunnel || {};
57
+ const configured = !!(managed.configured || managed.desired_running || managed.tunnel_id);
58
+ if (!d.installed) {
59
+ return _rec('microsoft', { state: 'not_configured', detail: 'devtunnel CLI is not installed.',
60
+ action: { kind: 'install', endpoint: '/api/setup/network/microsoft-dev-tunnel/install', method: 'POST' } });
61
+ }
62
+ if (!d.signed_in) {
63
+ // Signed out: if a tunnel was previously configured this is almost always an EXPIRED login.
64
+ return _rec('microsoft', configured
65
+ ? { state: 'expired', detail: 'Microsoft sign-in expired — reconnect.',
66
+ action: { kind: 'login', endpoint: '/api/setup/network/microsoft-dev-tunnel/login', method: 'POST' } }
67
+ : { state: 'not_configured', detail: 'Not signed in.',
68
+ action: { kind: 'login', endpoint: '/api/setup/network/microsoft-dev-tunnel/login', method: 'POST' } });
69
+ }
70
+ if (managed.desired_running && !managed.running) {
71
+ return _rec('microsoft', { state: 'offline', detail: 'Tunnel should be running but is not — reconnect.', origin: d.mobile_url,
72
+ action: { kind: 'recover', endpoint: '/api/setup/network/microsoft-dev-tunnel/recover', method: 'POST' } });
73
+ }
74
+ if (!configured || !managed.running) {
75
+ return _rec('microsoft', { state: 'not_configured', detail: 'Tunnel not started yet.', origin: d.mobile_url,
76
+ action: { kind: 'start', endpoint: '/api/setup/network/microsoft-dev-tunnel/start', method: 'POST' } });
77
+ }
78
+ if (managed.access && managed.access.status === 'error') {
79
+ return _rec('microsoft', { state: 'warning', detail: managed.access_error || 'Tunnel access needs attention.', origin: d.mobile_url,
80
+ action: { kind: 'recover', endpoint: '/api/setup/network/microsoft-dev-tunnel/recover', method: 'POST' } });
81
+ }
82
+ // Renewal runs at day 25 of the 30-day Dev Tunnels expiration. If it is
83
+ // overdue the tunnel is burning its ~5-day runway toward expiry — and an
84
+ // expired tunnel gets RECREATED with a NEW URL, which invalidates every
85
+ // paired phone (cookies + passkeys are hostname-bound). Escalate before that:
86
+ // >24h overdue = warning, >3 days = broken (fires the action-required alert).
87
+ const overdue = _renewalOverdueMs(managed);
88
+ if (overdue > 3 * 24 * 60 * 60 * 1000) {
89
+ return _rec('microsoft', { state: 'error', detail: 'Tunnel renewal has been failing for days — the URL will expire and rotate. Reconnect.', origin: d.mobile_url,
90
+ action: { kind: 'recover', endpoint: '/api/setup/network/microsoft-dev-tunnel/recover', method: 'POST' } });
91
+ }
92
+ if (overdue > 24 * 60 * 60 * 1000) {
93
+ return _rec('microsoft', { state: 'warning', detail: 'Tunnel renewal is overdue — reconnect before the URL expires and rotates.', origin: d.mobile_url,
94
+ action: { kind: 'recover', endpoint: '/api/setup/network/microsoft-dev-tunnel/recover', method: 'POST' } });
95
+ }
96
+ return _rec('microsoft', { state: 'ok', detail: 'Connected.', origin: d.mobile_url });
97
+ } catch (err) {
98
+ return _rec('microsoft', { state: 'error', detail: String(err?.message || err).slice(0, 200) });
99
+ }
100
+ }
101
+
102
+ async function probeWalleHealth(db) {
103
+ try {
104
+ if (!db) return _rec('walle', { state: 'not_configured', detail: 'Relay store unavailable.' });
105
+ remoteRelay.ensureRemoteRelaySchema(db);
106
+ const s = remoteRelay.buildRemoteStatus(db);
107
+ if (!s.configured) return _rec('walle', { state: 'not_configured', detail: 'Walle Remote is not set up.' });
108
+ if (s.connected) return _rec('walle', { state: 'ok', detail: 'Relay connected.', origin: s.relay && s.relay.mobile_url });
109
+ // Configured but not connected. Only treat as a BREAK if it was connected before
110
+ // (last_connected_at exists) — otherwise it's a default instance that was never used,
111
+ // which must NOT raise a false "offline — reconnect" alert.
112
+ const last = Number(s.instance && s.instance.last_connected_at) || 0;
113
+ if (!last) return _rec('walle', { state: 'not_configured', detail: 'Set up Walle Remote to pair a phone.', origin: s.relay && s.relay.mobile_url });
114
+ const stale = (Date.now() - last) > WALLE_STALE_MS;
115
+ return _rec('walle', {
116
+ state: 'offline',
117
+ detail: stale ? 'Relay is not connected.' : 'Relay reconnecting…',
118
+ origin: s.relay && s.relay.mobile_url,
119
+ action: null, // no programmatic re-dial yet (relay client auto-reconnects); surface guidance
120
+ });
121
+ } catch (err) {
122
+ return _rec('walle', { state: 'error', detail: String(err?.message || err).slice(0, 200) });
123
+ }
124
+ }
125
+
126
+ // Returns { expired, soon, notAfter } or null if cert can't be read/parsed.
127
+ function _certStatus(certPath) {
128
+ try {
129
+ if (!certPath || !fs.existsSync(certPath)) return null;
130
+ const x = new X509Certificate(fs.readFileSync(certPath));
131
+ const notAfter = new Date(x.validTo).getTime();
132
+ if (!Number.isFinite(notAfter)) return null;
133
+ const now = Date.now();
134
+ return { expired: now > notAfter, soon: notAfter - now < CERT_SOON_MS, notAfter };
135
+ } catch {
136
+ return null;
137
+ }
138
+ }
139
+
140
+ async function probeTailscaleHealth(opts) {
141
+ try {
142
+ const t = await tailscale.detectTailscaleSetup(opts);
143
+ const tailscaleAuto = { kind: 'recover', endpoint: '/api/setup/network/tailscale-auto', method: 'POST' };
144
+ if (!t.installed) return _rec('tailscale', { state: 'not_configured', detail: 'Tailscale is not installed.' });
145
+ // "Set up for CTM phone access" = an HTTPS cert was generated. Without it, Tailscale
146
+ // may be installed/used for other things but isn't a CTM phone path — so a stopped/
147
+ // logged-out node must NOT raise a false "offline — reconnect" alert.
148
+ if (!t.cert_exists) {
149
+ return _rec('tailscale', {
150
+ state: 'not_configured',
151
+ detail: t.available ? 'Run auto setup to enable HTTPS for phones.' : 'Tailscale is not set up for phone access.',
152
+ origin: t.mobile_url, action: tailscaleAuto,
153
+ });
154
+ }
155
+ // Cert exists → the user set this up. Now report real liveness (authoritative
156
+ // BackendState + cert validity), so a genuine break surfaces as offline/expired.
157
+ const cert = _certStatus(t.cert_path);
158
+ if (cert && cert.expired) {
159
+ return _rec('tailscale', { state: 'expired', detail: 'HTTPS certificate expired — regenerate.', origin: t.mobile_url, action: tailscaleAuto });
160
+ }
161
+ const backend = String(t.backend_state || '');
162
+ if (backend === 'NeedsLogin') {
163
+ return _rec('tailscale', { state: 'expired', detail: 'Tailscale needs sign-in — run “tailscale up”.', origin: t.mobile_url, action: tailscaleAuto });
164
+ }
165
+ if (!t.available || (backend && backend !== 'Running')) {
166
+ const detail = backend === 'Stopped' ? 'Tailscale is stopped — run “tailscale up”.'
167
+ : backend === 'Starting' ? 'Tailscale is starting…'
168
+ : 'Tailscale is not connected — run “tailscale up”.';
169
+ return _rec('tailscale', { state: backend === 'Starting' ? 'warning' : 'offline', detail, origin: t.mobile_url, action: tailscaleAuto });
170
+ }
171
+ if (cert && cert.soon) {
172
+ return _rec('tailscale', { state: 'warning', detail: 'HTTPS certificate expires soon.', origin: t.mobile_url, action: tailscaleAuto });
173
+ }
174
+ return _rec('tailscale', { state: 'ok', detail: 'Reachable on your tailnet.', origin: t.mobile_url });
175
+ } catch (err) {
176
+ return _rec('tailscale', { state: 'error', detail: String(err?.message || err).slice(0, 200) });
177
+ }
178
+ }
179
+
180
+ // Update each record's `since`/`checked_at` from the cache and return broken-edge transitions.
181
+ function _applyTransitions(records, nowIso) {
182
+ const transitions = [];
183
+ for (const r of records) {
184
+ const prev = _stateCache.get(r.method);
185
+ if (!prev || prev.state !== r.state) {
186
+ r.since = nowIso;
187
+ if (prev && !BROKEN.has(prev.state) && BROKEN.has(r.state)) {
188
+ transitions.push({ method: r.method, from: prev.state, to: r.state });
189
+ }
190
+ _stateCache.set(r.method, { state: r.state, since: nowIso });
191
+ } else {
192
+ r.since = prev.since;
193
+ }
194
+ r.checked_at = nowIso;
195
+ }
196
+ return transitions;
197
+ }
198
+
199
+ const _ORDER = { microsoft: 0, walle: 1, tailscale: 2 };
200
+ function _pickRecommended(records) {
201
+ const oks = records.filter((r) => r.state === 'ok');
202
+ if (oks.length >= 1) {
203
+ return oks.sort((a, b) => _ORDER[a.method] - _ORDER[b.method])[0].method;
204
+ }
205
+ return 'microsoft'; // simplest to set up (no VPN app, no custom domain)
206
+ }
207
+
208
+ async function getConnectionHealth(deps = {}) {
209
+ const opts = { configDir: deps.configDir, port: deps.port };
210
+ const nowIso = new Date().toISOString();
211
+ const order = ['microsoft', 'walle', 'tailscale'];
212
+ const settled = await Promise.allSettled([
213
+ probeMicrosoftHealth(opts),
214
+ probeWalleHealth(deps.db),
215
+ probeTailscaleHealth(opts),
216
+ ]);
217
+ const records = settled.map((s, i) => (s.status === 'fulfilled'
218
+ ? s.value
219
+ : _rec(order[i], { state: 'error', detail: String(s.reason && s.reason.message || s.reason).slice(0, 200) })));
220
+ const transitions = _applyTransitions(records, nowIso);
221
+ return { ok: true, checked_at: nowIso, recommended: _pickRecommended(records), methods: records, transitions };
222
+ }
223
+
224
+ module.exports = {
225
+ getConnectionHealth,
226
+ probeMicrosoftHealth,
227
+ probeWalleHealth,
228
+ probeTailscaleHealth,
229
+ BROKEN,
230
+ _stateCache,
231
+ _internal: { _certStatus, _applyTransitions, _pickRecommended, _renewalOverdueMs },
232
+ };
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ // Shared parse of a session_conversations cache blob into a sorted message array.
4
+ //
5
+ // This is the heavy work behind `session-messages:parseCacheBlob`: JSON.parse of a
6
+ // 0.5–2MB messages string (real Claude/Codex messages are deeply nested, so the parse
7
+ // is 150–360ms on the main thread per the [freeze-probe] samples), a codex-synthetic
8
+ // scan, and an in-place timeline sort. Extracted so it runs IDENTICALLY on the main
9
+ // thread (cold/first-load + pool-failure fallback) and on a read-pool worker (the
10
+ // off-thread re-parse for growing sessions) — parity by construction (same function).
11
+ //
12
+ // Deliberately dependency-light so a worker can require it without pulling in server.js:
13
+ // only lib/timeline-order + lib/session-history (both already worker-safe).
14
+
15
+ const { sortTimelineMessagesInPlace } = require('./timeline-order');
16
+
17
+ // Lazy + defensive: if session-history can't load in a worker context, treat nothing as
18
+ // synthetic (the main-thread path uses the real predicate; worst case a worker keeps a
19
+ // synthetic codex context row that main would have dropped — corrected on the next
20
+ // main-served parse). Mirrors lib/session-prompt-index.js's resolution.
21
+ let _isCodexSyntheticUserText = null;
22
+ function _codexSynthetic() {
23
+ if (_isCodexSyntheticUserText) return _isCodexSyntheticUserText;
24
+ try { _isCodexSyntheticUserText = require('./session-history').isCodexSyntheticUserText; }
25
+ catch { _isCodexSyntheticUserText = () => false; }
26
+ return _isCodexSyntheticUserText;
27
+ }
28
+
29
+ // Returns the parsed+sorted message array, or null when the blob is empty/invalid or the
30
+ // conversation is entirely injected codex context (matches the server.js inline behavior
31
+ // that this replaces). Throws only on malformed JSON — callers catch and fall back.
32
+ function parseConversationBlob(messagesString) {
33
+ if (typeof messagesString !== 'string' || messagesString.length === 0) return null;
34
+ const messages = JSON.parse(messagesString);
35
+ if (!Array.isArray(messages) || messages.length === 0) return null;
36
+ const isSynthetic = _codexSynthetic();
37
+ if (messages.some((m) => m && m.role === 'user' && isSynthetic(m.text))) return null;
38
+ sortTimelineMessagesInPlace(messages);
39
+ return messages;
40
+ }
41
+
42
+ module.exports = { parseConversationBlob };
@@ -1,9 +1,14 @@
1
1
  'use strict';
2
2
 
3
3
  const crypto = require('crypto');
4
+ const { sortTimelineMessages, timelineTimestampMs } = require('./timeline-order');
4
5
 
5
6
  const MESSAGE_ROLES = new Set(['user', 'assistant']);
6
7
 
8
+ // Below this length, identical text is too likely to be a legitimately-repeated short
9
+ // turn (e.g. a user typing "yes" twice) to safely use as a cross-timestamp dedup key.
10
+ const CONTENT_IDENTITY_MIN_LEN = 200;
11
+
7
12
  function textHash(text) {
8
13
  return crypto.createHash('sha1').update(String(text || '')).digest('hex').slice(0, 16);
9
14
  }
@@ -14,32 +19,65 @@ function messageText(message) {
14
19
 
15
20
  function timestampValue(timestamp) {
16
21
  if (timestamp == null || timestamp === '') return '';
17
- if (typeof timestamp === 'number' && Number.isFinite(timestamp)) {
18
- try { return new Date(timestamp).toISOString(); } catch { return String(timestamp); }
19
- }
20
22
  const raw = String(timestamp || '');
21
- const parsed = Date.parse(raw);
22
- if (Number.isFinite(parsed)) return new Date(parsed).toISOString();
23
+ const parsed = timelineTimestampMs(timestamp);
24
+ if (parsed != null) {
25
+ try { return new Date(parsed).toISOString(); } catch {}
26
+ }
23
27
  return raw;
24
28
  }
25
29
 
26
- function timestampMs(timestamp) {
27
- if (typeof timestamp === 'number' && Number.isFinite(timestamp)) return timestamp;
28
- const parsed = Date.parse(String(timestamp || ''));
29
- return Number.isFinite(parsed) ? parsed : null;
30
- }
31
-
32
- function messageKey(message) {
30
+ function messageKey(message, textHashValue) {
33
31
  const role = String(message?.role || '').trim();
34
32
  const text = messageText(message);
35
33
  if (!role || !text) return '';
36
34
  return [
37
35
  role,
38
36
  timestampValue(message?.timestamp),
39
- textHash(text),
37
+ textHashValue || textHash(text),
40
38
  ].join(':');
41
39
  }
42
40
 
41
+ function messageIdentityKeys(message, textHashValue) {
42
+ const role = String(message?.role || '').trim();
43
+ if (!MESSAGE_ROLES.has(role)) return [];
44
+ const keys = [];
45
+ const parentUuid = String(
46
+ message?.parentUuid
47
+ || message?._parent
48
+ || message?.metadata?.parentUuid
49
+ || message?.metadata?.uuid
50
+ || ''
51
+ ).trim();
52
+ const ts = timestampValue(message?.timestamp);
53
+ if (parentUuid) keys.push(['provider', role, parentUuid].join(':'));
54
+ if (ts) keys.push(['timestamp', role, ts].join(':'));
55
+ // Content identity: collapses an exact-duplicate long turn that shares neither a
56
+ // parentUuid nor a timestamp with its twin — the provider-side hazards (stream-json
57
+ // duplicate rows, a non-streaming fallback re-bundling blocks, or a session resume
58
+ // rewriting history with fresh uuids/timestamps). Gated to long text so genuinely
59
+ // repeated short turns are never collapsed.
60
+ const text = messageText(message);
61
+ if (text.length >= CONTENT_IDENTITY_MIN_LEN) {
62
+ keys.push(['content', role, textHashValue || textHash(text)].join(':'));
63
+ }
64
+ return keys;
65
+ }
66
+
67
+ function messagesRepresentSameEvent(a, b) {
68
+ const aText = messageText(a);
69
+ const bText = messageText(b);
70
+ if (!aText || !bText) return false;
71
+ if (aText === bText) return true;
72
+ const shorter = aText.length <= bText.length ? aText : bText;
73
+ const longer = aText.length <= bText.length ? bText : aText;
74
+ // The conversation cache intentionally stores long rows as bounded visible
75
+ // text, while the live stream tail can still have the full JSONL row. Treat
76
+ // a long cached prefix and the full stream event as the same turn. Keep this
77
+ // bounded to long text so two short same-timestamp messages are not merged.
78
+ return shorter.length >= 512 && longer.startsWith(shorter);
79
+ }
80
+
43
81
  function eventRole(event) {
44
82
  const type = String(event?.type || '').trim();
45
83
  if (MESSAGE_ROLES.has(type)) return type;
@@ -64,33 +102,32 @@ function streamEventToMessage(event) {
64
102
  };
65
103
  if (event?.eventId) message.streamEventId = event.eventId;
66
104
  if (Array.isArray(event?.data?.contentBlocks)) message.contentBlocks = event.data.contentBlocks;
105
+ if (event?.data?.parentUuid) message.parentUuid = event.data.parentUuid;
67
106
  return message;
68
107
  }
69
108
 
70
109
  function sortMessagesChronologically(messages) {
71
- return messages
72
- .slice()
73
- .sort((a, b) => {
74
- const ams = timestampMs(a.timestamp);
75
- const bms = timestampMs(b.timestamp);
76
- if (ams != null && bms != null && ams !== bms) return ams - bms;
77
- if (ams != null && bms == null) return -1;
78
- if (ams == null && bms != null) return 1;
79
- if (a._mergeSourceOrder !== b._mergeSourceOrder) return a._mergeSourceOrder - b._mergeSourceOrder;
80
- return a._mergeIndex - b._mergeIndex;
81
- })
110
+ return sortTimelineMessages(messages)
82
111
  .map(({ _mergeSourceOrder, _mergeIndex, ...message }) => message);
83
112
  }
84
113
 
85
114
  function mergeMessagesWithStreamEvents(messages, events) {
86
115
  const merged = [];
87
116
  const seen = new Set();
117
+ const byIdentity = new Map();
88
118
  let added = 0;
119
+ let replaced = 0;
89
120
 
90
121
  const addMessage = (message, sourceOrder, index, fromStream = false) => {
91
122
  const text = messageText(message);
92
123
  const role = String(message?.role || '').trim();
93
- if (!MESSAGE_ROLES.has(role) || !text) return;
124
+ if (!role || !text) return;
125
+ // Only user/assistant turns ever arrive via the live stream ring; durable
126
+ // base messages with other roles (tool-result rows, structured-capture
127
+ // system rows) must pass through, or a hot stream tail hides them. They
128
+ // get exact-key dedup below but no identity-key merging (identity keys
129
+ // are user/assistant-only).
130
+ if (fromStream && !MESSAGE_ROLES.has(role)) return;
94
131
  const normalized = {
95
132
  ...message,
96
133
  role,
@@ -99,9 +136,34 @@ function mergeMessagesWithStreamEvents(messages, events) {
99
136
  _mergeSourceOrder: sourceOrder,
100
137
  _mergeIndex: index,
101
138
  };
102
- const key = messageKey(normalized);
139
+ const th = textHash(text);
140
+ const key = messageKey(normalized, th);
103
141
  if (!key || seen.has(key)) return;
142
+ const identityKeys = messageIdentityKeys(normalized, th);
143
+ for (const identityKey of identityKeys) {
144
+ if (!byIdentity.has(identityKey)) continue;
145
+ const existing = byIdentity.get(identityKey);
146
+ const existingMessage = merged[existing.index];
147
+ // A shared provider key (parentUuid) means the SAME provider row: a still-streaming
148
+ // partial in the live ring and its finalized copy in the cache. Collapse it
149
+ // unconditionally (keep the longer text) — the partial is not always a clean >=512
150
+ // prefix of the final, so messagesRepresentSameEvent's prefix gate can miss it.
151
+ // Weaker keys (timestamp / content-hash) still require a text match so two genuinely
152
+ // different turns that merely collide on one weak key are never merged.
153
+ const sameEvent = identityKey.startsWith('provider:')
154
+ || messagesRepresentSameEvent(existingMessage, normalized);
155
+ if (sameEvent) {
156
+ seen.add(key);
157
+ if (text.length > messageText(existingMessage).length) {
158
+ merged[existing.index] = normalized;
159
+ replaced++;
160
+ }
161
+ identityKeys.forEach(k => byIdentity.set(k, existing));
162
+ return;
163
+ }
164
+ }
104
165
  seen.add(key);
166
+ identityKeys.forEach(identityKey => byIdentity.set(identityKey, { index: merged.length, fromStream }));
105
167
  merged.push(normalized);
106
168
  if (fromStream) added++;
107
169
  };
@@ -117,6 +179,7 @@ function mergeMessagesWithStreamEvents(messages, events) {
117
179
  return {
118
180
  messages: sortMessagesChronologically(merged),
119
181
  added,
182
+ replaced,
120
183
  };
121
184
  }
122
185
 
@@ -13,7 +13,7 @@ function loadCtmSessionContext() {
13
13
  return ctmSessionContext;
14
14
  }
15
15
 
16
- function handleCtmSessionContextApi(req, res, url, { db, dbModule } = {}) {
16
+ function handleCtmSessionContextApi(req, res, url, { db, dbModule, runHeavy } = {}) {
17
17
  if (!url.pathname.startsWith('/api/ctm/session-memory')) return false;
18
18
 
19
19
  const context = loadCtmSessionContext();
@@ -26,19 +26,46 @@ function handleCtmSessionContextApi(req, res, url, { db, dbModule } = {}) {
26
26
  });
27
27
  }
28
28
 
29
+ // The search / context / context-pack reads are the heavy ones (FTS + LIKE scans over
30
+ // 220k+ message rows and a tens-of-MB conversation-blob column, plus full JSON.parse of
31
+ // matched conversations) — a single search was measured blocking the main loop 7.74s.
32
+ // When the server wires an off-thread runner, run them on the read-pool worker and reply
33
+ // when it resolves; `args` is the plain (clone-safe) input the worker re-runs with its own
34
+ // read-only connection, and `computeSync` is the main-thread fallback used when no runner
35
+ // is wired (tests / direct callers) or the pool is unavailable.
36
+ const serveHeavy = (op, args, computeSync) => {
37
+ if (typeof runHeavy !== 'function') {
38
+ return respondJson(res, 200, asApiResult(computeSync()));
39
+ }
40
+ Promise.resolve()
41
+ .then(() => runHeavy(op, args, computeSync))
42
+ .then((result) => respondJson(res, 200, asApiResult(result)))
43
+ .catch((err) => respondJson(res, 500, {
44
+ ok: false,
45
+ source: 'ctm-api',
46
+ authority: 'ctm',
47
+ reason: 'ctm_session_context_failed',
48
+ error: err.message,
49
+ }));
50
+ return true;
51
+ };
52
+
29
53
  try {
30
54
  if (url.pathname === '/api/ctm/session-memory/health' && req.method === 'GET') {
31
55
  return respondJson(res, 200, asApiResult(context.getCtmDbHealth({ db })));
32
56
  }
33
57
 
34
58
  if (url.pathname === '/api/ctm/session-memory/search' && req.method === 'GET') {
35
- const query = url.searchParams.get('q') || url.searchParams.get('query') || '';
36
- const limit = url.searchParams.get('limit') || undefined;
37
- return respondJson(res, 200, asApiResult(context.searchCtmSessions({ query, limit, db })));
59
+ const args = {
60
+ query: url.searchParams.get('q') || url.searchParams.get('query') || '',
61
+ limit: url.searchParams.get('limit') || undefined,
62
+ };
63
+ return serveHeavy('ctmSessionMemorySearch', args,
64
+ () => context.searchCtmSessions({ ...args, db }));
38
65
  }
39
66
 
40
67
  if (url.pathname === '/api/ctm/session-memory/context' && req.method === 'GET') {
41
- return respondJson(res, 200, asApiResult(context.getCtmSessionContext({
68
+ const args = {
42
69
  session_id: url.searchParams.get('session_id') || url.searchParams.get('id') || '',
43
70
  session_ids: parseListParams(url.searchParams, 'session_id', 'session_ids', 'ids'),
44
71
  limit: url.searchParams.get('limit') || undefined,
@@ -46,8 +73,9 @@ function handleCtmSessionContextApi(req, res, url, { db, dbModule } = {}) {
46
73
  include_raw: parseBool(url.searchParams.get('include_raw')),
47
74
  dedupe: !parseBool(url.searchParams.get('no_dedupe')),
48
75
  format: url.searchParams.get('format') || 'messages',
49
- db,
50
- })));
76
+ };
77
+ return serveHeavy('ctmSessionMemoryContext', args,
78
+ () => context.getCtmSessionContext({ ...args, db }));
51
79
  }
52
80
 
53
81
  if (
@@ -76,7 +104,7 @@ function handleCtmSessionContextApi(req, res, url, { db, dbModule } = {}) {
76
104
  if (url.pathname === '/api/ctm/session-memory/context-pack' && req.method === 'POST') {
77
105
  readJsonBody(req, 128 * 1024)
78
106
  .then((body) => {
79
- respondJson(res, 200, asApiResult(context.buildContextPack({
107
+ const args = {
80
108
  task: body.task || '',
81
109
  query: body.query || '',
82
110
  session_ids: body.session_ids || body.ids,
@@ -84,8 +112,9 @@ function handleCtmSessionContextApi(req, res, url, { db, dbModule } = {}) {
84
112
  token_budget: body.token_budget,
85
113
  include_raw: Boolean(body.include_raw),
86
114
  mode: body.mode || 'auto',
87
- db,
88
- })));
115
+ };
116
+ serveHeavy('ctmSessionMemoryContextPack', args,
117
+ () => context.buildContextPack({ ...args, db }));
89
118
  })
90
119
  .catch((err) => respondJson(res, 400, {
91
120
  ok: false,