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.
- package/README.md +27 -5
- package/package.json +2 -2
- package/template/CLAUDE.md +2 -2
- package/template/LICENSE +1 -1
- package/template/bin/ctm-dev-cleanup.js +24 -3
- package/template/bin/ctm-launch.sh +13 -0
- package/template/bin/dev.sh +156 -18
- package/template/bin/node-bin.sh +84 -0
- package/template/bin/pin-node.sh +51 -0
- package/template/claude-task-manager/api-prompts.js +1203 -182
- package/template/claude-task-manager/api-reviews.js +109 -15
- package/template/claude-task-manager/approval-agent.js +1360 -280
- package/template/claude-task-manager/bin/restart-ctm.sh +64 -23
- package/template/claude-task-manager/bin/storage-migration-supervisor.js +338 -0
- package/template/claude-task-manager/db.js +4417 -295
- package/template/claude-task-manager/docs/app-update-refresh-protocol.md +69 -0
- package/template/claude-task-manager/docs/approval-ai-refinement.md +138 -0
- package/template/claude-task-manager/docs/approval-rescue-loop.md +74 -0
- package/template/claude-task-manager/docs/codex-operational-warning-health.md +107 -0
- package/template/claude-task-manager/docs/codex-resume-state-guard-design.md +17 -12
- package/template/claude-task-manager/docs/codex-terminal-render-controller-handoff.md +311 -0
- package/template/claude-task-manager/docs/coding-agent-hooks-architecture.md +418 -0
- package/template/claude-task-manager/docs/conversation-import-freshness.md +20 -0
- package/template/claude-task-manager/docs/google-workspace-auth-health.md +77 -0
- package/template/claude-task-manager/docs/image-paste-ux.md +13 -0
- package/template/claude-task-manager/docs/ipad-web-preview.md +88 -0
- package/template/claude-task-manager/docs/main-loop-offload-architecture.md +66 -0
- package/template/claude-task-manager/docs/microsoft-dev-tunnel-phone-access-design.md +274 -519
- package/template/claude-task-manager/docs/mobile-live-streaming.md +27 -5
- package/template/claude-task-manager/docs/mobile-remote-submission-lifecycle.md +69 -0
- package/template/claude-task-manager/docs/phone-access-design.md +53 -15
- package/template/claude-task-manager/docs/phone-passkey-identity.md +122 -0
- package/template/claude-task-manager/docs/phone-setup.md +3 -0
- package/template/claude-task-manager/docs/prompt-editing-tree-design.md +25 -1
- package/template/claude-task-manager/docs/remote-desktop-access-design.md +268 -0
- package/template/claude-task-manager/docs/restart-lifecycle-architecture.md +95 -0
- package/template/claude-task-manager/docs/runtime-work-control-plane.md +53 -0
- package/template/claude-task-manager/docs/session-interactive-wait-surfaces.md +38 -0
- package/template/claude-task-manager/docs/session-needs-you-dismissal.md +84 -0
- package/template/claude-task-manager/docs/session-render-state-management-design.md +91 -3
- package/template/claude-task-manager/docs/session-standup-command-center-design.md +25 -1
- package/template/claude-task-manager/docs/session-title-authority.md +32 -0
- package/template/claude-task-manager/docs/session-workspace-binding.md +33 -0
- package/template/claude-task-manager/docs/skill-intent-resolution-design.md +72 -0
- package/template/claude-task-manager/docs/walle-mcp-supervisor-health.md +86 -0
- package/template/claude-task-manager/docs/walle-relay-phone-access-design.md +24 -15
- package/template/claude-task-manager/docs/walle-session-history-hydration.md +114 -0
- package/template/claude-task-manager/docs/walle-session-input-queue.md +104 -0
- package/template/claude-task-manager/docs/walle-session-model-catalog.md +90 -0
- package/template/claude-task-manager/docs/walle-session-model-preferences.md +15 -6
- package/template/claude-task-manager/git-utils.js +897 -27
- package/template/claude-task-manager/lib/agent-capabilities.js +33 -0
- package/template/claude-task-manager/lib/agent-cli-cache.js +37 -7
- package/template/claude-task-manager/lib/agent-hooks-installer.js +26 -2
- package/template/claude-task-manager/lib/agent-presets.js +17 -1
- package/template/claude-task-manager/lib/all-sessions-query.js +108 -0
- package/template/claude-task-manager/lib/approval-ai-refinement.js +488 -0
- package/template/claude-task-manager/lib/approval-self-adapt.js +168 -0
- package/template/claude-task-manager/lib/async-semaphore.js +44 -0
- package/template/claude-task-manager/lib/auth-context.js +5 -0
- package/template/claude-task-manager/lib/auth-rate-limit.js +47 -4
- package/template/claude-task-manager/lib/auth-rules.js +29 -2
- package/template/claude-task-manager/lib/auto-approval-verifier.js +129 -16
- package/template/claude-task-manager/lib/background-llm.js +144 -17
- package/template/claude-task-manager/lib/branch-inventory.js +212 -0
- package/template/claude-task-manager/lib/claude-desktop-sessions.js +15 -3
- package/template/claude-task-manager/lib/coalesce-sync-frames.js +151 -0
- package/template/claude-task-manager/lib/codex-launch-health.js +762 -0
- package/template/claude-task-manager/lib/codex-transcript-pager.js +51 -0
- package/template/claude-task-manager/lib/codex-zst.js +124 -0
- package/template/claude-task-manager/lib/coding-agent-models.js +233 -30
- package/template/claude-task-manager/lib/connection-health.js +232 -0
- package/template/claude-task-manager/lib/conversation-blob-parser.js +42 -0
- package/template/claude-task-manager/lib/conversation-tail-merge.js +89 -26
- package/template/claude-task-manager/lib/ctm-session-context-api.js +39 -10
- package/template/claude-task-manager/lib/cursor-conversation-store.js +354 -0
- package/template/claude-task-manager/lib/db-owner-worker-client.js +315 -0
- package/template/claude-task-manager/lib/document-review.js +141 -6
- package/template/claude-task-manager/lib/escalation-review.js +152 -0
- package/template/claude-task-manager/lib/graceful-shutdown.js +159 -0
- package/template/claude-task-manager/lib/headless-term-service.js +678 -0
- package/template/claude-task-manager/lib/heavy-worker-fallback.js +38 -0
- package/template/claude-task-manager/lib/jsonl-conversation-parser.js +542 -0
- package/template/claude-task-manager/lib/jsonl-range-reader.js +112 -0
- package/template/claude-task-manager/lib/main-db-census.js +216 -0
- package/template/claude-task-manager/lib/message-pagination.js +106 -4
- package/template/claude-task-manager/lib/microsoft-dev-tunnel-setup.js +750 -26
- package/template/claude-task-manager/lib/mobile-auth-api.js +274 -7
- package/template/claude-task-manager/lib/mobile-auth-store.js +592 -10
- package/template/claude-task-manager/lib/mobile-notification-dispatcher.js +15 -0
- package/template/claude-task-manager/lib/model-overview-brain-fallback.js +311 -0
- package/template/claude-task-manager/lib/model-overview-cache.js +141 -0
- package/template/claude-task-manager/lib/models-health-routing-notice.js +126 -0
- package/template/claude-task-manager/lib/node-pin-guard.js +93 -0
- package/template/claude-task-manager/lib/perf-tracker.js +242 -6
- package/template/claude-task-manager/lib/permission-match.js +76 -0
- package/template/claude-task-manager/lib/permission-sync.js +133 -20
- package/template/claude-task-manager/lib/process-title.js +35 -0
- package/template/claude-task-manager/lib/prompt-executions-query.js +25 -0
- package/template/claude-task-manager/lib/prompt-index-disk-cache.js +44 -0
- package/template/claude-task-manager/lib/prompt-intent.js +132 -0
- package/template/claude-task-manager/lib/provider-user-context.js +34 -0
- package/template/claude-task-manager/lib/read-pool-client.js +313 -0
- package/template/claude-task-manager/lib/readpool-breaker.js +31 -0
- package/template/claude-task-manager/lib/recent-sessions-breaker.js +12 -0
- package/template/claude-task-manager/lib/remote-feedback-client.js +72 -0
- package/template/claude-task-manager/lib/remote-relay-protocol.js +37 -4
- package/template/claude-task-manager/lib/remote-relay-store.js +159 -0
- package/template/claude-task-manager/lib/remote-submission-observer.js +278 -0
- package/template/claude-task-manager/lib/restart-guard.js +109 -0
- package/template/claude-task-manager/lib/restore-interruption-detector.js +439 -0
- package/template/claude-task-manager/lib/restore-policy.js +13 -0
- package/template/claude-task-manager/lib/restore-resume-batch.js +74 -0
- package/template/claude-task-manager/lib/restore-runtime.js +68 -0
- package/template/claude-task-manager/lib/restore-storm.js +34 -0
- package/template/claude-task-manager/lib/resume-cwd.js +36 -0
- package/template/claude-task-manager/lib/resume-preflight.js +313 -0
- package/template/claude-task-manager/lib/runtime-work-registry.js +444 -0
- package/template/claude-task-manager/lib/sanitize-openai-auth.js +31 -0
- package/template/claude-task-manager/lib/scheduler.js +21 -1
- package/template/claude-task-manager/lib/scrollback-snapshot-store.js +159 -0
- package/template/claude-task-manager/lib/serial-task-queue.js +64 -0
- package/template/claude-task-manager/lib/server-listeners.js +239 -0
- package/template/claude-task-manager/lib/session-capture.js +42 -7
- package/template/claude-task-manager/lib/session-content-backfill.js +131 -0
- package/template/claude-task-manager/lib/session-history.js +388 -43
- package/template/claude-task-manager/lib/session-host-manager.js +287 -0
- package/template/claude-task-manager/lib/session-image-refs.js +209 -0
- package/template/claude-task-manager/lib/session-jobs.js +399 -59
- package/template/claude-task-manager/lib/session-prompt-index.js +137 -0
- package/template/claude-task-manager/lib/session-restore.js +53 -0
- package/template/claude-task-manager/lib/session-standup.js +123 -23
- package/template/claude-task-manager/lib/session-state-bus.js +14 -0
- package/template/claude-task-manager/lib/session-stream.js +64 -16
- package/template/claude-task-manager/lib/session-timeline-summary.js +260 -0
- package/template/claude-task-manager/lib/session-token-usage.js +494 -0
- package/template/claude-task-manager/lib/session-workspace-binding.js +356 -0
- package/template/claude-task-manager/lib/setup-network-config.js +9 -0
- package/template/claude-task-manager/lib/size-cap.js +45 -0
- package/template/claude-task-manager/lib/size-cap.test.js +62 -0
- package/template/claude-task-manager/lib/skill-autocomplete.js +180 -1
- package/template/claude-task-manager/lib/skill-intent-resolver.js +304 -0
- package/template/claude-task-manager/lib/sqlite-driver.js +19 -3
- package/template/claude-task-manager/lib/standup-attention.js +7 -3
- package/template/claude-task-manager/lib/status-authority.js +39 -0
- package/template/claude-task-manager/lib/status-hooks.js +4 -0
- package/template/claude-task-manager/lib/storage-migration.js +235 -0
- package/template/claude-task-manager/lib/structured-capture.js +298 -0
- package/template/claude-task-manager/lib/sync-io-census.js +163 -0
- package/template/claude-task-manager/lib/tailscale-setup.js +6 -0
- package/template/claude-task-manager/lib/terminal-activity-evidence.js +33 -0
- package/template/claude-task-manager/lib/terminal-choice.js +364 -0
- package/template/claude-task-manager/lib/terminal-control-sanitize.js +17 -0
- package/template/claude-task-manager/lib/terminal-fingerprint.js +48 -0
- package/template/claude-task-manager/lib/terminal-output-flush.js +84 -0
- package/template/claude-task-manager/lib/timeline-order.js +122 -0
- package/template/claude-task-manager/lib/transcript-store.js +348 -43
- package/template/claude-task-manager/lib/transport-security.js +84 -1
- package/template/claude-task-manager/lib/wait-state.js +184 -0
- package/template/claude-task-manager/lib/walle-client.js +47 -5
- package/template/claude-task-manager/lib/walle-ctm-history.js +564 -4
- package/template/claude-task-manager/lib/walle-external-actions.js +135 -16
- package/template/claude-task-manager/lib/walle-history-hydration.js +46 -0
- package/template/claude-task-manager/lib/walle-native-health.js +403 -0
- package/template/claude-task-manager/lib/walle-repair.js +701 -0
- package/template/claude-task-manager/lib/walle-session-cache.js +109 -0
- package/template/claude-task-manager/lib/walle-session-context.js +57 -21
- package/template/claude-task-manager/lib/walle-session-model-catalog.js +34 -0
- package/template/claude-task-manager/lib/walle-supervisor.js +539 -63
- package/template/claude-task-manager/lib/walle-transcript.js +52 -0
- package/template/claude-task-manager/lib/worktree-active-sync.js +11 -7
- package/template/claude-task-manager/lib/worktree-cwd.js +32 -1
- package/template/claude-task-manager/package.json +1 -1
- package/template/claude-task-manager/prompt-harvest.js +89 -66
- package/template/claude-task-manager/providers/claude-code.js +51 -3
- package/template/claude-task-manager/providers/cursor.js +140 -45
- package/template/claude-task-manager/public/css/reviews.css +551 -61
- package/template/claude-task-manager/public/css/setup.css +191 -0
- package/template/claude-task-manager/public/css/walle-session.css +865 -10
- package/template/claude-task-manager/public/css/walle.css +154 -0
- package/template/claude-task-manager/public/designs/ai-providers-consolidation-v2.html +830 -0
- package/template/claude-task-manager/public/index.html +18516 -2058
- package/template/claude-task-manager/public/ipad.html +363 -0
- package/template/claude-task-manager/public/js/document-review-links.js +301 -0
- package/template/claude-task-manager/public/js/image-normalize.js +69 -36
- package/template/claude-task-manager/public/js/message-renderer.js +1265 -77
- package/template/claude-task-manager/public/js/prompts.js +66 -29
- package/template/claude-task-manager/public/js/reviews.js +901 -133
- package/template/claude-task-manager/public/js/session-activity-utils.js +11 -1
- package/template/claude-task-manager/public/js/session-search-utils.js +94 -10
- package/template/claude-task-manager/public/js/session-status-precedence.js +23 -5
- package/template/claude-task-manager/public/js/setup.js +1273 -176
- package/template/claude-task-manager/public/js/stream-view.js +691 -73
- package/template/claude-task-manager/public/js/terminal-reconciler.js +210 -0
- package/template/claude-task-manager/public/js/walle-session.js +2455 -158
- package/template/claude-task-manager/public/js/walle.js +455 -28
- package/template/claude-task-manager/public/m/app.css +2909 -262
- package/template/claude-task-manager/public/m/app.js +6601 -398
- package/template/claude-task-manager/public/m/claim.html +224 -17
- package/template/claude-task-manager/public/m/index.html +117 -21
- package/template/claude-task-manager/public/m/sw.js +3 -1
- package/template/claude-task-manager/public/manifest.json +2 -2
- package/template/claude-task-manager/public/prompts.html +30 -14
- package/template/claude-task-manager/queue-engine.js +507 -28
- package/template/claude-task-manager/scripts/repair-claude-session-images.js +27 -8
- package/template/claude-task-manager/server.js +14341 -2197
- package/template/claude-task-manager/session-integrity.js +160 -18
- package/template/claude-task-manager/session-search-ranking.js +1 -0
- package/template/claude-task-manager/session-utils.js +25 -5
- package/template/claude-task-manager/workers/approval-blocklist.js +96 -6
- package/template/claude-task-manager/workers/approval-widget-validator.js +14 -8
- package/template/claude-task-manager/workers/conversation-import-worker.js +11 -50
- package/template/claude-task-manager/workers/db-owner-worker.js +386 -0
- package/template/claude-task-manager/workers/harvest-worker.js +9 -55
- package/template/claude-task-manager/workers/headless-term-worker.js +9 -530
- package/template/claude-task-manager/workers/read-pool-worker.js +387 -0
- package/template/claude-task-manager/workers/scrollback-worker.js +11 -72
- package/template/claude-task-manager/workers/session-host-process.js +146 -0
- package/template/claude-task-manager/workers/session-integrity-worker.js +10 -54
- package/template/claude-task-manager/workers/state-detectors/base.js +18 -1
- package/template/claude-task-manager/workers/state-detectors/claude-code.js +182 -9
- package/template/claude-task-manager/workers/state-detectors/codex.js +150 -2
- package/template/claude-task-manager/workers/state-detectors/cursor.js +127 -0
- package/template/claude-task-manager/workers/state-detectors/gemini.js +21 -0
- package/template/claude-task-manager/workers/state-detectors/index.js +29 -0
- package/template/claude-task-manager/workers/state-detectors/opencode.js +103 -0
- package/template/docs/design/markdown-review-pane.md +206 -0
- package/template/docs/designs/2026-05-17-portkey-gateway-provider-ux.md +129 -38
- package/template/docs/designs/2026-05-20-mobile-worktree-finish-command.md +27 -0
- package/template/docs/designs/2026-05-22-ai-configuration-consolidation.md +248 -0
- package/template/docs/designs/ai-configuration-consolidation-mock.html +812 -0
- package/template/docs/private-memory-and-pii-policy.md +69 -0
- package/template/package.json +2 -1
- package/template/scripts/check-private-data.js +201 -0
- package/template/shared/sqlite-owner-guard.js +30 -0
- package/template/shared/sqlite-owner-write-queue.js +225 -0
- package/template/shared/sqlite-storage-policy.js +111 -0
- package/template/shared/sqlite-write-lock.js +428 -0
- package/template/wall-e/agent-runners/claude-code.js +5 -0
- package/template/wall-e/agent.js +166 -22
- package/template/wall-e/api-walle.js +524 -70
- package/template/wall-e/auth/provider-flows.js +11 -1
- package/template/wall-e/bin/walle-mcp-stdio.js +341 -17
- package/template/wall-e/brain.js +1614 -141
- package/template/wall-e/chat/attachment-blocks.js +96 -0
- package/template/wall-e/chat/attachments.js +2 -1
- package/template/wall-e/chat/capability-resolver.js +7 -7
- package/template/wall-e/chat/context-messages.js +28 -0
- package/template/wall-e/chat/conversation-frame.js +630 -0
- package/template/wall-e/chat/provider-messages.js +125 -0
- package/template/wall-e/chat.js +1002 -233
- package/template/wall-e/coding/acceptance-contract.js +170 -0
- package/template/wall-e/coding/acp-adapter.js +1 -1
- package/template/wall-e/coding/agent-catalog.js +3 -0
- package/template/wall-e/coding/artifact-store.js +93 -0
- package/template/wall-e/coding/capability-router.js +120 -0
- package/template/wall-e/coding/coding-run-controller.js +423 -0
- package/template/wall-e/coding/compaction-service.js +157 -12
- package/template/wall-e/coding/frontend-verification.js +258 -0
- package/template/wall-e/coding/lifecycle-hooks.js +75 -0
- package/template/wall-e/coding/local-preview-contract.js +157 -0
- package/template/wall-e/coding/permission-service.js +57 -13
- package/template/wall-e/coding/prompt-bundle.js +19 -1
- package/template/wall-e/coding/prompt-section-registry.js +227 -0
- package/template/wall-e/coding/provider-compat.js +15 -0
- package/template/wall-e/coding/runtime-events.js +224 -0
- package/template/wall-e/coding/runtime-mode.js +3 -0
- package/template/wall-e/coding/side-git-snapshot.js +160 -4
- package/template/wall-e/coding/snapshot-service.js +143 -1
- package/template/wall-e/coding/stream-processor.js +388 -34
- package/template/wall-e/coding/task-tool.js +141 -4
- package/template/wall-e/coding/tool-execution-controller.js +365 -0
- package/template/wall-e/coding/tool-registry.js +43 -5
- package/template/wall-e/coding/user-hooks.js +217 -0
- package/template/wall-e/coding-orchestrator.js +1330 -221
- package/template/wall-e/coding-prompts.js +20 -4
- package/template/wall-e/context/context-builder.js +15 -2
- package/template/wall-e/decision/confidence.js +1 -1
- package/template/wall-e/docs/coding-acceptance-contract.md +41 -0
- package/template/wall-e/docs/external-action-controller.md +26 -6
- package/template/wall-e/docs/telemetry-lifecycle.md +8 -2
- package/template/wall-e/embeddings.js +591 -53
- package/template/wall-e/external-action-controller.js +12 -0
- package/template/wall-e/http/auth.js +1 -0
- package/template/wall-e/http/chat-api.js +46 -11
- package/template/wall-e/http/model-admin.js +836 -34
- package/template/wall-e/lib/boot-profile.js +88 -0
- package/template/wall-e/lib/event-loop-monitor.js +93 -0
- package/template/wall-e/lib/service-health.js +194 -0
- package/template/wall-e/llm/anthropic.js +130 -5
- package/template/wall-e/llm/client.js +266 -63
- package/template/wall-e/llm/default-fallback.js +382 -0
- package/template/wall-e/llm/health.js +19 -0
- package/template/wall-e/llm/message-guard.js +78 -0
- package/template/wall-e/llm/model-catalog.js +252 -1
- package/template/wall-e/llm/openai.js +26 -4
- package/template/wall-e/llm/portkey-sync.js +654 -0
- package/template/wall-e/llm/provider-error.js +30 -2
- package/template/wall-e/llm/registry.js +5 -1
- package/template/wall-e/llm/request-compat.js +67 -0
- package/template/wall-e/loops/backfill.js +79 -23
- package/template/wall-e/loops/brain-optimize.js +67 -0
- package/template/wall-e/loops/ingest.js +25 -10
- package/template/wall-e/loops/question-digest.js +160 -0
- package/template/wall-e/loops/reflect.js +6 -4
- package/template/wall-e/loops/think.js +39 -12
- package/template/wall-e/mcp-server.js +318 -36
- package/template/wall-e/memory/ctm-context-client.js +52 -14
- package/template/wall-e/memory/ctm-operational-context.js +237 -0
- package/template/wall-e/memory/ctm-prompt-executions-client.js +128 -0
- package/template/wall-e/memory/ctm-session-context.js +111 -63
- package/template/wall-e/prompts/coding/deepseek.txt +3 -0
- package/template/wall-e/prompts/coding/gemini.txt +6 -0
- package/template/wall-e/prompts/coding/gpt.txt +6 -0
- package/template/wall-e/prompts/coding/local.txt +7 -0
- package/template/wall-e/runtime/decision-hooks.js +115 -0
- package/template/wall-e/runtime/devbox-gateway.js +82 -8
- package/template/wall-e/runtime/prompt-manifest.js +86 -0
- package/template/wall-e/runtime/tool-executor.js +269 -0
- package/template/wall-e/runtime/tool-result-envelope.js +138 -0
- package/template/wall-e/runtime/transcript-projection.js +60 -0
- package/template/wall-e/runtime/walle-runtime.js +224 -0
- package/template/wall-e/scripts/db-optimize/migrate.js +162 -0
- package/template/wall-e/scripts/db-optimize/recall-eval.js +117 -0
- package/template/wall-e/server.js +15 -0
- package/template/wall-e/session-files.js +9 -0
- package/template/wall-e/skills/_bundled/google-calendar/run.js +1 -1
- package/template/wall-e/skills/_bundled/gws-workspace/run.js +1 -1
- package/template/wall-e/skills/_bundled/slack-mentions/run.js +76 -6
- package/template/wall-e/skills/claude-code-reader.js +7 -3
- package/template/wall-e/skills/script-skill-runner.js +10 -0
- package/template/wall-e/skills/skill-planner.js +38 -0
- package/template/wall-e/tools/builtin-middleware.js +19 -9
- package/template/wall-e/tools/local-tools.js +1428 -16
- package/template/wall-e/tools/permission-checker.js +73 -5
- package/template/wall-e/tools/question-manager.js +117 -7
- package/template/wall-e/training/harvester.js +12 -28
- package/template/wall-e/training/replay.js +25 -80
- package/template/website/index.html +10 -10
- package/template/wall-e/eval/ab-test.js +0 -203
- package/template/wall-e/eval/agent-runner.js +0 -772
- package/template/wall-e/eval/agent-scorer.js +0 -461
- package/template/wall-e/eval/aggregator.js +0 -414
- package/template/wall-e/eval/allowed-test-commands.js +0 -34
- package/template/wall-e/eval/benchmark-generator.js +0 -113
- package/template/wall-e/eval/benchmarks/chat-eval.json +0 -1662
- package/template/wall-e/eval/benchmarks/chat.json +0 -82
- package/template/wall-e/eval/benchmarks/coding-agent-real.json +0 -1
- package/template/wall-e/eval/benchmarks/coding-agent.json +0 -1581
- package/template/wall-e/eval/benchmarks/coding.json +0 -122
- package/template/wall-e/eval/benchmarks/memory-retrieval.json +0 -234
- package/template/wall-e/eval/benchmarks/reasoning.json +0 -82
- package/template/wall-e/eval/benchmarks/swebench-lite-30.json +0 -212
- package/template/wall-e/eval/benchmarks.js +0 -669
- package/template/wall-e/eval/cc-replay.js +0 -719
- package/template/wall-e/eval/chat-eval.js +0 -525
- package/template/wall-e/eval/check-keys.js +0 -15
- package/template/wall-e/eval/check-providers.js +0 -42
- package/template/wall-e/eval/codex-cli-baseline.js +0 -669
- package/template/wall-e/eval/coding-agent-real.js +0 -570
- package/template/wall-e/eval/context-compactor.js +0 -251
- package/template/wall-e/eval/debug-agent003.js +0 -68
- package/template/wall-e/eval/diagnostics.js +0 -216
- package/template/wall-e/eval/eval-orchestrator.js +0 -642
- package/template/wall-e/eval/evaluate.js +0 -202
- package/template/wall-e/eval/evaluator.js +0 -373
- package/template/wall-e/eval/exporter.js +0 -212
- package/template/wall-e/eval/fixtures/express-basic/package.json +0 -9
- package/template/wall-e/eval/fixtures/express-basic/server.js +0 -115
- package/template/wall-e/eval/fixtures/express-basic/test.js +0 -83
- package/template/wall-e/eval/fixtures/express-buggy/package.json +0 -9
- package/template/wall-e/eval/fixtures/express-buggy/server.js +0 -113
- package/template/wall-e/eval/fixtures/express-buggy/test.js +0 -83
- package/template/wall-e/eval/fixtures/express-buggy-items/package.json +0 -9
- package/template/wall-e/eval/fixtures/express-buggy-items/server.js +0 -112
- package/template/wall-e/eval/fixtures/express-buggy-items/test.js +0 -83
- package/template/wall-e/eval/fixtures/express-buggy-search/package.json +0 -9
- package/template/wall-e/eval/fixtures/express-buggy-search/server.js +0 -121
- package/template/wall-e/eval/fixtures/express-buggy-search/test.js +0 -83
- package/template/wall-e/eval/fixtures/express-rename-data/data.js +0 -34
- package/template/wall-e/eval/fixtures/express-rename-data/package.json +0 -9
- package/template/wall-e/eval/fixtures/express-rename-data/server.js +0 -97
- package/template/wall-e/eval/fixtures/express-rename-data/test.js +0 -88
- package/template/wall-e/eval/fixtures/express-xss/package.json +0 -12
- package/template/wall-e/eval/fixtures/express-xss/server.js +0 -90
- package/template/wall-e/eval/fixtures/express-xss/test.js +0 -67
- package/template/wall-e/eval/fixtures/express-xss/views/profile.ejs +0 -9
- package/template/wall-e/eval/fixtures/fullstack-app/config/default.js +0 -9
- package/template/wall-e/eval/fixtures/fullstack-app/config/test.js +0 -13
- package/template/wall-e/eval/fixtures/fullstack-app/package.json +0 -11
- package/template/wall-e/eval/fixtures/fullstack-app/public/css/style.css +0 -137
- package/template/wall-e/eval/fixtures/fullstack-app/public/index.html +0 -46
- package/template/wall-e/eval/fixtures/fullstack-app/public/js/app.js +0 -121
- package/template/wall-e/eval/fixtures/fullstack-app/public/js/auth.js +0 -71
- package/template/wall-e/eval/fixtures/fullstack-app/public/js/items.js +0 -80
- package/template/wall-e/eval/fixtures/fullstack-app/public/js/users.js +0 -46
- package/template/wall-e/eval/fixtures/fullstack-app/public/login.html +0 -45
- package/template/wall-e/eval/fixtures/fullstack-app/public/register.html +0 -38
- package/template/wall-e/eval/fixtures/fullstack-app/scripts/migrate.js +0 -23
- package/template/wall-e/eval/fixtures/fullstack-app/scripts/seed.js +0 -46
- package/template/wall-e/eval/fixtures/fullstack-app/server/db.js +0 -99
- package/template/wall-e/eval/fixtures/fullstack-app/server/index.js +0 -94
- package/template/wall-e/eval/fixtures/fullstack-app/server/middleware/auth.js +0 -19
- package/template/wall-e/eval/fixtures/fullstack-app/server/middleware/logger.js +0 -19
- package/template/wall-e/eval/fixtures/fullstack-app/server/router.js +0 -50
- package/template/wall-e/eval/fixtures/fullstack-app/server/routes/auth.js +0 -69
- package/template/wall-e/eval/fixtures/fullstack-app/server/routes/health.js +0 -23
- package/template/wall-e/eval/fixtures/fullstack-app/server/routes/items.js +0 -88
- package/template/wall-e/eval/fixtures/fullstack-app/server/routes/users.js +0 -75
- package/template/wall-e/eval/fixtures/fullstack-app/server/test.js +0 -198
- package/template/wall-e/eval/fixtures/fullstack-app/server/utils/response.js +0 -34
- package/template/wall-e/eval/fixtures/fullstack-app/server/utils/validate.js +0 -26
- package/template/wall-e/eval/fixtures/fullstack-app/server.js +0 -8
- package/template/wall-e/eval/fixtures/fullstack-app/test.js +0 -12
- package/template/wall-e/eval/fixtures/monorepo-basic/package.json +0 -8
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/data.js +0 -58
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/middleware.js +0 -46
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/package.json +0 -8
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/routes.js +0 -64
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/server.js +0 -56
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/test.js +0 -116
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/commands.js +0 -61
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/index.js +0 -62
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/output.js +0 -43
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/package.json +0 -11
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/test.js +0 -44
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/formatters.js +0 -43
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/index.js +0 -12
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/package.json +0 -5
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/test.js +0 -55
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/validators.js +0 -29
- package/template/wall-e/eval/fixtures/monorepo-basic/test.js +0 -46
- package/template/wall-e/eval/fixtures/node-cli/index.js +0 -78
- package/template/wall-e/eval/fixtures/node-cli/package.json +0 -10
- package/template/wall-e/eval/fixtures/node-cli/test.js +0 -57
- package/template/wall-e/eval/fixtures/node-typed/package.json +0 -8
- package/template/wall-e/eval/fixtures/node-typed/src/handlers.js +0 -31
- package/template/wall-e/eval/fixtures/node-typed/src/utils.js +0 -33
- package/template/wall-e/eval/fixtures/node-typed/test.js +0 -36
- package/template/wall-e/eval/fixtures/python-flask/app.py +0 -14
- package/template/wall-e/eval/fixtures/python-flask/requirements.txt +0 -2
- package/template/wall-e/eval/fixtures/python-flask/test_app.py +0 -25
- package/template/wall-e/eval/fixtures/wall-e-subset/brain.js +0 -105
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/aggregator.js +0 -101
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/benchmarks/chat.json +0 -20
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/benchmarks/coding.json +0 -32
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/benchmarks.js +0 -64
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/package.json +0 -6
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/server.js +0 -31
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/test.js +0 -18
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/utils.js +0 -34
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/runner.js +0 -104
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/scorer.js +0 -73
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/test.js +0 -134
- package/template/wall-e/eval/fixtures/wall-e-subset/llm/client.js +0 -99
- package/template/wall-e/eval/fixtures/wall-e-subset/llm/providers.js +0 -63
- package/template/wall-e/eval/fixtures/wall-e-subset/llm/test.js +0 -70
- package/template/wall-e/eval/fixtures/wall-e-subset/package.json +0 -10
- package/template/wall-e/eval/fixtures/wall-e-subset/test.js +0 -86
- package/template/wall-e/eval/harvester.js +0 -685
- package/template/wall-e/eval/head-to-head.js +0 -388
- package/template/wall-e/eval/humaneval-adapter.js +0 -321
- package/template/wall-e/eval/list-models.js +0 -31
- package/template/wall-e/eval/livecodebench-adapter.js +0 -291
- package/template/wall-e/eval/mail-integration.js +0 -443
- package/template/wall-e/eval/manifest.js +0 -186
- package/template/wall-e/eval/meta-harness/adapters/coding-agent.js +0 -57
- package/template/wall-e/eval/meta-harness/bootstrap-snapshot.js +0 -149
- package/template/wall-e/eval/meta-harness/candidate-store.js +0 -117
- package/template/wall-e/eval/meta-harness/cli.js +0 -86
- package/template/wall-e/eval/meta-harness/domain-spec.js +0 -154
- package/template/wall-e/eval/meta-harness/domains/coding-agent.domain.json +0 -84
- package/template/wall-e/eval/meta-harness/examples/env-bootstrap-candidate.js +0 -29
- package/template/wall-e/eval/meta-harness/experience-store.js +0 -174
- package/template/wall-e/eval/meta-harness/frontier.js +0 -96
- package/template/wall-e/eval/meta-harness/harness-interface.js +0 -90
- package/template/wall-e/eval/meta-harness/leakage-guard.js +0 -80
- package/template/wall-e/eval/meta-harness/optimizer.js +0 -207
- package/template/wall-e/eval/meta-harness/proposer-runner.js +0 -110
- package/template/wall-e/eval/meta-harness/reporting.js +0 -58
- package/template/wall-e/eval/meta-harness/telemetry.js +0 -27
- package/template/wall-e/eval/meta-harness/validation.js +0 -81
- package/template/wall-e/eval/promoter.js +0 -228
- package/template/wall-e/eval/provider-normalizer.js +0 -33
- package/template/wall-e/eval/replay.js +0 -395
- package/template/wall-e/eval/run-agent-benchmarks.js +0 -386
- package/template/wall-e/eval/run-codex-cli-baseline.js +0 -177
- package/template/wall-e/eval/run-coding-agent-real.js +0 -187
- package/template/wall-e/eval/run-eval.js +0 -435
- package/template/wall-e/eval/run-model-comparison.js +0 -142
- package/template/wall-e/eval/session-evaluator.js +0 -187
- package/template/wall-e/eval/session-miner.js +0 -207
- package/template/wall-e/eval/session-retrieval-benchmark.js +0 -150
- package/template/wall-e/eval/session-transcripts.js +0 -509
- package/template/wall-e/eval/shadow.js +0 -161
- package/template/wall-e/eval/swebench-adapter.js +0 -345
- package/template/wall-e/eval/swebench-docker.js +0 -192
- package/template/wall-e/eval/train.py +0 -320
- package/template/wall-e/eval/trainer.js +0 -232
- 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 };
|