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
@@ -7,6 +7,26 @@ const db = require('./db');
7
7
  // Map<sessionId, QueueState>
8
8
  const queues = new Map();
9
9
 
10
+ // Lifecycle tracing for the "queued message stuck / sent all at once /
11
+ // disappeared" investigation, tagged [queue-trace] so it's greppable in ctm.log.
12
+ // Each queue object gets a stable _uid so we can SEE when the live object is
13
+ // swapped out from under an in-flight completion (the completeAsyncSend
14
+ // `live !== q` drop). Anomalies (_qlog) are always on; verbose per-op trace
15
+ // (_qtrace) is gated behind CTM_DEBUG_QUEUE.
16
+ let _queueUidSeq = 0;
17
+ // Anomaly logs (drops, dedupe, queue-object replacement, recovery) are always on
18
+ // — they are rare and each one flags a real problem worth seeing in prod.
19
+ function _qlog(msg) { try { console.log(`[queue-trace] ${msg}`); } catch {} }
20
+ // Verbose per-operation trace (every wake/dispatch/complete) is gated behind an
21
+ // env flag so it doesn't spam prod; enable with CTM_DEBUG_QUEUE=1 when diagnosing.
22
+ const _QUEUE_TRACE = !!process.env.CTM_DEBUG_QUEUE;
23
+ function _qtrace(msg) { if (_QUEUE_TRACE) { try { console.log(`[queue-trace] ${msg}`); } catch {} } }
24
+ function _itemSummary(q) {
25
+ try {
26
+ return (q.items || []).map((it, i) => `${i}${i === q.currentIndex ? '*' : ''}:${it.status}`).join(',');
27
+ } catch { return '?'; }
28
+ }
29
+
10
30
  // Hook called when a new queue is created (set by server.js)
11
31
  let _onQueueCreated = null;
12
32
  function setOnQueueCreated(fn) { _onQueueCreated = fn; }
@@ -33,6 +53,107 @@ function appendMissingImageLabels(text, images) {
33
53
  return out;
34
54
  }
35
55
 
56
+ const ITEM_STATUSES = new Set(['pending', 'sending', 'sent', 'skipped', 'failed']);
57
+
58
+ function normalizeQueueItem(item = {}, options = {}) {
59
+ const text = item.text || item.body || item.prompt || '';
60
+ const status = options.preserveStatus && ITEM_STATUSES.has(String(item.status || '').toLowerCase())
61
+ ? String(item.status || '').toLowerCase()
62
+ : 'pending';
63
+ return {
64
+ id: item.id || crypto.randomUUID(),
65
+ type: item.type || 'inline',
66
+ promptId: item.promptId || item.prompt_id || null,
67
+ title: item.title || String(text).slice(0, 60) || 'Untitled',
68
+ text,
69
+ images: Array.isArray(item.images) ? item.images : [],
70
+ status,
71
+ };
72
+ }
73
+
74
+ function normalizeReadiness(value) {
75
+ if (value === false) {
76
+ return {
77
+ ok: false,
78
+ code: 'target_not_ready',
79
+ reason: 'target_not_ready',
80
+ message: 'The target session is not ready for another prompt.',
81
+ };
82
+ }
83
+ if (value && typeof value === 'object' && value.ok === false) {
84
+ return {
85
+ ok: false,
86
+ code: value.code || value.reason || 'target_not_ready',
87
+ reason: value.reason || value.code || 'target_not_ready',
88
+ message: value.message || value.reason || 'The target session is not ready for another prompt.',
89
+ };
90
+ }
91
+ return { ok: true };
92
+ }
93
+
94
+ function setWaiting(q, readiness) {
95
+ const code = readiness.code || readiness.reason || 'target_not_ready';
96
+ const reason = readiness.reason || code;
97
+ const message = readiness.message || reason;
98
+ const changed = q.waitingCode !== code || q.waitingReason !== reason || q.waitingMessage !== message;
99
+ q.waitingCode = code;
100
+ q.waitingReason = reason;
101
+ q.waitingMessage = message;
102
+ return changed;
103
+ }
104
+
105
+ function clearWaiting(q) {
106
+ const changed = !!(q.waitingCode || q.waitingReason || q.waitingMessage);
107
+ q.waitingCode = '';
108
+ q.waitingReason = '';
109
+ q.waitingMessage = '';
110
+ return changed;
111
+ }
112
+
113
+ function canDispatch(q, item) {
114
+ if (!q._canSendFn) return { ok: true };
115
+ try {
116
+ return normalizeReadiness(q._canSendFn({
117
+ sessionId: q.sessionId,
118
+ item: { ...item },
119
+ queue: getState(q.sessionId),
120
+ }));
121
+ } catch (err) {
122
+ return {
123
+ ok: false,
124
+ code: 'readiness_check_failed',
125
+ reason: 'readiness_check_failed',
126
+ message: err && err.message ? err.message : 'Could not verify session readiness.',
127
+ };
128
+ }
129
+ }
130
+
131
+ function canRecoverSending(q, item, reason) {
132
+ if (!q._canRecoverSendingFn) {
133
+ return {
134
+ ok: false,
135
+ code: 'sending_in_flight',
136
+ reason: 'sending_in_flight',
137
+ message: 'The current queued item is still marked as in flight.',
138
+ };
139
+ }
140
+ try {
141
+ return normalizeReadiness(q._canRecoverSendingFn({
142
+ sessionId: q.sessionId,
143
+ item: { ...item },
144
+ queue: getState(q.sessionId),
145
+ reason,
146
+ }));
147
+ } catch (err) {
148
+ return {
149
+ ok: false,
150
+ code: 'recovery_check_failed',
151
+ reason: 'recovery_check_failed',
152
+ message: err && err.message ? err.message : 'Could not verify whether the queued turn is still running.',
153
+ };
154
+ }
155
+ }
156
+
36
157
  // --- Persistence (SQLite) ---
37
158
 
38
159
  function saveToDb(q) {
@@ -61,21 +182,53 @@ function loadFromDb() {
61
182
  try {
62
183
  const rows = db.loadAllQueues();
63
184
  for (const saved of rows) {
64
- // Pause any running queues on restore (PTY may have moved on)
185
+ // Pause any running queues on restore (PTY may have moved on). This is a
186
+ // SYSTEM pause, not a user pause — flag it so wake() can auto-resume the
187
+ // queue once the session signals readiness again, instead of stranding
188
+ // still-pending items forever (the "I queued it but it never sent after a
189
+ // restart" bug). A user-initiated pause does NOT set this flag.
190
+ const wasRunning = saved.status === 'running';
191
+ // An item left in 'sending' was mid-dispatch when the previous process
192
+ // exited. The turn that owned it — and would have resolved it to 'sent'
193
+ // (completeAsyncSend) or rolled it back to 'pending' (failAsyncSend) — died
194
+ // with that process, so it can NEVER leave 'sending' on its own. Both
195
+ // sendNext() and wake() bail on a 'sending' item at currentIndex, so an
196
+ // orphaned 'sending' permanently wedges the queue: the item shows "sending"
197
+ // forever and no further queued message can dispatch ("I can't send my
198
+ // queued message"). Requeue it so it re-dispatches once the session is
199
+ // ready again. This is distinct from the restart-pause auto-resume below:
200
+ // that unblocks a still-PENDING item; this unblocks a SENDING one.
201
+ let earliestRequeued = -1;
202
+ const items = saved.items.map((item, idx) => {
203
+ const norm = normalizeQueueItem(item, { preserveStatus: true });
204
+ if (norm.status === 'sending') {
205
+ norm.status = 'pending';
206
+ if (earliestRequeued === -1) earliestRequeued = idx;
207
+ }
208
+ return norm;
209
+ });
210
+ // sendNext()/wake() only scan for the next pending AFTER currentIndex, so
211
+ // move the cursor just before the recovered item; otherwise the re-dispatch
212
+ // scan skips right over it and the message is silently dropped.
213
+ let currentIndex = saved.currentIndex;
214
+ if (earliestRequeued !== -1 && earliestRequeued <= currentIndex) {
215
+ currentIndex = earliestRequeued - 1;
216
+ }
217
+ if (earliestRequeued !== -1) {
218
+ console.log(`[queue] recovered orphaned 'sending' item on restore session=${saved.sessionId} (mid-turn restart) → requeued for re-dispatch`);
219
+ }
220
+ const prior = queues.get(saved.sessionId);
221
+ if (prior) {
222
+ _qlog(`loadFromDb REPLACING live queue session=${saved.sessionId} olduid=${prior._uid} oldStatus=${prior.status} oldItems=[${_itemSummary(prior)}] — any in-flight completion bound to olduid will be DROPPED (live!==q)`);
223
+ }
65
224
  const queue = {
66
225
  sessionId: saved.sessionId,
226
+ _uid: ++_queueUidSeq,
67
227
  mode: saved.mode,
68
- status: saved.status === 'running' ? 'paused' : saved.status,
69
- currentIndex: saved.currentIndex,
70
- items: saved.items.map(item => ({
71
- id: item.id || crypto.randomUUID(),
72
- type: item.type || 'inline',
73
- promptId: item.promptId || null,
74
- title: item.title || 'Untitled',
75
- text: item.text || '',
76
- images: Array.isArray(item.images) ? item.images : [],
77
- status: item.status || 'pending',
78
- })),
228
+ status: wasRunning ? 'paused' : saved.status,
229
+ _restorePaused: wasRunning,
230
+ currentIndex,
231
+ items,
79
232
  idleTimeoutMs: saved.idleTimeoutMs || 10000,
80
233
  promptPatterns: DEFAULT_PROMPT_PATTERNS,
81
234
  _idleTimer: null,
@@ -84,8 +237,16 @@ function loadFromDb() {
84
237
  _lastOutputTime: 0,
85
238
  _onStateChange: null,
86
239
  _sendFn: null,
240
+ _canSendFn: null,
241
+ _canRecoverSendingFn: null,
242
+ revision: 0,
243
+ updatedAt: Date.now(),
244
+ waitingCode: '',
245
+ waitingReason: '',
246
+ waitingMessage: '',
87
247
  };
88
248
  queues.set(saved.sessionId, queue);
249
+ _qlog(`loadFromDb created uid=${queue._uid} session=${saved.sessionId} status=${queue.status} items=[${_itemSummary(queue)}]`);
89
250
  }
90
251
  if (queues.size > 0) {
91
252
  console.log(` Restored ${queues.size} queue(s) from database`);
@@ -102,20 +263,17 @@ function init() {
102
263
  }
103
264
 
104
265
  function createQueue(sessionId, { mode = 'manual', items = [], idleTimeoutMs = 10000, promptPatterns } = {}) {
266
+ const prior = queues.get(sessionId);
267
+ if (prior) {
268
+ _qlog(`createQueue REPLACING live queue session=${sessionId} olduid=${prior._uid} oldStatus=${prior.status} oldItems=[${_itemSummary(prior)}] — in-flight completion on olduid will be DROPPED`);
269
+ }
105
270
  const queue = {
106
271
  sessionId,
272
+ _uid: ++_queueUidSeq,
107
273
  mode,
108
274
  status: 'idle',
109
275
  currentIndex: -1,
110
- items: items.map(item => ({
111
- id: crypto.randomUUID(),
112
- type: item.type || 'inline',
113
- promptId: item.promptId || null,
114
- title: item.title || item.text?.slice(0, 60) || 'Untitled',
115
- text: item.text || '',
116
- images: Array.isArray(item.images) ? item.images : [],
117
- status: 'pending',
118
- })),
276
+ items: items.map(item => normalizeQueueItem(item)),
119
277
  idleTimeoutMs: idleTimeoutMs || 10000,
120
278
  promptPatterns: promptPatterns || DEFAULT_PROMPT_PATTERNS,
121
279
  _idleTimer: null,
@@ -124,6 +282,13 @@ function createQueue(sessionId, { mode = 'manual', items = [], idleTimeoutMs = 1
124
282
  _lastOutputTime: 0,
125
283
  _onStateChange: null,
126
284
  _sendFn: null,
285
+ _canSendFn: null,
286
+ _canRecoverSendingFn: null,
287
+ revision: 0,
288
+ updatedAt: Date.now(),
289
+ waitingCode: '',
290
+ waitingReason: '',
291
+ waitingMessage: '',
127
292
  };
128
293
  queues.set(sessionId, queue);
129
294
  saveToDb(queue);
@@ -153,10 +318,57 @@ function getState(sessionId) {
153
318
  status: i.status,
154
319
  })),
155
320
  idleTimeoutMs: q.idleTimeoutMs,
321
+ revision: q.revision || 0,
322
+ updatedAt: q.updatedAt || 0,
156
323
  error: q.error || null,
324
+ waitingCode: q.waitingCode || '',
325
+ waitingReason: q.waitingReason || '',
326
+ waitingMessage: q.waitingMessage || '',
327
+ };
328
+ }
329
+
330
+ function stateFromSavedQueue(saved) {
331
+ if (!saved || !saved.sessionId) return null;
332
+ const currentIndex = Number(saved.currentIndex);
333
+ return {
334
+ sessionId: saved.sessionId,
335
+ mode: saved.mode || 'manual',
336
+ status: saved.status || 'idle',
337
+ currentIndex: Number.isFinite(currentIndex) ? currentIndex : -1,
338
+ items: (Array.isArray(saved.items) ? saved.items : []).map(item => {
339
+ const normalized = normalizeQueueItem(item, { preserveStatus: true });
340
+ return {
341
+ id: normalized.id,
342
+ type: normalized.type,
343
+ promptId: normalized.promptId,
344
+ title: normalized.title,
345
+ text: normalized.text,
346
+ images: Array.isArray(normalized.images) ? normalized.images : [],
347
+ status: normalized.status,
348
+ };
349
+ }),
350
+ idleTimeoutMs: saved.idleTimeoutMs || 10000,
351
+ revision: 0,
352
+ updatedAt: saved.updatedAt || 0,
353
+ error: null,
354
+ waitingCode: '',
355
+ waitingReason: '',
356
+ waitingMessage: '',
357
+ persisted: true,
157
358
  };
158
359
  }
159
360
 
361
+ function getPersistedState(sessionId) {
362
+ const live = getState(sessionId);
363
+ if (live) return live;
364
+ try {
365
+ return stateFromSavedQueue(db.loadQueue(sessionId));
366
+ } catch (err) {
367
+ console.error('[queue] failed to load persisted queue state:', err && err.message ? err.message : err);
368
+ return null;
369
+ }
370
+ }
371
+
160
372
  function getAllStates() {
161
373
  const result = {};
162
374
  for (const [sessionId] of queues) {
@@ -174,7 +386,108 @@ function deleteQueue(sessionId) {
174
386
  }
175
387
  }
176
388
 
389
+ function appendItems(sessionId, { mode, items = [], idleTimeoutMs, autoStart = false } = {}) {
390
+ const normalized = Array.isArray(items) ? items.map(item => normalizeQueueItem(item)) : [];
391
+ if (normalized.length === 0) return getState(sessionId);
392
+
393
+ let q = queues.get(sessionId);
394
+ if (!q || q.status === 'done') {
395
+ const state = createQueue(sessionId, {
396
+ mode: mode || 'manual',
397
+ items: normalized,
398
+ idleTimeoutMs,
399
+ });
400
+ return autoStart ? start(sessionId) || state : state;
401
+ }
402
+
403
+ // Reconcile an incoming post against the existing queue, by id. The Wall-E
404
+ // "Send Queue" path re-posts items, so the same id can arrive again for three
405
+ // distinct reasons — each needs different handling:
406
+ //
407
+ // 1. New id → append (genuinely new message).
408
+ // 2. Existing is 'pending' → refresh text/title/images in place (honor an
409
+ // EDIT re-send) but do NOT push a duplicate.
410
+ // 3. Existing is 'sending' → leave it; re-dispatching would double-send the
411
+ // or 'queued' (in flight) same turn. (This is the duplicate-suppression
412
+ // that stops "sent all at once" on a double-click.)
413
+ // 4. Existing is DONE → a genuine RE-SEND ("Re-send this item" / a
414
+ // (sent/skipped/failed) re-post after the queue restored 'paused' on a
415
+ // CTM restart). Reset it to 'pending', refresh its
416
+ // content, and re-dispatch. The OLD behavior only
417
+ // handled case 2 and silently swallowed this one,
418
+ // so the re-sent message never reached Wall-E.
419
+ const DONE_STATUSES = new Set(['sent', 'skipped', 'failed']);
420
+ const fresh = [];
421
+ let deduped = 0;
422
+ let resent = 0;
423
+ let earliestResent = -1;
424
+ for (const it of normalized) {
425
+ const idx = q.items.findIndex(x => x && x.id === it.id);
426
+ if (idx === -1) { fresh.push(it); continue; }
427
+ const existing = q.items[idx];
428
+ if (DONE_STATUSES.has(existing.status)) {
429
+ existing.status = 'pending';
430
+ existing.statusUpdatedAt = Date.now();
431
+ existing.text = it.text;
432
+ existing.title = it.title;
433
+ existing.images = it.images;
434
+ existing.promptId = it.promptId;
435
+ resent++;
436
+ if (earliestResent === -1 || idx < earliestResent) earliestResent = idx;
437
+ } else {
438
+ deduped++;
439
+ if (existing.status === 'pending') {
440
+ existing.text = it.text;
441
+ existing.title = it.title;
442
+ existing.images = it.images;
443
+ existing.promptId = it.promptId;
444
+ }
445
+ }
446
+ }
447
+ if (deduped > 0) {
448
+ _qlog(`appendItems deduped ${deduped} already-queued item(s) session=${sessionId} uid=${q._uid} — re-send did NOT duplicate-dispatch`);
449
+ }
450
+ if (resent > 0) {
451
+ _qlog(`appendItems re-sent ${resent} finished item(s) session=${sessionId} uid=${q._uid} — reset to 'pending' for re-dispatch`);
452
+ // sendNext()/wake() only scan for the next pending AFTER currentIndex, so a
453
+ // re-sent item sitting BEHIND the cursor would be skipped. Roll the cursor to
454
+ // just before the earliest re-sent item (sent items in between are skipped by
455
+ // the pending-only scan, so they are NOT re-dispatched). Mirrors the
456
+ // orphaned-'sending' recovery in loadFromDb().
457
+ if (earliestResent <= q.currentIndex) q.currentIndex = earliestResent - 1;
458
+ }
459
+
460
+ if (fresh.length > 0) q.items.push(...fresh);
461
+ if (mode) q.mode = mode;
462
+ if (idleTimeoutMs) q.idleTimeoutMs = idleTimeoutMs;
463
+
464
+ const hasNewWork = fresh.length > 0 || resent > 0;
465
+ if (hasNewWork) {
466
+ q.error = null;
467
+ if (autoStart && (q.status === 'idle' || q.status === 'paused')) {
468
+ q.status = 'running';
469
+ q._restorePaused = false;
470
+ }
471
+ notifyChange(q);
472
+ if (autoStart && q.status === 'running') return wake(sessionId, 'append');
473
+ return getState(sessionId);
474
+ }
475
+
476
+ // Nothing new to dispatch (every incoming id was an in-flight or pending
477
+ // duplicate). Still honor autoStart so a re-click can resume/kick a stalled
478
+ // queue, and persist any in-place edits we just applied — but never duplicate.
479
+ if (autoStart && (q.status === 'idle' || q.status === 'paused')) {
480
+ q.status = 'running';
481
+ notifyChange(q);
482
+ return wake(sessionId, 'append');
483
+ }
484
+ if (deduped > 0) notifyChange(q); // persist edited-in-place text
485
+ return getState(sessionId);
486
+ }
487
+
177
488
  function notifyChange(q) {
489
+ q.revision = (Number(q.revision) || 0) + 1;
490
+ q.updatedAt = Date.now();
178
491
  saveToDb(q);
179
492
  if (q._onStateChange) {
180
493
  q._onStateChange(getState(q.sessionId));
@@ -189,6 +502,7 @@ function start(sessionId) {
189
502
  if (q.items.length === 0) return getState(sessionId);
190
503
 
191
504
  q.status = 'running';
505
+ q._restorePaused = false;
192
506
  q.currentIndex = -1;
193
507
  sendNext(sessionId);
194
508
  return getState(sessionId);
@@ -212,13 +526,24 @@ function sendNext(sessionId) {
212
526
  if (nextIdx === -1) {
213
527
  // All done
214
528
  q.status = 'done';
529
+ clearWaiting(q);
215
530
  clearIdleTimer(q);
216
531
  notifyChange(q);
217
532
  return getState(sessionId);
218
533
  }
219
534
 
220
- q.currentIndex = nextIdx;
221
535
  const item = q.items[nextIdx];
536
+ const readiness = canDispatch(q, item);
537
+ if (!readiness.ok) {
538
+ console.log(`[queue-diag] sendNext DEFERRED session=${sessionId} reason=${readiness.code || readiness.reason || 'not-ready'} item="${String(item.text || '').slice(0, 50)}"`);
539
+ if (setWaiting(q, readiness)) notifyChange(q);
540
+ return getState(sessionId);
541
+ }
542
+ clearWaiting(q);
543
+ console.log(`[queue-diag] sendNext DISPATCHING session=${sessionId} mode=${q.mode} item="${String(item.text || '').slice(0, 50)}"`);
544
+ _qtrace(`sendNext DISPATCH uid=${q._uid} session=${sessionId} idx=${nextIdx} item=${item.id} mode=${q.mode} items_before=[${_itemSummary(q)}] — completion will bind to uid=${q._uid}`);
545
+
546
+ q.currentIndex = nextIdx;
222
547
  item.status = 'sending';
223
548
  q.error = null;
224
549
  q._outputSinceLastSend = '';
@@ -262,10 +587,18 @@ function sendNext(sessionId) {
262
587
 
263
588
  function completeAsyncSend(q, item) {
264
589
  const live = queues.get(q.sessionId);
265
- if (live !== q) return;
590
+ if (live !== q) {
591
+ _qlog(`completeAsyncSend DROPPED (queue swapped) session=${q.sessionId} bounduid=${q._uid} liveuid=${live ? live._uid : 'none'} item=${item.id} → item NEVER marked 'sent' (point #1)`);
592
+ return;
593
+ }
266
594
  const current = q.items[q.currentIndex];
267
- if (!current || current.id !== item.id || current.status !== 'sending') return;
595
+ if (!current || current.id !== item.id || current.status !== 'sending') {
596
+ _qlog(`completeAsyncSend DROPPED (cursor/status moved) session=${q.sessionId} uid=${q._uid} item=${item.id} currentIdx=${q.currentIndex} currentId=${current ? current.id : 'none'} currentStatus=${current ? current.status : 'none'} items=[${_itemSummary(q)}]`);
597
+ return;
598
+ }
268
599
  current.status = 'sent';
600
+ _qtrace(`completeAsyncSend OK session=${q.sessionId} uid=${q._uid} item=${item.id} → sent items=[${_itemSummary(q)}]`);
601
+ clearWaiting(q);
269
602
  notifyChange(q);
270
603
  if (q.mode === 'auto' && q.status === 'running') {
271
604
  setTimeout(() => {
@@ -274,18 +607,105 @@ function completeAsyncSend(q, item) {
274
607
  }
275
608
  }
276
609
 
610
+ // A "busy"/not-ready failure is transient: the dispatch lost the race against
611
+ // an in-flight turn (the server throws "Wall-E is still thinking") or the target
612
+ // briefly wasn't ready. These must NOT dead-pause the queue — that strands the
613
+ // item until a manual resume. Keep it running + waiting so the next wake (turn
614
+ // finished) retries. Genuine errors still pause so we don't hammer a broken
615
+ // provider in a loop.
616
+ const RETRYABLE_SEND_FAILURE = /still thinking|walle[_ ]busy|not ready|not connected|queued until|finishes the current turn/i;
617
+
277
618
  function failAsyncSend(q, item, err) {
278
619
  const live = queues.get(q.sessionId);
279
- if (live !== q) return;
620
+ if (live !== q) {
621
+ _qlog(`failAsyncSend DROPPED (queue swapped) session=${q.sessionId} bounduid=${q._uid} liveuid=${live ? live._uid : 'none'} item=${item.id}`);
622
+ return;
623
+ }
280
624
  const current = q.items[q.currentIndex];
281
- if (!current || current.id !== item.id) return;
625
+ if (!current || current.id !== item.id) {
626
+ _qlog(`failAsyncSend DROPPED (cursor moved) session=${q.sessionId} uid=${q._uid} item=${item.id} currentIdx=${q.currentIndex} currentId=${current ? current.id : 'none'}`);
627
+ return;
628
+ }
282
629
  current.status = 'pending';
283
- q.status = 'paused';
284
- q.error = err && err.message ? err.message : String(err || 'Queue send failed');
630
+ const message = err && err.message ? err.message : String(err || 'Queue send failed');
631
+ _qlog(`failAsyncSend session=${q.sessionId} uid=${q._uid} item=${item.id} err="${String(message).slice(0, 80)}" retryable=${RETRYABLE_SEND_FAILURE.test(message)}`);
285
632
  clearIdleTimer(q);
633
+ if (q.status === 'running' && RETRYABLE_SEND_FAILURE.test(message)) {
634
+ // Transient — stay running and surface as "waiting" so wake() retries.
635
+ // sendNext advanced currentIndex onto this (now-failed) item before the
636
+ // send threw; roll it back one so the retry's "next pending after
637
+ // currentIndex" scan re-finds it instead of skipping it.
638
+ q.currentIndex = Math.max(-1, q.currentIndex - 1);
639
+ q.error = null;
640
+ setWaiting(q, { code: 'walle_busy', reason: 'walle_busy', message });
641
+ notifyChange(q);
642
+ return;
643
+ }
644
+ q.status = 'paused';
645
+ q.error = message;
646
+ clearWaiting(q);
286
647
  notifyChange(q);
287
648
  }
288
649
 
650
+ // Reasons that prove no turn is running for this session, so a still-'sending'
651
+ // item is definitionally orphaned and must be recovered (not bailed on). The
652
+ // server fires wake() with 'walle-turn-finished' from handleWalleMessage's
653
+ // finally — i.e. AFTER the turn that owned the in-flight item has ended.
654
+ const TURN_FINISHED_WAKE_REASONS = new Set(['walle-turn-finished']);
655
+
656
+ function wake(sessionId, reason) {
657
+ const q = queues.get(sessionId);
658
+ if (!q) return getState(sessionId);
659
+ _qtrace(`wake reason=${reason || '(none)'} uid=${q._uid} session=${sessionId} status=${q.status} items=[${_itemSummary(q)}]`);
660
+ // A queue auto-paused by the system (a CTM restart restore) should resume the
661
+ // moment the session is ready again — otherwise queued messages sit forever.
662
+ // Only auto-resume SYSTEM pauses with work left; a user pause stays paused.
663
+ if (q.status === 'paused' && q._restorePaused && hasPendingItems(q)) {
664
+ q._restorePaused = false;
665
+ q.status = 'running';
666
+ notifyChange(q);
667
+ }
668
+ if (q.status !== 'running') return getState(sessionId);
669
+ const current = q.items[q.currentIndex];
670
+ if (current && current.status === 'sending') {
671
+ // An item is held 'sending' for the entire turn and has exactly ONE
672
+ // completion path: the _sendFn promise's .then(completeAsyncSend) /
673
+ // .catch(failAsyncSend). If that single callback is ever missed or its guard
674
+ // drops (queue object swapped, currentIndex moved, status raced under it),
675
+ // the item is stranded 'sending' and every later wake()/sendNext() bails on
676
+ // it — wedging the queue in-process until a restart ("my queued message is
677
+ // stuck sending, I can't send"). The restart-restore recovery in loadFromDb
678
+ // only heals this across a restart; this heals it live.
679
+ //
680
+ // A turn-finished wake is the server's authoritative "no turn is running"
681
+ // signal, so a still-'sending' item is orphaned: resolve it (the message WAS
682
+ // delivered — the turn ran and ended) and let the queue advance. In the
683
+ // normal case completeAsyncSend (a microtask) has already flipped the item to
684
+ // 'sent' before this macrotask wake runs, so this branch is a no-op safety
685
+ // net; it only fires when the promise completion was genuinely lost. Any
686
+ // other wake reason keeps bailing, since a real turn may be in flight.
687
+ const recovery = TURN_FINISHED_WAKE_REASONS.has(reason)
688
+ ? { ok: true, code: 'turn_finished' }
689
+ : canRecoverSending(q, current, reason);
690
+ if (recovery.ok) {
691
+ console.log(`[queue] recovered orphaned 'sending' item on ${reason || 'manual'} wake session=${sessionId} (${recovery.code || 'target idle'}) → resolved`);
692
+ current.status = 'sent';
693
+ clearWaiting(q);
694
+ notifyChange(q);
695
+ return sendNext(sessionId);
696
+ }
697
+ if (reason === 'manual-next' || reason === 'manual-recover') {
698
+ if (setWaiting(q, recovery)) notifyChange(q);
699
+ }
700
+ return getState(sessionId);
701
+ }
702
+ return sendNext(sessionId);
703
+ }
704
+
705
+ function hasPendingItems(q) {
706
+ return Array.isArray(q.items) && q.items.some(it => it && it.status === 'pending');
707
+ }
708
+
289
709
  function skip(sessionId) {
290
710
  const q = queues.get(sessionId);
291
711
  if (!q || q.status !== 'running') return null;
@@ -302,11 +722,50 @@ function skip(sessionId) {
302
722
  return getState(sessionId);
303
723
  }
304
724
 
725
+ // Remove a single queued item by id (used by the per-bubble ✕/edit actions).
726
+ // Only pending/failed items can be pulled; an item mid-send is left alone.
727
+ function removeItem(sessionId, itemId) {
728
+ const q = queues.get(sessionId);
729
+ if (!q) return null;
730
+ const idx = q.items.findIndex(it => it && it.id === itemId);
731
+ if (idx === -1) return getState(sessionId);
732
+ if (q.items[idx].status === 'sending') return getState(sessionId);
733
+ q.items.splice(idx, 1);
734
+ if (idx <= q.currentIndex) q.currentIndex -= 1;
735
+ const anyActive = q.items.some(it => it && (it.status === 'pending' || it.status === 'sending'));
736
+ if (!anyActive && (q.status === 'running' || q.status === 'paused')) q.status = 'done';
737
+ notifyChange(q);
738
+ return getState(sessionId);
739
+ }
740
+
741
+ // Reorder the PENDING items to match `orderedIds` (drag-to-reorder). Sent/
742
+ // sending/skipped items keep their slots; any pending id not listed is appended.
743
+ function reorderItems(sessionId, orderedIds) {
744
+ const q = queues.get(sessionId);
745
+ if (!q) return null;
746
+ if (!Array.isArray(orderedIds)) return getState(sessionId);
747
+ const byId = new Map(q.items.map(it => [it.id, it]));
748
+ const pending = q.items.filter(it => it && it.status === 'pending');
749
+ const pendingIds = new Set(pending.map(it => it.id));
750
+ const seen = new Set();
751
+ const newPending = [];
752
+ for (const idv of orderedIds) {
753
+ if (pendingIds.has(idv) && !seen.has(idv)) { newPending.push(byId.get(idv)); seen.add(idv); }
754
+ }
755
+ for (const it of pending) if (!seen.has(it.id)) newPending.push(it);
756
+ let pi = 0;
757
+ q.items = q.items.map(it => (it && it.status === 'pending') ? newPending[pi++] : it);
758
+ notifyChange(q);
759
+ return getState(sessionId);
760
+ }
761
+
305
762
  function pause(sessionId) {
306
763
  const q = queues.get(sessionId);
307
764
  if (!q || q.status !== 'running') return null;
308
765
 
309
766
  q.status = 'paused';
767
+ q._restorePaused = false; // deliberate user pause — wake() must respect it
768
+ clearWaiting(q);
310
769
  clearIdleTimer(q);
311
770
  notifyChange(q);
312
771
  return getState(sessionId);
@@ -317,6 +776,7 @@ function resume(sessionId) {
317
776
  if (!q || q.status !== 'paused') return null;
318
777
 
319
778
  q.status = 'running';
779
+ q._restorePaused = false;
320
780
 
321
781
  // If current item is already sent, check if we should auto-advance
322
782
  const currentItem = q.items[q.currentIndex];
@@ -327,6 +787,7 @@ function resume(sessionId) {
327
787
  }
328
788
 
329
789
  notifyChange(q);
790
+ wake(sessionId);
330
791
  return getState(sessionId);
331
792
  }
332
793
 
@@ -335,6 +796,7 @@ function stop(sessionId) {
335
796
  if (!q) return null;
336
797
 
337
798
  q.status = 'done';
799
+ clearWaiting(q);
338
800
  clearIdleTimer(q);
339
801
  notifyChange(q);
340
802
  return getState(sessionId);
@@ -437,6 +899,16 @@ function setSendFn(sessionId, fn) {
437
899
  if (q) q._sendFn = fn;
438
900
  }
439
901
 
902
+ function setCanSendFn(sessionId, fn) {
903
+ const q = queues.get(sessionId);
904
+ if (q) q._canSendFn = typeof fn === 'function' ? fn : null;
905
+ }
906
+
907
+ function setCanRecoverSendingFn(sessionId, fn) {
908
+ const q = queues.get(sessionId);
909
+ if (q) q._canRecoverSendingFn = typeof fn === 'function' ? fn : null;
910
+ }
911
+
440
912
  function onSessionExit(sessionId) {
441
913
  const q = queues.get(sessionId);
442
914
  // Preserve queues that have unsent items — they'll be restored on next startup.
@@ -458,20 +930,27 @@ function onSessionExit(sessionId) {
458
930
  module.exports = {
459
931
  init,
460
932
  createQueue,
933
+ appendItems,
461
934
  getQueue,
462
935
  getState,
936
+ getPersistedState,
463
937
  getAllStates,
464
938
  deleteQueue,
465
939
  start,
466
940
  sendNext,
467
941
  skip,
942
+ removeItem,
943
+ reorderItems,
468
944
  pause,
469
945
  resume,
470
946
  stop,
947
+ wake,
471
948
  setMode,
472
949
  feedOutput,
473
950
  setOnStateChange,
474
951
  setSendFn,
952
+ setCanSendFn,
953
+ setCanRecoverSendingFn,
475
954
  onSessionExit,
476
955
  setOnQueueCreated,
477
956
  };