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
|
@@ -18,8 +18,12 @@ const { verifyCandidateForTab, readFirstUserMessage, wordOverlap } = require('./
|
|
|
18
18
|
const { verifyLineage } = require('./session-lineage.js');
|
|
19
19
|
const claudeDesktopSessions = require('./claude-desktop-sessions');
|
|
20
20
|
const { detectAgentType, normalizeAgentType } = require('./agent-capabilities');
|
|
21
|
-
const {
|
|
21
|
+
const {
|
|
22
|
+
resolveRestoreArgsFromAgentSessions,
|
|
23
|
+
resolveRestoreCwdFromAgentSession,
|
|
24
|
+
} = require('./session-restore');
|
|
22
25
|
const { findCodexSessionFilesByFilename } = require('./session-history');
|
|
26
|
+
const { runSync } = require('./perf-tracker');
|
|
23
27
|
|
|
24
28
|
const HOSTNAME = os.hostname();
|
|
25
29
|
const fsp = fs.promises;
|
|
@@ -58,6 +62,26 @@ function statFileIfReadable(filePath) {
|
|
|
58
62
|
}
|
|
59
63
|
}
|
|
60
64
|
|
|
65
|
+
// Short-TTL readdir cache: the session-relink scan readdir's a Claude project dir per STALE
|
|
66
|
+
// session, so multiple stale sessions sharing a project dir each re-listed it within one tick.
|
|
67
|
+
// Coalesce those (1.5s TTL) to one readdirSync per dir per scan. Project-dir contents change
|
|
68
|
+
// slowly (a new JSONL appears on /resume), so a brief TTL is safe.
|
|
69
|
+
const _relinkReaddirCache = new Map(); // dir -> { at, files }
|
|
70
|
+
const _RELINK_READDIR_TTL_MS = Math.max(0, Number(process.env.CTM_RELINK_READDIR_TTL_MS) || 1500);
|
|
71
|
+
function _cachedRelinkReaddir(dir) {
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
if (_RELINK_READDIR_TTL_MS > 0) {
|
|
74
|
+
const hit = _relinkReaddirCache.get(dir);
|
|
75
|
+
if (hit && (now - hit.at) < _RELINK_READDIR_TTL_MS) return hit.files;
|
|
76
|
+
}
|
|
77
|
+
const files = fs.readdirSync(dir);
|
|
78
|
+
if (_RELINK_READDIR_TTL_MS > 0) {
|
|
79
|
+
_relinkReaddirCache.set(dir, { at: now, files });
|
|
80
|
+
if (_relinkReaddirCache.size > 256) _relinkReaddirCache.delete(_relinkReaddirCache.keys().next().value);
|
|
81
|
+
}
|
|
82
|
+
return files;
|
|
83
|
+
}
|
|
84
|
+
|
|
61
85
|
function nextCursorAfterBatch(start, batchLength, totalRows, deletedRows = 0) {
|
|
62
86
|
if (batchLength <= 0 || start + batchLength >= totalRows) return 0;
|
|
63
87
|
return Math.max(0, start + batchLength - Math.max(0, deletedRows));
|
|
@@ -82,6 +106,48 @@ function resolveAgentSessionJsonlInfo(row, opts = {}) {
|
|
|
82
106
|
return null;
|
|
83
107
|
}
|
|
84
108
|
|
|
109
|
+
// Same resolution as resolveAgentSessionJsonlInfo, but the primary-path stat comes from a
|
|
110
|
+
// pre-computed off-thread `statFiles` result (Map<statPath,{exists,size,mtimeMs}>) instead of a
|
|
111
|
+
// synchronous fs.statSync on the main loop. Only the rare codex readdir fallback (primary path
|
|
112
|
+
// missing on a codex row) drops to main. The synthetic stat carries the {size, mtime} the caller
|
|
113
|
+
// reads (mtime is a Date so `.toISOString()` matches the live-stat path).
|
|
114
|
+
function resolveJsonlInfoFromStatMap(row, statMap) {
|
|
115
|
+
const storedPath = row?.jsonl_path || '';
|
|
116
|
+
const statPath = claudeDesktopSessions.sourcePathForStat(storedPath);
|
|
117
|
+
const st = statPath ? statMap.get(statPath) : null;
|
|
118
|
+
if (st && st.exists) {
|
|
119
|
+
return { filePath: storedPath, stat: { size: st.size, mtime: new Date(st.mtimeMs) } };
|
|
120
|
+
}
|
|
121
|
+
const provider = normalizeAgentType(row?.provider || '') || row?.provider || '';
|
|
122
|
+
if (provider === 'codex' && row?.agent_session_id) {
|
|
123
|
+
// Primary path absent on a codex row → the only alternate location is a readdir scan; do it
|
|
124
|
+
// on main (rare). Skip the redundant primary statSync we already know missed.
|
|
125
|
+
return resolveAgentSessionJsonlInfo(row);
|
|
126
|
+
}
|
|
127
|
+
return null; // non-codex miss → no file (matches resolveAgentSessionJsonlInfo → null)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function parsedThreadLineage(parsed = {}) {
|
|
131
|
+
return {
|
|
132
|
+
threadSource: String(parsed.threadSource || parsed.thread_source || '').trim().toLowerCase(),
|
|
133
|
+
parentAgentSessionId: String(
|
|
134
|
+
parsed.parentAgentSessionId
|
|
135
|
+
|| parsed.parent_agent_session_id
|
|
136
|
+
|| parsed.parentThreadId
|
|
137
|
+
|| parsed.parent_thread_id
|
|
138
|
+
|| ''
|
|
139
|
+
).trim(),
|
|
140
|
+
agentNickname: String(parsed.agentNickname || parsed.agent_nickname || '').trim(),
|
|
141
|
+
agentRole: String(parsed.agentRole || parsed.agent_role || '').trim(),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function isProviderChildParsedSession(parsed = {}) {
|
|
146
|
+
if (normalizeAgentType(parsed.agent || parsed.provider || '') !== 'codex') return false;
|
|
147
|
+
const lineage = parsedThreadLineage(parsed);
|
|
148
|
+
return lineage.threadSource === 'subagent' && !!lineage.parentAgentSessionId;
|
|
149
|
+
}
|
|
150
|
+
|
|
85
151
|
/**
|
|
86
152
|
* Register all session-related scheduler jobs.
|
|
87
153
|
* @param {import('./scheduler').Scheduler} scheduler
|
|
@@ -108,7 +174,9 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
108
174
|
runAutoTitleGeneration, healStartupTaskSessionIds,
|
|
109
175
|
sessionIntegrity, getAllSessionFiles, telemetry,
|
|
110
176
|
syncCodingSessionModels,
|
|
111
|
-
|
|
177
|
+
statFilesOffThread,
|
|
178
|
+
isRestoreInProgress, getServerStartedAt, getRestoreCompletedAt, isDbWarming, isDbWorkerDegraded,
|
|
179
|
+
getLastUserInteractiveInputAt,
|
|
112
180
|
} = deps;
|
|
113
181
|
|
|
114
182
|
// Grace window for session-relink: the job is disabled during startup restore
|
|
@@ -118,6 +186,7 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
118
186
|
// paths handle legit mappings in real time; the periodic relink is only a
|
|
119
187
|
// safety net for context-overflow continuations, which can wait 5 minutes.
|
|
120
188
|
const RELINK_GRACE_MS = 5 * 60 * 1000;
|
|
189
|
+
const STARTUP_MAINTENANCE_GRACE_MS = positiveInteger(process.env.CTM_STARTUP_MAINTENANCE_GRACE_MS, 5 * 60 * 1000);
|
|
121
190
|
function _isInRelinkGraceWindow() {
|
|
122
191
|
if (typeof isRestoreInProgress === 'function' && isRestoreInProgress()) return true;
|
|
123
192
|
if (typeof getServerStartedAt === 'function') {
|
|
@@ -126,21 +195,73 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
126
195
|
}
|
|
127
196
|
return false;
|
|
128
197
|
}
|
|
198
|
+
function _isInStartupMaintenanceQuietWindow() {
|
|
199
|
+
if (typeof isRestoreInProgress === 'function' && isRestoreInProgress()) return true;
|
|
200
|
+
// Defer while the single serial db-owner worker is degraded under load: piling more
|
|
201
|
+
// maintenance onto a struggling worker deepens the contention cascade that wedges the
|
|
202
|
+
// cold-boot storm. The scheduler re-runs the job once the worker recovers.
|
|
203
|
+
if (typeof isDbWorkerDegraded === 'function' && isDbWorkerDegraded()) return true;
|
|
204
|
+
// isDbWarming() is currently a retired compatibility gate; keep honoring it
|
|
205
|
+
// here so any future SQLite-owned warmup can defer maintenance safely. The
|
|
206
|
+
// fixed startup grace below remains the active maintenance quiet window.
|
|
207
|
+
if (typeof isDbWarming === 'function' && isDbWarming()) return true;
|
|
208
|
+
if (typeof getServerStartedAt === 'function') {
|
|
209
|
+
const startedAt = Number(getServerStartedAt());
|
|
210
|
+
if (Number.isFinite(startedAt) && startedAt > 0 && (Date.now() - startedAt) < STARTUP_MAINTENANCE_GRACE_MS) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (typeof getRestoreCompletedAt === 'function') {
|
|
215
|
+
const completedAt = Number(getRestoreCompletedAt());
|
|
216
|
+
if (Number.isFinite(completedAt) && completedAt > 0 && (Date.now() - completedAt) < STARTUP_MAINTENANCE_GRACE_MS) {
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
// Interactive guard: heavy maintenance jobs (the io-pool scans) must not START
|
|
223
|
+
// their synchronous DB/fs work within INTERACTIVE_DEFER_MS of a user keystroke.
|
|
224
|
+
// Those scans block the event loop, and a keystroke that lands while one runs
|
|
225
|
+
// stalls until the loop frees — the measured cause of per-keystroke lag. The
|
|
226
|
+
// jobs are safety nets, so deferring a few seconds is harmless. INTERACTIVE_MAX_
|
|
227
|
+
// DEFER_MS caps the defer so continuous typing can't starve maintenance forever.
|
|
228
|
+
const INTERACTIVE_DEFER_MS = positiveInteger(process.env.CTM_MAINT_INTERACTIVE_DEFER_MS, 1500);
|
|
229
|
+
const INTERACTIVE_MAX_DEFER_MS = positiveInteger(process.env.CTM_MAINT_INTERACTIVE_MAX_DEFER_MS, 20000);
|
|
230
|
+
let _interactiveDeferSince = 0;
|
|
231
|
+
function _userInteractingNow() {
|
|
232
|
+
if (typeof getLastUserInteractiveInputAt !== 'function') return false;
|
|
233
|
+
const last = Number(getLastUserInteractiveInputAt()) || 0;
|
|
234
|
+
return last > 0 && (Date.now() - last) < INTERACTIVE_DEFER_MS;
|
|
235
|
+
}
|
|
236
|
+
function _canRunStartupMaintenance() {
|
|
237
|
+
if (_isInStartupMaintenanceQuietWindow()) return false;
|
|
238
|
+
if (_userInteractingNow()) {
|
|
239
|
+
const now = Date.now();
|
|
240
|
+
if (!_interactiveDeferSince) _interactiveDeferSince = now;
|
|
241
|
+
// Starvation cap: once we've been deferring for MAX_DEFER, let a run through
|
|
242
|
+
// (then re-arm the window so we keep yielding to bursts of typing).
|
|
243
|
+
if (now - _interactiveDeferSince < INTERACTIVE_MAX_DEFER_MS) return false;
|
|
244
|
+
}
|
|
245
|
+
_interactiveDeferSince = 0;
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
129
248
|
|
|
130
249
|
let sessionMetadataSyncCursor = 0;
|
|
131
250
|
let ghostReaperLegacyCursor = 0;
|
|
132
251
|
let ghostReaperAgentCursor = 0;
|
|
133
252
|
|
|
134
253
|
// --- jsonl-reconciliation: safety-net full scan (catches fs.watch misses) ---
|
|
135
|
-
// Runs
|
|
136
|
-
//
|
|
137
|
-
//
|
|
254
|
+
// Runs immediately, then every 10min. fs.watch is the primary detector; this
|
|
255
|
+
// is the reliability guarantee. The work itself is bounded and yields between
|
|
256
|
+
// batches, so freshness is not traded for arbitrary startup quiet time.
|
|
138
257
|
scheduler.register({
|
|
139
258
|
name: 'jsonl-reconciliation',
|
|
140
|
-
|
|
141
|
-
|
|
259
|
+
heavy: true, // long synchronous DB-map build + per-file parse; cap to 1 heavy/tick
|
|
260
|
+
intervalMs: 10 * 60 * 1000,
|
|
261
|
+
startDelayMs: 0,
|
|
142
262
|
priority: 4,
|
|
143
263
|
pool: 'io',
|
|
264
|
+
shouldRun: _canRunStartupMaintenance,
|
|
144
265
|
run: async () => {
|
|
145
266
|
const files = await getAllSessionFilesAsync();
|
|
146
267
|
const db = dbModule.getDb();
|
|
@@ -158,10 +279,15 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
158
279
|
const claimedMap = {};
|
|
159
280
|
const existingMap = new Map();
|
|
160
281
|
try {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
282
|
+
// This loads EVERY agent_sessions row in one synchronous span; it grows
|
|
283
|
+
// with session count and is a prime event-loop-block suspect. Wrap it so
|
|
284
|
+
// a [perf-lag] block here is attributed by name instead of `(unknown)`.
|
|
285
|
+
runSync('jsonl-reconciliation:loadAgentSessionsMap', () => {
|
|
286
|
+
for (const r of db.prepare("SELECT agent_session_id, ctm_session_id, file_size, modified_at FROM agent_sessions").all()) {
|
|
287
|
+
existingMap.set(r.agent_session_id, r);
|
|
288
|
+
if (r.ctm_session_id) claimedMap[r.agent_session_id] = r.ctm_session_id;
|
|
289
|
+
}
|
|
290
|
+
});
|
|
165
291
|
} catch (e) { console.error('[jsonl-reconciliation] metadata map build error:', e.message); }
|
|
166
292
|
|
|
167
293
|
const updateMetadata = db.prepare("UPDATE agent_sessions SET file_size = ?, modified_at = ?, updated_at = datetime('now') WHERE agent_session_id = ?");
|
|
@@ -194,19 +320,20 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
194
320
|
}
|
|
195
321
|
|
|
196
322
|
for (let index = 0; index < files.length; index++) {
|
|
197
|
-
const { filePath, projectPath, projectEntry } = files[index];
|
|
323
|
+
const { filePath, projectPath, projectEntry, sessionId: discoveredSessionId } = files[index];
|
|
198
324
|
try {
|
|
199
325
|
const virtualSession = claudeDesktopSessions.parseVirtualSessionPath(filePath);
|
|
200
|
-
const
|
|
326
|
+
const fileSessionId = virtualSession ? virtualSession.sessionId : path.basename(filePath).replace(/\.jsonl(\.bak)?$/, '');
|
|
327
|
+
let agentSessionId = String(discoveredSessionId || fileSessionId || '').trim();
|
|
201
328
|
const statPath = claudeDesktopSessions.sourcePathForStat(filePath);
|
|
202
329
|
const stat = await fsp.stat(statPath);
|
|
203
330
|
const modifiedAt = stat.mtime.toISOString();
|
|
204
|
-
|
|
331
|
+
let existing = existingMap.get(agentSessionId) || existingMap.get(fileSessionId);
|
|
205
332
|
|
|
206
333
|
if (existing) {
|
|
207
334
|
// Update metadata if stale
|
|
208
335
|
if (existing.file_size !== stat.size || existing.modified_at !== modifiedAt) {
|
|
209
|
-
pendingMetadataUpdates.push({ sessionId, fileSize: stat.size, modifiedAt });
|
|
336
|
+
pendingMetadataUpdates.push({ sessionId: existing.agent_session_id || agentSessionId, fileSize: stat.size, modifiedAt });
|
|
210
337
|
existing.file_size = stat.size;
|
|
211
338
|
existing.modified_at = modifiedAt;
|
|
212
339
|
upserted++;
|
|
@@ -219,8 +346,22 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
219
346
|
// New file not in DB — parse and insert. parseSessionFile reads a
|
|
220
347
|
// bounded head/tail slice synchronously, so yield around it during
|
|
221
348
|
// startup scans rather than wrapping hundreds of parses in one turn.
|
|
349
|
+
// Wrap the sync parse so a [perf-lag] block lands as a named span.
|
|
222
350
|
await maybeYield();
|
|
223
|
-
const parsed = parseSessionFile(filePath, projectPath, projectEntry);
|
|
351
|
+
const parsed = runSync('jsonl-reconciliation:parseSessionFile', () => parseSessionFile(filePath, projectPath, projectEntry));
|
|
352
|
+
agentSessionId = String(parsed.sessionId || agentSessionId || fileSessionId || '').trim();
|
|
353
|
+
existing = existingMap.get(agentSessionId);
|
|
354
|
+
if (existing) {
|
|
355
|
+
if (existing.file_size !== stat.size || existing.modified_at !== modifiedAt) {
|
|
356
|
+
pendingMetadataUpdates.push({ sessionId: agentSessionId, fileSize: stat.size, modifiedAt });
|
|
357
|
+
existing.file_size = stat.size;
|
|
358
|
+
existing.modified_at = modifiedAt;
|
|
359
|
+
upserted++;
|
|
360
|
+
if (pendingMetadataUpdates.length >= 25) flushMetadataUpdates();
|
|
361
|
+
}
|
|
362
|
+
await maybeYield(index % 20 === 19);
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
224
365
|
|
|
225
366
|
// Skip ephemeral/aborted sessions (tiny file, no content) — prevents ghost rows
|
|
226
367
|
if (parsed.fileSize < 1024 && !parsed.userMsgCount && !parsed.title && !parsed.firstMessage) {
|
|
@@ -228,29 +369,73 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
228
369
|
continue;
|
|
229
370
|
}
|
|
230
371
|
|
|
231
|
-
const
|
|
372
|
+
const lineage = parsedThreadLineage(parsed);
|
|
373
|
+
const parsedProvider = normalizeAgentType(parsed.agent || '') || parsed.agent || 'claude';
|
|
374
|
+
if (isProviderChildParsedSession(parsed)) {
|
|
375
|
+
try {
|
|
376
|
+
dbModule.upsertAgentSessionIdentity(agentSessionId, {
|
|
377
|
+
provider: parsedProvider,
|
|
378
|
+
providerResumeId: agentSessionId,
|
|
379
|
+
projectEntry: parsed.projectEntry || projectEntry,
|
|
380
|
+
projectPath: parsed.project || parsed.cwd || projectPath,
|
|
381
|
+
cwd: parsed.cwd || parsed.project || projectPath,
|
|
382
|
+
title: parsed.title || '',
|
|
383
|
+
firstMessage: parsed.firstMessage || '',
|
|
384
|
+
jsonlPath: filePath,
|
|
385
|
+
fileSize: parsed.fileSize || stat.size,
|
|
386
|
+
modifiedAt: parsed.modifiedAt || modifiedAt,
|
|
387
|
+
createdAt: parsed.timestamp || '',
|
|
388
|
+
model: parsed.modelId || parsed.model || '',
|
|
389
|
+
gitBranch: parsed.gitBranch || '',
|
|
390
|
+
slug: parsed.slug || '',
|
|
391
|
+
userMsgCount: parsed.userMsgCount || 0,
|
|
392
|
+
hostname: HOSTNAME,
|
|
393
|
+
...lineage,
|
|
394
|
+
});
|
|
395
|
+
existingMap.set(agentSessionId, {
|
|
396
|
+
agent_session_id: agentSessionId,
|
|
397
|
+
ctm_session_id: null,
|
|
398
|
+
file_size: parsed.fileSize || stat.size,
|
|
399
|
+
modified_at: parsed.modifiedAt || modifiedAt,
|
|
400
|
+
});
|
|
401
|
+
upserted++;
|
|
402
|
+
} catch (e) {
|
|
403
|
+
console.error(`[jsonl-reconciliation] child identity upsert error for ${agentSessionId}:`, e.message);
|
|
404
|
+
}
|
|
405
|
+
await maybeYield(index % 10 === 9);
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const ctmTabId = claimedMap[agentSessionId];
|
|
410
|
+
const parsedProjectEntry = parsed.projectEntry || projectEntry;
|
|
411
|
+
const parsedProjectPath = parsed.project || parsed.cwd || projectPath;
|
|
232
412
|
|
|
233
413
|
if (ctmTabId) {
|
|
234
414
|
// Already claimed by a CTM tab — update metadata
|
|
235
415
|
pendingUpserts.push({ id: ctmTabId, data: {
|
|
236
|
-
agentSessionId
|
|
237
|
-
projectEntry
|
|
416
|
+
agentSessionId,
|
|
417
|
+
projectEntry: parsedProjectEntry,
|
|
418
|
+
projectPath: parsedProjectPath,
|
|
419
|
+
cwd: parsed.cwd || parsedProjectPath,
|
|
238
420
|
jsonlPath: filePath,
|
|
239
421
|
fileSize: parsed.fileSize,
|
|
240
422
|
modifiedAt: parsed.modifiedAt,
|
|
241
423
|
firstMessage: parsed.firstMessage || '',
|
|
242
424
|
createdAt: parsed.timestamp || '',
|
|
243
|
-
provider:
|
|
244
|
-
model: parsed.modelId || '',
|
|
425
|
+
provider: parsedProvider,
|
|
426
|
+
model: parsed.modelId || parsed.model || '',
|
|
245
427
|
gitBranch: parsed.gitBranch || '',
|
|
246
428
|
slug: parsed.slug || '',
|
|
247
429
|
userMsgCount: parsed.userMsgCount || 0,
|
|
430
|
+
hostname: HOSTNAME,
|
|
431
|
+
...lineage,
|
|
248
432
|
} });
|
|
249
433
|
} else {
|
|
250
434
|
// Standalone session — auto-create ctm_sessions + agent_sessions
|
|
251
|
-
pendingUpserts.push({ id:
|
|
252
|
-
agentSessionId
|
|
253
|
-
projectEntry
|
|
435
|
+
pendingUpserts.push({ id: agentSessionId, data: {
|
|
436
|
+
agentSessionId,
|
|
437
|
+
projectEntry: parsedProjectEntry,
|
|
438
|
+
projectPath: parsedProjectPath,
|
|
254
439
|
cwd: parsed.cwd || projectPath,
|
|
255
440
|
title: parsed.title || '',
|
|
256
441
|
jsonlPath: filePath,
|
|
@@ -258,12 +443,13 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
258
443
|
modifiedAt: parsed.modifiedAt,
|
|
259
444
|
firstMessage: parsed.firstMessage || '',
|
|
260
445
|
createdAt: parsed.timestamp || '',
|
|
261
|
-
provider:
|
|
262
|
-
model: parsed.modelId || '',
|
|
446
|
+
provider: parsedProvider,
|
|
447
|
+
model: parsed.modelId || parsed.model || '',
|
|
263
448
|
gitBranch: parsed.gitBranch || '',
|
|
264
449
|
slug: parsed.slug || '',
|
|
265
450
|
userMsgCount: parsed.userMsgCount || 0,
|
|
266
451
|
hostname: HOSTNAME,
|
|
452
|
+
...lineage,
|
|
267
453
|
} });
|
|
268
454
|
}
|
|
269
455
|
if (pendingUpserts.length >= 3) {
|
|
@@ -281,15 +467,17 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
281
467
|
});
|
|
282
468
|
|
|
283
469
|
// --- ghost-reaper: remove DB rows whose JSONL files no longer exist ---
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
//
|
|
470
|
+
// Checks a bounded round-robin batch. The old full-table scan could spend
|
|
471
|
+
// minutes walking Codex history paths; batching is the protection, not a long
|
|
472
|
+
// startup delay.
|
|
287
473
|
scheduler.register({
|
|
288
474
|
name: 'ghost-reaper',
|
|
289
|
-
|
|
290
|
-
|
|
475
|
+
heavy: true,
|
|
476
|
+
intervalMs: 30 * 60 * 1000,
|
|
477
|
+
startDelayMs: 0,
|
|
291
478
|
priority: 8,
|
|
292
479
|
pool: 'io',
|
|
480
|
+
shouldRun: _canRunStartupMaintenance,
|
|
293
481
|
run: async () => {
|
|
294
482
|
const db = dbModule.getDb();
|
|
295
483
|
let reaped = 0;
|
|
@@ -358,10 +546,12 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
358
546
|
// --- session-metadata-sync: update file metadata for active sessions ---
|
|
359
547
|
scheduler.register({
|
|
360
548
|
name: 'session-metadata-sync',
|
|
361
|
-
|
|
362
|
-
|
|
549
|
+
heavy: true,
|
|
550
|
+
intervalMs: 30 * 1000,
|
|
551
|
+
startDelayMs: 0,
|
|
363
552
|
priority: 5,
|
|
364
553
|
pool: 'io',
|
|
554
|
+
shouldRun: _canRunStartupMaintenance,
|
|
365
555
|
run: async () => {
|
|
366
556
|
if (typeof deps.isConversationImportRunning === 'function' && deps.isConversationImportRunning()) {
|
|
367
557
|
return { updated: 0, skipped: 'conversation_import_running' };
|
|
@@ -373,11 +563,16 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
373
563
|
const whereClause = hasProviderColumn
|
|
374
564
|
? "(jsonl_path != '' OR provider = 'codex')"
|
|
375
565
|
: "jsonl_path != ''";
|
|
376
|
-
const rows = db.prepare(`SELECT agent_session_id, jsonl_path, file_size, modified_at, ${providerExpr} FROM agent_sessions WHERE ${whereClause} ORDER BY agent_session_id`).all();
|
|
377
566
|
const maxRows = positiveInteger(process.env.CTM_METADATA_SYNC_ROWS_PER_RUN, 50);
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
567
|
+
// Load only this run's batch via SQL LIMIT/OFFSET. The previous `.all()`
|
|
568
|
+
// pulled EVERY agent_sessions row into memory each tick (then sliced 50) —
|
|
569
|
+
// a synchronous main-thread load that grew with session count and was a
|
|
570
|
+
// top event-loop-block offender. Keyset over the stable agent_session_id
|
|
571
|
+
// order; wrap to the start when a short page signals the end.
|
|
572
|
+
const batch = db.prepare(
|
|
573
|
+
`SELECT agent_session_id, jsonl_path, file_size, modified_at, ${providerExpr} FROM agent_sessions WHERE ${whereClause} ORDER BY agent_session_id LIMIT ? OFFSET ?`
|
|
574
|
+
).all(maxRows, sessionMetadataSyncCursor);
|
|
575
|
+
sessionMetadataSyncCursor = batch.length < maxRows ? 0 : sessionMetadataSyncCursor + maxRows;
|
|
381
576
|
const updateMetadata = db.prepare("UPDATE agent_sessions SET jsonl_path = ?, file_size = ?, modified_at = ?, updated_at = datetime('now') WHERE agent_session_id = ?");
|
|
382
577
|
let lastYieldAt = Date.now();
|
|
383
578
|
|
|
@@ -387,10 +582,23 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
387
582
|
lastYieldAt = Date.now();
|
|
388
583
|
}
|
|
389
584
|
|
|
585
|
+
// Offload the per-row fs.statSync to the read-pool as ONE batched `statFiles` call instead
|
|
586
|
+
// of a synchronous stat loop on the main event loop (the freeze). On pool unavailable/timeout
|
|
587
|
+
// statMap is null → fall back to the on-main resolver (only 50 rows). CTM_METADATA_SYNC_OFFLOAD=0 opts out.
|
|
588
|
+
let statMap = null;
|
|
589
|
+
if (process.env.CTM_METADATA_SYNC_OFFLOAD !== '0' && typeof statFilesOffThread === 'function') {
|
|
590
|
+
const statPaths = [...new Set(batch
|
|
591
|
+
.map((r) => claudeDesktopSessions.sourcePathForStat(r.jsonl_path || ''))
|
|
592
|
+
.filter(Boolean))];
|
|
593
|
+
try {
|
|
594
|
+
statMap = await statFilesOffThread(statPaths, positiveInteger(process.env.CTM_METADATA_SYNC_READPOOL_TIMEOUT_MS, 10000));
|
|
595
|
+
} catch { statMap = null; }
|
|
596
|
+
}
|
|
597
|
+
|
|
390
598
|
for (let index = 0; index < batch.length; index++) {
|
|
391
599
|
const row = batch[index];
|
|
392
600
|
try {
|
|
393
|
-
const fileInfo = resolveAgentSessionJsonlInfo(row);
|
|
601
|
+
const fileInfo = statMap ? resolveJsonlInfoFromStatMap(row, statMap) : resolveAgentSessionJsonlInfo(row);
|
|
394
602
|
if (!fileInfo) continue;
|
|
395
603
|
const stat = fileInfo.stat;
|
|
396
604
|
const newMtime = stat.mtime.toISOString();
|
|
@@ -401,17 +609,18 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
401
609
|
} catch (e) { console.error(`[session-metadata-sync] error for ${row.agent_session_id}:`, e.message); }
|
|
402
610
|
await maybeYield(index % 25 === 24);
|
|
403
611
|
}
|
|
404
|
-
return { updated, checked: batch.length,
|
|
612
|
+
return { updated, checked: batch.length, partial: sessionMetadataSyncCursor !== 0 };
|
|
405
613
|
},
|
|
406
614
|
});
|
|
407
615
|
|
|
408
616
|
// --- auto-title: AI-generated titles ---
|
|
409
617
|
scheduler.register({
|
|
410
618
|
name: 'auto-title',
|
|
411
|
-
intervalMs:
|
|
412
|
-
startDelayMs:
|
|
619
|
+
intervalMs: 5 * 60 * 1000,
|
|
620
|
+
startDelayMs: 30 * 1000,
|
|
413
621
|
priority: 7,
|
|
414
622
|
pool: 'llm',
|
|
623
|
+
shouldRun: _canRunStartupMaintenance,
|
|
415
624
|
run: async () => {
|
|
416
625
|
await runAutoTitleGeneration();
|
|
417
626
|
return { done: true };
|
|
@@ -424,10 +633,11 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
424
633
|
// synthesize startup_tasks mappings from live runtime state.
|
|
425
634
|
scheduler.register({
|
|
426
635
|
name: 'session-healer',
|
|
427
|
-
intervalMs:
|
|
428
|
-
startDelayMs:
|
|
636
|
+
intervalMs: 5 * 60 * 1000,
|
|
637
|
+
startDelayMs: 30 * 1000,
|
|
429
638
|
priority: 6,
|
|
430
639
|
pool: 'io',
|
|
640
|
+
shouldRun: _canRunStartupMaintenance,
|
|
431
641
|
run: async () => {
|
|
432
642
|
await healStartupTaskSessionIds();
|
|
433
643
|
return { done: true };
|
|
@@ -438,8 +648,9 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
438
648
|
scheduler.register({
|
|
439
649
|
name: 'session-integrity',
|
|
440
650
|
intervalMs: 30 * 60 * 1000,
|
|
441
|
-
startDelayMs:
|
|
651
|
+
startDelayMs: 60 * 1000,
|
|
442
652
|
priority: 8,
|
|
653
|
+
shouldRun: _canRunStartupMaintenance,
|
|
443
654
|
run: async () => {
|
|
444
655
|
const result = deps.runSessionIntegrity
|
|
445
656
|
? await deps.runSessionIntegrity()
|
|
@@ -450,6 +661,55 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
450
661
|
},
|
|
451
662
|
});
|
|
452
663
|
|
|
664
|
+
// --- approval-self-adapt: keep the learned approval-detection rule set healthy
|
|
665
|
+
// (retire rules whose patterns no longer compile/are unsafe). The behavioral
|
|
666
|
+
// learning signals fire inline on user input; this is the periodic GC. ---
|
|
667
|
+
if (deps.approvalSelfAdapt && deps.approvalAiRefinement) {
|
|
668
|
+
scheduler.register({
|
|
669
|
+
name: 'approval-self-adapt',
|
|
670
|
+
intervalMs: 45 * 60 * 1000,
|
|
671
|
+
startDelayMs: 90 * 1000,
|
|
672
|
+
priority: 4,
|
|
673
|
+
pool: 'io',
|
|
674
|
+
// runSelfAdaptMaintenance is synchronous and has been seen blocking the
|
|
675
|
+
// event loop for several seconds (recompiles/validates the learned rule
|
|
676
|
+
// set). Defer it out of the startup-quiet window and away from active
|
|
677
|
+
// typing — the interactive guard's starvation cap still forces it to run.
|
|
678
|
+
shouldRun: _canRunStartupMaintenance,
|
|
679
|
+
run: async () => {
|
|
680
|
+
const result = deps.approvalSelfAdapt.runSelfAdaptMaintenance({
|
|
681
|
+
dbModule,
|
|
682
|
+
refinement: deps.approvalAiRefinement,
|
|
683
|
+
});
|
|
684
|
+
if (result && result.disabled) {
|
|
685
|
+
console.log(`[approval-self-adapt] maintenance: checked=${result.checked} retired=${result.disabled} kept=${result.kept}`);
|
|
686
|
+
}
|
|
687
|
+
return result || { checked: 0 };
|
|
688
|
+
},
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// --- walle-health-watchdog: detect unrecoverable Wall-E failures and raise
|
|
693
|
+
// a repair incident (notify + offer full-auto coding-agent repair). ---
|
|
694
|
+
if (deps.walleRepair) {
|
|
695
|
+
scheduler.register({
|
|
696
|
+
name: 'walle-health-watchdog',
|
|
697
|
+
intervalMs: 20000,
|
|
698
|
+
startDelayMs: 45000,
|
|
699
|
+
priority: 7,
|
|
700
|
+
pool: 'io',
|
|
701
|
+
// Health detection a few seconds late is fine; never let the 20s watchdog
|
|
702
|
+
// tick block keystroke echo. Defer during the startup-quiet window (Wall-E
|
|
703
|
+
// is still booting → avoids false-positive failure detection) and while
|
|
704
|
+
// the user is typing. Starvation cap bounds the defer to ~20s.
|
|
705
|
+
shouldRun: () => process.env.WALLE_REPAIR_DISABLED !== '1' && _canRunStartupMaintenance(),
|
|
706
|
+
run: async () => {
|
|
707
|
+
const result = await deps.walleRepair.onWatchdogTick();
|
|
708
|
+
return result || { ticked: true };
|
|
709
|
+
},
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
|
|
453
713
|
// --- session-relink: detect continuation files for active sessions ---
|
|
454
714
|
scheduler.register({
|
|
455
715
|
name: 'session-relink',
|
|
@@ -474,7 +734,7 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
474
734
|
|
|
475
735
|
let newerMatch = null;
|
|
476
736
|
let newerMtime = 0;
|
|
477
|
-
for (const file of
|
|
737
|
+
for (const file of _cachedRelinkReaddir(session._claudeProjectDir)) {
|
|
478
738
|
if (!file.endsWith('.jsonl')) continue;
|
|
479
739
|
const csId = file.replace(/\.jsonl$/, '');
|
|
480
740
|
if (csId === session._claudeSessionId) continue;
|
|
@@ -561,11 +821,19 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
561
821
|
if (syncCodingSessionModels) {
|
|
562
822
|
scheduler.register({
|
|
563
823
|
name: 'model-sync',
|
|
564
|
-
intervalMs:
|
|
824
|
+
intervalMs: Math.max(3000, Number(process.env.CTM_MODEL_SYNC_INTERVAL_MS) || 5000),
|
|
565
825
|
startDelayMs: 5000,
|
|
566
826
|
priority: 9,
|
|
567
|
-
|
|
568
|
-
|
|
827
|
+
shouldRun: _canRunStartupMaintenance,
|
|
828
|
+
run: async () => {
|
|
829
|
+
// Prefer the off-thread variant: the claude JSONL scan + Codex state-DB reads run on the
|
|
830
|
+
// read-pool worker; only the small apply (model→registry, title decision, session mutate)
|
|
831
|
+
// stays on main. Falls back to the synchronous version when offload is disabled.
|
|
832
|
+
if (process.env.CTM_MODEL_SYNC_OFFLOAD !== '0' && typeof deps.syncCodingSessionModelsOffThread === 'function') {
|
|
833
|
+
await deps.syncCodingSessionModelsOffThread();
|
|
834
|
+
} else {
|
|
835
|
+
syncCodingSessionModels();
|
|
836
|
+
}
|
|
569
837
|
return { done: true };
|
|
570
838
|
},
|
|
571
839
|
});
|
|
@@ -575,29 +843,62 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
575
843
|
if (deps.runIncrementalConversationImport) {
|
|
576
844
|
scheduler.register({
|
|
577
845
|
name: 'conversation-import',
|
|
846
|
+
heavy: true,
|
|
578
847
|
intervalMs: 10 * 60 * 1000,
|
|
579
|
-
startDelayMs:
|
|
848
|
+
startDelayMs: 0,
|
|
580
849
|
priority: 6,
|
|
581
850
|
pool: 'io',
|
|
851
|
+
shouldRun: _canRunStartupMaintenance,
|
|
582
852
|
run: async () => {
|
|
583
853
|
return await deps.runIncrementalConversationImport();
|
|
584
854
|
},
|
|
585
855
|
});
|
|
586
856
|
}
|
|
587
857
|
|
|
858
|
+
// --- cursor-conversation-import: import Cursor blob-graph stores → session_conversations ---
|
|
859
|
+
// Cursor conversations aren't JSONL; they're a content-addressed blob graph that is
|
|
860
|
+
// expensive to reconstruct. Import them on a cadence (off the main thread, on the db-owner
|
|
861
|
+
// worker) so the cache read path serves them instead of reconstructing on every UI poll.
|
|
862
|
+
// Signature-gated inside the job, so idle stores cost ~nothing; runs even when there are no
|
|
863
|
+
// cursor sessions (then it's a no-op query).
|
|
864
|
+
if (deps.runCursorConversationImport) {
|
|
865
|
+
scheduler.register({
|
|
866
|
+
name: 'cursor-conversation-import',
|
|
867
|
+
heavy: true,
|
|
868
|
+
intervalMs: Math.max(60 * 1000, Number(process.env.CTM_CURSOR_IMPORT_INTERVAL_MS) || 3 * 60 * 1000),
|
|
869
|
+
startDelayMs: 15 * 1000,
|
|
870
|
+
priority: 7,
|
|
871
|
+
pool: 'io',
|
|
872
|
+
shouldRun: _canRunStartupMaintenance,
|
|
873
|
+
run: async () => {
|
|
874
|
+
return await deps.runCursorConversationImport();
|
|
875
|
+
},
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
|
|
588
879
|
// --- message-backfill: extract session_messages from session_conversations for FTS ---
|
|
589
880
|
if (deps.backfillSessionMessages) {
|
|
590
881
|
scheduler.register({
|
|
591
882
|
name: 'message-backfill',
|
|
883
|
+
heavy: true,
|
|
592
884
|
intervalMs: 10 * 60 * 1000,
|
|
593
|
-
startDelayMs:
|
|
885
|
+
startDelayMs: 0,
|
|
886
|
+
dependsOn: ['conversation-import'],
|
|
887
|
+
cascadeFrom: ['conversation-import'],
|
|
888
|
+
// Only cascade when conversation-import actually imported a session. The default
|
|
889
|
+
// cascade check fires on ANY numeric > 0 in the import result — including `total`
|
|
890
|
+
// (sessions on disk) and `scanned` — so an idle import scan that imported nothing
|
|
891
|
+
// still triggered a backfill on nearly every 5s tick (347 runs / 33 min in one
|
|
892
|
+
// sample, churning the db-owner worker + write lock). Gate on real new work.
|
|
893
|
+
cascadeCheck: (result) => Number(result && result.imported) > 0,
|
|
594
894
|
priority: 8,
|
|
595
895
|
pool: 'io',
|
|
896
|
+
shouldRun: _canRunStartupMaintenance,
|
|
596
897
|
run: async () => {
|
|
597
|
-
const
|
|
898
|
+
const result = deps.backfillSessionMessagesAsync
|
|
598
899
|
? await deps.backfillSessionMessagesAsync({ limit: 1, chunkSize: 10 })
|
|
599
900
|
: deps.backfillSessionMessages();
|
|
600
|
-
return { extracted:
|
|
901
|
+
return result && typeof result === 'object' ? result : { extracted: Number(result || 0) };
|
|
601
902
|
},
|
|
602
903
|
});
|
|
603
904
|
}
|
|
@@ -609,10 +910,11 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
609
910
|
scheduler.register({
|
|
610
911
|
name: 'persist-agent-session-id',
|
|
611
912
|
intervalMs: 60 * 1000,
|
|
612
|
-
startDelayMs:
|
|
913
|
+
startDelayMs: 45 * 1000,
|
|
613
914
|
priority: 6,
|
|
614
915
|
pool: 'io',
|
|
615
|
-
|
|
916
|
+
shouldRun: _canRunStartupMaintenance,
|
|
917
|
+
run: async () => {
|
|
616
918
|
let synced = 0;
|
|
617
919
|
let skippedNoMapping = 0;
|
|
618
920
|
try {
|
|
@@ -620,7 +922,12 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
620
922
|
const rows = dbModule.getDb().prepare(
|
|
621
923
|
`SELECT * FROM startup_tasks WHERE (cmd LIKE '%claude%' OR cmd LIKE '%codex%' OR cmd LIKE '%gemini%')`
|
|
622
924
|
).all();
|
|
925
|
+
// Per-row resolution does several DB queries + a cwd fs check; over many rows this
|
|
926
|
+
// is an unbounded synchronous main-thread scan. Yield to the loop every ~25ms so a
|
|
927
|
+
// large startup_tasks table can't stall keystroke echo (matches session-metadata-sync).
|
|
928
|
+
let _lastYield = Date.now();
|
|
623
929
|
for (const r of rows) {
|
|
930
|
+
if (Date.now() - _lastYield > 25) { await yieldToEventLoop(); _lastYield = Date.now(); }
|
|
624
931
|
const ctmSessionId = r.ctm_session_id;
|
|
625
932
|
const session = sessions.get(ctmSessionId);
|
|
626
933
|
const agentType = detectAgentType(r.cmd || session?.cmd || '') || normalizeAgentType(r.type || session?.type || '');
|
|
@@ -647,7 +954,14 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
647
954
|
const projectDir = choice.row?.jsonl_path ? path.dirname(choice.row.jsonl_path) : (r.agent_project_dir || r.claude_project_dir || '');
|
|
648
955
|
const currentProjectDir = r.agent_project_dir || r.claude_project_dir || '';
|
|
649
956
|
const hasLegacyProjectDir = cols.has('agent_project_dir') || cols.has('claude_project_dir');
|
|
650
|
-
|
|
957
|
+
const cwdChoice = resolveRestoreCwdFromAgentSession({
|
|
958
|
+
task: { ...r, cwd: r.cwd, ctm_session_id: ctmSessionId, session_id: ctmSessionId },
|
|
959
|
+
row: choice.row,
|
|
960
|
+
agentType,
|
|
961
|
+
fsExists: fs.existsSync,
|
|
962
|
+
});
|
|
963
|
+
const needsCwdHeal = !!(cwdChoice.changed && cwdChoice.cwd && cwdChoice.cwd !== r.cwd);
|
|
964
|
+
if (currentArgs === nextArgs && (!hasLegacyProjectDir || currentProjectDir === projectDir) && !needsCwdHeal) continue;
|
|
651
965
|
try {
|
|
652
966
|
dbModule.updateStartupTaskAgentSession(
|
|
653
967
|
ctmSessionId,
|
|
@@ -655,6 +969,9 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
655
969
|
projectDir,
|
|
656
970
|
choice.args
|
|
657
971
|
);
|
|
972
|
+
if (needsCwdHeal && typeof dbModule.updateStartupTaskCwd === 'function') {
|
|
973
|
+
dbModule.updateStartupTaskCwd(ctmSessionId, cwdChoice.cwd);
|
|
974
|
+
}
|
|
658
975
|
synced++;
|
|
659
976
|
} catch (e) { console.error('[persist-agent-session-id] update error:', e.message); }
|
|
660
977
|
}
|
|
@@ -671,15 +988,36 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
671
988
|
* @param {import('./session-stream').SessionStream} deps.sessionStream
|
|
672
989
|
*/
|
|
673
990
|
function registerStreamJobs(scheduler, deps) {
|
|
674
|
-
const { sessionStream } = deps;
|
|
991
|
+
const { sessionStream, isRestoreInProgress, getServerStartedAt, getRestoreCompletedAt, isDbWarming, isDbWorkerDegraded } = deps;
|
|
675
992
|
if (!sessionStream) return;
|
|
993
|
+
const STARTUP_MAINTENANCE_GRACE_MS = positiveInteger(process.env.CTM_STARTUP_MAINTENANCE_GRACE_MS, 5 * 60 * 1000);
|
|
994
|
+
function _canRunStartupMaintenance() {
|
|
995
|
+
if (typeof isRestoreInProgress === 'function' && isRestoreInProgress()) return false;
|
|
996
|
+
// Defer while the serial db-owner worker is degraded under load (cold-boot contention cascade).
|
|
997
|
+
if (typeof isDbWorkerDegraded === 'function' && isDbWorkerDegraded()) return false;
|
|
998
|
+
if (typeof isDbWarming === 'function' && isDbWarming()) return false;
|
|
999
|
+
if (typeof getServerStartedAt === 'function') {
|
|
1000
|
+
const startedAt = Number(getServerStartedAt());
|
|
1001
|
+
if (Number.isFinite(startedAt) && startedAt > 0 && (Date.now() - startedAt) < STARTUP_MAINTENANCE_GRACE_MS) {
|
|
1002
|
+
return false;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
if (typeof getRestoreCompletedAt === 'function') {
|
|
1006
|
+
const completedAt = Number(getRestoreCompletedAt());
|
|
1007
|
+
if (Number.isFinite(completedAt) && completedAt > 0 && (Date.now() - completedAt) < STARTUP_MAINTENANCE_GRACE_MS) {
|
|
1008
|
+
return false;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return true;
|
|
1012
|
+
}
|
|
676
1013
|
|
|
677
1014
|
// stream-cleanup: remove stale non-CTM sessions inactive >30min
|
|
678
1015
|
scheduler.register({
|
|
679
1016
|
name: 'stream-cleanup',
|
|
680
1017
|
intervalMs: 10 * 60 * 1000,
|
|
681
|
-
startDelayMs:
|
|
1018
|
+
startDelayMs: 30 * 1000,
|
|
682
1019
|
priority: 9,
|
|
1020
|
+
shouldRun: _canRunStartupMaintenance,
|
|
683
1021
|
run: () => {
|
|
684
1022
|
const cleaned = sessionStream.cleanStale();
|
|
685
1023
|
return { cleaned };
|
|
@@ -689,10 +1027,12 @@ function registerStreamJobs(scheduler, deps) {
|
|
|
689
1027
|
// stream-reconciliation: stat all tracked files, trigger incremental read if changed
|
|
690
1028
|
scheduler.register({
|
|
691
1029
|
name: 'stream-reconciliation',
|
|
692
|
-
|
|
693
|
-
|
|
1030
|
+
heavy: true,
|
|
1031
|
+
intervalMs: 60 * 1000,
|
|
1032
|
+
startDelayMs: 30 * 1000,
|
|
694
1033
|
priority: 5,
|
|
695
1034
|
pool: 'io',
|
|
1035
|
+
shouldRun: _canRunStartupMaintenance,
|
|
696
1036
|
run: async () => {
|
|
697
1037
|
return await sessionStream.reconcile({ maxSessions: 12, maxChunksPerSession: 4 });
|
|
698
1038
|
},
|