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
@@ -1,60 +1,16 @@
1
- // --- Session Integrity Worker Thread ---
2
- // Runs session-integrity scans off the main event loop.
1
+ 'use strict';
3
2
 
4
- const { parentPort, workerData } = require('worker_threads');
5
- const path = require('path');
6
-
7
- const dbModule = require(path.join(workerData.ctmDir, 'db'));
8
- dbModule.connectDb(workerData.dbPath);
9
-
10
- const sessionIntegrity = require(path.join(workerData.ctmDir, 'session-integrity'));
11
- const { getAllSessionFiles } = require(path.join(workerData.ctmDir, 'session-utils'));
12
-
13
- let running = false;
14
- let closing = false;
15
- let closed = false;
16
-
17
- function closeDbAndAck() {
18
- if (closed) return;
19
- closed = true;
20
- try { dbModule.closeDb({ checkpointMode: 'PASSIVE' }); } catch {}
21
- parentPort.postMessage({ type: 'closed' });
22
- setImmediate(() => process.exit(0));
23
- }
3
+ const { parentPort } = require('node:worker_threads');
24
4
 
25
5
  parentPort.on('message', (msg) => {
26
- if (msg.type === 'close') {
27
- closing = true;
28
- if (!running) closeDbAndAck();
29
- return;
30
- }
31
- if (msg.type !== 'run') return;
32
- if (closing) {
33
- parentPort.postMessage({ type: 'error', requestId: msg.requestId, dur: 0, error: 'worker closing' });
6
+ if (msg?.type === 'close') {
7
+ parentPort.postMessage({ type: 'closed' });
34
8
  return;
35
9
  }
36
- if (running) {
37
- parentPort.postMessage({ type: 'done', requestId: msg.requestId, dur: 0, result: { skipped: 'already_running' } });
38
- return;
39
- }
40
- running = true;
41
- const t0 = Date.now();
42
- try {
43
- const result = sessionIntegrity.runIntegrityCheck({
44
- db: dbModule.getDb(),
45
- getAllSessionFiles,
46
- telemetry: null,
47
- autoRecover: true,
48
- });
49
- parentPort.postMessage({ type: 'done', requestId: msg.requestId, dur: Date.now() - t0, result: result || { checked: true } });
50
- } catch (e) {
51
- parentPort.postMessage({ type: 'error', requestId: msg.requestId, dur: Date.now() - t0, error: e.message });
52
- } finally {
53
- running = false;
54
- if (closing) closeDbAndAck();
55
- }
56
- });
57
-
58
- process.on('exit', () => {
59
- try { dbModule.closeDb({ checkpointMode: 'NONE' }); } catch {}
10
+ parentPort.postMessage({
11
+ type: 'error',
12
+ requestId: msg?.requestId,
13
+ dur: 0,
14
+ error: 'session integrity DB work is owned by the CTM DB owner worker',
15
+ });
60
16
  });
@@ -16,6 +16,13 @@
16
16
  // A detector MAY also opt into custom waiting-input detection by providing
17
17
  // a `detectWaitingInput(text)` hook, but most providers get that for free
18
18
  // via providers/<id>.detect() which is already wired into approval-agent.
19
+ //
20
+ // A detector MAY also provide `detectTurnFinished(text)`: positive evidence the
21
+ // agent returned to its ready prompt (e.g. a TUI repainting its idle composer
22
+ // with no in-progress status line). Distinct from detectWaitingInput — finished
23
+ // means Idle (nothing needed), not Needs-You. Hookless TUIs like Codex rely on
24
+ // this to close their active turn; without it a finished session would ride the
25
+ // long open-turn "running" hold. server.js _idlePromptDetections consumes it.
19
26
 
20
27
  const DEFAULT_IDLE_DEBOUNCE_MS = 1500;
21
28
 
@@ -26,7 +33,17 @@ const TRIVIAL_CHUNK_RE = /^[\s\x1b\[\]\d;A-Za-z⠁⠂⠄⠈⠐⠠⡀⢀\\|/\-─
26
33
 
27
34
  function stripAnsi(data) {
28
35
  return String(data || '')
29
- .replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, '')
36
+ // `<=>` covers private-parameter CSI like modifyOtherKeys "\x1b[>4;0m"
37
+ // and kitty keyboard queries "\x1b[>7u" — without them the prefix byte
38
+ // survives and "[>4;0m[>7u" reads as printable content.
39
+ .replace(/\x1b\[[0-9;?<=>]*[a-zA-Z]/g, '')
40
+ // CSI sequences with intermediate bytes (0x20-0x2F) before the final byte —
41
+ // e.g. DECSCUSR cursor-shape "\x1b[0 q" (note the space). The rule above
42
+ // requires the final byte immediately after the params, so it misses these
43
+ // and leaves a stray "q"/"0 q" that reads as printable content (which made
44
+ // composer-cursor repaints score as activity). Additive: only matches the
45
+ // intermediate form the rule above cannot.
46
+ .replace(/\x1b\[[0-9;?]*[ -/]+[@-~]/g, '')
30
47
  .replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, '')
31
48
  .replace(/[\x00-\x1f\x7f]/g, '');
32
49
  }
@@ -9,7 +9,10 @@
9
9
  const { baseDetector, stripAnsi } = require('./base');
10
10
 
11
11
  const CLAUDE_STATUS_FRAGMENT_RE = /^(?:[✻✳✢✶◐◓◑◒⏺●○■□✔✓]+|Run|Runn|Runni|Runnin|Running\.{0,3}|unning|Thinking\.{0,3}|\(?ctrl\+o\s*to\s*expand\)?|>?\s*esc\s+to\s+interrupt|[⏵>]+\s*(?:accept edits|auto mode)\s+on\s+\(shift\+tab\s+to\s+cycle\)(?:\s*·\s*esc\s+to\s+interrupt)?(?:\s*·\s*ctrl\+t\s+to\s+(?:show|hide)\s+task)?|ctrl\+t\s+to\s+show\s+tasks?|\?\s+for\s+shortcuts|Native installation exists but .*? is not in your PATH\. Run:|echo\s+['"]export\s+PATH=.*)$/i;
12
- const CLAUDE_TOOL_OUTPUT_RE = /\b(?:Bash|Edit|Edited|Error|Failed|Grep|Glob|MultiEdit|NotebookEdit|Read|Recalled|Search|Searched|Task|TodoWrite|Update|Updated|WebFetch|WebSearch|Write|Wrote)\b/i;
12
+ const CLAUDE_PROMPT_LINE_RE = /^(?:›|❯|>)\s*(?:\S.*)?$/;
13
+ const CLAUDE_IDLE_FOOTER_LINE_RE = /(?:accept edits|auto mode)\s+on\s+\(shift\+tab\s+to\s+cycle\)|\besc\s+to\s+interrupt\b|\bctrl\+t\s+to\s+(?:show|hide)\s+tasks?\b|\bctrl\+o\s+to\s+expand\b|\?\s+for\s+shortcuts/i;
14
+ const CLAUDE_WORKED_LINE_RE = /^[*•]?\s*Worked for\s+\d/i;
15
+ const CLAUDE_PASSIVE_BORDER_LINE_RE = /^[\s─━═│┃┌┐└┘├┤┬┴┼╭╮╰╯]+$/u;
13
16
 
14
17
  function isClaudeRedraw(data) {
15
18
  const s = String(data || '');
@@ -17,6 +20,169 @@ function isClaudeRedraw(data) {
17
20
  /\x1b\[[0-9]+A/.test(s) && /\x1b\[[0-9;?]*J/.test(s)
18
21
  ) || (
19
22
  /\x1b\[[0-9]+;[0-9]+H/.test(s) && /\x1b\[[0-9;?]*[KJ]/.test(s)
23
+ ) || /\x1b\[\?2026h/.test(s);
24
+ }
25
+
26
+ function stripAnsiPreserveLines(data) {
27
+ return String(data || '')
28
+ .replace(/\x1b\[[0-9]+;[0-9]+[Hf]/g, '\n')
29
+ .replace(/\x1b\[[0-9]+[ABCD]/g, '\n')
30
+ .replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, '')
31
+ .replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, '')
32
+ .replace(/\r/g, '\n')
33
+ .replace(/[\x00-\x09\x0b\x0c\x0e-\x1f\x7f]/g, '');
34
+ }
35
+
36
+ function printableLines(data) {
37
+ return stripAnsiPreserveLines(data)
38
+ .replace(/\u00a0/g, ' ')
39
+ .split(/\n+/)
40
+ .map(line => line.trim())
41
+ .filter(Boolean);
42
+ }
43
+
44
+ function isClaudePassivePromptLine(line) {
45
+ const text = String(line || '').trim();
46
+ if (!text) return true;
47
+ return CLAUDE_PROMPT_LINE_RE.test(text) ||
48
+ CLAUDE_IDLE_FOOTER_LINE_RE.test(text) ||
49
+ CLAUDE_WORKED_LINE_RE.test(text) ||
50
+ CLAUDE_STATUS_FRAGMENT_RE.test(text) ||
51
+ CLAUDE_PASSIVE_BORDER_LINE_RE.test(text);
52
+ }
53
+
54
+ function isClaudeIdlePromptRedraw(data) {
55
+ if (!isClaudeRedraw(data)) return false;
56
+ const lines = printableLines(data);
57
+ if (!lines.length) return false;
58
+ const hasPrompt = lines.some(line => CLAUDE_PROMPT_LINE_RE.test(line));
59
+ const hasFooter = lines.some(line => CLAUDE_IDLE_FOOTER_LINE_RE.test(line));
60
+ if (!hasPrompt || !hasFooter) return false;
61
+ return lines.every(isClaudePassivePromptLine);
62
+ }
63
+
64
+ // Live Claude approval/choice widget shapes. A real prompt highlights a
65
+ // selection row (❯ 1. Yes / ❯ Allow / ❯ Run) and/or asks an approval question
66
+ // with a confirm footer. These parallel approval-agent.js's LIVE_SELECTION_CURSOR_RE
67
+ // / LIVE_PROMPT_FOOTER_RE (which serve the auto-approver); kept here so the banner
68
+ // path's "at a live prompt?" answer comes from the provider detector, not from
69
+ // global content regexes tested over the whole tail.
70
+ // Leading box-frame border ("│ ❯ 1. …") — a menu rendered inside a bordered widget
71
+ // prefixes each row with a vertical rule. It is allowed (optional) before the cursor
72
+ // so a boxed AskUserQuestion reads like a borderless one; without it the menu rows
73
+ // fail to match and the whole widget classifies as `none`, stranding a stale banner.
74
+ const CLAUDE_BOX_BORDER = '(?:[│┃║▏▕|]\\s*)?';
75
+ const CLAUDE_SELECTION_CURSOR_RE = new RegExp('^\\s*' + CLAUDE_BOX_BORDER + '[❯▸›▶➤◆→]\\s*(?:\\d+[.)]\\s*)?(?:yes\\b|allow\\b|approve\\b|proceed\\b|accept\\b|run\\b|make edits|don\'?t ask|always)', 'i');
76
+ const CLAUDE_APPROVAL_QUESTION_RE = /\bDo you want to (?:proceed|make this edit|create|run|allow)\b/i;
77
+ const CLAUDE_CONFIRM_FOOTER_RE = /\benter to confirm\b|\besc to (?:cancel|reject|go back)\b|press enter to (?:confirm|continue)/i;
78
+ // Any numbered option row (cursor optional): "❯ 1. X", " 2. Y". Loose on its own —
79
+ // it also matches enumerated PROSE ("2. Double-stat (low): ..."), so it only counts
80
+ // toward a real menu alongside a highlight cursor or a menu nav footer (below).
81
+ const CLAUDE_NUMBERED_OPTION_RE = new RegExp('^\\s*' + CLAUDE_BOX_BORDER + '[❯▸›▶➤◆→]?\\s*\\d+[.)]\\s+\\S');
82
+ // A HIGHLIGHTED numbered option (selection cursor REQUIRED) — present in a real
83
+ // interactive menu ("❯ 1. Investigate"), absent from a prose findings list.
84
+ const CLAUDE_MENU_CURSOR_OPTION_RE = new RegExp('^\\s*' + CLAUDE_BOX_BORDER + '[❯▸▶➤◆→]\\s*\\d+[.)]\\s+\\S');
85
+ // A menu navigation footer ("Enter to select · ↑/↓ to navigate"): the other positive
86
+ // tell that the numbered rows are an interactive selection menu, not prose.
87
+ const CLAUDE_MENU_NAV_FOOTER_RE = /\bto\s+(?:select|navigate)\b|↑\s*\/\s*↓|↑\/↓/i;
88
+ // A yes/no-flavored option — the hallmark of a real yes/no APPROVAL menu (Yes / No /
89
+ // Allow / Deny / …) as opposed to an arbitrary multi-option CHOICE menu. The menu's
90
+ // OPTIONS, not its question text, decide approval-vs-choice: an AskUserQuestion-style
91
+ // menu can ask "...do you want to proceed?" (which matches the approval question regex)
92
+ // yet offer several non-yes/no answers, and it must read as a choice — otherwise the
93
+ // banner renders Approve/Deny (inject '1'/Esc) on it and the approval label path,
94
+ // which hunts for a command, comes back blank.
95
+ const CLAUDE_YESNO_OPTION_RE = /^\s*[❯▸›▶➤◆→]?\s*(?:\d+[.)]\s*)?(?:yes\b|no\b|allow\b|deny\b|approve\b|reject\b|proceed\b|cancel\b|accept\b|always\b|don'?t\b|keep planning\b)/i;
96
+ const CLAUDE_YN_RE = /\(Y\/n\)\s*$/i;
97
+ // The agent's free-text composer: a bare prompt line (›/❯/> with nothing typed).
98
+ // Its presence BELOW an approval/choice anchor is the real "idle, awaiting text"
99
+ // signal — the live approval widget REPLACES this composer. (The idle/working
100
+ // FOOTER — "esc to interrupt", "? for shortcuts" — can still be painted beneath a
101
+ // live approval, so footer presence alone must NOT veto. That blanket veto was the
102
+ // cause of approval cards never appearing live until a restart re-rendered a frame
103
+ // that happened to drop the footer from the captured tail.)
104
+ const CLAUDE_EMPTY_COMPOSER_RE = /^(?:›|❯|>)\s*$/;
105
+
106
+ // Negative gate: the idle/working composer footer (esc to interrupt, ? for
107
+ // shortcuts, accept edits on …) is on screen, which a live approval widget would
108
+ // have REPLACED — so the agent is at its ready composer, not blocked at a prompt.
109
+ function detectReadyComposer(text) {
110
+ const lines = printableLines(text);
111
+ if (!lines.length) return false;
112
+ return lines.slice(-12).some(line => CLAUDE_IDLE_FOOTER_LINE_RE.test(line));
113
+ }
114
+
115
+ // "esc to interrupt" is the ONE footer phrase Claude shows exclusively while a
116
+ // turn is generating (or a tool/shell it spawned is still running) — the genuinely
117
+ // idle composer shows "? for shortcuts" / "auto mode on (shift+tab to cycle)" but
118
+ // never "esc to interrupt". So its presence in the tail is decisive evidence the
119
+ // agent is actively working, NOT idle awaiting input. server.js _idlePromptDetections
120
+ // uses this to suppress the bare-`❯`-composer 'input' idle-notify during a long,
121
+ // output-quiet tool turn (which otherwise mislabels the session "Needs You").
122
+ const CLAUDE_GENERATING_FOOTER_RE = /\besc\s+to\s+interrupt\b/i;
123
+ function detectActivelyGenerating(text) {
124
+ const lines = printableLines(text);
125
+ if (!lines.length) return false;
126
+ return lines.slice(-12).some(line => CLAUDE_GENERATING_FOOTER_RE.test(line));
127
+ }
128
+
129
+ // Positive "blocked at a live approval/choice widget" signal. Returns
130
+ // {reason:'approval'|'choice'} only when a real widget is anchored in the active
131
+ // prompt region and the ready-composer footer is absent; null otherwise.
132
+ function detectWaitingInput(text) {
133
+ const raw = String(text || '');
134
+ if (!raw.trim()) return null;
135
+ const lines = printableLines(raw);
136
+ if (!lines.length) return null;
137
+ const tail = lines.slice(-14);
138
+ // Locate the anchors and the idle free-text composer by POSITION (closest to the
139
+ // bottom = the active element). A widget is live only when it is not sitting above
140
+ // a bare composer prompt (which would mean the agent already moved on and the
141
+ // widget is stale scrollback). Footer text is intentionally NOT consulted here.
142
+ let selIdx = -1, qIdx = -1, confIdx = -1, ynIdx = -1, composerIdx = -1;
143
+ let numberedOptCount = 0, lastNumberedOptIdx = -1, hasYesNoOption = false;
144
+ let hasMenuCursor = false, hasMenuNavFooter = false;
145
+ for (let i = 0; i < tail.length; i++) {
146
+ const line = tail[i];
147
+ if (CLAUDE_SELECTION_CURSOR_RE.test(line)) selIdx = i;
148
+ if (CLAUDE_APPROVAL_QUESTION_RE.test(line)) qIdx = i;
149
+ if (CLAUDE_CONFIRM_FOOTER_RE.test(line)) confIdx = i;
150
+ if (CLAUDE_YN_RE.test(line)) ynIdx = i;
151
+ if (CLAUDE_EMPTY_COMPOSER_RE.test(line)) composerIdx = i;
152
+ if (CLAUDE_MENU_NAV_FOOTER_RE.test(line)) hasMenuNavFooter = true;
153
+ if (CLAUDE_NUMBERED_OPTION_RE.test(line)) {
154
+ numberedOptCount += 1; lastNumberedOptIdx = i;
155
+ if (CLAUDE_YESNO_OPTION_RE.test(line)) hasYesNoOption = true;
156
+ if (CLAUDE_MENU_CURSOR_OPTION_RE.test(line)) hasMenuCursor = true;
157
+ }
158
+ }
159
+ // A multi-option numbered menu whose options are NOT yes/no-flavored is a CHOICE,
160
+ // even when its question matches the approval phrasing. A genuine yes/no approval
161
+ // (Yes/No options) keeps hasYesNoOption true and stays an approval below. Require a
162
+ // positive menu tell — a highlight cursor or a nav footer — so an enumerated PROSE
163
+ // findings list ("2. ... 3. ... 4. ...") is NOT mistaken for an interactive menu.
164
+ const multiChoiceMenu = numberedOptCount >= 2 && !hasYesNoOption &&
165
+ (hasMenuCursor || hasMenuNavFooter);
166
+ let approvalIdx = -1;
167
+ if (selIdx >= 0 && (qIdx >= 0 || confIdx >= 0)) approvalIdx = Math.max(selIdx, qIdx, confIdx);
168
+ else if (qIdx >= 0 && confIdx >= 0 && !multiChoiceMenu) approvalIdx = Math.max(qIdx, confIdx);
169
+ else if (ynIdx >= 0 && !multiChoiceMenu) approvalIdx = ynIdx;
170
+ // A bare composer BELOW the anchor ⇒ the widget is stale; the agent is idle at its
171
+ // composer. No composer-below ⇒ the widget is the live, active prompt.
172
+ if (approvalIdx >= 0 && !(composerIdx > approvalIdx)) return { reason: 'approval' };
173
+ // Choice: a highlighted yes/no-style selection cursor (selIdx) OR a non-yes/no
174
+ // numbered menu (anchored at its last option). Same composer-below staleness gate.
175
+ const choiceIdx = selIdx >= 0 ? selIdx : (multiChoiceMenu ? lastNumberedOptIdx : -1);
176
+ if (choiceIdx >= 0 && !(composerIdx > choiceIdx)) return { reason: 'choice' };
177
+ return null;
178
+ }
179
+
180
+ function isClaudeStatusRedraw(data) {
181
+ const stripped = stripAnsi(data).trim();
182
+ return (
183
+ stripped.length <= 160 &&
184
+ CLAUDE_STATUS_FRAGMENT_RE.test(stripped) &&
185
+ isClaudeRedraw(data)
20
186
  );
21
187
  }
22
188
 
@@ -28,14 +194,21 @@ module.exports = {
28
194
  // Base detector's TRIVIAL_CHUNK_RE already covers these.
29
195
  isActiveChunk(data) {
30
196
  if (!baseDetector.isActiveChunk(data)) return false;
31
- const stripped = stripAnsi(data).trim();
32
- if (
33
- stripped.length <= 160 &&
34
- isClaudeRedraw(data) &&
35
- CLAUDE_STATUS_FRAGMENT_RE.test(stripped)
36
- ) {
37
- return false;
38
- }
197
+ if (isClaudeIdlePromptRedraw(data) || isClaudeStatusRedraw(data)) return false;
39
198
  return true;
40
199
  },
200
+ isStatusOnlyChunk(data) {
201
+ return isClaudeIdlePromptRedraw(data) || isClaudeStatusRedraw(data);
202
+ },
203
+ isBusyStatusChunk() {
204
+ return false;
205
+ },
206
+ isClaudeIdlePromptRedraw,
207
+ detectWaitingInput,
208
+ detectReadyComposer,
209
+ detectActivelyGenerating,
210
+ _test: {
211
+ printableLines,
212
+ isClaudePassivePromptLine,
213
+ },
41
214
  };
@@ -11,12 +11,20 @@
11
11
  const { baseDetector, stripAnsi } = require('./base');
12
12
 
13
13
  const CODEX_STATUS_FRAGMENT_RE = /^[\s\d•◦·∙●○WwOoRrKkIiNnGg]+$/u;
14
- const CODEX_BUSY_STATUS_LINE_RE = /^(?:working(?:\s*\([^)]*\))?(?:\s*[•◦·∙●○]\s*esc\s+to\s+interrupt)?|(?:working\s*)?esc\s+to\s+interrupt)$/iu;
14
+ const CODEX_BUSY_STATUS_LINE_RE = /^(?:(?:waiting\s+for\s+background\s+terminal\b.*)|working(?:\s*\([^)]*\))?(?:\s*[•◦·∙●○]\s*esc\s+to\s+interrupt)?|(?:working\s*)?esc\s+to\s+interrupt)$/iu;
15
15
  const CODEX_BUSY_COMPACT_STATUS_LINE_RE = /^working\s+(?:\d+(?::\d+){0,2}|\d+(?:\.\d+)?\s*(?:ms|s|sec|secs|m|min|mins|h|hr|hrs))$/iu;
16
16
  const CODEX_BUSY_WORD = 'working';
17
17
  const CODEX_BUSY_HINT_RE = /esc\s+to\s+interrupt/i;
18
+ const CODEX_PROMPT_LINE_RE = /^(?:›|❯|>)\s*(?:\S.*)?$/;
19
+ const CODEX_FOOTER_LINE_RE = /^(?:gpt|claude|codex|gemini|deepseek|kimi|moonshot|qwen|llama|mistral)[\w.+-]*(?:\s+[\w.+-]+){0,8}\s+[·-]\s+(?:~|\/|\.\.?\/)/i;
20
+ const CODEX_SKILL_WARNING_RE = /^(?:⚠\s*)?Skipped loading\s+\d+\s+skill\(s\)\s+due to invalid SKILL\.md files\./i;
21
+ const CODEX_SKILL_DESCRIPTION_RE = /^\/.*\/SKILL\.md:\s+invalid description:\s+exceeds maximum length of\s+\d+\s+characters$/i;
22
+ // An approval/choice widget is pending — NOT a finished turn, even when the
23
+ // model footer is still painted at the bottom of the screen.
24
+ const CODEX_PENDING_PROMPT_RE = /Would you like to run the following command\?|Press enter to confirm or esc to cancel|tell Codex what to do differently|\b\d+\.\s*Yes\b/i;
18
25
 
19
26
  function hasCodexBusyStatusFragment(text) {
27
+ if (/waiting\s+for\s+background\s+terminal/i.test(String(text || ''))) return true;
20
28
  if (CODEX_BUSY_HINT_RE.test(String(text || ''))) return true;
21
29
  const letters = String(text || '').toLowerCase().replace(/[^a-z]/g, '');
22
30
  if (letters.length < 3) return false;
@@ -36,6 +44,105 @@ function isCursorAddressedRedraw(data) {
36
44
  ) || /\x1b\[\?2026h/.test(s);
37
45
  }
38
46
 
47
+ function stripAnsiPreserveLines(data) {
48
+ return String(data || '')
49
+ .replace(/\x1b\[[0-9]+;[0-9]+[Hf]/g, '\n')
50
+ .replace(/\x1b\[[0-9]+[ABCD]/g, '\n')
51
+ .replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, '')
52
+ .replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, '')
53
+ .replace(/\r/g, '\n')
54
+ .replace(/[\x00-\x09\x0b\x0c\x0e-\x1f\x7f]/g, '');
55
+ }
56
+
57
+ function printableLines(data) {
58
+ return stripAnsiPreserveLines(data)
59
+ .replace(/\u00a0/g, ' ')
60
+ .split(/\n+/)
61
+ .map(line => line.trim())
62
+ .filter(Boolean);
63
+ }
64
+
65
+ function isCodexPassiveDiagnosticChunk(data) {
66
+ const lines = printableLines(data);
67
+ return !!(lines.length && lines.every(line => (
68
+ CODEX_SKILL_WARNING_RE.test(line) ||
69
+ CODEX_SKILL_DESCRIPTION_RE.test(line)
70
+ )));
71
+ }
72
+
73
+ function isCodexIdlePromptRedraw(data) {
74
+ if (!isCursorAddressedRedraw(data)) return false;
75
+ const lines = printableLines(data);
76
+ if (!lines.length) return false;
77
+ const compact = lines.join(' ');
78
+ // A live busy status line ("Working (5s) • esc to interrupt") means still
79
+ // working — not an idle composer. Use the PRECISE per-line busy-status check
80
+ // (same as detectTurnFinished), NOT the loose 3-letter fragment matcher: this
81
+ // classifier already requires a full composer frame (prompt + footer), so a
82
+ // coalesced full-screen repaint carries the conversation history above the
83
+ // composer, and the loose matcher trips on ordinary words (coding / going /
84
+ // rendering / work → wor/ork/rki/ing). That false-busy match mis-scored the
85
+ // idle repaint as active and flipped idle Codex sessions to "Running".
86
+ if (lines.some(line => textHasCodexBusyStatus(line))) return false;
87
+ const hasPrompt = lines.some(line => CODEX_PROMPT_LINE_RE.test(line)) ||
88
+ /(?:^|\s)(?:›|❯|>)\s*\S/.test(compact);
89
+ if (!hasPrompt) return false;
90
+ const hasFooter = lines.some(line => CODEX_FOOTER_LINE_RE.test(line)) ||
91
+ /\b(?:gpt-[\w.-]+|claude|sonnet|opus|haiku|deepseek|gemini|qwen|llama|mistral|kimi|moonshot)\b/i.test(compact) &&
92
+ /(?:\b(?:x?high|medium|low|fast)\b|\s[-·]\s|[~/])/.test(compact);
93
+ return hasFooter;
94
+ }
95
+
96
+ // Positive "the turn is over" signal: Codex has repainted its ready composer
97
+ // (prompt line + model footer) with NO live "Working… esc to interrupt" status
98
+ // line. Unlike detectWaitingInput (which means Needs-You), this means the agent
99
+ // finished and is idle. Server uses it to close the active turn so a finished
100
+ // Codex session settles to Idle instead of riding the long open-turn hold.
101
+ // Operates on viewport text (already stripped) or a raw ANSI tail.
102
+ function detectTurnFinished(text) {
103
+ const raw = String(text || '');
104
+ if (!raw.trim()) return false;
105
+ if (CODEX_PENDING_PROMPT_RE.test(raw)) return false;
106
+ // printableLines preserves line structure (base.stripAnsi strips newlines, so
107
+ // a whole-buffer busy check would miss the status line). Check busy per line.
108
+ const lines = printableLines(raw);
109
+ if (!lines.length) return false;
110
+ // A live busy status line ("Working (5s) • esc to interrupt") means still
111
+ // working, even though the composer is also on screen.
112
+ if (lines.some(line => textHasCodexBusyStatus(line))) return false;
113
+ const hasPrompt = lines.some(line => CODEX_PROMPT_LINE_RE.test(line));
114
+ if (!hasPrompt) return false;
115
+ return lines.some(line => CODEX_FOOTER_LINE_RE.test(line));
116
+ }
117
+
118
+ // The unambiguous live Codex approval/confirm widgets — the actual interactive
119
+ // prompt text, distinct from prose the agent may print. Kept narrow on purpose
120
+ // (hide-unless-confident): the run-command question and the confirm footer are
121
+ // real widget chrome; a numbered Yes/No PAIR is a menu. We deliberately do NOT
122
+ // fire on "tell Codex/the model what to do differently" alone, since that line
123
+ // also appears in the passive "Conversation interrupted" message after an Esc.
124
+ const CODEX_RUN_APPROVAL_RE = /Would you like to run the following command\?/i;
125
+ const CODEX_CONFIRM_WIDGET_RE = /Press enter to confirm or esc to cancel/i;
126
+ const CODEX_YESNO_MENU_RE = /\b1\.\s*Yes\b[\s\S]{0,80}\b2\.\s*No\b/i;
127
+
128
+ // Positive "the agent is blocked at a live approval/choice widget" signal.
129
+ // Returns {reason:'approval'|'choice'} ONLY when a real widget is in the active
130
+ // prompt region (bottom of the viewport) and the agent is not mid-work. Returns
131
+ // null otherwise — an idle/finished composer is reported by detectTurnFinished,
132
+ // not here. Operates on viewport text or a raw ANSI tail.
133
+ function detectWaitingInput(text) {
134
+ const raw = String(text || '');
135
+ if (!raw.trim()) return null;
136
+ const lines = printableLines(raw);
137
+ if (!lines.length) return null;
138
+ // Still working ("Working (5s) • esc to interrupt") → not waiting on the user.
139
+ if (lines.some(line => textHasCodexBusyStatus(line))) return null;
140
+ const tail = lines.slice(-10).join('\n');
141
+ if (CODEX_RUN_APPROVAL_RE.test(tail)) return { reason: 'approval' };
142
+ if (CODEX_CONFIRM_WIDGET_RE.test(tail) || CODEX_YESNO_MENU_RE.test(tail)) return { reason: 'choice' };
143
+ return null;
144
+ }
145
+
39
146
  function isCodexStatusRedraw(data) {
40
147
  const stripped = stripAnsi(data).trim();
41
148
  return (
@@ -61,6 +168,17 @@ function textHasCodexBusyStatus(text) {
61
168
  return clean.split('\n').some((line) => isCodexBusyStatusLine(line));
62
169
  }
63
170
 
171
+ // The agent is mid-turn when its "Working… • esc to interrupt" status line (or a
172
+ // background-terminal wait) is on screen. server.js's generating-gate uses this to
173
+ // suppress the idle-notify so a long, output-quiet Codex turn whose composer matches
174
+ // a global prompt pattern is not mislabeled "Needs You". Same signal Codex's own
175
+ // detectWaitingInput/detectTurnFinished already trust.
176
+ function detectActivelyGenerating(text) {
177
+ // Per line: base.stripAnsi (inside textHasCodexBusyStatus) drops newlines, so a
178
+ // whole-buffer call would miss the status line — mirror detectWaitingInput.
179
+ return printableLines(text).some((line) => textHasCodexBusyStatus(line));
180
+ }
181
+
64
182
  module.exports = {
65
183
  ...baseDetector,
66
184
  id: 'codex',
@@ -73,17 +191,47 @@ module.exports = {
73
191
 
74
192
  isActiveChunk(data) {
75
193
  if (!baseDetector.isActiveChunk(data)) return false;
194
+ if (isCodexIdlePromptRedraw(data) || isCodexPassiveDiagnosticChunk(data)) return false;
76
195
  if (isCodexStatusRedraw(data)) {
196
+ // Tiny status-region redraws (≤160 chars of WORKING-alphabet content):
197
+ // keep the LOOSE fragment matcher — "Working" routinely splits across
198
+ // chunk boundaries ("king", "Wo7") and these fragments are the only
199
+ // busy signal while the tab is hidden. The alphabet pre-filter in
200
+ // isCodexStatusRedraw bounds the false-positive surface.
77
201
  return hasCodexBusyStatusFragment(stripAnsi(data).trim());
78
202
  }
203
+ if (isCursorAddressedRedraw(data)) {
204
+ // Positive-evidence rule: a cursor-addressed frame/region repaint is
205
+ // agent work ONLY when a live busy-status line is in it (strict
206
+ // per-line match — printableLines preserves the row structure that
207
+ // stripAnsi would flatten). Everything else — MCP startup status,
208
+ // welcome animation, update nudges, the history half of a split
209
+ // full-frame repaint — is TUI chrome an IDLE codex paints too, and one
210
+ // mis-scored chunk holds the sidebar on "Running" for the whole 15s
211
+ // codex hold. Genuine work keeps its busy row on screen and
212
+ // repainting, so per-chunk strictness costs nothing (split fragments
213
+ // are reassembled by the server's rolling busy-evidence buffer).
214
+ return detectActivelyGenerating(data);
215
+ }
79
216
  return true;
80
217
  },
81
218
  isStatusOnlyChunk(data) {
82
- return isCodexStatusRedraw(data);
219
+ return isCodexStatusRedraw(data) ||
220
+ isCodexIdlePromptRedraw(data) ||
221
+ isCodexPassiveDiagnosticChunk(data) ||
222
+ (isCursorAddressedRedraw(data) && !detectActivelyGenerating(data));
83
223
  },
84
224
  isBusyStatusChunk(data) {
85
225
  return isCodexStatusRedraw(data) && textHasCodexBusyStatus(data);
86
226
  },
87
227
  hasCodexBusyStatusFragment,
88
228
  textHasCodexBusyStatus,
229
+ isCodexIdlePromptRedraw,
230
+ detectTurnFinished,
231
+ detectWaitingInput,
232
+ detectActivelyGenerating,
233
+ _test: {
234
+ printableLines,
235
+ isCodexPassiveDiagnosticChunk,
236
+ },
89
237
  };
@@ -0,0 +1,127 @@
1
+ 'use strict';
2
+ // Cursor (cursor-agent) TUI state detector.
3
+ //
4
+ // cursor-agent renders an interactive TUI: a prompt/composer region, a status
5
+ // footer (model + workspace), and a spinner/status line while the model works.
6
+ // Like OpenCode, it redraws those regions while sitting idle at the prompt, so
7
+ // those frames must paint but must NOT be treated as model activity. Real tool
8
+ // output, approval widgets, and "generating" status keep the session busy.
9
+ //
10
+ // The footer / prompt / busy regexes below are a grounded first cut; they are
11
+ // tuned against live cursor-agent output during the dev verification pass.
12
+ // Anything we don't recognize falls through to baseDetector, so an unmatched
13
+ // frame is treated as activity (fail-safe: never wrongly idle a working agent).
14
+
15
+ const { baseDetector } = require('./base');
16
+
17
+ // Footer: model id + workspace path (e.g. "composer-2.5 · ~/ws/tools").
18
+ const CURSOR_FOOTER_LINE_RE = /^(?:composer|gpt|claude|sonnet|opus|haiku|gemini|o\d|auto)[\w.+-]*(?:\s+[\w.+-]+){0,8}\s+[·|-]\s+(?:~|\/|\.\.?\/)/i;
19
+ // Newer Cursor Agent builds render an idle welcome/composer frame with title,
20
+ // version, help, model, and workspace lines split across rows. These are UI
21
+ // chrome, not agent output, and selecting an idle tab can replay exactly this
22
+ // frame.
23
+ const CURSOR_STATIC_HEADER_LINE_RE = /^Cursor Agent$/i;
24
+ const CURSOR_VERSION_LINE_RE = /^v\d{4}\.\d{2}\.\d{2}[-\w.]*$/i;
25
+ const CURSOR_IDLE_HELP_LINE_RE = /^Use\s+\/plan\b.*\bbefore code changes\.?$/i;
26
+ const CURSOR_IDLE_PLACEHOLDER_RE = /^(?:Plan,\s*)?search,\s*build anything$/i;
27
+ const CURSOR_MODEL_LINE_RE = /^Composer\s+\d+(?:\.\d+)*(?:[-\w.]*)?(?:\s+\w+){0,3}$/i;
28
+ const CURSOR_WORKSPACE_LINE_RE = /^(?:~|\/|\.\.?\/).+\s+[·|-]\s+[\w.+-]+$/;
29
+ // Composer prompt glyphs.
30
+ const CURSOR_PROMPT_LINE_RE = /^(?:>|›|❯|→|▌|\|)\s*(?:\S.*)?$/;
31
+ // Spinner/status while the model is producing output. cursor-agent shows a
32
+ // running verb plus an interrupt hint (esc/ctrl-c) while busy.
33
+ const CURSOR_BUSY_LINE_RE = /^(?:[•●◦▪·*⠿⠷⠯⠟⠻⠽⠾]\s*)?(?:(?:Waiting\s+for\s+background\s+terminal\b.*)|(?:Composing|Generating|Thinking|Working|Reading|Writing|Editing|Running|Searching|Planning|Analyzing|Calling|Using|Processing|Reasoning|Applying|Streaming)\b.*\b(?:esc|interrupt|cancel|stop)\b)/i;
34
+ // Passive status lines that should not hold the session busy.
35
+ const CURSOR_PASSIVE_STATUS_LINE_RE = /^(?:[•●◦▪·*]\s*)?(?:Ready|Waiting|Idle|Stopped|Done|Complete|Completed|Accept|Reject|Run everything|Allowlist)\b/i;
36
+ const CURSOR_CONTROL_HINT_RE = /^(?:esc|ctrl-c|ctrl\+c|tab|enter|press\s+enter)\b.*(?:interrupt|cancel|confirm|send|accept|reject|toggle)/i;
37
+
38
+ function stripAnsiPreserveLines(data) {
39
+ return String(data || '')
40
+ // Cursor-addressed redraws otherwise collapse rows together after stripping
41
+ // CSI. Preserve row boundaries before removing control codes.
42
+ .replace(/\x1b\[[0-9]+;[0-9]+[Hf]/g, '\n')
43
+ .replace(/\x1b\[[0-9]+[ABCD]/g, '\n')
44
+ .replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, '')
45
+ .replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, '')
46
+ .replace(/\r/g, '\n')
47
+ .replace(/[\x00-\x09\x0b\x0c\x0e-\x1f\x7f]/g, '');
48
+ }
49
+
50
+ function printableLines(data) {
51
+ return stripAnsiPreserveLines(data)
52
+ .replace(/\u00a0/g, ' ')
53
+ .split(/\n+/)
54
+ .map(line => line.trim())
55
+ .filter(Boolean);
56
+ }
57
+
58
+ function isCursorPassiveLine(line) {
59
+ const text = String(line || '').trim();
60
+ if (!text) return true;
61
+ return CURSOR_FOOTER_LINE_RE.test(text) ||
62
+ CURSOR_STATIC_HEADER_LINE_RE.test(text) ||
63
+ CURSOR_VERSION_LINE_RE.test(text) ||
64
+ CURSOR_IDLE_HELP_LINE_RE.test(text) ||
65
+ CURSOR_IDLE_PLACEHOLDER_RE.test(text) ||
66
+ CURSOR_MODEL_LINE_RE.test(text) ||
67
+ CURSOR_WORKSPACE_LINE_RE.test(text) ||
68
+ CURSOR_PROMPT_LINE_RE.test(text) ||
69
+ CURSOR_PASSIVE_STATUS_LINE_RE.test(text) ||
70
+ CURSOR_CONTROL_HINT_RE.test(text);
71
+ }
72
+
73
+ function isCursorBusyLine(line) {
74
+ return CURSOR_BUSY_LINE_RE.test(String(line || '').trim());
75
+ }
76
+
77
+ function detectWaitingInput(text) {
78
+ const lines = printableLines(text).slice(-10);
79
+ if (!lines.length) return null;
80
+ if (lines.some(isCursorBusyLine)) return null;
81
+ if (!lines.some(line => CURSOR_PROMPT_LINE_RE.test(line))) return null;
82
+ return { reason: 'input' };
83
+ }
84
+
85
+ // The agent is mid-turn when its busy/spinner line ("Generating… esc to interrupt")
86
+ // is on screen. server.js's generating-gate uses this to suppress the idle-notify so
87
+ // a long, output-quiet Cursor turn whose composer matches a global prompt pattern is
88
+ // not mislabeled "Needs You". Tail-scoped so a stale spinner up in scrollback doesn't
89
+ // count; same busy signal detectWaitingInput already gates on.
90
+ function detectActivelyGenerating(text) {
91
+ return printableLines(text).slice(-12).some(isCursorBusyLine);
92
+ }
93
+
94
+ module.exports = {
95
+ ...baseDetector,
96
+ id: 'cursor',
97
+ // Cursor redraws prompt/footer regions periodically; hold the provider only
98
+ // for meaningful activity frames, mirroring the OpenCode debounce.
99
+ idleDebounceMs: 10000,
100
+
101
+ isActiveChunk(data) {
102
+ if (!baseDetector.isActiveChunk(data)) return false;
103
+ const lines = printableLines(data);
104
+ if (lines.length && lines.every(line => isCursorPassiveLine(line) && !isCursorBusyLine(line))) {
105
+ return false;
106
+ }
107
+ return true;
108
+ },
109
+
110
+ isStatusOnlyChunk(data) {
111
+ const lines = printableLines(data);
112
+ return !!(lines.length && lines.every(line => isCursorPassiveLine(line) || isCursorBusyLine(line)));
113
+ },
114
+
115
+ isBusyStatusChunk(data) {
116
+ const lines = printableLines(data);
117
+ return lines.some(isCursorBusyLine);
118
+ },
119
+
120
+ detectWaitingInput,
121
+ detectActivelyGenerating,
122
+ _test: {
123
+ printableLines,
124
+ isCursorPassiveLine,
125
+ isCursorBusyLine,
126
+ },
127
+ };