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
@@ -81,6 +81,12 @@ function buildTailscaleSetup(status, options = {}) {
81
81
  return {
82
82
  installed: true,
83
83
  available: true,
84
+ // Authoritative tailnet liveness for connection-health: BackendState ('Running' = up,
85
+ // 'Stopped'/'NeedsLogin'/'Starting' = not connected) + the human-readable Health
86
+ // reasons. More reliable than Self.Online (which can be false for the local node even
87
+ // when healthy). Absent backend_state → treated as up (old/odd `tailscale status`).
88
+ backend_state: status?.BackendState || status?.backend_state || '',
89
+ health: Array.isArray(status?.Health) ? status.Health.slice(0, 4) : [],
84
90
  dns_name: dnsName,
85
91
  host,
86
92
  origin,
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ function isUiRefreshActivitySource(source) {
4
+ const text = String(source || '').toLowerCase();
5
+ return text.includes('ui-refresh');
6
+ }
7
+
8
+ function shouldRecordProviderBusyStatusEvidence(source) {
9
+ return !isUiRefreshActivitySource(source);
10
+ }
11
+
12
+ function positiveMs(value) {
13
+ const n = Number(value || 0);
14
+ return Number.isFinite(n) && n > 0 ? n : 0;
15
+ }
16
+
17
+ function activeTurnLatestEvidenceMs({
18
+ startedAt = 0,
19
+ evidenceAt = 0,
20
+ evidenceSource = '',
21
+ lastActivityAt = 0,
22
+ lastPtyActivityAt = 0,
23
+ } = {}) {
24
+ const values = [positiveMs(startedAt), positiveMs(lastActivityAt), positiveMs(lastPtyActivityAt)];
25
+ if (!isUiRefreshActivitySource(evidenceSource)) values.push(positiveMs(evidenceAt));
26
+ return values.reduce((max, value) => Math.max(max, value), 0);
27
+ }
28
+
29
+ module.exports = {
30
+ activeTurnLatestEvidenceMs,
31
+ isUiRefreshActivitySource,
32
+ shouldRecordProviderBusyStatusEvidence,
33
+ };
@@ -0,0 +1,364 @@
1
+ 'use strict';
2
+
3
+ // Server-side detection and keystroke planning for interactive terminal choice
4
+ // menus (Claude/Codex/etc. numbered selectors). This mirrors the phone client's
5
+ // detection in public/m/app.js (terminalChoiceOptionFromLine / extractLiveTerminalChoice)
6
+ // so the two agree on what the menu is — but here it runs against the authoritative
7
+ // headless xterm buffer and produces the keystrokes that actually drive the agent.
8
+ //
9
+ // Why this exists: phone selection taps used to be routed through the free-form
10
+ // reply pipeline (session.send_message), which is blocked/swallowed when the agent
11
+ // is parked on a menu. CTM already drives live prompts elsewhere by writing keys to
12
+ // the PTY (Esc, prompt-history arrows, approval keystrokes). This module is the
13
+ // menu-aware equivalent: read the live options, find the highlight, and move it to
14
+ // the tapped option with arrow keys + Enter (with a number-key fallback).
15
+
16
+ const SELECTOR_RE = /^(?:›|❯|➜|▶|▸)\s*(.+)$/;
17
+ const NUMBERED_RE = /^(\d{1,2})[.)]\s+(.+)$/;
18
+ const CHECKBOX_PREFIX_RE = /^\[\s*([ xX✓✔])\s*\]\s*/;
19
+ const CHECKBOX_GLYPH_RE = /^([☐□☑☒✓✔])\s*/;
20
+ const SUBMIT_CONTROL_RE = /(?:^|[\s✓✔])submit\s*(?:→|$|\b)/i;
21
+
22
+ function stripAnsi(value) {
23
+ return String(value || '').replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '');
24
+ }
25
+
26
+ function normalizeLine(line) {
27
+ return stripAnsi(String(line || '')).replace(/\u00a0/g, " ").replace(/\s+$/, '');
28
+ }
29
+
30
+ function checkboxFromLabel(label) {
31
+ const raw = String(label || '').trim();
32
+ let match = raw.match(CHECKBOX_PREFIX_RE);
33
+ if (match) {
34
+ const marker = match[1];
35
+ return {
36
+ checkbox: true,
37
+ checked: /[xX✓✔]/.test(marker),
38
+ label: raw.slice(match[0].length).trim(),
39
+ rawLabel: raw,
40
+ };
41
+ }
42
+ match = raw.match(CHECKBOX_GLYPH_RE);
43
+ if (match) {
44
+ return {
45
+ checkbox: true,
46
+ checked: /[☑☒✓✔]/.test(match[1]),
47
+ label: raw.slice(match[0].length).trim(),
48
+ rawLabel: raw,
49
+ };
50
+ }
51
+ return { checkbox: false, checked: false, label: raw, rawLabel: raw };
52
+ }
53
+
54
+ function fingerprintLabel(label) {
55
+ return checkboxFromLabel(label).label.replace(/\s+/g, ' ').trim();
56
+ }
57
+
58
+ // Parse a single buffer line into a numbered option, capturing whether it is the
59
+ // currently highlighted row (prefixed by a selector glyph like ❯).
60
+ // A widget rendered inside a box frame prefixes (and suffixes) each row with a
61
+ // vertical border ("│ ❯ 1. Moderate │"). Strip surrounding box-frame chars so a
62
+ // boxed menu parses like a borderless one — otherwise the leading │ defeats both
63
+ // SELECTOR_RE and NUMBERED_RE and the whole AskUserQuestion classifies as `none`,
64
+ // stranding a stale approval banner over it.
65
+ const BOX_BORDER_PREFIX_RE = /^[│┃║▏▕|]\s*/;
66
+ const BOX_BORDER_SUFFIX_RE = /\s*[│┃║▏▕|]$/;
67
+
68
+ function choiceOptionFromLine(line) {
69
+ const raw = normalizeLine(line).trim().replace(BOX_BORDER_PREFIX_RE, '').replace(BOX_BORDER_SUFFIX_RE, '').trim();
70
+ if (!raw) return null;
71
+ const selector = raw.match(SELECTOR_RE);
72
+ const text = selector ? selector[1].trim() : raw;
73
+ const numbered = text.match(NUMBERED_RE);
74
+ if (!numbered) return null;
75
+ const parsed = checkboxFromLabel(numbered[2]);
76
+ const label = parsed.label;
77
+ if (!label) return null;
78
+ return {
79
+ value: numbered[1],
80
+ label,
81
+ rawLabel: parsed.rawLabel,
82
+ checkbox: parsed.checkbox,
83
+ checked: parsed.checked,
84
+ selected: !!selector,
85
+ text,
86
+ };
87
+ }
88
+
89
+ // Window (in buffer rows) we scan above/below the highlighted row for sibling
90
+ // options. Claude's AskUserQuestion widget puts a description line (sometimes
91
+ // wrapped to two) under every option plus a divider before the trailing
92
+ // "Chat about this", so a tight window drops real options. 12 comfortably spans
93
+ // a 5-6 option menu with descriptions while staying local to the menu block.
94
+ // Mirrored by terminalChoiceOptionsNear in public/m/app.js.
95
+ const OPTION_SCAN_RADIUS = 12;
96
+ function optionsNear(lines, selectedRow) {
97
+ const options = [];
98
+ const start = Math.max(0, selectedRow - OPTION_SCAN_RADIUS);
99
+ const end = Math.min(lines.length - 1, selectedRow + OPTION_SCAN_RADIUS);
100
+ for (let row = start; row <= end; row += 1) {
101
+ const option = choiceOptionFromLine(lines[row]);
102
+ if (!option) continue;
103
+ options.push({ ...option, row });
104
+ }
105
+ return options;
106
+ }
107
+
108
+ // A row qualifies as the menu's selected row when it is a highlighted numbered
109
+ // option with at least one more numbered option nearby (so a lone "❯ 1. foo" in
110
+ // prose is not mistaken for a menu).
111
+ function isSelectorRow(lines, row) {
112
+ const option = choiceOptionFromLine(lines[row]);
113
+ if (!option || !option.selected) return false;
114
+ return optionsNear(lines, row).length >= 2;
115
+ }
116
+
117
+ // Deterministic short fingerprint over the option set (value+label, in order).
118
+ // Mirrored byte-for-byte in public/m/app.js so client and server agree on whether
119
+ // the menu the user tapped still matches the live menu. FNV-1a (no Node crypto, so
120
+ // the browser can compute the identical value).
121
+ function choiceFingerprint(options) {
122
+ const basis = (Array.isArray(options) ? options : [])
123
+ .map((option) => `${option.value}:${fingerprintLabel(option.label || option.rawLabel || '')}`)
124
+ .join('|');
125
+ let hash = 0x811c9dc5;
126
+ for (let i = 0; i < basis.length; i += 1) {
127
+ hash ^= basis.charCodeAt(i);
128
+ hash = Math.imul(hash, 0x01000193) >>> 0;
129
+ }
130
+ return hash.toString(16).padStart(8, '0');
131
+ }
132
+
133
+ function hasSubmitControl(lines) {
134
+ return (Array.isArray(lines) ? lines : []).some((line) => SUBMIT_CONTROL_RE.test(normalizeLine(line)));
135
+ }
136
+
137
+ // Extract the active choice menu from raw terminal text. Returns null when no
138
+ // numbered selection menu is present. `selectedIndex` is the ordinal of the
139
+ // highlighted option within `options` (not a buffer row), which is what the
140
+ // keystroke planner navigates against.
141
+ function extractNumberedChoice(lines) {
142
+ let selectedRow = -1;
143
+ for (let row = 0; row < lines.length; row += 1) {
144
+ if (isSelectorRow(lines, row)) { selectedRow = row; break; }
145
+ }
146
+ if (selectedRow < 0) return null;
147
+ const near = optionsNear(lines, selectedRow).sort((a, b) => a.row - b.row);
148
+ if (near.length < 2) return null;
149
+
150
+ const seen = new Set();
151
+ const options = [];
152
+ for (const option of near) {
153
+ const key = `${option.value}\n${option.label}`;
154
+ if (seen.has(key)) continue;
155
+ seen.add(key);
156
+ options.push({
157
+ value: option.value,
158
+ label: option.label,
159
+ rawLabel: option.rawLabel,
160
+ checkbox: !!option.checkbox,
161
+ checked: !!option.checked,
162
+ selected: option.selected,
163
+ });
164
+ }
165
+ const selectedIndex = options.findIndex((option) => option.selected);
166
+ const requiresSubmit = options.some((option) => option.checkbox) && hasSubmitControl(lines);
167
+ return {
168
+ kind: requiresSubmit ? 'form' : 'numbered',
169
+ inline: false,
170
+ requiresSubmit,
171
+ submitAvailable: requiresSubmit,
172
+ options,
173
+ selectedValue: options.find((option) => option.selected)?.value || '',
174
+ selectedIndex,
175
+ fingerprint: choiceFingerprint(options),
176
+ };
177
+ }
178
+
179
+ // Inline approval prompts (e.g. Gemini's "Approve? (y/n/always)" or a shell's
180
+ // "Overwrite? [y/N]") are un-numbered: there is no navigable list or highlight,
181
+ // you answer by typing a shortcut. We detect the parenthesised "/"-separated
182
+ // option group near the tail and turn each token into a tappable option whose
183
+ // value IS the keystroke to type. The keystroke planner's no-highlight path then
184
+ // types that token + Enter — the same thing the per-provider approval engine does.
185
+ const INLINE_QUESTION_RE = /(?:approve|proceed|continue|allow|overwrite|apply|replace|confirm|delete|ok to|do you want|would you like|run this|execute)\b[^?\n]*\?\s*[([]\s*([A-Za-z][A-Za-z]*(?:\s*\/\s*[A-Za-z]+)+)\s*[)\]]/i;
186
+
187
+ const INLINE_TOKEN_LABELS = {
188
+ y: 'Yes',
189
+ yes: 'Yes',
190
+ n: 'No',
191
+ no: 'No',
192
+ a: 'Always allow',
193
+ always: 'Always allow',
194
+ all: 'Allow all',
195
+ s: 'Skip',
196
+ skip: 'Skip',
197
+ q: 'Cancel',
198
+ quit: 'Cancel',
199
+ c: 'Cancel',
200
+ cancel: 'Cancel',
201
+ d: 'Show diff',
202
+ diff: 'Show diff',
203
+ };
204
+
205
+ function inlineTokenLabel(token) {
206
+ const key = token.toLowerCase();
207
+ if (INLINE_TOKEN_LABELS[key]) return INLINE_TOKEN_LABELS[key];
208
+ return token.length === 1 ? token.toUpperCase() : `${token[0].toUpperCase()}${token.slice(1)}`;
209
+ }
210
+
211
+ function extractInlineChoice(lines) {
212
+ // Only consider the active prompt: scan the last few non-empty lines so a
213
+ // scrolled-up, already-answered prompt is never matched.
214
+ const nonEmpty = [];
215
+ for (let i = lines.length - 1; i >= 0 && nonEmpty.length < 6; i -= 1) {
216
+ const text = normalizeLine(lines[i]).trim();
217
+ if (text) nonEmpty.push(text);
218
+ }
219
+ for (const line of nonEmpty) {
220
+ const match = line.match(INLINE_QUESTION_RE);
221
+ if (!match) continue;
222
+ const tokens = match[1].split('/').map((token) => token.trim()).filter(Boolean);
223
+ if (tokens.length < 2) continue;
224
+ const seen = new Set();
225
+ const options = [];
226
+ for (const token of tokens) {
227
+ const value = token.length === 1 ? token.toLowerCase() : token;
228
+ if (seen.has(value)) continue;
229
+ seen.add(value);
230
+ options.push({ value, label: inlineTokenLabel(token), selected: false });
231
+ }
232
+ if (options.length < 2) continue;
233
+ return {
234
+ kind: 'inline',
235
+ inline: true,
236
+ options,
237
+ selectedValue: '',
238
+ selectedIndex: -1,
239
+ fingerprint: choiceFingerprint(options),
240
+ };
241
+ }
242
+ return null;
243
+ }
244
+
245
+ // Extract the active choice prompt from raw terminal text. Returns null when no
246
+ // numbered selection menu is present. `selectedIndex` is the ordinal of the
247
+ // highlighted option within `options` (not a buffer row), which is what the
248
+ // keystroke planner navigates against. Inline prompts report selectedIndex -1 so
249
+ // the planner types the option's shortcut instead of navigating.
250
+ function extractTerminalChoice(text) {
251
+ const lines = stripAnsi(String(text || '')).split('\n');
252
+ return extractNumberedChoice(lines) || extractInlineChoice(lines);
253
+ }
254
+
255
+ function findTargetOrdinal(options, targetValue, targetLabel) {
256
+ const value = targetValue == null ? '' : String(targetValue).trim();
257
+ const label = String(targetLabel || '').trim();
258
+ if (value) {
259
+ const byValue = options.findIndex((option) => String(option.value) === value);
260
+ if (byValue >= 0) return byValue;
261
+ }
262
+ if (label) {
263
+ const byLabel = options.findIndex((option) => option.label === label);
264
+ if (byLabel >= 0) return byLabel;
265
+ }
266
+ return -1;
267
+ }
268
+
269
+ const ARROW_DOWN = '\x1b[B';
270
+ const ARROW_UP = '\x1b[A';
271
+ const ARROW_RIGHT = '\x1b[C';
272
+ const ENTER = '\r';
273
+
274
+ // Plan the keystrokes that move the highlight to the tapped option and confirm it.
275
+ // Preferred strategy is arrow-relative navigation from the live highlight (works
276
+ // across Claude/Codex/un-numbered menus). When the highlight can't be located we
277
+ // fall back to typing the option's number — the same thing CTM's approval engine
278
+ // does for Claude. The final frame is always Enter; callers may verify the
279
+ // highlight landed before sending it.
280
+ function planChoiceKeystrokes({ options, selectedIndex, targetValue, targetLabel } = {}) {
281
+ if (!Array.isArray(options) || options.length === 0) return null;
282
+ const targetOrdinal = findTargetOrdinal(options, targetValue, targetLabel);
283
+ if (targetOrdinal < 0) return null;
284
+
285
+ const hasHighlight = Number.isInteger(selectedIndex) && selectedIndex >= 0;
286
+ if (!hasHighlight) {
287
+ return {
288
+ strategy: 'number',
289
+ targetOrdinal,
290
+ navFrames: [String(options[targetOrdinal].value)],
291
+ commitFrame: ENTER,
292
+ };
293
+ }
294
+
295
+ const delta = targetOrdinal - selectedIndex;
296
+ const arrow = delta >= 0 ? ARROW_DOWN : ARROW_UP;
297
+ const navFrames = [];
298
+ for (let i = 0; i < Math.abs(delta); i += 1) navFrames.push(arrow);
299
+ return {
300
+ strategy: 'arrows',
301
+ targetOrdinal,
302
+ delta,
303
+ navFrames,
304
+ commitFrame: ENTER,
305
+ };
306
+ }
307
+
308
+ // Normalize a label for matching a phone tap against the live menu: strip the
309
+ // checkbox marker, collapse whitespace, lowercase. Used by tappedOptionStillPresent.
310
+ function normalizeChoiceLabel(label) {
311
+ return checkboxFromLabel(label).label.replace(/\s+/g, ' ').trim().toLowerCase();
312
+ }
313
+
314
+ // Reconcile a phone tap against the live menu when the full-set fingerprint
315
+ // disagrees. The phone parses the cleaned terminal tail (dividers/non-content
316
+ // rows removed) while the server parses the raw headless grid, so for the SAME
317
+ // live menu the two can capture a different trailing-option SET (e.g. the phone
318
+ // includes "Chat about this" that the server's window cut off behind a divider).
319
+ // A strict fingerprint match false-rejects every such tap as stale. Instead we
320
+ // confirm the SPECIFIC tapped option is still present in the live menu by
321
+ // value+label — that (together with the post-navigation closed-loop verify in
322
+ // the caller) is the real correctness guarantee. Returns false only when the
323
+ // tapped option cannot be confirmed, i.e. the menu genuinely changed.
324
+ function tappedOptionStillPresent(menu, targetValue, targetLabel) {
325
+ const options = Array.isArray(menu && menu.options) ? menu.options : [];
326
+ if (!options.length) return false;
327
+ const value = targetValue == null ? '' : String(targetValue).trim();
328
+ const label = normalizeChoiceLabel(targetLabel);
329
+ if (value) {
330
+ const byValue = options.find((option) => String(option.value) === value);
331
+ // Same slot AND same text => same option. A label that no longer matches the
332
+ // option at this value means the menu was replaced, so fall through to reject.
333
+ if (byValue) return label ? normalizeChoiceLabel(byValue.label) === label : true;
334
+ }
335
+ // No value match (e.g. the server's window excluded that ordinal): accept only
336
+ // an unambiguous single label match so we never drive the wrong option.
337
+ if (label) {
338
+ return options.filter((option) => normalizeChoiceLabel(option.label) === label).length === 1;
339
+ }
340
+ return false;
341
+ }
342
+
343
+ function planFormSubmitKeystrokes(menu = {}) {
344
+ if (!menu.requiresSubmit && menu.kind !== 'form') return null;
345
+ if (!Array.isArray(menu.options) || !menu.options.some((option) => option.checked)) return null;
346
+ return {
347
+ strategy: 'form-submit',
348
+ frames: [ARROW_RIGHT, ENTER],
349
+ };
350
+ }
351
+
352
+ module.exports = {
353
+ stripAnsi,
354
+ choiceOptionFromLine,
355
+ extractTerminalChoice,
356
+ choiceFingerprint,
357
+ planChoiceKeystrokes,
358
+ planFormSubmitKeystrokes,
359
+ tappedOptionStillPresent,
360
+ ARROW_DOWN,
361
+ ARROW_UP,
362
+ ARROW_RIGHT,
363
+ ENTER,
364
+ };
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ // xterm answers OSC 10/11/12 color queries by sending bytes back to the host.
4
+ // CTM must never persist those terminal-generated replies as provider output.
5
+ // Scope this narrowly to color reply/set payloads; leave OSC queries such as
6
+ // ]10;? and ordinary text containing rgb values untouched.
7
+ const TERMINAL_GENERATED_COLOR_REPLY_RE = /(?:\x1b\]|\u009d|\])(?:10|11|12|110|111|112);(?:rgb:[0-9a-fA-F]{1,4}\/[0-9a-fA-F]{1,4}\/[0-9a-fA-F]{1,4}|#[0-9a-fA-F]{6,12})(?:\x07|\x1b\\|\u009c|\\)?/g;
8
+
9
+ function stripLeakedTerminalGeneratedColorReplies(value) {
10
+ const raw = String(value || '');
11
+ return raw ? raw.replace(TERMINAL_GENERATED_COLOR_REPLY_RE, '') : raw;
12
+ }
13
+
14
+ module.exports = {
15
+ TERMINAL_GENERATED_COLOR_REPLY_RE,
16
+ stripLeakedTerminalGeneratedColorReplies,
17
+ };
@@ -0,0 +1,48 @@
1
+ 'use strict';
2
+ // Deterministic fingerprint of a terminal's visible grid + scrollback descriptor.
3
+ // Used on BOTH sides: the server's authoritative headless xterm and the browser
4
+ // client's xterm. Equal fingerprint => client render content matches server truth.
5
+ //
6
+ // Design choices that matter for correctness:
7
+ // - Trailing whitespace is stripped per row: xterm pads rows to `cols` with spaces,
8
+ // so two buffers that differ only in pad width must still compare equal.
9
+ // - Dimensions (cols) are folded in: a foreign-width render IS a divergence.
10
+ // - scrollbackLen is folded in (not the whole scrollback — too expensive) so that
11
+ // gross scrollback drift (missing/duplicated blobs above the viewport) is caught.
12
+ // - FNV-1a 32-bit: cheap, dependency-free, stable across Node and the browser.
13
+
14
+ const FNV_OFFSET = 0x811c9dc5;
15
+ const FNV_PRIME = 0x01000193;
16
+ const EMPTY_FINGERPRINT = '0:empty';
17
+
18
+ // IMPORTANT: the browser mirror (public/js/terminal-reconciler.js) MUST keep identical logic — any change here requires a matching change there, or fingerprints diverge.
19
+ function fnv1a(str, seed) {
20
+ let h = seed >>> 0;
21
+ for (let i = 0; i < str.length; i++) {
22
+ h ^= str.charCodeAt(i);
23
+ h = Math.imul(h, FNV_PRIME);
24
+ }
25
+ return h >>> 0;
26
+ }
27
+
28
+ // rowStrings: array of visible-row strings (already translateToString'd on server side).
29
+ // meta: { cols, rows, scrollbackLen }
30
+ function fingerprintRows(rowStrings, meta) {
31
+ const cols = Number(meta && meta.cols) || 0;
32
+ const rows = Number(meta && meta.rows) || 0;
33
+ const scrollbackLen = Number(meta && meta.scrollbackLen) || 0;
34
+ let h = FNV_OFFSET;
35
+ let nonEmpty = false;
36
+ // Header binds dims so width drift is divergence.
37
+ h = fnv1a(`${cols}x${rows}|${scrollbackLen}\n`, h);
38
+ for (let i = 0; i < rowStrings.length; i++) {
39
+ const trimmed = String(rowStrings[i] == null ? '' : rowStrings[i]).replace(/\s+$/, '');
40
+ if (trimmed.length) nonEmpty = true;
41
+ // Row index is part of the hash so reordered rows differ.
42
+ h = fnv1a(`${i}:${trimmed}\n`, h);
43
+ }
44
+ if (!nonEmpty) return EMPTY_FINGERPRINT;
45
+ return `${cols}x${rows}:${h.toString(16)}`;
46
+ }
47
+
48
+ module.exports = { fingerprintRows, EMPTY_FINGERPRINT, fnv1a };
@@ -0,0 +1,84 @@
1
+ 'use strict';
2
+
3
+ const RECENT_INPUT_ECHO_WINDOW_MS = 100;
4
+ const CODEX_BULK_INPUT_REDRAW_COALESCE_MS = 350;
5
+ const CODEX_SEMANTIC_REDRAW_FAST_MAX_BYTES = 2048;
6
+ // A single Codex composer keystroke echo is a small synchronized-output TUI
7
+ // redraw (cursor moves + one row repaint). It is far below a full-screen
8
+ // repaint. Flushing it at ~1ms (instead of the 8ms generic recent-input path)
9
+ // makes typing feel snappy without touching bulk-output coalescing.
10
+ const CODEX_COMPOSER_INPUT_REDRAW_FAST_MAX_BYTES = 512;
11
+
12
+ function hasTerminalControl(data) {
13
+ return /[\x00-\x1f\x7f\x1b]/.test(String(data || ''));
14
+ }
15
+
16
+ function isCodexSession(session) {
17
+ const value = String(
18
+ (session && (session.agentType || session._providerId || session.type || session.cmd)) || ''
19
+ ).toLowerCase();
20
+ return /\bcodex\b|\/codex(?:\s|$)|\\codex(?:\s|$)/.test(value);
21
+ }
22
+
23
+ function isCodexSemanticComposerRedraw(data) {
24
+ const text = String(data || '');
25
+ if (!text || text.length > CODEX_SEMANTIC_REDRAW_FAST_MAX_BYTES) return false;
26
+ if (!text.includes('\x1b[?2026h') || !text.includes('\x1b[?2026l')) return false;
27
+ return /\[(?:Image #[0-9]+(?::[^\]]*)?|Attached images?:[^\]]+)\]/.test(text) ||
28
+ /\x1b\[[0-9;]*m[$/][A-Za-z0-9][A-Za-z0-9_.:-]*/.test(text);
29
+ }
30
+
31
+ // A small Codex composer keystroke echo: a synchronized-output (DEC 2026)
32
+ // wrapped redraw under the composer-size bound. This is broader than
33
+ // isCodexSemanticComposerRedraw (which only matches image tokens / styled
34
+ // command triggers) — it covers ordinary character typing into the native
35
+ // composer so each keystroke echo can flush promptly instead of waiting 8ms.
36
+ function isCodexComposerInputRedraw(data) {
37
+ const text = String(data || '');
38
+ if (!text || text.length > CODEX_COMPOSER_INPUT_REDRAW_FAST_MAX_BYTES) return false;
39
+ return text.includes('\x1b[?2026h') && text.includes('\x1b[?2026l');
40
+ }
41
+
42
+ function isRecentInput(session, now) {
43
+ const ts = Number(session && (session._lastInputTs || session.lastUserInputAt) || 0);
44
+ return !!ts && (now - ts) >= 0 && (now - ts) < RECENT_INPUT_ECHO_WINDOW_MS;
45
+ }
46
+
47
+ function terminalOutputFlushDelay(data, session, now = Date.now()) {
48
+ const text = String(data || '');
49
+ const len = text.length;
50
+ if (len < 4) return 1;
51
+
52
+ const recentInput = isRecentInput(session, now);
53
+ const hasControl = hasTerminalControl(text);
54
+ const bulkInputUntil = Number(session && session._codexComposerBulkInputUntil || 0);
55
+ if (hasControl && bulkInputUntil > now) {
56
+ return Math.max(32, Math.min(CODEX_BULK_INPUT_REDRAW_COALESCE_MS, bulkInputUntil - now));
57
+ }
58
+
59
+ // Plain local shell echoes should stay near-instant. ANSI/TUI redraw frames
60
+ // need a short coalescing window; otherwise each key can fan out into many
61
+ // websocket/browser writes and make real typing feel worse.
62
+ if (recentInput && !hasControl && len <= 10) return 1;
63
+ if (recentInput && hasControl && isCodexSession(session) && isCodexSemanticComposerRedraw(text)) return 1;
64
+ // Ordinary Codex composer keystroke echo: small synchronized-output redraw on
65
+ // recent input. Flush near-instant so native typing feels snappy. Bounded by
66
+ // CODEX_COMPOSER_INPUT_REDRAW_FAST_MAX_BYTES so bulk frames never qualify.
67
+ if (recentInput && hasControl && isCodexSession(session) && isCodexComposerInputRedraw(text)) return 1;
68
+ if (recentInput && len < 512) return 8;
69
+
70
+ return len < 256 ? 8 : 32;
71
+ }
72
+
73
+ module.exports = {
74
+ RECENT_INPUT_ECHO_WINDOW_MS,
75
+ CODEX_BULK_INPUT_REDRAW_COALESCE_MS,
76
+ CODEX_SEMANTIC_REDRAW_FAST_MAX_BYTES,
77
+ CODEX_COMPOSER_INPUT_REDRAW_FAST_MAX_BYTES,
78
+ hasTerminalControl,
79
+ isCodexSession,
80
+ isCodexSemanticComposerRedraw,
81
+ isCodexComposerInputRedraw,
82
+ isRecentInput,
83
+ terminalOutputFlushDelay,
84
+ };
@@ -0,0 +1,122 @@
1
+ 'use strict';
2
+
3
+ const TIMESTAMP_FIELDS = ['timestamp', 'created_at', 'createdAt', 'time', 'ts'];
4
+ const SEQUENCE_FIELDS = [
5
+ 'message_index',
6
+ 'messageIndex',
7
+ 'index',
8
+ 'seq',
9
+ 'sequence',
10
+ '_mergeIndex',
11
+ '_promptSourceIndex',
12
+ '_sourceLine',
13
+ ];
14
+
15
+ function firstPresentTimestamp(item) {
16
+ if (!item || typeof item !== 'object') return item;
17
+ for (const field of TIMESTAMP_FIELDS) {
18
+ if (item[field] != null && item[field] !== '') return item[field];
19
+ }
20
+ const data = item.data && typeof item.data === 'object' ? item.data : null;
21
+ if (data) {
22
+ for (const field of TIMESTAMP_FIELDS) {
23
+ if (data[field] != null && data[field] !== '') return data[field];
24
+ }
25
+ }
26
+ return '';
27
+ }
28
+
29
+ function normalizeEpochMs(value) {
30
+ if (!Number.isFinite(value)) return null;
31
+ if (value <= 0) return null;
32
+ // Accept Unix seconds when an upstream provider emits 10-digit epochs.
33
+ if (value >= 1_000_000_000 && value < 10_000_000_000) return value * 1000;
34
+ return value;
35
+ }
36
+
37
+ function timelineTimestampMs(itemOrTimestamp) {
38
+ const raw = firstPresentTimestamp(itemOrTimestamp);
39
+ if (typeof raw === 'number') return normalizeEpochMs(raw);
40
+ if (typeof raw === 'bigint') {
41
+ const value = Number(raw);
42
+ return normalizeEpochMs(value);
43
+ }
44
+ const text = String(raw || '').trim();
45
+ if (!text) return null;
46
+ if (/^[+-]?\d+(?:\.\d+)?$/.test(text)) {
47
+ const numeric = Number(text);
48
+ const normalized = normalizeEpochMs(numeric);
49
+ if (normalized != null) return normalized;
50
+ }
51
+ const parsed = Date.parse(text);
52
+ return Number.isFinite(parsed) ? parsed : null;
53
+ }
54
+
55
+ function timelineSequenceValue(item) {
56
+ if (!item || typeof item !== 'object') return null;
57
+ for (const field of SEQUENCE_FIELDS) {
58
+ const value = Number(item[field]);
59
+ if (Number.isFinite(value)) return value;
60
+ }
61
+ const data = item.data && typeof item.data === 'object' ? item.data : null;
62
+ if (data) {
63
+ for (const field of SEQUENCE_FIELDS) {
64
+ const value = Number(data[field]);
65
+ if (Number.isFinite(value)) return value;
66
+ }
67
+ }
68
+ return null;
69
+ }
70
+
71
+ function compareTimelineMessages(a, b) {
72
+ const ams = timelineTimestampMs(a);
73
+ const bms = timelineTimestampMs(b);
74
+ if (ams != null && bms != null && ams !== bms) return ams - bms;
75
+ if (ams != null && bms == null) return -1;
76
+ if (ams == null && bms != null) return 1;
77
+
78
+ const aMergeSource = Number(a?._mergeSourceOrder);
79
+ const bMergeSource = Number(b?._mergeSourceOrder);
80
+ if (Number.isFinite(aMergeSource) && Number.isFinite(bMergeSource) && aMergeSource !== bMergeSource) {
81
+ return aMergeSource - bMergeSource;
82
+ }
83
+
84
+ const aSeq = timelineSequenceValue(a);
85
+ const bSeq = timelineSequenceValue(b);
86
+ if (aSeq != null && bSeq != null && aSeq !== bSeq) return aSeq - bSeq;
87
+ if (aSeq != null && bSeq == null) return -1;
88
+ if (aSeq == null && bSeq != null) return 1;
89
+
90
+ return 0;
91
+ }
92
+
93
+ function sortTimelineMessages(messages) {
94
+ return (Array.isArray(messages) ? messages : [])
95
+ .map((message, index) => ({ message, index }))
96
+ .sort((a, b) => compareTimelineMessages(a.message, b.message) || a.index - b.index)
97
+ .map((entry) => entry.message);
98
+ }
99
+
100
+ function sortTimelineMessagesInPlace(messages) {
101
+ if (!Array.isArray(messages) || messages.length < 2) return messages;
102
+ const sorted = sortTimelineMessages(messages);
103
+ messages.splice(0, messages.length, ...sorted);
104
+ return messages;
105
+ }
106
+
107
+ function latestTimelineTimestampMs(messages) {
108
+ let latest = 0;
109
+ for (const message of Array.isArray(messages) ? messages : []) {
110
+ const ms = timelineTimestampMs(message);
111
+ if (ms != null && ms > latest) latest = ms;
112
+ }
113
+ return latest;
114
+ }
115
+
116
+ module.exports = {
117
+ compareTimelineMessages,
118
+ latestTimelineTimestampMs,
119
+ sortTimelineMessages,
120
+ sortTimelineMessagesInPlace,
121
+ timelineTimestampMs,
122
+ };