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,64 @@
1
+ 'use strict';
2
+
3
+ class SerialTaskQueue {
4
+ constructor(name = 'serial-task-queue') {
5
+ this.name = name;
6
+ this._tail = Promise.resolve();
7
+ this._active = null;
8
+ this._pending = 0;
9
+ }
10
+
11
+ run(label, fn) {
12
+ if (typeof fn !== 'function') throw new TypeError('SerialTaskQueue.run requires a function');
13
+ const taskLabel = label || 'task';
14
+ this._pending += 1;
15
+ const runTask = async () => {
16
+ this._pending -= 1;
17
+ this._active = taskLabel;
18
+ try {
19
+ return await fn();
20
+ } finally {
21
+ this._active = null;
22
+ }
23
+ };
24
+ const job = this._tail.then(runTask, runTask);
25
+ this._tail = job.catch(() => {});
26
+ return job;
27
+ }
28
+
29
+ async drain(timeoutMs = 5000) {
30
+ const timeout = Math.max(0, Number(timeoutMs) || 0);
31
+ if (timeout === 0) {
32
+ await this._tail;
33
+ return { ok: true, timedOut: false };
34
+ }
35
+ let timer = null;
36
+ try {
37
+ return await Promise.race([
38
+ this._tail.then(() => ({ ok: true, timedOut: false })),
39
+ new Promise((resolve) => {
40
+ timer = setTimeout(() => resolve({
41
+ ok: false,
42
+ timedOut: true,
43
+ active: this._active,
44
+ pending: this._pending,
45
+ }), timeout);
46
+ if (typeof timer.unref === 'function') timer.unref();
47
+ }),
48
+ ]);
49
+ } finally {
50
+ if (timer) clearTimeout(timer);
51
+ }
52
+ }
53
+
54
+ getState() {
55
+ return {
56
+ name: this.name,
57
+ active: this._active,
58
+ pending: this._pending,
59
+ idle: !this._active && this._pending === 0,
60
+ };
61
+ }
62
+ }
63
+
64
+ module.exports = { SerialTaskQueue };
@@ -37,8 +37,247 @@ function listenerUrl(listener, hostOverride = '') {
37
37
  return `${listener.scheme}://${host}:${listener.port}/`;
38
38
  }
39
39
 
40
+ // Errors that mean "the bind address is not currently assigned to any local
41
+ // interface" — typically because Tailscale logged out, a NIC was unplugged, or
42
+ // an IPv6-only address vanished. These are not permanent: a slow background
43
+ // retry recovers automatically when the interface reappears.
44
+ const INTERFACE_MISSING_CODES = new Set(['EADDRNOTAVAIL', 'EINVAL']);
45
+
46
+ const DEFAULT_FAST_RETRY_BASE_MS = 1000;
47
+ const DEFAULT_MAX_FAST_ATTEMPTS = 5;
48
+ const DEFAULT_SLOW_RETRY_MS = 30_000;
49
+
50
+ function classifyListenerError(err) {
51
+ const code = err && err.code ? String(err.code) : null;
52
+ const message = (err && err.message) ? String(err.message) : String(err || '');
53
+ if (code === 'EADDRINUSE') return { kind: 'transient-port', code, message };
54
+ if (code && INTERFACE_MISSING_CODES.has(code)) return { kind: 'interface-missing', code, message };
55
+ return { kind: 'permanent', code, message };
56
+ }
57
+
58
+ // Coordinates `listen()` retries across a set of listener entries.
59
+ //
60
+ // Soft-degrade policy: if the primary bind fails because its interface is
61
+ // missing (e.g. Tailscale IP not currently assigned), the loopback fallback
62
+ // listener — already in the same listeners array via buildCtmListenerConfigs —
63
+ // keeps serving. The missing-interface listener is retried in the background
64
+ // at slowRetryMs cadence so remote access is restored automatically when the
65
+ // interface returns, without a CTM restart.
66
+ //
67
+ // Fatal exit (via onFatal) is only triggered when every listener has failed
68
+ // AND no retry is pending — i.e. nothing will ever serve.
69
+ //
70
+ // Dependency injection (timers, logger, onFatal) makes the coordinator
71
+ // testable without binding real ports or calling process.exit.
72
+ function createListenerRetryCoordinator({
73
+ listeners,
74
+ logger = console,
75
+ onFatal = (msg) => { try { console.error(msg); } catch {} process.exit(1); },
76
+ setTimeoutFn = (fn, delay) => {
77
+ const t = setTimeout(fn, delay);
78
+ if (t && typeof t.unref === 'function') t.unref();
79
+ return t;
80
+ },
81
+ clearTimeoutFn = clearTimeout,
82
+ fastRetryBaseMs = DEFAULT_FAST_RETRY_BASE_MS,
83
+ maxFastAttempts = DEFAULT_MAX_FAST_ATTEMPTS,
84
+ slowRetryMs = DEFAULT_SLOW_RETRY_MS,
85
+ } = {}) {
86
+ if (!Array.isArray(listeners) || listeners.length === 0) {
87
+ throw new Error('createListenerRetryCoordinator requires a non-empty listeners array');
88
+ }
89
+
90
+ const entries = listeners.map((listener) => ({
91
+ listener,
92
+ state: 'idle',
93
+ slowRetryTimer: null,
94
+ everListening: false,
95
+ everFailed: false,
96
+ // True once we've logged the current "interface missing" outage. Gates the
97
+ // slow-retry warning to ONE line per outage episode instead of every
98
+ // slowRetryMs tick (otherwise a logged-out Tailscale floods the log forever).
99
+ interfaceMissingLogged: false,
100
+ }));
101
+ let fastRetryAttempts = 0;
102
+ let fastRetryTimer = null;
103
+ let started = false;
104
+ let stopped = false;
105
+
106
+ const fmtAddr = (l) => `${l.host}:${l.port}`;
107
+
108
+ const anyListening = () => entries.some((e) => e.state === 'listening');
109
+
110
+ const anyRetryPending = () => {
111
+ if (fastRetryTimer) return true;
112
+ for (const e of entries) {
113
+ if (e.state === 'pending') return true;
114
+ if (e.slowRetryTimer) return true;
115
+ }
116
+ return false;
117
+ };
118
+
119
+ function clearSlowRetry(entry) {
120
+ if (entry.slowRetryTimer) {
121
+ clearTimeoutFn(entry.slowRetryTimer);
122
+ entry.slowRetryTimer = null;
123
+ }
124
+ }
125
+
126
+ function startListen(entry) {
127
+ if (stopped) return;
128
+ entry.state = 'pending';
129
+ try {
130
+ entry.listener.server.listen(entry.listener.port, entry.listener.host);
131
+ } catch (err) {
132
+ // Some listen() failures throw synchronously instead of emitting 'error'.
133
+ // Route through the same handler so policy stays consistent.
134
+ handleError(entry, err);
135
+ }
136
+ }
137
+
138
+ function scheduleFastRetry() {
139
+ if (stopped || fastRetryTimer) return null;
140
+ fastRetryAttempts += 1;
141
+ const delay = fastRetryBaseMs * fastRetryAttempts;
142
+ fastRetryTimer = setTimeoutFn(() => {
143
+ fastRetryTimer = null;
144
+ if (stopped) return;
145
+ for (const e of entries) {
146
+ if (e.state === 'pending' || e.state === 'listening') continue;
147
+ if (e.slowRetryTimer) continue;
148
+ startListen(e);
149
+ }
150
+ }, delay);
151
+ return delay;
152
+ }
153
+
154
+ function scheduleSlowRetry(entry) {
155
+ if (stopped || entry.slowRetryTimer) return;
156
+ entry.slowRetryTimer = setTimeoutFn(() => {
157
+ entry.slowRetryTimer = null;
158
+ if (stopped || entry.state === 'listening') return;
159
+ startListen(entry);
160
+ }, slowRetryMs);
161
+ }
162
+
163
+ function maybeFatal(entry, message) {
164
+ if (stopped) return;
165
+ if (anyListening() || anyRetryPending()) {
166
+ logger.error(
167
+ `[startup] ${entry.listener.id} listener ${fmtAddr(entry.listener)} failed: ${message} — ` +
168
+ (anyListening() ? 'continuing on other listener(s).' : 'waiting on other listener(s).')
169
+ );
170
+ return;
171
+ }
172
+ onFatal(`[FATAL] ${entry.listener.id} listener ${fmtAddr(entry.listener)}: ${message}`);
173
+ }
174
+
175
+ function handleListening(entry) {
176
+ const isRecovery = entry.everFailed;
177
+ entry.state = 'listening';
178
+ clearSlowRetry(entry);
179
+ if (isRecovery) {
180
+ logger.log(
181
+ `[startup] ${entry.listener.id} listener re-bound at ${fmtAddr(entry.listener)} ` +
182
+ '(recovered after earlier bind failure).'
183
+ );
184
+ }
185
+ entry.everListening = true;
186
+ entry.everFailed = false;
187
+ // A successful (re)bind ends any outage — re-arm the one-shot warning so a
188
+ // future down→up→down cycle logs again.
189
+ entry.interfaceMissingLogged = false;
190
+ }
191
+
192
+ function handleError(entry, err) {
193
+ if (stopped) return;
194
+ entry.state = 'failed';
195
+ entry.everFailed = true;
196
+ const decision = classifyListenerError(err);
197
+
198
+ if (decision.kind === 'transient-port') {
199
+ if (fastRetryTimer) {
200
+ logger.error(
201
+ `[startup] ${entry.listener.id} ${fmtAddr(entry.listener)} EADDRINUSE; ` +
202
+ 'covered by pending retry.'
203
+ );
204
+ return;
205
+ }
206
+ if (fastRetryAttempts >= maxFastAttempts) {
207
+ maybeFatal(entry, `EADDRINUSE after ${maxFastAttempts} attempts`);
208
+ return;
209
+ }
210
+ const delay = scheduleFastRetry();
211
+ logger.error(
212
+ `[startup] ${entry.listener.id} ${fmtAddr(entry.listener)} EADDRINUSE ` +
213
+ `(attempt ${fastRetryAttempts}/${maxFastAttempts}), retrying in ${delay}ms...`
214
+ );
215
+ return;
216
+ }
217
+
218
+ if (decision.kind === 'interface-missing') {
219
+ scheduleSlowRetry(entry);
220
+ // Log ONCE per outage episode, not on every slowRetryMs tick. The slow
221
+ // retry keeps running silently; handleListening() re-arms this flag when
222
+ // the interface returns, so a new outage logs again.
223
+ if (!entry.interfaceMissingLogged) {
224
+ entry.interfaceMissingLogged = true;
225
+ const tail = anyListening()
226
+ ? 'Other listener(s) still serving in the meantime.'
227
+ : 'No other listener bound yet; CTM will become reachable when the interface returns or another listener binds.';
228
+ logger.warn(
229
+ `[startup] ${entry.listener.id} listener ${fmtAddr(entry.listener)} ${decision.code}: ` +
230
+ `bind interface not available — retrying every ${Math.round(slowRetryMs / 1000)}s (further attempts logged once on recovery). ${tail}`
231
+ );
232
+ }
233
+ // Safety invariant only: never exits while this entry's slow retry is
234
+ // pending (anyRetryPending() is true). Checked inline so repeat failures
235
+ // don't re-log the "continuing on other listener(s)" line.
236
+ if (!anyListening() && !anyRetryPending()) {
237
+ onFatal(`[FATAL] ${entry.listener.id} listener ${fmtAddr(entry.listener)}: ${decision.code}: ${decision.message}`);
238
+ }
239
+ return;
240
+ }
241
+
242
+ maybeFatal(entry, decision.message);
243
+ }
244
+
245
+ function start() {
246
+ if (started) return;
247
+ started = true;
248
+ for (const entry of entries) {
249
+ entry.listener.server.on('listening', () => handleListening(entry));
250
+ entry.listener.server.on('error', (err) => handleError(entry, err));
251
+ }
252
+ for (const entry of entries) startListen(entry);
253
+ }
254
+
255
+ function stop() {
256
+ stopped = true;
257
+ if (fastRetryTimer) { clearTimeoutFn(fastRetryTimer); fastRetryTimer = null; }
258
+ for (const entry of entries) clearSlowRetry(entry);
259
+ }
260
+
261
+ function snapshot() {
262
+ return entries.map((e) => ({
263
+ id: e.listener.id,
264
+ state: e.state,
265
+ slowRetryScheduled: !!e.slowRetryTimer,
266
+ everListening: e.everListening,
267
+ }));
268
+ }
269
+
270
+ return { start, stop, snapshot };
271
+ }
272
+
40
273
  module.exports = {
41
274
  buildCtmListenerConfigs,
42
275
  hostForUrl,
43
276
  listenerUrl,
277
+ classifyListenerError,
278
+ createListenerRetryCoordinator,
279
+ // Exposed for tests / callers that want to tune defaults via env:
280
+ DEFAULT_FAST_RETRY_BASE_MS,
281
+ DEFAULT_MAX_FAST_ATTEMPTS,
282
+ DEFAULT_SLOW_RETRY_MS,
44
283
  };
@@ -13,10 +13,39 @@ function toMs(value) {
13
13
  return Number.isFinite(n) && n > 0 ? n : nowMs();
14
14
  }
15
15
 
16
+ function explicitActivityMs(input = {}) {
17
+ const candidates = [
18
+ input.lastActivity,
19
+ input.lastAgentOutputAt,
20
+ input.agentOutputAt,
21
+ input.lastPtyActivity,
22
+ input.lastJsonlActivity,
23
+ input.outputAt,
24
+ ];
25
+ for (const value of candidates) {
26
+ const n = Number(value);
27
+ if (Number.isFinite(n) && n > 0) return n;
28
+ }
29
+ return 0;
30
+ }
31
+
32
+ function isActivityBearingStatus(input = {}) {
33
+ const status = normalizeStatus(input.status);
34
+ const source = String(input.source || '').toLowerCase();
35
+ const reason = String(input.reason || '').toLowerCase();
36
+ if (reason === 'snapshot') return false;
37
+ if (status === 'waiting' && /waiting-for-input|terminal-prompt/.test(`${source} ${reason}`)) return false;
38
+ if (/pty-activity|jsonl-event|codex-jsonl-event|claude-jsonl|terminal-output|session-resumed/.test(`${source} ${reason}`)) return true;
39
+ if (status === 'running' && /authoritative:hook|hook|worker/.test(source)) return true;
40
+ return false;
41
+ }
42
+
16
43
  function normalizeStatus(status) {
17
- if (status === 'busy') return 'running';
18
- if (status === 'waiting_input') return 'waiting';
19
- if (status === 'running' || status === 'waiting' || status === 'idle' || status === 'exited') return status;
44
+ const text = String(status || '').trim().toLowerCase().replace(/[-\s]+/g, '_');
45
+ if (text === 'busy' || text === 'active' || text === 'working' || text === 'thinking') return 'running';
46
+ if (text === 'restoring' || text === 'resuming' || text === 'restore_starting' || text === 'restarting') return 'resuming';
47
+ if (text === 'waiting_input' || text === 'waiting_for_input') return 'waiting';
48
+ if (text === 'running' || text === 'resuming' || text === 'waiting' || text === 'idle' || text === 'exited') return text;
20
49
  return 'unknown';
21
50
  }
22
51
 
@@ -33,6 +62,8 @@ function statusFromServerMessage(msg) {
33
62
  reason: msg.reason || 'stream-status',
34
63
  timestamp: msg.timestamp,
35
64
  lastActivity: msg.lastActivity,
65
+ lastPtyActivity: msg.lastPtyActivity,
66
+ lastJsonlActivity: msg.lastJsonlActivity,
36
67
  };
37
68
  }
38
69
 
@@ -254,6 +285,8 @@ class SessionCapture extends EventEmitter {
254
285
  reason: statusEvt.reason || 'stream-status',
255
286
  timestamp: statusEvt.timestamp,
256
287
  lastActivity: statusEvt.lastActivity,
288
+ lastPtyActivity: statusEvt.lastPtyActivity,
289
+ lastJsonlActivity: statusEvt.lastJsonlActivity,
257
290
  eventCount: statusEvt.eventCount,
258
291
  });
259
292
  }
@@ -285,8 +318,10 @@ class SessionCapture extends EventEmitter {
285
318
  decisionId: input.decisionId,
286
319
  };
287
320
  record.lastUpdated = Math.max(record.lastUpdated || 0, timestamp);
288
- if (input.lastActivity) record.lastActivity = Math.max(record.lastActivity || 0, input.lastActivity);
289
- else record.lastActivity = Math.max(record.lastActivity || 0, timestamp);
321
+ const activityMs = explicitActivityMs(input) || (isActivityBearingStatus(input) ? timestamp : 0);
322
+ if (activityMs) {
323
+ record.lastActivity = Math.max(record.lastActivity || 0, activityMs);
324
+ }
290
325
  if (input.eventCount != null) record.eventCount = input.eventCount;
291
326
 
292
327
  const projected = this._project(record);
@@ -335,7 +370,7 @@ class SessionCapture extends EventEmitter {
335
370
  ctmSessionId: record.ctmSessionId || null,
336
371
  status: projected.status,
337
372
  captureStatus: projected.status,
338
- lastActivity: record.lastActivity || record.lastUpdated || 0,
373
+ lastActivity: record.lastActivity || 0,
339
374
  eventCount: record.eventCount || 0,
340
375
  statusEvidence: projected.evidence,
341
376
  });
@@ -403,7 +438,7 @@ class SessionCapture extends EventEmitter {
403
438
  ctmSessionId: record.ctmSessionId || null,
404
439
  agentSessionId: record.agentSessionId || null,
405
440
  status: normalizeStatus(status),
406
- lastActivity: record.lastActivity || record.lastUpdated || 0,
441
+ lastActivity: record.lastActivity || 0,
407
442
  evidence: evidence.map((ev) => ({
408
443
  source: ev.source,
409
444
  status: ev.status,
@@ -0,0 +1,131 @@
1
+ 'use strict';
2
+
3
+ // Background migration: backfill session_message_rows for EXISTING sessions whose rows are
4
+ // not yet complete. Source = the existing (small, ≤24 MB) blob; over-cap sessions ('[]')
5
+ // stay on the JSONL render path (the gate keeps them off rows) until re-imported. Idempotent,
6
+ // bounded, resumable — and the render gate (db.sessionContentRowsAvailable) keeps a
7
+ // half-migrated session on the blob path, so a sweep in progress never breaks reads.
8
+ //
9
+ // `db` is the better-sqlite3 handle (dbModule.getDb()); `replaceSessionMessageRows` is the
10
+ // db.js writer (operates on the same handle). Passed in so this stays unit-testable in
11
+ // isolation.
12
+
13
+ // Sessions with a real blob whose row count is below the conversation's message count.
14
+ function findUnbackfilledSessions(db, limit = 50) {
15
+ const lim = Math.max(1, Math.min(100000, Number(limit) || 50));
16
+ return db.prepare(`
17
+ SELECT sc.ctm_session_id AS id
18
+ FROM session_conversations sc
19
+ WHERE sc.extracted_source_len > 0
20
+ AND sc.messages IS NOT NULL AND sc.messages != '[]' AND length(sc.messages) > 2
21
+ AND (SELECT COUNT(*) FROM session_message_rows r WHERE r.ctm_session_id = sc.ctm_session_id) < sc.extracted_source_len
22
+ LIMIT ?
23
+ `).all(lim).map(r => r.id);
24
+ }
25
+
26
+ // Backfill one session's rows from its blob. Returns rows written (0 if no parseable blob).
27
+ function backfillSessionContentRows(db, sessionId, replaceSessionMessageRows) {
28
+ const row = db.prepare('SELECT messages FROM session_conversations WHERE ctm_session_id = ?').get(sessionId);
29
+ if (!row || !row.messages) return 0;
30
+ let msgs;
31
+ try { msgs = JSON.parse(row.messages); } catch { return 0; }
32
+ if (!Array.isArray(msgs) || msgs.length === 0) return 0;
33
+ return replaceSessionMessageRows(sessionId, msgs);
34
+ }
35
+
36
+ // Disk reclaim: sessions whose faithful rows are COMPLETE (count >= extracted_source_len > 0 — the
37
+ // same gate as sessionContentRowsAvailable) but whose blob is still present. Once rows serve the
38
+ // render, the blob is dead weight; nulling it reclaims disk. Reversible: re-import from the JSONL
39
+ // rebuilds it. Excludes already-empty/over-cap blobs.
40
+ function findReclaimableSessions(db, limit = 50) {
41
+ const lim = Math.max(1, Math.min(100000, Number(limit) || 50));
42
+ return db.prepare(`
43
+ SELECT sc.ctm_session_id AS id
44
+ FROM session_conversations sc
45
+ WHERE sc.messages IS NOT NULL AND sc.messages != '[]' AND length(sc.messages) > 2
46
+ AND sc.extracted_source_len > 0
47
+ AND (SELECT COUNT(*) FROM session_message_rows r WHERE r.ctm_session_id = sc.ctm_session_id) >= sc.extracted_source_len
48
+ LIMIT ?
49
+ `).all(lim).map(r => r.id);
50
+ }
51
+
52
+ // Null the blob (messages='[]') for complete-rows sessions. Bounded + idempotent. The caller only
53
+ // runs this when blob retirement is active (CTM_DUAL_WRITE_BLOB=0) — otherwise the next import would
54
+ // just rewrite the blob, so reclaiming would be pointless churn.
55
+ function reclaimRetiredBlobs(db, { limit = 50 } = {}) {
56
+ const ids = findReclaimableSessions(db, limit);
57
+ let reclaimed = 0;
58
+ const upd = db.prepare("UPDATE session_conversations SET messages = '[]' WHERE ctm_session_id = ? AND messages != '[]'");
59
+ for (const id of ids) {
60
+ try { if (upd.run(id).changes > 0) reclaimed++; }
61
+ catch (e) { console.error('[blob-reclaim] session', String(id).slice(0, 8), 'failed:', e.message); }
62
+ }
63
+ return { swept: ids.length, reclaimed, remaining: findReclaimableSessions(db, 1).length };
64
+ }
65
+
66
+ // One bounded sweep. Safe to call repeatedly (idempotent); converges as sessions complete. When blob
67
+ // retirement is active, the same sweep also reclaims disk from complete-rows sessions (one off-thread
68
+ // pass does both — no extra scheduler job).
69
+ function runContentRowsBackfillSweep(db, replaceSessionMessageRows, { limit = 50 } = {}) {
70
+ const ids = findUnbackfilledSessions(db, limit);
71
+ let migrated = 0, rows = 0;
72
+ for (const id of ids) {
73
+ try {
74
+ const n = backfillSessionContentRows(db, id, replaceSessionMessageRows);
75
+ if (n > 0) { migrated++; rows += n; }
76
+ } catch (e) {
77
+ console.error('[content-backfill] session', String(id).slice(0, 8), 'failed:', e.message);
78
+ }
79
+ }
80
+ let reclaimed = 0;
81
+ if (process.env.CTM_DUAL_WRITE_BLOB === '0') {
82
+ try { reclaimed = reclaimRetiredBlobs(db, { limit }).reclaimed; }
83
+ catch (e) { console.error('[blob-reclaim] sweep failed:', e.message); }
84
+ }
85
+ let droppedLegacy = null;
86
+ if (process.env.CTM_RETIRE_LEGACY_STORES === '1') {
87
+ try { const r = retireLegacyStores(db); if (r.dropped.length) droppedLegacy = r.dropped; }
88
+ catch (e) { console.error('[retire-legacy] sweep failed:', e.message); }
89
+ }
90
+ return { swept: ids.length, migrated, rows, reclaimed, droppedLegacy, remaining: findUnbackfilledSessions(db, 1).length };
91
+ }
92
+
93
+ // Drop the fully-retired legacy message stores once migration is COMPLETE (every eligible session
94
+ // has rows, so the search/freshness/reference readers never need the legacy fallback). The readers
95
+ // are independently guarded against a missing table, so a drop mid-process can't blank results.
96
+ // Gated by the caller on CTM_RETIRE_LEGACY_STORES=1. Reversible: set CTM_DUAL_WRITE_SESSION_MESSAGES=1
97
+ // + re-run the legacy backfill, or re-import from the JSONL.
98
+ function retireLegacyStores(db) {
99
+ const status = getSessionRowsStatus(db);
100
+ if (!status.complete) return { dropped: [], reason: 'migration_incomplete', remaining: status.remaining };
101
+ const dropped = [];
102
+ for (const t of ['session_messages_fts', 'session_messages']) {
103
+ try {
104
+ if (db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?").get(t)) {
105
+ db.exec(`DROP TABLE IF EXISTS ${t}`);
106
+ dropped.push(t);
107
+ }
108
+ } catch (e) { console.error('[retire-legacy] drop', t, 'failed:', e.message); }
109
+ }
110
+ return { dropped };
111
+ }
112
+
113
+ // Migration completeness (for /api/ctm/session-rows-status + the cutover gate).
114
+ function getSessionRowsStatus(db) {
115
+ const total = Number(db.prepare(
116
+ "SELECT COUNT(*) AS c FROM session_conversations WHERE extracted_source_len > 0 AND messages != '[]' AND length(messages) > 2"
117
+ ).get().c);
118
+ const remaining = findUnbackfilledSessions(db, 100000).length;
119
+ const reclaimable = findReclaimableSessions(db, 100000).length;
120
+ return { total, migrated: total - remaining, remaining, complete: remaining === 0, reclaimable };
121
+ }
122
+
123
+ module.exports = {
124
+ findUnbackfilledSessions,
125
+ backfillSessionContentRows,
126
+ runContentRowsBackfillSweep,
127
+ findReclaimableSessions,
128
+ reclaimRetiredBlobs,
129
+ retireLegacyStores,
130
+ getSessionRowsStatus,
131
+ };