create-walle 0.9.21 → 0.9.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (500) hide show
  1. package/README.md +27 -5
  2. package/package.json +2 -2
  3. package/template/CLAUDE.md +2 -2
  4. package/template/LICENSE +1 -1
  5. package/template/bin/ctm-dev-cleanup.js +24 -3
  6. package/template/bin/ctm-launch.sh +13 -0
  7. package/template/bin/dev.sh +156 -18
  8. package/template/bin/node-bin.sh +84 -0
  9. package/template/bin/pin-node.sh +51 -0
  10. package/template/claude-task-manager/api-prompts.js +1203 -182
  11. package/template/claude-task-manager/api-reviews.js +109 -15
  12. package/template/claude-task-manager/approval-agent.js +1360 -280
  13. package/template/claude-task-manager/bin/restart-ctm.sh +64 -23
  14. package/template/claude-task-manager/bin/storage-migration-supervisor.js +338 -0
  15. package/template/claude-task-manager/db.js +4417 -295
  16. package/template/claude-task-manager/docs/app-update-refresh-protocol.md +69 -0
  17. package/template/claude-task-manager/docs/approval-ai-refinement.md +138 -0
  18. package/template/claude-task-manager/docs/approval-rescue-loop.md +74 -0
  19. package/template/claude-task-manager/docs/codex-operational-warning-health.md +107 -0
  20. package/template/claude-task-manager/docs/codex-resume-state-guard-design.md +17 -12
  21. package/template/claude-task-manager/docs/codex-terminal-render-controller-handoff.md +311 -0
  22. package/template/claude-task-manager/docs/coding-agent-hooks-architecture.md +418 -0
  23. package/template/claude-task-manager/docs/conversation-import-freshness.md +20 -0
  24. package/template/claude-task-manager/docs/google-workspace-auth-health.md +77 -0
  25. package/template/claude-task-manager/docs/image-paste-ux.md +13 -0
  26. package/template/claude-task-manager/docs/ipad-web-preview.md +88 -0
  27. package/template/claude-task-manager/docs/main-loop-offload-architecture.md +66 -0
  28. package/template/claude-task-manager/docs/microsoft-dev-tunnel-phone-access-design.md +274 -519
  29. package/template/claude-task-manager/docs/mobile-live-streaming.md +27 -5
  30. package/template/claude-task-manager/docs/mobile-remote-submission-lifecycle.md +69 -0
  31. package/template/claude-task-manager/docs/phone-access-design.md +53 -15
  32. package/template/claude-task-manager/docs/phone-passkey-identity.md +122 -0
  33. package/template/claude-task-manager/docs/phone-setup.md +3 -0
  34. package/template/claude-task-manager/docs/prompt-editing-tree-design.md +25 -1
  35. package/template/claude-task-manager/docs/remote-desktop-access-design.md +268 -0
  36. package/template/claude-task-manager/docs/restart-lifecycle-architecture.md +95 -0
  37. package/template/claude-task-manager/docs/runtime-work-control-plane.md +53 -0
  38. package/template/claude-task-manager/docs/session-interactive-wait-surfaces.md +38 -0
  39. package/template/claude-task-manager/docs/session-needs-you-dismissal.md +84 -0
  40. package/template/claude-task-manager/docs/session-render-state-management-design.md +91 -3
  41. package/template/claude-task-manager/docs/session-standup-command-center-design.md +25 -1
  42. package/template/claude-task-manager/docs/session-title-authority.md +32 -0
  43. package/template/claude-task-manager/docs/session-workspace-binding.md +33 -0
  44. package/template/claude-task-manager/docs/skill-intent-resolution-design.md +72 -0
  45. package/template/claude-task-manager/docs/walle-mcp-supervisor-health.md +86 -0
  46. package/template/claude-task-manager/docs/walle-relay-phone-access-design.md +24 -15
  47. package/template/claude-task-manager/docs/walle-session-history-hydration.md +114 -0
  48. package/template/claude-task-manager/docs/walle-session-input-queue.md +104 -0
  49. package/template/claude-task-manager/docs/walle-session-model-catalog.md +90 -0
  50. package/template/claude-task-manager/docs/walle-session-model-preferences.md +15 -6
  51. package/template/claude-task-manager/git-utils.js +897 -27
  52. package/template/claude-task-manager/lib/agent-capabilities.js +33 -0
  53. package/template/claude-task-manager/lib/agent-cli-cache.js +37 -7
  54. package/template/claude-task-manager/lib/agent-hooks-installer.js +26 -2
  55. package/template/claude-task-manager/lib/agent-presets.js +17 -1
  56. package/template/claude-task-manager/lib/all-sessions-query.js +108 -0
  57. package/template/claude-task-manager/lib/approval-ai-refinement.js +488 -0
  58. package/template/claude-task-manager/lib/approval-self-adapt.js +168 -0
  59. package/template/claude-task-manager/lib/async-semaphore.js +44 -0
  60. package/template/claude-task-manager/lib/auth-context.js +5 -0
  61. package/template/claude-task-manager/lib/auth-rate-limit.js +47 -4
  62. package/template/claude-task-manager/lib/auth-rules.js +29 -2
  63. package/template/claude-task-manager/lib/auto-approval-verifier.js +129 -16
  64. package/template/claude-task-manager/lib/background-llm.js +144 -17
  65. package/template/claude-task-manager/lib/branch-inventory.js +212 -0
  66. package/template/claude-task-manager/lib/claude-desktop-sessions.js +15 -3
  67. package/template/claude-task-manager/lib/coalesce-sync-frames.js +151 -0
  68. package/template/claude-task-manager/lib/codex-launch-health.js +762 -0
  69. package/template/claude-task-manager/lib/codex-transcript-pager.js +51 -0
  70. package/template/claude-task-manager/lib/codex-zst.js +124 -0
  71. package/template/claude-task-manager/lib/coding-agent-models.js +233 -30
  72. package/template/claude-task-manager/lib/connection-health.js +232 -0
  73. package/template/claude-task-manager/lib/conversation-blob-parser.js +42 -0
  74. package/template/claude-task-manager/lib/conversation-tail-merge.js +89 -26
  75. package/template/claude-task-manager/lib/ctm-session-context-api.js +39 -10
  76. package/template/claude-task-manager/lib/cursor-conversation-store.js +354 -0
  77. package/template/claude-task-manager/lib/db-owner-worker-client.js +315 -0
  78. package/template/claude-task-manager/lib/document-review.js +141 -6
  79. package/template/claude-task-manager/lib/escalation-review.js +152 -0
  80. package/template/claude-task-manager/lib/graceful-shutdown.js +159 -0
  81. package/template/claude-task-manager/lib/headless-term-service.js +678 -0
  82. package/template/claude-task-manager/lib/heavy-worker-fallback.js +38 -0
  83. package/template/claude-task-manager/lib/jsonl-conversation-parser.js +542 -0
  84. package/template/claude-task-manager/lib/jsonl-range-reader.js +112 -0
  85. package/template/claude-task-manager/lib/main-db-census.js +216 -0
  86. package/template/claude-task-manager/lib/message-pagination.js +106 -4
  87. package/template/claude-task-manager/lib/microsoft-dev-tunnel-setup.js +750 -26
  88. package/template/claude-task-manager/lib/mobile-auth-api.js +274 -7
  89. package/template/claude-task-manager/lib/mobile-auth-store.js +592 -10
  90. package/template/claude-task-manager/lib/mobile-notification-dispatcher.js +15 -0
  91. package/template/claude-task-manager/lib/model-overview-brain-fallback.js +311 -0
  92. package/template/claude-task-manager/lib/model-overview-cache.js +141 -0
  93. package/template/claude-task-manager/lib/models-health-routing-notice.js +126 -0
  94. package/template/claude-task-manager/lib/node-pin-guard.js +93 -0
  95. package/template/claude-task-manager/lib/perf-tracker.js +242 -6
  96. package/template/claude-task-manager/lib/permission-match.js +76 -0
  97. package/template/claude-task-manager/lib/permission-sync.js +133 -20
  98. package/template/claude-task-manager/lib/process-title.js +35 -0
  99. package/template/claude-task-manager/lib/prompt-executions-query.js +25 -0
  100. package/template/claude-task-manager/lib/prompt-index-disk-cache.js +44 -0
  101. package/template/claude-task-manager/lib/prompt-intent.js +132 -0
  102. package/template/claude-task-manager/lib/provider-user-context.js +34 -0
  103. package/template/claude-task-manager/lib/read-pool-client.js +313 -0
  104. package/template/claude-task-manager/lib/readpool-breaker.js +31 -0
  105. package/template/claude-task-manager/lib/recent-sessions-breaker.js +12 -0
  106. package/template/claude-task-manager/lib/remote-feedback-client.js +72 -0
  107. package/template/claude-task-manager/lib/remote-relay-protocol.js +37 -4
  108. package/template/claude-task-manager/lib/remote-relay-store.js +159 -0
  109. package/template/claude-task-manager/lib/remote-submission-observer.js +278 -0
  110. package/template/claude-task-manager/lib/restart-guard.js +109 -0
  111. package/template/claude-task-manager/lib/restore-interruption-detector.js +439 -0
  112. package/template/claude-task-manager/lib/restore-policy.js +13 -0
  113. package/template/claude-task-manager/lib/restore-resume-batch.js +74 -0
  114. package/template/claude-task-manager/lib/restore-runtime.js +68 -0
  115. package/template/claude-task-manager/lib/restore-storm.js +34 -0
  116. package/template/claude-task-manager/lib/resume-cwd.js +36 -0
  117. package/template/claude-task-manager/lib/resume-preflight.js +313 -0
  118. package/template/claude-task-manager/lib/runtime-work-registry.js +444 -0
  119. package/template/claude-task-manager/lib/sanitize-openai-auth.js +31 -0
  120. package/template/claude-task-manager/lib/scheduler.js +21 -1
  121. package/template/claude-task-manager/lib/scrollback-snapshot-store.js +159 -0
  122. package/template/claude-task-manager/lib/serial-task-queue.js +64 -0
  123. package/template/claude-task-manager/lib/server-listeners.js +239 -0
  124. package/template/claude-task-manager/lib/session-capture.js +42 -7
  125. package/template/claude-task-manager/lib/session-content-backfill.js +131 -0
  126. package/template/claude-task-manager/lib/session-history.js +388 -43
  127. package/template/claude-task-manager/lib/session-host-manager.js +287 -0
  128. package/template/claude-task-manager/lib/session-image-refs.js +209 -0
  129. package/template/claude-task-manager/lib/session-jobs.js +399 -59
  130. package/template/claude-task-manager/lib/session-prompt-index.js +137 -0
  131. package/template/claude-task-manager/lib/session-restore.js +53 -0
  132. package/template/claude-task-manager/lib/session-standup.js +123 -23
  133. package/template/claude-task-manager/lib/session-state-bus.js +14 -0
  134. package/template/claude-task-manager/lib/session-stream.js +64 -16
  135. package/template/claude-task-manager/lib/session-timeline-summary.js +260 -0
  136. package/template/claude-task-manager/lib/session-token-usage.js +494 -0
  137. package/template/claude-task-manager/lib/session-workspace-binding.js +356 -0
  138. package/template/claude-task-manager/lib/setup-network-config.js +9 -0
  139. package/template/claude-task-manager/lib/size-cap.js +45 -0
  140. package/template/claude-task-manager/lib/size-cap.test.js +62 -0
  141. package/template/claude-task-manager/lib/skill-autocomplete.js +180 -1
  142. package/template/claude-task-manager/lib/skill-intent-resolver.js +304 -0
  143. package/template/claude-task-manager/lib/sqlite-driver.js +19 -3
  144. package/template/claude-task-manager/lib/standup-attention.js +7 -3
  145. package/template/claude-task-manager/lib/status-authority.js +39 -0
  146. package/template/claude-task-manager/lib/status-hooks.js +4 -0
  147. package/template/claude-task-manager/lib/storage-migration.js +235 -0
  148. package/template/claude-task-manager/lib/structured-capture.js +298 -0
  149. package/template/claude-task-manager/lib/sync-io-census.js +163 -0
  150. package/template/claude-task-manager/lib/tailscale-setup.js +6 -0
  151. package/template/claude-task-manager/lib/terminal-activity-evidence.js +33 -0
  152. package/template/claude-task-manager/lib/terminal-choice.js +364 -0
  153. package/template/claude-task-manager/lib/terminal-control-sanitize.js +17 -0
  154. package/template/claude-task-manager/lib/terminal-fingerprint.js +48 -0
  155. package/template/claude-task-manager/lib/terminal-output-flush.js +84 -0
  156. package/template/claude-task-manager/lib/timeline-order.js +122 -0
  157. package/template/claude-task-manager/lib/transcript-store.js +348 -43
  158. package/template/claude-task-manager/lib/transport-security.js +84 -1
  159. package/template/claude-task-manager/lib/wait-state.js +184 -0
  160. package/template/claude-task-manager/lib/walle-client.js +47 -5
  161. package/template/claude-task-manager/lib/walle-ctm-history.js +564 -4
  162. package/template/claude-task-manager/lib/walle-external-actions.js +135 -16
  163. package/template/claude-task-manager/lib/walle-history-hydration.js +46 -0
  164. package/template/claude-task-manager/lib/walle-native-health.js +403 -0
  165. package/template/claude-task-manager/lib/walle-repair.js +701 -0
  166. package/template/claude-task-manager/lib/walle-session-cache.js +109 -0
  167. package/template/claude-task-manager/lib/walle-session-context.js +57 -21
  168. package/template/claude-task-manager/lib/walle-session-model-catalog.js +34 -0
  169. package/template/claude-task-manager/lib/walle-supervisor.js +539 -63
  170. package/template/claude-task-manager/lib/walle-transcript.js +52 -0
  171. package/template/claude-task-manager/lib/worktree-active-sync.js +11 -7
  172. package/template/claude-task-manager/lib/worktree-cwd.js +32 -1
  173. package/template/claude-task-manager/package.json +1 -1
  174. package/template/claude-task-manager/prompt-harvest.js +89 -66
  175. package/template/claude-task-manager/providers/claude-code.js +51 -3
  176. package/template/claude-task-manager/providers/cursor.js +140 -45
  177. package/template/claude-task-manager/public/css/reviews.css +551 -61
  178. package/template/claude-task-manager/public/css/setup.css +191 -0
  179. package/template/claude-task-manager/public/css/walle-session.css +865 -10
  180. package/template/claude-task-manager/public/css/walle.css +154 -0
  181. package/template/claude-task-manager/public/designs/ai-providers-consolidation-v2.html +830 -0
  182. package/template/claude-task-manager/public/index.html +18516 -2058
  183. package/template/claude-task-manager/public/ipad.html +363 -0
  184. package/template/claude-task-manager/public/js/document-review-links.js +301 -0
  185. package/template/claude-task-manager/public/js/image-normalize.js +69 -36
  186. package/template/claude-task-manager/public/js/message-renderer.js +1265 -77
  187. package/template/claude-task-manager/public/js/prompts.js +66 -29
  188. package/template/claude-task-manager/public/js/reviews.js +901 -133
  189. package/template/claude-task-manager/public/js/session-activity-utils.js +11 -1
  190. package/template/claude-task-manager/public/js/session-search-utils.js +94 -10
  191. package/template/claude-task-manager/public/js/session-status-precedence.js +23 -5
  192. package/template/claude-task-manager/public/js/setup.js +1273 -176
  193. package/template/claude-task-manager/public/js/stream-view.js +691 -73
  194. package/template/claude-task-manager/public/js/terminal-reconciler.js +210 -0
  195. package/template/claude-task-manager/public/js/walle-session.js +2455 -158
  196. package/template/claude-task-manager/public/js/walle.js +455 -28
  197. package/template/claude-task-manager/public/m/app.css +2909 -262
  198. package/template/claude-task-manager/public/m/app.js +6601 -398
  199. package/template/claude-task-manager/public/m/claim.html +224 -17
  200. package/template/claude-task-manager/public/m/index.html +117 -21
  201. package/template/claude-task-manager/public/m/sw.js +3 -1
  202. package/template/claude-task-manager/public/manifest.json +2 -2
  203. package/template/claude-task-manager/public/prompts.html +30 -14
  204. package/template/claude-task-manager/queue-engine.js +507 -28
  205. package/template/claude-task-manager/scripts/repair-claude-session-images.js +27 -8
  206. package/template/claude-task-manager/server.js +14341 -2197
  207. package/template/claude-task-manager/session-integrity.js +160 -18
  208. package/template/claude-task-manager/session-search-ranking.js +1 -0
  209. package/template/claude-task-manager/session-utils.js +25 -5
  210. package/template/claude-task-manager/workers/approval-blocklist.js +96 -6
  211. package/template/claude-task-manager/workers/approval-widget-validator.js +14 -8
  212. package/template/claude-task-manager/workers/conversation-import-worker.js +11 -50
  213. package/template/claude-task-manager/workers/db-owner-worker.js +386 -0
  214. package/template/claude-task-manager/workers/harvest-worker.js +9 -55
  215. package/template/claude-task-manager/workers/headless-term-worker.js +9 -530
  216. package/template/claude-task-manager/workers/read-pool-worker.js +387 -0
  217. package/template/claude-task-manager/workers/scrollback-worker.js +11 -72
  218. package/template/claude-task-manager/workers/session-host-process.js +146 -0
  219. package/template/claude-task-manager/workers/session-integrity-worker.js +10 -54
  220. package/template/claude-task-manager/workers/state-detectors/base.js +18 -1
  221. package/template/claude-task-manager/workers/state-detectors/claude-code.js +182 -9
  222. package/template/claude-task-manager/workers/state-detectors/codex.js +150 -2
  223. package/template/claude-task-manager/workers/state-detectors/cursor.js +127 -0
  224. package/template/claude-task-manager/workers/state-detectors/gemini.js +21 -0
  225. package/template/claude-task-manager/workers/state-detectors/index.js +29 -0
  226. package/template/claude-task-manager/workers/state-detectors/opencode.js +103 -0
  227. package/template/docs/design/markdown-review-pane.md +206 -0
  228. package/template/docs/designs/2026-05-17-portkey-gateway-provider-ux.md +129 -38
  229. package/template/docs/designs/2026-05-20-mobile-worktree-finish-command.md +27 -0
  230. package/template/docs/designs/2026-05-22-ai-configuration-consolidation.md +248 -0
  231. package/template/docs/designs/ai-configuration-consolidation-mock.html +812 -0
  232. package/template/docs/private-memory-and-pii-policy.md +69 -0
  233. package/template/package.json +2 -1
  234. package/template/scripts/check-private-data.js +201 -0
  235. package/template/shared/sqlite-owner-guard.js +30 -0
  236. package/template/shared/sqlite-owner-write-queue.js +225 -0
  237. package/template/shared/sqlite-storage-policy.js +111 -0
  238. package/template/shared/sqlite-write-lock.js +428 -0
  239. package/template/wall-e/agent-runners/claude-code.js +5 -0
  240. package/template/wall-e/agent.js +166 -22
  241. package/template/wall-e/api-walle.js +524 -70
  242. package/template/wall-e/auth/provider-flows.js +11 -1
  243. package/template/wall-e/bin/walle-mcp-stdio.js +341 -17
  244. package/template/wall-e/brain.js +1614 -141
  245. package/template/wall-e/chat/attachment-blocks.js +96 -0
  246. package/template/wall-e/chat/attachments.js +2 -1
  247. package/template/wall-e/chat/capability-resolver.js +7 -7
  248. package/template/wall-e/chat/context-messages.js +28 -0
  249. package/template/wall-e/chat/conversation-frame.js +630 -0
  250. package/template/wall-e/chat/provider-messages.js +125 -0
  251. package/template/wall-e/chat.js +1002 -233
  252. package/template/wall-e/coding/acceptance-contract.js +170 -0
  253. package/template/wall-e/coding/acp-adapter.js +1 -1
  254. package/template/wall-e/coding/agent-catalog.js +3 -0
  255. package/template/wall-e/coding/artifact-store.js +93 -0
  256. package/template/wall-e/coding/capability-router.js +120 -0
  257. package/template/wall-e/coding/coding-run-controller.js +423 -0
  258. package/template/wall-e/coding/compaction-service.js +157 -12
  259. package/template/wall-e/coding/frontend-verification.js +258 -0
  260. package/template/wall-e/coding/lifecycle-hooks.js +75 -0
  261. package/template/wall-e/coding/local-preview-contract.js +157 -0
  262. package/template/wall-e/coding/permission-service.js +57 -13
  263. package/template/wall-e/coding/prompt-bundle.js +19 -1
  264. package/template/wall-e/coding/prompt-section-registry.js +227 -0
  265. package/template/wall-e/coding/provider-compat.js +15 -0
  266. package/template/wall-e/coding/runtime-events.js +224 -0
  267. package/template/wall-e/coding/runtime-mode.js +3 -0
  268. package/template/wall-e/coding/side-git-snapshot.js +160 -4
  269. package/template/wall-e/coding/snapshot-service.js +143 -1
  270. package/template/wall-e/coding/stream-processor.js +388 -34
  271. package/template/wall-e/coding/task-tool.js +141 -4
  272. package/template/wall-e/coding/tool-execution-controller.js +365 -0
  273. package/template/wall-e/coding/tool-registry.js +43 -5
  274. package/template/wall-e/coding/user-hooks.js +217 -0
  275. package/template/wall-e/coding-orchestrator.js +1330 -221
  276. package/template/wall-e/coding-prompts.js +20 -4
  277. package/template/wall-e/context/context-builder.js +15 -2
  278. package/template/wall-e/decision/confidence.js +1 -1
  279. package/template/wall-e/docs/coding-acceptance-contract.md +41 -0
  280. package/template/wall-e/docs/external-action-controller.md +26 -6
  281. package/template/wall-e/docs/telemetry-lifecycle.md +8 -2
  282. package/template/wall-e/embeddings.js +591 -53
  283. package/template/wall-e/external-action-controller.js +12 -0
  284. package/template/wall-e/http/auth.js +1 -0
  285. package/template/wall-e/http/chat-api.js +46 -11
  286. package/template/wall-e/http/model-admin.js +836 -34
  287. package/template/wall-e/lib/boot-profile.js +88 -0
  288. package/template/wall-e/lib/event-loop-monitor.js +93 -0
  289. package/template/wall-e/lib/service-health.js +194 -0
  290. package/template/wall-e/llm/anthropic.js +130 -5
  291. package/template/wall-e/llm/client.js +266 -63
  292. package/template/wall-e/llm/default-fallback.js +382 -0
  293. package/template/wall-e/llm/health.js +19 -0
  294. package/template/wall-e/llm/message-guard.js +78 -0
  295. package/template/wall-e/llm/model-catalog.js +252 -1
  296. package/template/wall-e/llm/openai.js +26 -4
  297. package/template/wall-e/llm/portkey-sync.js +654 -0
  298. package/template/wall-e/llm/provider-error.js +30 -2
  299. package/template/wall-e/llm/registry.js +5 -1
  300. package/template/wall-e/llm/request-compat.js +67 -0
  301. package/template/wall-e/loops/backfill.js +79 -23
  302. package/template/wall-e/loops/brain-optimize.js +67 -0
  303. package/template/wall-e/loops/ingest.js +25 -10
  304. package/template/wall-e/loops/question-digest.js +160 -0
  305. package/template/wall-e/loops/reflect.js +6 -4
  306. package/template/wall-e/loops/think.js +39 -12
  307. package/template/wall-e/mcp-server.js +318 -36
  308. package/template/wall-e/memory/ctm-context-client.js +52 -14
  309. package/template/wall-e/memory/ctm-operational-context.js +237 -0
  310. package/template/wall-e/memory/ctm-prompt-executions-client.js +128 -0
  311. package/template/wall-e/memory/ctm-session-context.js +111 -63
  312. package/template/wall-e/prompts/coding/deepseek.txt +3 -0
  313. package/template/wall-e/prompts/coding/gemini.txt +6 -0
  314. package/template/wall-e/prompts/coding/gpt.txt +6 -0
  315. package/template/wall-e/prompts/coding/local.txt +7 -0
  316. package/template/wall-e/runtime/decision-hooks.js +115 -0
  317. package/template/wall-e/runtime/devbox-gateway.js +82 -8
  318. package/template/wall-e/runtime/prompt-manifest.js +86 -0
  319. package/template/wall-e/runtime/tool-executor.js +269 -0
  320. package/template/wall-e/runtime/tool-result-envelope.js +138 -0
  321. package/template/wall-e/runtime/transcript-projection.js +60 -0
  322. package/template/wall-e/runtime/walle-runtime.js +224 -0
  323. package/template/wall-e/scripts/db-optimize/migrate.js +162 -0
  324. package/template/wall-e/scripts/db-optimize/recall-eval.js +117 -0
  325. package/template/wall-e/server.js +15 -0
  326. package/template/wall-e/session-files.js +9 -0
  327. package/template/wall-e/skills/_bundled/google-calendar/run.js +1 -1
  328. package/template/wall-e/skills/_bundled/gws-workspace/run.js +1 -1
  329. package/template/wall-e/skills/_bundled/slack-mentions/run.js +76 -6
  330. package/template/wall-e/skills/claude-code-reader.js +7 -3
  331. package/template/wall-e/skills/script-skill-runner.js +10 -0
  332. package/template/wall-e/skills/skill-planner.js +38 -0
  333. package/template/wall-e/tools/builtin-middleware.js +19 -9
  334. package/template/wall-e/tools/local-tools.js +1428 -16
  335. package/template/wall-e/tools/permission-checker.js +73 -5
  336. package/template/wall-e/tools/question-manager.js +117 -7
  337. package/template/wall-e/training/harvester.js +12 -28
  338. package/template/wall-e/training/replay.js +25 -80
  339. package/template/website/index.html +10 -10
  340. package/template/wall-e/eval/ab-test.js +0 -203
  341. package/template/wall-e/eval/agent-runner.js +0 -772
  342. package/template/wall-e/eval/agent-scorer.js +0 -461
  343. package/template/wall-e/eval/aggregator.js +0 -414
  344. package/template/wall-e/eval/allowed-test-commands.js +0 -34
  345. package/template/wall-e/eval/benchmark-generator.js +0 -113
  346. package/template/wall-e/eval/benchmarks/chat-eval.json +0 -1662
  347. package/template/wall-e/eval/benchmarks/chat.json +0 -82
  348. package/template/wall-e/eval/benchmarks/coding-agent-real.json +0 -1
  349. package/template/wall-e/eval/benchmarks/coding-agent.json +0 -1581
  350. package/template/wall-e/eval/benchmarks/coding.json +0 -122
  351. package/template/wall-e/eval/benchmarks/memory-retrieval.json +0 -234
  352. package/template/wall-e/eval/benchmarks/reasoning.json +0 -82
  353. package/template/wall-e/eval/benchmarks/swebench-lite-30.json +0 -212
  354. package/template/wall-e/eval/benchmarks.js +0 -669
  355. package/template/wall-e/eval/cc-replay.js +0 -719
  356. package/template/wall-e/eval/chat-eval.js +0 -525
  357. package/template/wall-e/eval/check-keys.js +0 -15
  358. package/template/wall-e/eval/check-providers.js +0 -42
  359. package/template/wall-e/eval/codex-cli-baseline.js +0 -669
  360. package/template/wall-e/eval/coding-agent-real.js +0 -570
  361. package/template/wall-e/eval/context-compactor.js +0 -251
  362. package/template/wall-e/eval/debug-agent003.js +0 -68
  363. package/template/wall-e/eval/diagnostics.js +0 -216
  364. package/template/wall-e/eval/eval-orchestrator.js +0 -642
  365. package/template/wall-e/eval/evaluate.js +0 -202
  366. package/template/wall-e/eval/evaluator.js +0 -373
  367. package/template/wall-e/eval/exporter.js +0 -212
  368. package/template/wall-e/eval/fixtures/express-basic/package.json +0 -9
  369. package/template/wall-e/eval/fixtures/express-basic/server.js +0 -115
  370. package/template/wall-e/eval/fixtures/express-basic/test.js +0 -83
  371. package/template/wall-e/eval/fixtures/express-buggy/package.json +0 -9
  372. package/template/wall-e/eval/fixtures/express-buggy/server.js +0 -113
  373. package/template/wall-e/eval/fixtures/express-buggy/test.js +0 -83
  374. package/template/wall-e/eval/fixtures/express-buggy-items/package.json +0 -9
  375. package/template/wall-e/eval/fixtures/express-buggy-items/server.js +0 -112
  376. package/template/wall-e/eval/fixtures/express-buggy-items/test.js +0 -83
  377. package/template/wall-e/eval/fixtures/express-buggy-search/package.json +0 -9
  378. package/template/wall-e/eval/fixtures/express-buggy-search/server.js +0 -121
  379. package/template/wall-e/eval/fixtures/express-buggy-search/test.js +0 -83
  380. package/template/wall-e/eval/fixtures/express-rename-data/data.js +0 -34
  381. package/template/wall-e/eval/fixtures/express-rename-data/package.json +0 -9
  382. package/template/wall-e/eval/fixtures/express-rename-data/server.js +0 -97
  383. package/template/wall-e/eval/fixtures/express-rename-data/test.js +0 -88
  384. package/template/wall-e/eval/fixtures/express-xss/package.json +0 -12
  385. package/template/wall-e/eval/fixtures/express-xss/server.js +0 -90
  386. package/template/wall-e/eval/fixtures/express-xss/test.js +0 -67
  387. package/template/wall-e/eval/fixtures/express-xss/views/profile.ejs +0 -9
  388. package/template/wall-e/eval/fixtures/fullstack-app/config/default.js +0 -9
  389. package/template/wall-e/eval/fixtures/fullstack-app/config/test.js +0 -13
  390. package/template/wall-e/eval/fixtures/fullstack-app/package.json +0 -11
  391. package/template/wall-e/eval/fixtures/fullstack-app/public/css/style.css +0 -137
  392. package/template/wall-e/eval/fixtures/fullstack-app/public/index.html +0 -46
  393. package/template/wall-e/eval/fixtures/fullstack-app/public/js/app.js +0 -121
  394. package/template/wall-e/eval/fixtures/fullstack-app/public/js/auth.js +0 -71
  395. package/template/wall-e/eval/fixtures/fullstack-app/public/js/items.js +0 -80
  396. package/template/wall-e/eval/fixtures/fullstack-app/public/js/users.js +0 -46
  397. package/template/wall-e/eval/fixtures/fullstack-app/public/login.html +0 -45
  398. package/template/wall-e/eval/fixtures/fullstack-app/public/register.html +0 -38
  399. package/template/wall-e/eval/fixtures/fullstack-app/scripts/migrate.js +0 -23
  400. package/template/wall-e/eval/fixtures/fullstack-app/scripts/seed.js +0 -46
  401. package/template/wall-e/eval/fixtures/fullstack-app/server/db.js +0 -99
  402. package/template/wall-e/eval/fixtures/fullstack-app/server/index.js +0 -94
  403. package/template/wall-e/eval/fixtures/fullstack-app/server/middleware/auth.js +0 -19
  404. package/template/wall-e/eval/fixtures/fullstack-app/server/middleware/logger.js +0 -19
  405. package/template/wall-e/eval/fixtures/fullstack-app/server/router.js +0 -50
  406. package/template/wall-e/eval/fixtures/fullstack-app/server/routes/auth.js +0 -69
  407. package/template/wall-e/eval/fixtures/fullstack-app/server/routes/health.js +0 -23
  408. package/template/wall-e/eval/fixtures/fullstack-app/server/routes/items.js +0 -88
  409. package/template/wall-e/eval/fixtures/fullstack-app/server/routes/users.js +0 -75
  410. package/template/wall-e/eval/fixtures/fullstack-app/server/test.js +0 -198
  411. package/template/wall-e/eval/fixtures/fullstack-app/server/utils/response.js +0 -34
  412. package/template/wall-e/eval/fixtures/fullstack-app/server/utils/validate.js +0 -26
  413. package/template/wall-e/eval/fixtures/fullstack-app/server.js +0 -8
  414. package/template/wall-e/eval/fixtures/fullstack-app/test.js +0 -12
  415. package/template/wall-e/eval/fixtures/monorepo-basic/package.json +0 -8
  416. package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/data.js +0 -58
  417. package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/middleware.js +0 -46
  418. package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/package.json +0 -8
  419. package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/routes.js +0 -64
  420. package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/server.js +0 -56
  421. package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/test.js +0 -116
  422. package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/commands.js +0 -61
  423. package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/index.js +0 -62
  424. package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/output.js +0 -43
  425. package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/package.json +0 -11
  426. package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/test.js +0 -44
  427. package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/formatters.js +0 -43
  428. package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/index.js +0 -12
  429. package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/package.json +0 -5
  430. package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/test.js +0 -55
  431. package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/validators.js +0 -29
  432. package/template/wall-e/eval/fixtures/monorepo-basic/test.js +0 -46
  433. package/template/wall-e/eval/fixtures/node-cli/index.js +0 -78
  434. package/template/wall-e/eval/fixtures/node-cli/package.json +0 -10
  435. package/template/wall-e/eval/fixtures/node-cli/test.js +0 -57
  436. package/template/wall-e/eval/fixtures/node-typed/package.json +0 -8
  437. package/template/wall-e/eval/fixtures/node-typed/src/handlers.js +0 -31
  438. package/template/wall-e/eval/fixtures/node-typed/src/utils.js +0 -33
  439. package/template/wall-e/eval/fixtures/node-typed/test.js +0 -36
  440. package/template/wall-e/eval/fixtures/python-flask/app.py +0 -14
  441. package/template/wall-e/eval/fixtures/python-flask/requirements.txt +0 -2
  442. package/template/wall-e/eval/fixtures/python-flask/test_app.py +0 -25
  443. package/template/wall-e/eval/fixtures/wall-e-subset/brain.js +0 -105
  444. package/template/wall-e/eval/fixtures/wall-e-subset/eval/aggregator.js +0 -101
  445. package/template/wall-e/eval/fixtures/wall-e-subset/eval/benchmarks/chat.json +0 -20
  446. package/template/wall-e/eval/fixtures/wall-e-subset/eval/benchmarks/coding.json +0 -32
  447. package/template/wall-e/eval/fixtures/wall-e-subset/eval/benchmarks.js +0 -64
  448. package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/package.json +0 -6
  449. package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/server.js +0 -31
  450. package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/test.js +0 -18
  451. package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/utils.js +0 -34
  452. package/template/wall-e/eval/fixtures/wall-e-subset/eval/runner.js +0 -104
  453. package/template/wall-e/eval/fixtures/wall-e-subset/eval/scorer.js +0 -73
  454. package/template/wall-e/eval/fixtures/wall-e-subset/eval/test.js +0 -134
  455. package/template/wall-e/eval/fixtures/wall-e-subset/llm/client.js +0 -99
  456. package/template/wall-e/eval/fixtures/wall-e-subset/llm/providers.js +0 -63
  457. package/template/wall-e/eval/fixtures/wall-e-subset/llm/test.js +0 -70
  458. package/template/wall-e/eval/fixtures/wall-e-subset/package.json +0 -10
  459. package/template/wall-e/eval/fixtures/wall-e-subset/test.js +0 -86
  460. package/template/wall-e/eval/harvester.js +0 -685
  461. package/template/wall-e/eval/head-to-head.js +0 -388
  462. package/template/wall-e/eval/humaneval-adapter.js +0 -321
  463. package/template/wall-e/eval/list-models.js +0 -31
  464. package/template/wall-e/eval/livecodebench-adapter.js +0 -291
  465. package/template/wall-e/eval/mail-integration.js +0 -443
  466. package/template/wall-e/eval/manifest.js +0 -186
  467. package/template/wall-e/eval/meta-harness/adapters/coding-agent.js +0 -57
  468. package/template/wall-e/eval/meta-harness/bootstrap-snapshot.js +0 -149
  469. package/template/wall-e/eval/meta-harness/candidate-store.js +0 -117
  470. package/template/wall-e/eval/meta-harness/cli.js +0 -86
  471. package/template/wall-e/eval/meta-harness/domain-spec.js +0 -154
  472. package/template/wall-e/eval/meta-harness/domains/coding-agent.domain.json +0 -84
  473. package/template/wall-e/eval/meta-harness/examples/env-bootstrap-candidate.js +0 -29
  474. package/template/wall-e/eval/meta-harness/experience-store.js +0 -174
  475. package/template/wall-e/eval/meta-harness/frontier.js +0 -96
  476. package/template/wall-e/eval/meta-harness/harness-interface.js +0 -90
  477. package/template/wall-e/eval/meta-harness/leakage-guard.js +0 -80
  478. package/template/wall-e/eval/meta-harness/optimizer.js +0 -207
  479. package/template/wall-e/eval/meta-harness/proposer-runner.js +0 -110
  480. package/template/wall-e/eval/meta-harness/reporting.js +0 -58
  481. package/template/wall-e/eval/meta-harness/telemetry.js +0 -27
  482. package/template/wall-e/eval/meta-harness/validation.js +0 -81
  483. package/template/wall-e/eval/promoter.js +0 -228
  484. package/template/wall-e/eval/provider-normalizer.js +0 -33
  485. package/template/wall-e/eval/replay.js +0 -395
  486. package/template/wall-e/eval/run-agent-benchmarks.js +0 -386
  487. package/template/wall-e/eval/run-codex-cli-baseline.js +0 -177
  488. package/template/wall-e/eval/run-coding-agent-real.js +0 -187
  489. package/template/wall-e/eval/run-eval.js +0 -435
  490. package/template/wall-e/eval/run-model-comparison.js +0 -142
  491. package/template/wall-e/eval/session-evaluator.js +0 -187
  492. package/template/wall-e/eval/session-miner.js +0 -207
  493. package/template/wall-e/eval/session-retrieval-benchmark.js +0 -150
  494. package/template/wall-e/eval/session-transcripts.js +0 -509
  495. package/template/wall-e/eval/shadow.js +0 -161
  496. package/template/wall-e/eval/swebench-adapter.js +0 -345
  497. package/template/wall-e/eval/swebench-docker.js +0 -192
  498. package/template/wall-e/eval/train.py +0 -320
  499. package/template/wall-e/eval/trainer.js +0 -232
  500. package/template/wall-e/eval/weekly-eval-loop.js +0 -241
@@ -0,0 +1,235 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+ const crypto = require('crypto');
7
+ const { spawn } = require('child_process');
8
+ const { classifySqlitePathRisk } = require('../../shared/sqlite-storage-policy');
9
+
10
+ const CONTROL_DIR = path.join(os.homedir(), '.walle', 'control');
11
+ const LOCK_FILE = 'storage-migration.lock';
12
+ const JOURNAL_PREFIX = 'storage-migration-';
13
+ const ACTIVE_JOURNAL = 'storage-migration-current.json';
14
+ const STALE_LOCK_MS = 15 * 60 * 1000;
15
+ const HEARTBEAT_STALE_MS = 2 * 60 * 1000;
16
+
17
+ function normalizeUserPath(value) {
18
+ const raw = String(value || '').trim().replace(/[\r\n\0]/g, '');
19
+ if (!raw) return '';
20
+ const expanded = raw === '~'
21
+ ? os.homedir()
22
+ : raw.replace(/^~(?=\/|$)/, os.homedir() || '~');
23
+ return path.resolve(expanded);
24
+ }
25
+
26
+ function ensureDir(dir) {
27
+ fs.mkdirSync(dir, { recursive: true });
28
+ const stat = fs.statSync(dir);
29
+ if (!stat.isDirectory()) throw new Error(`Not a directory: ${dir}`);
30
+ return dir;
31
+ }
32
+
33
+ function safeReadJson(file, fallback = null) {
34
+ try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch { return fallback; }
35
+ }
36
+
37
+ function atomicWriteJson(file, data) {
38
+ ensureDir(path.dirname(file));
39
+ const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
40
+ fs.writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', { mode: 0o600 });
41
+ fs.renameSync(tmp, file);
42
+ }
43
+
44
+ function migrationPaths(env = process.env) {
45
+ const root = normalizeUserPath(env.CTM_STORAGE_CONTROL_DIR || CONTROL_DIR);
46
+ return {
47
+ controlDir: root,
48
+ lockPath: path.join(root, LOCK_FILE),
49
+ activeJournalPath: path.join(root, ACTIVE_JOURNAL),
50
+ };
51
+ }
52
+
53
+ function isProcessAlive(pid) {
54
+ const n = Number(pid);
55
+ if (!Number.isInteger(n) || n <= 0) return false;
56
+ try {
57
+ process.kill(n, 0);
58
+ return true;
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+
64
+ function readActiveMigration(env = process.env, nowMs = Date.now()) {
65
+ const paths = migrationPaths(env);
66
+ const lock = safeReadJson(paths.lockPath, null);
67
+ const journal = safeReadJson(paths.activeJournalPath, null);
68
+ if (!lock && !journal) return { active: false, paths };
69
+ const state = String(lock?.state || journal?.state || '').toLowerCase();
70
+ const pid = Number(lock?.pid || journal?.supervisor_pid || 0);
71
+ const heartbeatAt = Date.parse(lock?.heartbeat_at || journal?.heartbeat_at || lock?.updated_at || journal?.updated_at || 0);
72
+ const startedAt = Date.parse(lock?.started_at || journal?.started_at || 0);
73
+ const pidAlive = isProcessAlive(pid);
74
+ const heartbeatFresh = Number.isFinite(heartbeatAt) && (nowMs - heartbeatAt) < HEARTBEAT_STALE_MS;
75
+ const recentlyStarted = Number.isFinite(startedAt) && (nowMs - startedAt) < STALE_LOCK_MS;
76
+ const terminal = ['completed', 'reverted', 'failed'].includes(state);
77
+ const allowStart = lock?.allow_start === true || journal?.allow_start === true;
78
+ const active = !terminal && !allowStart && pidAlive && (heartbeatFresh || recentlyStarted);
79
+ return { active, stale: !terminal && !allowStart && !active, terminal, lock, journal, paths, pidAlive, heartbeatFresh, state, allow_start: allowStart };
80
+ }
81
+
82
+ function startupGuard(label, env = process.env) {
83
+ const current = readActiveMigration(env);
84
+ if (!current.active) return { ok: true, active: false, stale: current.stale || false };
85
+ const err = new Error(`${label || 'process'} paused for active storage migration`);
86
+ err.code = 'CTM_STORAGE_MIGRATION_ACTIVE';
87
+ err.migration = current;
88
+ return { ok: false, active: true, error: err };
89
+ }
90
+
91
+ function serviceCurrentFromDb(dbModule) {
92
+ const dbPath = dbModule.getDbPath ? dbModule.getDbPath() : '';
93
+ const backupInfo = dbModule.getBackupDirInfo ? dbModule.getBackupDirInfo() : { backup_dir: dbModule.getBackupDir?.() || '' };
94
+ return {
95
+ data_dir: dbPath ? path.dirname(dbPath) : '',
96
+ db_path: dbPath || '',
97
+ db_file: 'task-manager.db',
98
+ backup_dir: backupInfo.backup_dir || '',
99
+ default_backup_dir: backupInfo.default_backup_dir || '',
100
+ configured_backup_dir: backupInfo.configured_backup_dir || '',
101
+ backup_dir_source: backupInfo.source || 'default',
102
+ };
103
+ }
104
+
105
+ function normalizeServiceInput(raw = {}, current = {}) {
106
+ const dataDir = normalizeUserPath(raw.data_dir || raw.dataDir || current.data_dir);
107
+ const backupDir = normalizeUserPath(raw.backup_dir || raw.backupDir || current.backup_dir);
108
+ return {
109
+ data_dir: dataDir,
110
+ backup_dir: backupDir,
111
+ move_backups: raw.move_backups !== false && raw.moveBackups !== false,
112
+ };
113
+ }
114
+
115
+ function _riskForDir(dir) {
116
+ try {
117
+ const risk = classifySqlitePathRisk(path.join(dir, 'probe.db'), process.env);
118
+ return risk && risk.risky ? { level: risk.level || 'warn', reason: risk.reason || risk.message || 'risky storage' } : null;
119
+ } catch {
120
+ return null;
121
+ }
122
+ }
123
+
124
+ function buildStorageMigrationPlan(input = {}, deps = {}) {
125
+ const env = deps.env || process.env;
126
+ const ctmCurrent = deps.ctmCurrent || serviceCurrentFromDb(deps.dbModule || {});
127
+ const walleCurrent = deps.walleCurrent || {};
128
+ const requested = input.services || {};
129
+ const services = {};
130
+ const warnings = [];
131
+ const now = new Date();
132
+ const id = `storage-${now.toISOString().replace(/[:.]/g, '-')}-${crypto.randomBytes(4).toString('hex')}`;
133
+
134
+ for (const [name, dbFile] of [['ctm', 'task-manager.db'], ['walle', 'wall-e-brain.db']]) {
135
+ const current = name === 'ctm' ? ctmCurrent : walleCurrent;
136
+ const next = normalizeServiceInput(requested[name] || {}, current);
137
+ const currentDataDir = normalizeUserPath(current.data_dir || (current.db_path ? path.dirname(current.db_path) : ''));
138
+ const currentBackupDir = normalizeUserPath(current.backup_dir || '');
139
+ const dataDirChanged = !!next.data_dir && !!currentDataDir && path.resolve(next.data_dir) !== path.resolve(currentDataDir);
140
+ const backupDirChanged = !!next.backup_dir && !!currentBackupDir && path.resolve(next.backup_dir) !== path.resolve(currentBackupDir);
141
+ const service = {
142
+ name,
143
+ label: name === 'ctm' ? 'CTM' : 'Wall-E',
144
+ db_file: dbFile,
145
+ current_data_dir: currentDataDir,
146
+ target_data_dir: next.data_dir || currentDataDir,
147
+ current_db_path: current.db_path || path.join(currentDataDir, dbFile),
148
+ target_db_path: path.join(next.data_dir || currentDataDir, dbFile),
149
+ current_backup_dir: currentBackupDir,
150
+ target_backup_dir: next.backup_dir || currentBackupDir,
151
+ data_dir_changed: dataDirChanged,
152
+ backup_dir_changed: backupDirChanged,
153
+ move_backups: !!next.move_backups,
154
+ };
155
+ const risk = service.target_data_dir ? _riskForDir(service.target_data_dir) : null;
156
+ if (risk) {
157
+ warnings.push({
158
+ service: name,
159
+ type: 'sqlite_storage',
160
+ message: `${service.label} live SQLite target may be unsafe: ${risk.reason}`,
161
+ });
162
+ }
163
+ services[name] = service;
164
+ }
165
+
166
+ const dataDirChanges = Object.values(services).filter(s => s.data_dir_changed);
167
+ const backupChanges = Object.values(services).filter(s => s.backup_dir_changed);
168
+ return {
169
+ id,
170
+ created_at: now.toISOString(),
171
+ state: 'planned',
172
+ control_dir: normalizeUserPath(env.CTM_STORAGE_CONTROL_DIR || CONTROL_DIR),
173
+ ctm_pid: Number(env.CTM_PID || process.pid),
174
+ services,
175
+ requires_restart: dataDirChanges.length > 0,
176
+ data_dir_change_count: dataDirChanges.length,
177
+ backup_dir_change_count: backupChanges.length,
178
+ warnings,
179
+ steps: [
180
+ ...(backupChanges.length && !dataDirChanges.length ? ['move_backup_snapshots'] : []),
181
+ ...(dataDirChanges.length ? [
182
+ 'write_journal',
183
+ 'stop_wall_e',
184
+ 'stop_ctm',
185
+ 'copy_live_data',
186
+ 'verify_sqlite_images',
187
+ ...(backupChanges.length ? ['copy_backup_snapshots'] : []),
188
+ 'update_env',
189
+ 'restart_ctm',
190
+ 'restart_wall_e',
191
+ ] : []),
192
+ ],
193
+ };
194
+ }
195
+
196
+ function writeMigrationPlan(plan, env = process.env) {
197
+ const paths = migrationPaths(env);
198
+ ensureDir(paths.controlDir);
199
+ const journalPath = path.join(paths.controlDir, `${JOURNAL_PREFIX}${plan.id}.json`);
200
+ const withPaths = { ...plan, journal_path: journalPath };
201
+ atomicWriteJson(journalPath, withPaths);
202
+ atomicWriteJson(paths.activeJournalPath, withPaths);
203
+ return { ...withPaths, journal_path: journalPath, active_journal_path: paths.activeJournalPath };
204
+ }
205
+
206
+ function spawnStorageMigrationSupervisor(plan, opts = {}) {
207
+ const written = writeMigrationPlan(plan, opts.env || process.env);
208
+ const script = path.resolve(__dirname, '..', 'bin', 'storage-migration-supervisor.js');
209
+ const args = ['--journal', written.journal_path];
210
+ if (opts.dryRun) args.push('--dry-run');
211
+ const child = spawn(process.execPath, [script, ...args], {
212
+ detached: true,
213
+ stdio: 'ignore',
214
+ env: { ...process.env, ...(opts.env || {}) },
215
+ });
216
+ child.unref();
217
+ return { ok: true, id: written.id, pid: child.pid, journal_path: written.journal_path };
218
+ }
219
+
220
+ module.exports = {
221
+ CONTROL_DIR,
222
+ LOCK_FILE,
223
+ STALE_LOCK_MS,
224
+ HEARTBEAT_STALE_MS,
225
+ normalizeUserPath,
226
+ ensureDir,
227
+ migrationPaths,
228
+ readActiveMigration,
229
+ startupGuard,
230
+ buildStorageMigrationPlan,
231
+ writeMigrationPlan,
232
+ spawnStorageMigrationSupervisor,
233
+ atomicWriteJson,
234
+ safeReadJson,
235
+ };
@@ -0,0 +1,298 @@
1
+ 'use strict';
2
+
3
+ // Structured capture: provider-neutral structured messages for coding-agent
4
+ // transcripts (Claude Code JSONL, Codex rollouts). A structured message is a
5
+ // normal {role, text, timestamp} message plus a `metadata` object describing
6
+ // what the text represents (tool call, tool result, reasoning, patch, ...).
7
+ //
8
+ // Contract notes the parsers and renderer rely on:
9
+ // - role is ALWAYS 'system' so every role-based heuristic (turn starts,
10
+ // exclusions, prompt index, title pickers) keeps working unchanged.
11
+ // - `text` is a human-readable fallback: legacy clients and FTS index it, and
12
+ // the renderer uses it when it doesn't recognize `metadata.kind`.
13
+ // - metadata NEVER carries parentUuid/uuid — conversation-tail-merge reads
14
+ // those as dedup identity keys and would collapse structured messages into
15
+ // their sibling assistant message.
16
+ // - tool_call / tool_result are separate messages paired at RENDER time via
17
+ // metadata.callId; pairing at parse time would require mutating
18
+ // already-persisted rows, breaking the append-only row-store fast path.
19
+
20
+ const STRUCTURED_META_VERSION = 1;
21
+
22
+ const ARGS_PREVIEW_CAP = 2048;
23
+ const TOOL_RESULT_TEXT_CAP = 4096;
24
+ const REASONING_TEXT_CAP = 8192;
25
+ const PATCH_TEXT_CAP = 16384;
26
+ const COMPACT_SUMMARY_CAP = 8192;
27
+ const STRUCTURED_PATCH_HUNK_LINE_CAP = 400;
28
+ const RAW_JSON_CAP = 4096;
29
+ const SHELL_COMMAND_CAP = 2048;
30
+
31
+ function structuredCaptureEnabled() {
32
+ return process.env.CTM_STRUCTURED_CAPTURE !== '0';
33
+ }
34
+
35
+ function capText(value, cap) {
36
+ const text = String(value == null ? '' : value);
37
+ const limit = Number.isFinite(cap) && cap > 0 ? Math.floor(cap) : text.length;
38
+ if (text.length <= limit) {
39
+ return { text, truncated: false, originalBytes: Buffer.byteLength(text, 'utf8') };
40
+ }
41
+ return {
42
+ text: text.slice(0, limit),
43
+ truncated: true,
44
+ originalBytes: Buffer.byteLength(text, 'utf8'),
45
+ };
46
+ }
47
+
48
+ const PATCH_SENTINEL = '*** Begin Patch';
49
+ const PATCH_FILE_RE = /^\*\*\* (?:Update|Add|Delete) File: (.+)$/gm;
50
+
51
+ function patchFilesFromText(patchText) {
52
+ const files = [];
53
+ const seen = new Set();
54
+ let match;
55
+ PATCH_FILE_RE.lastIndex = 0;
56
+ while ((match = PATCH_FILE_RE.exec(patchText)) !== null) {
57
+ const file = match[1].trim();
58
+ if (file && !seen.has(file)) { seen.add(file); files.push(file); }
59
+ }
60
+ return files;
61
+ }
62
+
63
+ // Detect an apply_patch payload in any of the three forms Codex emits:
64
+ // 1. function_call name="apply_patch" whose `arguments` is a JSON string
65
+ // (or pre-parsed object) shaped {"input": "*** Begin Patch..."}.
66
+ // 2. custom_tool_call with the raw patch text as `input`.
67
+ // 3. a shell call whose argv is ["apply_patch", "*** Begin Patch..."] or
68
+ // whose command string embeds the patch sentinel.
69
+ function detectApplyPatch({ name, argumentsJson, input, command } = {}) {
70
+ const fromText = (text) => {
71
+ const patchText = String(text == null ? '' : text);
72
+ if (!patchText.includes(PATCH_SENTINEL)) return null;
73
+ const start = patchText.indexOf(PATCH_SENTINEL);
74
+ const trimmed = patchText.slice(start);
75
+ return { patchText: trimmed, files: patchFilesFromText(trimmed) };
76
+ };
77
+
78
+ if (String(name || '').trim() === 'apply_patch' && argumentsJson != null) {
79
+ let args = argumentsJson;
80
+ if (typeof args === 'string') {
81
+ try { args = JSON.parse(args); } catch { return fromText(args); }
82
+ }
83
+ if (args && typeof args === 'object') {
84
+ const found = fromText(args.input != null ? args.input : args.patch);
85
+ if (found) return found;
86
+ }
87
+ }
88
+
89
+ if (input != null) {
90
+ const found = fromText(input);
91
+ if (found) return found;
92
+ }
93
+
94
+ if (Array.isArray(command)) {
95
+ if (String(command[0] || '').trim().endsWith('apply_patch')) {
96
+ for (const part of command.slice(1)) {
97
+ const found = fromText(part);
98
+ if (found) return found;
99
+ }
100
+ }
101
+ const joined = command.join(' ');
102
+ if (joined.includes(PATCH_SENTINEL)) return fromText(joined);
103
+ } else if (typeof command === 'string' && command.includes(PATCH_SENTINEL)) {
104
+ return fromText(command);
105
+ }
106
+
107
+ return null;
108
+ }
109
+
110
+ // Bound a Claude `toolUseResult.structuredPatch` (array of jsdiff-style hunks
111
+ // {oldStart, oldLines, newStart, newLines, lines}) to a total line budget so a
112
+ // giant edit can't bloat a stored row.
113
+ function summarizeStructuredPatch(structuredPatch) {
114
+ if (!Array.isArray(structuredPatch) || !structuredPatch.length) return null;
115
+ const out = [];
116
+ let budget = STRUCTURED_PATCH_HUNK_LINE_CAP;
117
+ let truncated = false;
118
+ for (const hunk of structuredPatch) {
119
+ if (!hunk || typeof hunk !== 'object') continue;
120
+ if (budget <= 0) { truncated = true; break; }
121
+ const lines = Array.isArray(hunk.lines) ? hunk.lines : [];
122
+ let kept = lines;
123
+ if (lines.length > budget) {
124
+ kept = lines.slice(0, budget);
125
+ truncated = true;
126
+ }
127
+ budget -= kept.length;
128
+ out.push({
129
+ oldStart: hunk.oldStart,
130
+ oldLines: hunk.oldLines,
131
+ newStart: hunk.newStart,
132
+ newLines: hunk.newLines,
133
+ lines: kept.map(l => String(l)),
134
+ });
135
+ }
136
+ if (!out.length) return null;
137
+ return { structuredPatch: out, truncated };
138
+ }
139
+
140
+ function buildMessage(kind, provider, text, timestamp, fields) {
141
+ const metadata = { v: STRUCTURED_META_VERSION, kind };
142
+ if (provider) metadata.provider = provider;
143
+ for (const [key, value] of Object.entries(fields || {})) {
144
+ if (value === undefined || value === null || value === '') continue;
145
+ metadata[key] = value;
146
+ }
147
+ const message = { role: 'system', text };
148
+ if (timestamp !== undefined && timestamp !== null && timestamp !== '') {
149
+ message.timestamp = timestamp;
150
+ }
151
+ message.metadata = metadata;
152
+ return message;
153
+ }
154
+
155
+ function toolCallMessage({ provider, tool, callId, args, timestamp } = {}) {
156
+ const toolName = String(tool || '').trim() || 'tool';
157
+ let argsText = '';
158
+ if (typeof args === 'string') argsText = args;
159
+ else if (args !== undefined && args !== null) {
160
+ try { argsText = JSON.stringify(args); } catch { argsText = String(args); }
161
+ }
162
+ const preview = capText(argsText, ARGS_PREVIEW_CAP);
163
+ return buildMessage('tool_call', provider, `[Tool: ${toolName}]`, timestamp, {
164
+ tool: toolName,
165
+ callId,
166
+ argsPreview: preview.text,
167
+ truncated: preview.truncated || undefined,
168
+ originalBytes: preview.truncated ? preview.originalBytes : undefined,
169
+ });
170
+ }
171
+
172
+ function toolResultMessage({
173
+ provider, tool, callId, output, exitCode, durationMs,
174
+ filePath, structuredPatch, isError, timestamp,
175
+ } = {}) {
176
+ const capped = capText(output, TOOL_RESULT_TEXT_CAP);
177
+ const label = String(tool || '').trim();
178
+ const text = `[Tool result${label ? `: ${label}` : ''}]${capped.text ? `\n${capped.text}` : ''}`;
179
+ const patch = summarizeStructuredPatch(structuredPatch);
180
+ return buildMessage('tool_result', provider, text, timestamp, {
181
+ tool: label || undefined,
182
+ callId,
183
+ exitCode: Number.isFinite(exitCode) ? exitCode : undefined,
184
+ durationMs: Number.isFinite(durationMs) ? durationMs : undefined,
185
+ filePath,
186
+ structuredPatch: patch ? patch.structuredPatch : undefined,
187
+ isError: isError === true || undefined,
188
+ truncated: (capped.truncated || (patch && patch.truncated)) || undefined,
189
+ originalBytes: capped.truncated ? capped.originalBytes : undefined,
190
+ });
191
+ }
192
+
193
+ function shellMessage({ provider, command, callId, timestamp } = {}) {
194
+ const commandText = Array.isArray(command) ? command.join(' ') : String(command == null ? '' : command);
195
+ const capped = capText(commandText, SHELL_COMMAND_CAP);
196
+ return buildMessage('shell', provider, `[Shell] ${capped.text}`, timestamp, {
197
+ command: capped.text,
198
+ callId,
199
+ truncated: capped.truncated || undefined,
200
+ originalBytes: capped.truncated ? capped.originalBytes : undefined,
201
+ });
202
+ }
203
+
204
+ function patchMessage({ provider, patchText, files, structuredPatch, filePath, callId, timestamp } = {}) {
205
+ const capped = capText(patchText, PATCH_TEXT_CAP);
206
+ const fileList = Array.isArray(files) && files.length
207
+ ? files
208
+ : (capped.text ? patchFilesFromText(capped.text) : []);
209
+ const header = fileList.length ? `[Patch] ${fileList.join(', ')}` : '[Patch]';
210
+ const patch = summarizeStructuredPatch(structuredPatch);
211
+ return buildMessage('patch', provider, `${header}${capped.text ? `\n${capped.text}` : ''}`, timestamp, {
212
+ files: fileList.length ? fileList : undefined,
213
+ filePath,
214
+ callId,
215
+ structuredPatch: patch ? patch.structuredPatch : undefined,
216
+ truncated: (capped.truncated || (patch && patch.truncated)) || undefined,
217
+ originalBytes: capped.truncated ? capped.originalBytes : undefined,
218
+ });
219
+ }
220
+
221
+ function reasoningMessage({ provider, text, encrypted, timestamp } = {}) {
222
+ if (encrypted && !String(text || '').trim()) {
223
+ return buildMessage('reasoning', provider, '[Reasoning (encrypted)]', timestamp, {
224
+ encrypted: true,
225
+ });
226
+ }
227
+ const capped = capText(text, REASONING_TEXT_CAP);
228
+ return buildMessage('reasoning', provider, `[Reasoning]${capped.text ? `\n${capped.text}` : ''}`, timestamp, {
229
+ truncated: capped.truncated || undefined,
230
+ originalBytes: capped.truncated ? capped.originalBytes : undefined,
231
+ });
232
+ }
233
+
234
+ function webSearchMessage({ provider, query, timestamp } = {}) {
235
+ const queryText = capText(query, ARGS_PREVIEW_CAP);
236
+ return buildMessage('web_search', provider, `[Web search] ${queryText.text}`.trim(), timestamp, {
237
+ query: queryText.text || undefined,
238
+ });
239
+ }
240
+
241
+ function compactBoundaryMessage({ provider, trigger, preTokens, logicalParentUuid, timestamp } = {}) {
242
+ return buildMessage('compact_boundary', provider, '[Conversation compacted]', timestamp, {
243
+ trigger: String(trigger || '').trim() || undefined,
244
+ preTokens: Number.isFinite(preTokens) ? preTokens : undefined,
245
+ logicalParentUuid: String(logicalParentUuid || '').trim() || undefined,
246
+ });
247
+ }
248
+
249
+ function compactSummaryMessage({ provider, text, timestamp } = {}) {
250
+ const capped = capText(text, COMPACT_SUMMARY_CAP);
251
+ return buildMessage('compact_summary', provider, capped.text, timestamp, {
252
+ truncated: capped.truncated || undefined,
253
+ originalBytes: capped.truncated ? capped.originalBytes : undefined,
254
+ });
255
+ }
256
+
257
+ // Forward-compat: an entry type the parser does not recognize. The capped raw
258
+ // JSON keeps it inspectable in the UI instead of silently dropped.
259
+ function unknownMessage({ provider, kindHint, raw, timestamp } = {}) {
260
+ let rawText = '';
261
+ if (typeof raw === 'string') rawText = raw;
262
+ else if (raw !== undefined && raw !== null) {
263
+ try { rawText = JSON.stringify(raw); } catch { rawText = String(raw); }
264
+ }
265
+ const capped = capText(rawText, RAW_JSON_CAP);
266
+ const hint = String(kindHint || '').trim();
267
+ return buildMessage('unknown', provider, `[Unrecorded event${hint ? `: ${hint}` : ''}]`, timestamp, {
268
+ kindHint: hint || undefined,
269
+ raw: capped.text || undefined,
270
+ truncated: capped.truncated || undefined,
271
+ originalBytes: capped.truncated ? capped.originalBytes : undefined,
272
+ });
273
+ }
274
+
275
+ module.exports = {
276
+ STRUCTURED_META_VERSION,
277
+ ARGS_PREVIEW_CAP,
278
+ TOOL_RESULT_TEXT_CAP,
279
+ REASONING_TEXT_CAP,
280
+ PATCH_TEXT_CAP,
281
+ COMPACT_SUMMARY_CAP,
282
+ STRUCTURED_PATCH_HUNK_LINE_CAP,
283
+ RAW_JSON_CAP,
284
+ SHELL_COMMAND_CAP,
285
+ structuredCaptureEnabled,
286
+ capText,
287
+ detectApplyPatch,
288
+ summarizeStructuredPatch,
289
+ toolCallMessage,
290
+ toolResultMessage,
291
+ shellMessage,
292
+ patchMessage,
293
+ reasoningMessage,
294
+ webSearchMessage,
295
+ compactBoundaryMessage,
296
+ compactSummaryMessage,
297
+ unknownMessage,
298
+ };
@@ -0,0 +1,163 @@
1
+ 'use strict';
2
+
3
+ // Synchronous-I/O census — the companion to lib/main-db-census.js for the OTHER half of the
4
+ // boot freeze.
5
+ //
6
+ // THE PROBLEM IT SOLVES: after the DB title hog (rowForCtmId) was fixed, the boot CPU profile
7
+ // went 87% IDLE during the freeze. "Idle" in a CPU profile means the main thread is blocked
8
+ // in a synchronous I/O SYSCALL — off-CPU, so a CPU sampler structurally CANNOT name it. The
9
+ // remaining 20s+ event-loop blocks are synchronous fs/child_process calls in the restore path
10
+ // (readFileSync of cold JSONL, readdirSync/realpathSync tree walks, spawnSync git) that no
11
+ // CPU profile will ever attribute. This census monkey-patches those sync syscalls (when armed)
12
+ // to time them and name the CALL SITE of every slow one — turning "idle" into a ranked list.
13
+ //
14
+ // SAFETY: OFF by default. HARD RULE: this monkey-patches global fs, so it must NEVER run on
15
+ // the primary — it only arms on a dev/staging instance (which sets DEV_CTM_PORT) with
16
+ // CTM_FS_CENSUS=1. There is no primary-arming path. When disabled, nothing is patched — zero
17
+ // overhead, zero behavior change. Uses performance.now() (no BigInt allocation) and only
18
+ // captures a stack for ops slower than STACK_MS, so even when armed the per-call cost is tiny.
19
+
20
+ const fs = require('fs');
21
+ const child_process = require('child_process');
22
+ const { performance } = require('perf_hooks');
23
+
24
+ const STACK_MS = clampNum(process.env.CTM_FS_CENSUS_STACK_MS, 3, 0.1, 60000);
25
+ const BUDGET_MS = clampNum(process.env.CTM_FS_CENSUS_BUDGET_MS, 100, 1, 600000);
26
+ const MAX_KEYS = clampNum(process.env.CTM_FS_CENSUS_MAX_KEYS, 4000, 16, 100000);
27
+
28
+ function clampNum(raw, dflt, lo, hi) {
29
+ const n = Number(raw);
30
+ if (!Number.isFinite(n)) return dflt;
31
+ return Math.max(lo, Math.min(hi, n));
32
+ }
33
+
34
+ let _enabled = false;
35
+ let _armedReason = '';
36
+ let _patched = false;
37
+ let _startedAt = 0;
38
+ let _budgetWarnings = 0;
39
+ const _byFn = new Map(); // fn → { count, sumMs, maxMs } (every call, cheap)
40
+ const _bySite = new Map(); // `fn @ callsite` → { fn, count, sumMs, maxMs, sampleArg, stack } (slow only)
41
+
42
+ // HARD RULE: this census MONKEY-PATCHES the global fs/child_process sync methods — even more
43
+ // invasive than the DB census. It must NEVER run on the primary. It only arms on a dev/staging
44
+ // instance (which sets DEV_CTM_PORT). No primary-arming path (no sentinel) — staging only.
45
+ function _shouldArm() {
46
+ if (!process.env.DEV_CTM_PORT) return ''; // primary: never arm, full stop
47
+ if (process.env.CTM_FS_CENSUS === '1') return 'env';
48
+ return '';
49
+ }
50
+
51
+ function _captureStack() {
52
+ const raw = new Error().stack || '';
53
+ return raw
54
+ .split('\n')
55
+ .slice(1)
56
+ .filter((l) => !l.includes('sync-io-census.js') && !l.includes('node:internal'))
57
+ .slice(0, 6)
58
+ .map((l) => l.trim())
59
+ .join(' <- ');
60
+ }
61
+
62
+ // Nearest CTM frame in the stack — the line of our code that issued the syscall.
63
+ function _ctmCallSite() {
64
+ const raw = new Error().stack || '';
65
+ for (const line of raw.split('\n').slice(1)) {
66
+ if (line.includes('/claude-task-manager/') && !line.includes('/node_modules/') && !line.includes('sync-io-census.js')) {
67
+ return line.trim().replace(/^at\s+/, '').replace(/.*\/claude-task-manager\//, '');
68
+ }
69
+ }
70
+ return '(non-ctm)';
71
+ }
72
+
73
+ function _argHint(arg) {
74
+ if (typeof arg !== 'string') return '';
75
+ // Keep the tail of the path (most informative) without leaking the full home prefix.
76
+ return arg.length > 80 ? '…' + arg.slice(-80) : arg;
77
+ }
78
+
79
+ function _record(fn, ms, firstArg) {
80
+ let fnSt = _byFn.get(fn);
81
+ if (!fnSt) { fnSt = { count: 0, sumMs: 0, maxMs: 0 }; _byFn.set(fn, fnSt); }
82
+ fnSt.count += 1; fnSt.sumMs += ms; if (ms > fnSt.maxMs) fnSt.maxMs = ms;
83
+
84
+ if (ms >= STACK_MS) {
85
+ const site = _ctmCallSite();
86
+ const key = `${fn} @ ${site}`;
87
+ let st = _bySite.get(key);
88
+ if (!st) {
89
+ if (_bySite.size >= MAX_KEYS) return;
90
+ st = { fn, site, count: 0, sumMs: 0, maxMs: 0, sampleArg: _argHint(firstArg), stack: '' };
91
+ _bySite.set(key, st);
92
+ }
93
+ st.count += 1; st.sumMs += ms; if (ms > st.maxMs) { st.maxMs = ms; st.stack = _captureStack(); st.sampleArg = _argHint(firstArg); }
94
+ if (ms >= BUDGET_MS) {
95
+ _budgetWarnings += 1;
96
+ console.warn(`[fsio-budget] main-thread ${fn}(${st.sampleArg}) blocked ${Math.round(ms)}ms\n ${st.stack}`);
97
+ }
98
+ }
99
+ }
100
+
101
+ // Wrap one sync function on a host object, timing every call. Transparent: same args, same
102
+ // return, same throw behavior — only a timing side effect.
103
+ function _wrap(host, name, label) {
104
+ const orig = host[name];
105
+ if (typeof orig !== 'function') return;
106
+ const wrapped = function (...args) {
107
+ const t0 = performance.now();
108
+ try {
109
+ return orig.apply(this, args);
110
+ } finally {
111
+ _record(label || name, performance.now() - t0, args[0]);
112
+ }
113
+ };
114
+ // Preserve sub-properties (e.g. fs.realpathSync.native).
115
+ for (const k of Object.keys(orig)) { try { wrapped[k] = orig[k]; } catch {} }
116
+ host[name] = wrapped;
117
+ return orig;
118
+ }
119
+
120
+ function arm() {
121
+ if (_enabled) return true;
122
+ const reason = _shouldArm();
123
+ if (!reason) return false;
124
+ _enabled = true;
125
+ _armedReason = reason;
126
+ _startedAt = Date.now();
127
+ if (!_patched) {
128
+ _patched = true;
129
+ for (const n of ['readFileSync', 'readdirSync', 'statSync', 'lstatSync', 'existsSync', 'realpathSync', 'writeFileSync', 'readlinkSync', 'openSync']) {
130
+ _wrap(fs, n);
131
+ }
132
+ // realpathSync.native is a hot path (it's its own function on the wrapper now).
133
+ try { if (fs.realpathSync && typeof fs.realpathSync.native === 'function') _wrap(fs.realpathSync, 'native', 'realpathSync.native'); } catch {}
134
+ for (const n of ['spawnSync', 'execSync', 'execFileSync']) { _wrap(child_process, n); }
135
+ }
136
+ console.warn(`[fsio-census] ARMED (${reason}) — timing synchronous fs/child_process syscalls; `
137
+ + `budget=${BUDGET_MS}ms stackThreshold=${STACK_MS}ms. GET /api/diagnostics/fs-census to read.`);
138
+ return true;
139
+ }
140
+
141
+ function getCensus({ limit = 60 } = {}) {
142
+ const sites = [..._bySite.values()]
143
+ .map((s) => ({ ...s, sumMs: +s.sumMs.toFixed(1), maxMs: +s.maxMs.toFixed(1) }))
144
+ .sort((a, b) => b.sumMs - a.sumMs)
145
+ .slice(0, Math.max(1, limit));
146
+ const byFn = [..._byFn.entries()]
147
+ .map(([fn, s]) => ({ fn, count: s.count, sumMs: +s.sumMs.toFixed(1), maxMs: +s.maxMs.toFixed(1) }))
148
+ .sort((a, b) => b.sumMs - a.sumMs);
149
+ return {
150
+ enabled: _enabled,
151
+ armedReason: _armedReason,
152
+ sinceMs: _startedAt ? Date.now() - _startedAt : 0,
153
+ budgetMs: BUDGET_MS,
154
+ stackThresholdMs: STACK_MS,
155
+ budgetWarnings: _budgetWarnings,
156
+ byFn,
157
+ topSites: sites,
158
+ };
159
+ }
160
+
161
+ function isEnabled() { return _enabled; }
162
+
163
+ module.exports = { arm, getCensus, isEnabled };