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,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const CTM_PROCESS_NAME = 'Coding Task Manager';
|
|
4
|
+
const WALLE_PROCESS_NAME = 'Wall-E';
|
|
5
|
+
|
|
6
|
+
function normalizeInstanceTag(tag) {
|
|
7
|
+
const text = String(tag || 'primary').trim();
|
|
8
|
+
return text || 'primary';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function legacyProcessTitle(kind, tag = 'primary') {
|
|
12
|
+
const normalized = normalizeInstanceTag(tag);
|
|
13
|
+
return `${kind === 'walle' ? 'walle' : 'ctm'}-${normalized}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function processTitle(kind, tag = 'primary') {
|
|
17
|
+
const normalized = normalizeInstanceTag(tag);
|
|
18
|
+
const base = kind === 'walle' ? WALLE_PROCESS_NAME : CTM_PROCESS_NAME;
|
|
19
|
+
return normalized === 'primary' ? base : `${base} ${normalized}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function processTitleCandidates(kind, tag = 'primary') {
|
|
23
|
+
const current = processTitle(kind, tag);
|
|
24
|
+
const legacy = legacyProcessTitle(kind, tag);
|
|
25
|
+
return current === legacy ? [current] : [current, legacy];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
CTM_PROCESS_NAME,
|
|
30
|
+
WALLE_PROCESS_NAME,
|
|
31
|
+
legacyProcessTitle,
|
|
32
|
+
normalizeInstanceTag,
|
|
33
|
+
processTitle,
|
|
34
|
+
processTitleCandidates,
|
|
35
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Shared query for /api/prompt-executions, used by BOTH the main-thread handler
|
|
4
|
+
// (api-prompts.js handleListExecutions) and the read-pool worker
|
|
5
|
+
// (read-pool-worker.js 'listPromptExecutions' op) so the two can never diverge —
|
|
6
|
+
// parity by construction. The .all() can return up to 10000 rows each carrying a
|
|
7
|
+
// large message_text, which a CPU profile showed blocking the main event loop
|
|
8
|
+
// ~3.9s on the request path; the worker runs the exact same SQL off-thread.
|
|
9
|
+
function queryPromptExecutions(conn, opts = {}) {
|
|
10
|
+
const limit = Math.max(1, Math.min(parseInt(opts.limit, 10) || 50, 10000));
|
|
11
|
+
const order = String(opts.order || 'desc').toLowerCase() === 'asc' ? 'ASC' : 'DESC';
|
|
12
|
+
const role = opts.role || null;
|
|
13
|
+
const project = opts.project || null;
|
|
14
|
+
const after = opts.after || null;
|
|
15
|
+
let sql = 'SELECT * FROM prompt_executions WHERE 1=1';
|
|
16
|
+
const params = [];
|
|
17
|
+
if (role) { sql += ' AND role = ?'; params.push(role); }
|
|
18
|
+
if (project) { sql += ' AND project_path LIKE ?'; params.push('%' + project + '%'); }
|
|
19
|
+
if (after) { sql += ' AND executed_at > ?'; params.push(after); }
|
|
20
|
+
sql += ` ORDER BY executed_at ${order}, session_id ASC, message_index ASC LIMIT ?`;
|
|
21
|
+
params.push(limit);
|
|
22
|
+
return conn.prepare(sql).all(...params);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = { queryPromptExecutions };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Pure data-shaping for the persisted prompt-index cache (prompt-index-cache.json). The fs I/O,
|
|
4
|
+
// the write throttle, and the live in-memory Map stay in server.js; this module is the testable
|
|
5
|
+
// serialize/deserialize core. Phase 2a: a cold RESTART used to evict the in-memory prompt-index,
|
|
6
|
+
// so every post-restart first-load of apiSessionPrompts paid the ~1.6s compute. Persisting + seeding
|
|
7
|
+
// the last-good indexes lets the storm fallback serve a warm list instead of stormEmpty/_deferred.
|
|
8
|
+
|
|
9
|
+
// Pick the most-recently-remembered N entries (Map iteration order = insertion order; remember()
|
|
10
|
+
// re-inserts on each refresh, so the tail is hottest) and drop pathologically large indexes (keeps
|
|
11
|
+
// the file small + the cold-boot read fast). Returns the on-disk `indexes` object.
|
|
12
|
+
function buildPromptIndexSnapshot(entries, { max = 40, maxPrompts = 2000 } = {}) {
|
|
13
|
+
const list = Array.isArray(entries) ? entries : [];
|
|
14
|
+
const tail = list.slice(Math.max(0, list.length - Math.max(1, max)));
|
|
15
|
+
const indexes = {};
|
|
16
|
+
for (const [sid, r] of tail) {
|
|
17
|
+
if (!sid || !r || !Array.isArray(r.messages)) continue;
|
|
18
|
+
if (r.messages.length > maxPrompts) continue;
|
|
19
|
+
indexes[sid] = { messages: r.messages, summary: r.summary || null };
|
|
20
|
+
}
|
|
21
|
+
return indexes;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Parse a persisted file's JSON text into [{ sid, value }] ready to seed the live Map. Returns []
|
|
25
|
+
// on any malformed/empty input — this is a best-effort cache and must never throw into the boot path.
|
|
26
|
+
function parsePromptIndexSnapshot(jsonText) {
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(jsonText);
|
|
29
|
+
const indexes = parsed && parsed.indexes && typeof parsed.indexes === 'object' ? parsed.indexes : null;
|
|
30
|
+
if (!indexes) return [];
|
|
31
|
+
const out = [];
|
|
32
|
+
for (const sid of Object.keys(indexes)) {
|
|
33
|
+
const r = indexes[sid];
|
|
34
|
+
if (r && Array.isArray(r.messages)) {
|
|
35
|
+
out.push({ sid, value: { messages: r.messages, summary: r.summary || null } });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
} catch {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { buildPromptIndexSnapshot, parsePromptIndexSnapshot };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// AI accuracy backstop for "what is this terminal frame?" — a prompt-INTENT classifier
|
|
4
|
+
// (approval / choice / none) that the server consults ONLY when the structural detectors
|
|
5
|
+
// conflict (assessAmbiguity), deduped per frame (frameFingerprint), with a pure hold
|
|
6
|
+
// decision (resolveIntentDecision) the server drives. The LLM call itself is
|
|
7
|
+
// classifyPromptIntent (added in Task 2). Pure parts here so they unit-test without I/O.
|
|
8
|
+
|
|
9
|
+
const { evaluateWaitState, _fnv1a } = require('./wait-state');
|
|
10
|
+
const terminalChoice = require('./terminal-choice');
|
|
11
|
+
const { callBackgroundLlm } = require('./background-llm');
|
|
12
|
+
let approvalAgent = null;
|
|
13
|
+
try { approvalAgent = require('../approval-agent'); } catch { approvalAgent = null; }
|
|
14
|
+
|
|
15
|
+
// Stable per-frame key shared by the reconcile and approver paths so a frame is
|
|
16
|
+
// classified ONCE regardless of which sees it first. Hash of the cleaned tail.
|
|
17
|
+
function frameFingerprint(viewportText) {
|
|
18
|
+
const s = String(viewportText || '').replace(/[ \t]+$/gm, '');
|
|
19
|
+
return 'intent:' + _fnv1a(s.slice(-600));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// First token of a real shell command. A parsed "command" whose head isn't a plausible
|
|
23
|
+
// binary/builtin (or that reads like prose) signals a low-confidence parse.
|
|
24
|
+
const _COMMAND_HEAD_RE = /^[\w][\w.\/-]*$/; // \w = [A-Za-z0-9_] — allow digit-leading binaries (1password, 2to3)
|
|
25
|
+
function _titleLooksLikeCommand(title) {
|
|
26
|
+
const t = String(title || '').trim();
|
|
27
|
+
if (!t) return false;
|
|
28
|
+
// Prose markers a one-line command would not contain (sentence punctuation + filler).
|
|
29
|
+
if (/[.?!]\s/.test(t) || /\b(but|and|the|allow-list|permission)\b/i.test(t)) return false;
|
|
30
|
+
const head = t.split(/\s+/)[0] || '';
|
|
31
|
+
return _COMMAND_HEAD_RE.test(head);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Pure ambiguity assessment over a settled frame. Returns { ambiguous, reasons }.
|
|
35
|
+
// ambiguous:true ONLY when structural signals conflict / are low-confidence — a
|
|
36
|
+
// confident frame returns false and the AI is never called.
|
|
37
|
+
function assessAmbiguity({ providerId, viewportText } = {}) {
|
|
38
|
+
const text = String(viewportText || '');
|
|
39
|
+
const reasons = [];
|
|
40
|
+
if (!text.trim()) return { ambiguous: false, reasons };
|
|
41
|
+
|
|
42
|
+
const ws = evaluateWaitState({ providerId, viewportText: text });
|
|
43
|
+
const liveApproval = !!(approvalAgent && typeof approvalAgent.isLiveApprovalPrompt === 'function'
|
|
44
|
+
&& approvalAgent.isLiveApprovalPrompt(text));
|
|
45
|
+
|
|
46
|
+
// 1) Approver gate matched a live prompt, but the canonical evaluator sees no widget
|
|
47
|
+
// (none/input) — a shape one detector recognizes and the other does not.
|
|
48
|
+
if (liveApproval && (ws.kind === 'none' || ws.kind === 'input')) {
|
|
49
|
+
reasons.push('approver-live-but-evaluator-' + ws.kind);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 2) Parsed an approval context whose derived title does not look like a command
|
|
53
|
+
// (Unknown tool or scraped prose) — low-confidence parse.
|
|
54
|
+
if (approvalAgent && typeof approvalAgent.parseApprovalContext === 'function') {
|
|
55
|
+
let ctx = null;
|
|
56
|
+
try { ctx = approvalAgent.parseApprovalContext(text, providerId); } catch { ctx = null; }
|
|
57
|
+
if (ctx) {
|
|
58
|
+
let title = '';
|
|
59
|
+
try { title = (approvalAgent.escalationCommandParts(ctx).title) || ''; } catch { title = ''; }
|
|
60
|
+
if (ctx.toolName === 'Unknown' || !_titleLooksLikeCommand(title)) reasons.push('low-confidence-parse');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 3) A numbered menu with NON-yes/no options while an approval-style question is on
|
|
65
|
+
// screen — the approval<->choice tie (AskUserQuestion that reads like a prompt).
|
|
66
|
+
let menu = null;
|
|
67
|
+
try { menu = terminalChoice.extractTerminalChoice(text); } catch { menu = null; }
|
|
68
|
+
if (menu && Array.isArray(menu.options) && menu.options.length >= 2) {
|
|
69
|
+
const first = String((menu.options[0] && menu.options[0].label) || '').trim();
|
|
70
|
+
const firstYesNo = /^(yes|no|allow|deny|approve|reject|proceed|cancel|accept)\b/i.test(first);
|
|
71
|
+
const askApproval = /\bdo you want to\b/i.test(text) || /\?\s*$/m.test(text);
|
|
72
|
+
if (!firstYesNo && askApproval) reasons.push('nonyesno-menu-with-approval-question');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { ambiguous: reasons.length > 0, reasons };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const INTENT_SYSTEM = `You classify what an AI coding CLI's terminal screen is currently asking for. Reply with ONLY JSON: {"intent":"approval"|"choice"|"none","reason":"<=1 sentence"}.
|
|
79
|
+
|
|
80
|
+
Definitions:
|
|
81
|
+
- "approval": the CLI is asking PERMISSION to run a TOOL or SHELL COMMAND it proposed (e.g. "Do you want to proceed?" with Yes/No, an edit/command confirmation). Acting on it means pressing yes/approve.
|
|
82
|
+
- "choice": the AGENT is asking the USER to answer a question or pick among several non-yes/no options (e.g. an AskUserQuestion menu: "Which approach?" 1/2/3/4). There is nothing to auto-approve; the user must choose.
|
|
83
|
+
- "none": no live interactive prompt — the agent is working/thinking, or the screen is prose/output.
|
|
84
|
+
|
|
85
|
+
When unsure between "approval" and "choice", answer "choice" — never treat a user-facing question as an approval.`;
|
|
86
|
+
|
|
87
|
+
const INTENT_TIMEOUT_MS = 5000;
|
|
88
|
+
|
|
89
|
+
// Async LLM classification. Returns { intent:'approval'|'choice'|'none'|'unknown',
|
|
90
|
+
// reason, durationMs, error? }. ANY failure (disabled provider, timeout, bad JSON,
|
|
91
|
+
// bad value) -> intent:'unknown' so the caller falls back to the structural verdict.
|
|
92
|
+
async function classifyPromptIntent(viewportText, { reasons, goal, cwd } = {}, { callModel, timeoutMs } = {}) {
|
|
93
|
+
const started = Date.now();
|
|
94
|
+
const call = callModel || callBackgroundLlm;
|
|
95
|
+
const screen = String(viewportText || '').replace(/[ \t]+$/gm, '').slice(-1600);
|
|
96
|
+
const ctxLine = (goal || cwd)
|
|
97
|
+
? `Session goal: ${String(goal || '').slice(0, 200)}\nCwd: ${String(cwd || '').slice(0, 200)}\n`
|
|
98
|
+
: '';
|
|
99
|
+
const why = Array.isArray(reasons) && reasons.length ? `Detectors disagreed: ${reasons.join(', ')}.\n` : '';
|
|
100
|
+
const prompt = `${ctxLine}${why}Terminal screen (bottom of viewport):\n"""\n${screen}\n"""\n\nReturn ONLY: {"intent":"approval|choice|none","reason":"..."}`;
|
|
101
|
+
|
|
102
|
+
let response;
|
|
103
|
+
try {
|
|
104
|
+
response = await call(prompt, { system: INTENT_SYSTEM, maxTokens: 120, temperature: 0, timeoutMs: timeoutMs || INTENT_TIMEOUT_MS });
|
|
105
|
+
} catch (e) {
|
|
106
|
+
return { intent: 'unknown', reason: '', error: e.reason || e.message || String(e), durationMs: Date.now() - started };
|
|
107
|
+
}
|
|
108
|
+
const text = (response && (response.text ?? response)) || '';
|
|
109
|
+
const match = String(text).match(/\{[\s\S]*\}/);
|
|
110
|
+
if (!match) return { intent: 'unknown', reason: '', error: 'no JSON in response', durationMs: Date.now() - started };
|
|
111
|
+
let parsed;
|
|
112
|
+
try { parsed = JSON.parse(match[0]); } catch (e) { return { intent: 'unknown', reason: '', error: `parse: ${e.message}`, durationMs: Date.now() - started }; }
|
|
113
|
+
const intent = ['approval', 'choice', 'none'].includes(parsed.intent) ? parsed.intent : 'unknown';
|
|
114
|
+
const reason = typeof parsed.reason === 'string' ? parsed.reason.slice(0, 200) : '';
|
|
115
|
+
if (intent === 'unknown') return { intent, reason, error: 'bad intent value', durationMs: Date.now() - started };
|
|
116
|
+
return { intent, reason, durationMs: Date.now() - started };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Pure decision the server drives for the brief hold. Inputs are the current cache /
|
|
120
|
+
// pending entries for the session plus the frame fp and clock. Output tells the server
|
|
121
|
+
// what to do this tick. (cachedIntent === null means a prior classification fell back.)
|
|
122
|
+
function resolveIntentDecision({ cachedFp, cachedIntent, pendingFp, pendingDeadline, fp, now, holdMs } = {}) {
|
|
123
|
+
if (cachedFp === fp) {
|
|
124
|
+
return cachedIntent ? { action: 'use', intent: cachedIntent } : { action: 'fallback' };
|
|
125
|
+
}
|
|
126
|
+
if (pendingFp === fp) {
|
|
127
|
+
return now < pendingDeadline ? { action: 'hold' } : { action: 'fallback' };
|
|
128
|
+
}
|
|
129
|
+
return { action: 'start', deadline: now + (holdMs || INTENT_TIMEOUT_MS) };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = { frameFingerprint, assessAmbiguity, classifyPromptIntent, resolveIntentDecision, _titleLooksLikeCommand, INTENT_TIMEOUT_MS };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Provider CLIs sometimes persist operational context as `role=user` so it is
|
|
4
|
+
// available to the model. These rows are not human-authored prompts and should
|
|
5
|
+
// not be projected into CTM timelines, prompt history, or message search.
|
|
6
|
+
const PROVIDER_GENERATED_USER_CONTEXT_PATTERNS = [
|
|
7
|
+
/^<environment_context>/i,
|
|
8
|
+
/^<permissions instructions>/i,
|
|
9
|
+
/^<collaboration_mode>/i,
|
|
10
|
+
/^<skills_instructions>/i,
|
|
11
|
+
/^<plugins_instructions>/i,
|
|
12
|
+
/^<task-notification>/i,
|
|
13
|
+
/^<skill>\s*<name>/i,
|
|
14
|
+
/^# AGENTS\.md instructions for\b/i,
|
|
15
|
+
/^Base directory for this skill:\s+/i,
|
|
16
|
+
/^This session is being continued from a previous conversation\b/i,
|
|
17
|
+
/^Continue from where you left off\b/i,
|
|
18
|
+
/^Compacted\b/i,
|
|
19
|
+
/^\[Image:\s*source:\s*(?:"[^"]+"|'[^']+'|[^\]]+)\]$/i,
|
|
20
|
+
/^\[Image:\s*original\s+\d+(?:\.\d+)?x\d+(?:\.\d+)?,\s*displayed at\s+\d+(?:\.\d+)?x\d+(?:\.\d+)?\.\s*Multiply coordinates by\s+[0-9.]+\s+to map to original image\.?\]$/i,
|
|
21
|
+
/^\[Request interrupted by user(?: for tool use)?\]$/i,
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
function isProviderGeneratedUserContextText(text) {
|
|
25
|
+
const cleaned = String(text || '').trim();
|
|
26
|
+
if (!cleaned) return false;
|
|
27
|
+
if (/^<turn_aborted>\s*[\s\S]*?<\/turn_aborted>\s*$/i.test(cleaned)) return true;
|
|
28
|
+
return PROVIDER_GENERATED_USER_CONTEXT_PATTERNS.some((pattern) => pattern.test(cleaned));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = {
|
|
32
|
+
PROVIDER_GENERATED_USER_CONTEXT_PATTERNS,
|
|
33
|
+
isProviderGeneratedUserContextText,
|
|
34
|
+
};
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Client for the read/parse worker pool (workers/read-pool-worker.js).
|
|
4
|
+
//
|
|
5
|
+
// A small fixed pool of DB-free workers that absorb the CPU/IO-heavy *read*
|
|
6
|
+
// work (JSONL parse, batch fs.stat) that otherwise blocks the main event loop.
|
|
7
|
+
// Unlike the serial db-owner worker, this is a POOL: requests dispatch to the
|
|
8
|
+
// least-busy worker so concurrent Conversation-tab loads + scan jobs run in
|
|
9
|
+
// parallel instead of head-of-line blocking each other.
|
|
10
|
+
//
|
|
11
|
+
// Each worker processes its own queue serially (one op at a time), which is why
|
|
12
|
+
// "least in-flight" is the right load metric. Mirrors the message protocol and
|
|
13
|
+
// robustness (timeouts, fatal/exit handling, reject-all) of
|
|
14
|
+
// lib/db-owner-worker-client.js.
|
|
15
|
+
|
|
16
|
+
const os = require('node:os');
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
const { Worker } = require('node:worker_threads');
|
|
19
|
+
|
|
20
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 30 * 1000;
|
|
21
|
+
const DEFAULT_CLOSE_TIMEOUT_MS = 5 * 1000;
|
|
22
|
+
const MIN_POOL_SIZE = 3;
|
|
23
|
+
// Raised 8→12: the cores-2 default wants 14 on a 16-core box but was capped at 8, needlessly
|
|
24
|
+
// throttling heavy-op concurrency (parse / prompt-index / getAllSessionsData all contend). 12
|
|
25
|
+
// leaves ~4 cores for the main loop + db-owner worker. Each worker is a thread + a read-only WAL
|
|
26
|
+
// connection (WAL allows many readers); smaller boxes still clamp to cores-2. CTM_READ_POOL_SIZE
|
|
27
|
+
// overrides up to this cap.
|
|
28
|
+
const MAX_POOL_SIZE = 12;
|
|
29
|
+
|
|
30
|
+
// Ops whose cost scales with transcript/blob size (seconds, not bounded ms) and
|
|
31
|
+
// can monopolize a worker during the cold-boot restore storm. They share the pool
|
|
32
|
+
// with latency-sensitive bounded reads (getAllSessionsData, listRichWorktrees,
|
|
33
|
+
// autoTitleCandidates, statFiles, …). If a heavy op occupies EVERY worker, those
|
|
34
|
+
// small reads queue behind a 10–30s parse, hit their request timeout, and fall
|
|
35
|
+
// back to the MAIN THREAD — which is exactly the [offthread-read:*] "falling back
|
|
36
|
+
// to main" cascade + 28s event-loop freeze seen at boot. We cap concurrent heavy
|
|
37
|
+
// ops below the pool size so ≥1 worker is always free for the light reads.
|
|
38
|
+
const HEAVY_OPS = new Set([
|
|
39
|
+
'parseSessionFiles',
|
|
40
|
+
'parseConversationBlob',
|
|
41
|
+
'computeUserPromptIndex',
|
|
42
|
+
'loadCursorConversation',
|
|
43
|
+
'sessionIntegrityDetect', // ~60s full-corpus scan on cold Dropbox
|
|
44
|
+
'auditRelink', // restore-path relink audit: full scan + per-agent cold-JSONL reads
|
|
45
|
+
]);
|
|
46
|
+
// Keep this many workers out of heavy reach so light reads never head-of-line
|
|
47
|
+
// block behind a heavy parse. Clamped so tiny pools still allow ≥1 heavy op.
|
|
48
|
+
const RESERVED_LIGHT_WORKERS = 2;
|
|
49
|
+
|
|
50
|
+
function _durationMs(value, fallback) {
|
|
51
|
+
const n = Number(value);
|
|
52
|
+
return Number.isFinite(n) ? Math.max(0, Math.trunc(n)) : fallback;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Core-aware default. A fixed 3 starved the pool during the 20-session restore storm:
|
|
56
|
+
// getAllSessionsData + the prompt-index compute + parseSessionFiles all contend on the same
|
|
57
|
+
// workers, so the small latency-sensitive reads timed out (5s) and fell back to a main-thread
|
|
58
|
+
// build/compute — the early-boot freezes the event-loop budget surfaced. On a 16-core box
|
|
59
|
+
// 3 of 16 cores is a needless bottleneck. Scale to cores-2 (capped at MAX) so the boot
|
|
60
|
+
// concurrency has room; CTM_READ_POOL_SIZE still overrides.
|
|
61
|
+
function _coreAwareDefaultPoolSize() {
|
|
62
|
+
let cores = 0;
|
|
63
|
+
try { cores = (typeof os.availableParallelism === 'function' ? os.availableParallelism() : os.cpus().length) || 0; } catch {}
|
|
64
|
+
return Math.min(MAX_POOL_SIZE, Math.max(MIN_POOL_SIZE, cores - 2));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function _resolvePoolSize(requested) {
|
|
68
|
+
const raw = requested ?? process.env.CTM_READ_POOL_SIZE;
|
|
69
|
+
const n = Number(raw);
|
|
70
|
+
if (Number.isFinite(n) && n >= 1) return Math.min(MAX_POOL_SIZE, Math.trunc(n));
|
|
71
|
+
return _coreAwareDefaultPoolSize();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// One worker_thread + its in-flight bookkeeping. Lazily (re)spawned on first use
|
|
75
|
+
// and respawned transparently after a crash so a single bad parse can't take the
|
|
76
|
+
// pool down permanently.
|
|
77
|
+
class _PoolWorker {
|
|
78
|
+
constructor(opts) {
|
|
79
|
+
this.workerPath = opts.workerPath;
|
|
80
|
+
this.logger = opts.logger || console;
|
|
81
|
+
this.worker = null;
|
|
82
|
+
this.ready = false;
|
|
83
|
+
this.startPromise = null;
|
|
84
|
+
this.pending = new Map(); // requestId -> { resolve, reject, timer }
|
|
85
|
+
this.nextRequestId = 1;
|
|
86
|
+
this.heavyInFlight = 0; // heavy ops currently running on THIS worker
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
get inFlight() { return this.pending.size; }
|
|
90
|
+
|
|
91
|
+
start() {
|
|
92
|
+
if (this.ready) return Promise.resolve(this);
|
|
93
|
+
if (this.startPromise) return this.startPromise;
|
|
94
|
+
this.startPromise = new Promise((resolve, reject) => {
|
|
95
|
+
const worker = new Worker(this.workerPath, { env: { ...process.env } });
|
|
96
|
+
this.worker = worker;
|
|
97
|
+
|
|
98
|
+
const failStart = (err) => {
|
|
99
|
+
if (this.ready) return;
|
|
100
|
+
this.startPromise = null;
|
|
101
|
+
this.worker = null;
|
|
102
|
+
try { worker.terminate(); } catch {}
|
|
103
|
+
reject(err instanceof Error ? err : new Error(String(err || 'read-pool worker failed to start')));
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
worker.on('message', (msg) => {
|
|
107
|
+
if (!msg || typeof msg !== 'object') return;
|
|
108
|
+
if (msg.type === 'ready') { this.ready = true; resolve(this); return; }
|
|
109
|
+
if (msg.type === 'response') { this._finish(msg.requestId, msg.error, msg.result); return; }
|
|
110
|
+
if (msg.type === 'fatal') {
|
|
111
|
+
const err = new Error(msg.message || 'read-pool worker fatal error');
|
|
112
|
+
if (!this.ready) failStart(err);
|
|
113
|
+
this._rejectAll(err);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// 'status' / 'closed' are advisory for the pool — nothing to track.
|
|
117
|
+
});
|
|
118
|
+
worker.on('error', (err) => {
|
|
119
|
+
if (!this.ready) failStart(err);
|
|
120
|
+
this._rejectAll(err);
|
|
121
|
+
});
|
|
122
|
+
worker.on('exit', (code) => {
|
|
123
|
+
const wasReady = this.ready;
|
|
124
|
+
this.ready = false;
|
|
125
|
+
this.worker = null;
|
|
126
|
+
this.startPromise = null;
|
|
127
|
+
const err = new Error(`read-pool worker exited with code ${code}`);
|
|
128
|
+
if (!wasReady) failStart(err);
|
|
129
|
+
// Reject in-flight so callers fail fast and fall back to the main thread;
|
|
130
|
+
// a fresh worker is spawned on the next request.
|
|
131
|
+
if (code !== 0) this._rejectAll(err);
|
|
132
|
+
else this._rejectAll(new Error('read-pool worker closed'));
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
return this.startPromise;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async request(op, payload, timeoutMs) {
|
|
139
|
+
await this.start();
|
|
140
|
+
if (!this.worker) throw new Error('read-pool worker is not running');
|
|
141
|
+
const requestId = this.nextRequestId++;
|
|
142
|
+
return await new Promise((resolve, reject) => {
|
|
143
|
+
let timer = null;
|
|
144
|
+
if (timeoutMs > 0) {
|
|
145
|
+
timer = setTimeout(() => {
|
|
146
|
+
this.pending.delete(requestId);
|
|
147
|
+
reject(new Error(`read-pool request timed out: ${op}`));
|
|
148
|
+
}, timeoutMs);
|
|
149
|
+
if (typeof timer.unref === 'function') timer.unref();
|
|
150
|
+
}
|
|
151
|
+
this.pending.set(requestId, { resolve, reject, timer });
|
|
152
|
+
try {
|
|
153
|
+
this.worker.postMessage({ type: 'request', requestId, op, payload });
|
|
154
|
+
} catch (err) {
|
|
155
|
+
this._finish(requestId, err);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
_finish(requestId, error, result) {
|
|
161
|
+
const p = this.pending.get(requestId);
|
|
162
|
+
if (!p) return;
|
|
163
|
+
this.pending.delete(requestId);
|
|
164
|
+
if (p.timer) clearTimeout(p.timer);
|
|
165
|
+
if (error) {
|
|
166
|
+
const err = error instanceof Error ? error : new Error(String(error.message || error || 'read-pool request failed'));
|
|
167
|
+
if (error.code) err.code = error.code;
|
|
168
|
+
p.reject(err);
|
|
169
|
+
} else {
|
|
170
|
+
p.resolve(result);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
_rejectAll(err) {
|
|
175
|
+
const all = Array.from(this.pending.values());
|
|
176
|
+
this.pending.clear();
|
|
177
|
+
for (const p of all) { if (p.timer) clearTimeout(p.timer); p.reject(err); }
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async close(timeoutMs) {
|
|
181
|
+
const worker = this.worker;
|
|
182
|
+
if (!worker) { this._rejectAll(new Error('read-pool worker closed')); return; }
|
|
183
|
+
await new Promise((resolve) => {
|
|
184
|
+
let done = false;
|
|
185
|
+
const finish = () => { if (done) return; done = true; resolve(); };
|
|
186
|
+
let timer = null;
|
|
187
|
+
if (timeoutMs > 0) {
|
|
188
|
+
timer = setTimeout(() => { worker.terminate().finally(finish); }, timeoutMs);
|
|
189
|
+
if (typeof timer.unref === 'function') timer.unref();
|
|
190
|
+
}
|
|
191
|
+
const onMessage = (msg) => {
|
|
192
|
+
if (msg?.type !== 'closed') return;
|
|
193
|
+
if (timer) clearTimeout(timer);
|
|
194
|
+
worker.off('message', onMessage);
|
|
195
|
+
finish();
|
|
196
|
+
};
|
|
197
|
+
worker.on('message', onMessage);
|
|
198
|
+
try { worker.postMessage({ type: 'close' }); }
|
|
199
|
+
catch { if (timer) clearTimeout(timer); worker.off('message', onMessage); worker.terminate().finally(finish); }
|
|
200
|
+
});
|
|
201
|
+
this.ready = false;
|
|
202
|
+
this.worker = null;
|
|
203
|
+
this._rejectAll(new Error('read-pool worker closed'));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
class CtmReadPoolClient {
|
|
208
|
+
constructor(options = {}) {
|
|
209
|
+
this.workerPath = options.workerPath || path.resolve(__dirname, '..', 'workers', 'read-pool-worker.js');
|
|
210
|
+
this.size = _resolvePoolSize(options.size);
|
|
211
|
+
this.requestTimeoutMs = _durationMs(options.requestTimeoutMs, DEFAULT_REQUEST_TIMEOUT_MS);
|
|
212
|
+
this.closeTimeoutMs = _durationMs(options.closeTimeoutMs, DEFAULT_CLOSE_TIMEOUT_MS);
|
|
213
|
+
this.logger = options.logger || console;
|
|
214
|
+
this._closed = false;
|
|
215
|
+
this._workers = [];
|
|
216
|
+
for (let i = 0; i < this.size; i++) {
|
|
217
|
+
this._workers.push(new _PoolWorker({ workerPath: this.workerPath, logger: this.logger }));
|
|
218
|
+
}
|
|
219
|
+
// Heavy-op admission control: reserve a couple of workers for latency-sensitive
|
|
220
|
+
// light reads so a heavy parse can't occupy the whole pool (see HEAVY_OPS note).
|
|
221
|
+
this._maxHeavy = Math.max(1, this.size - Math.min(RESERVED_LIGHT_WORKERS, this.size - 1));
|
|
222
|
+
this._heavyInFlight = 0; // heavy ops running across the whole pool
|
|
223
|
+
this._heavyWaiters = []; // FIFO of resolvers waiting for a heavy slot
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
_acquireHeavySlot() {
|
|
227
|
+
if (this._heavyInFlight < this._maxHeavy) {
|
|
228
|
+
this._heavyInFlight++;
|
|
229
|
+
return Promise.resolve();
|
|
230
|
+
}
|
|
231
|
+
return new Promise((resolve) => this._heavyWaiters.push(resolve));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
_releaseHeavySlot() {
|
|
235
|
+
const next = this._heavyWaiters.shift();
|
|
236
|
+
// Hand the slot directly to the next waiter (count is conserved); otherwise free it.
|
|
237
|
+
if (next) next();
|
|
238
|
+
else this._heavyInFlight = Math.max(0, this._heavyInFlight - 1);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async start() {
|
|
242
|
+
if (this._closed) throw new Error('read pool is closed');
|
|
243
|
+
await Promise.all(this._workers.map((w) => w.start()));
|
|
244
|
+
return this;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
_leastBusy() {
|
|
248
|
+
let best = this._workers[0];
|
|
249
|
+
for (const w of this._workers) {
|
|
250
|
+
if (w.inFlight < best.inFlight) best = w;
|
|
251
|
+
}
|
|
252
|
+
return best;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Pick a worker for a request. Light reads prefer a worker with NO heavy op in
|
|
256
|
+
// flight (the reserved capacity), so they never wait behind a multi-second parse;
|
|
257
|
+
// among eligible workers, least-in-flight wins. Heavy ops just take least-busy.
|
|
258
|
+
_pickWorker(heavy) {
|
|
259
|
+
if (!heavy) {
|
|
260
|
+
let best = null;
|
|
261
|
+
for (const w of this._workers) {
|
|
262
|
+
if (w.heavyInFlight > 0) continue;
|
|
263
|
+
if (!best || w.inFlight < best.inFlight) best = w;
|
|
264
|
+
}
|
|
265
|
+
if (best) return best;
|
|
266
|
+
}
|
|
267
|
+
return this._leastBusy();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async request(op, payload = {}, options = {}) {
|
|
271
|
+
if (this._closed) throw new Error('read pool is closed');
|
|
272
|
+
if (!op) throw new Error('read pool request requires op');
|
|
273
|
+
const timeoutMs = _durationMs(options.timeoutMs, this.requestTimeoutMs);
|
|
274
|
+
const heavy = options.heavy ?? HEAVY_OPS.has(op);
|
|
275
|
+
if (!heavy) return await this._pickWorker(false).request(op, payload, timeoutMs);
|
|
276
|
+
|
|
277
|
+
// Heavy op: wait for an admission slot, then dispatch to the least-busy worker.
|
|
278
|
+
await this._acquireHeavySlot();
|
|
279
|
+
const worker = this._pickWorker(true);
|
|
280
|
+
worker.heavyInFlight++;
|
|
281
|
+
try {
|
|
282
|
+
return await worker.request(op, payload, timeoutMs);
|
|
283
|
+
} finally {
|
|
284
|
+
worker.heavyInFlight = Math.max(0, worker.heavyInFlight - 1);
|
|
285
|
+
this._releaseHeavySlot();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
getStatus() {
|
|
290
|
+
return {
|
|
291
|
+
size: this.size,
|
|
292
|
+
closed: this._closed,
|
|
293
|
+
maxHeavy: this._maxHeavy,
|
|
294
|
+
heavyInFlight: this._heavyInFlight,
|
|
295
|
+
heavyWaiting: this._heavyWaiters.length,
|
|
296
|
+
workers: this._workers.map((w) => ({ ready: w.ready, inFlight: w.inFlight, heavyInFlight: w.heavyInFlight })),
|
|
297
|
+
inFlight: this._workers.reduce((s, w) => s + w.inFlight, 0),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async close() {
|
|
302
|
+
if (this._closed) return { ok: true, alreadyClosed: true };
|
|
303
|
+
this._closed = true;
|
|
304
|
+
await Promise.all(this._workers.map((w) => w.close(this.closeTimeoutMs).catch(() => {})));
|
|
305
|
+
return { ok: true };
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function createReadPoolClient(options = {}) {
|
|
310
|
+
return new CtmReadPoolClient(options);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
module.exports = { CtmReadPoolClient, createReadPoolClient };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Generic circuit breaker for off-thread read-pool calls (getAllSessionsData, the prompt
|
|
4
|
+
// index compute, message/timeline parse, ...). At early boot a worker's read-only
|
|
5
|
+
// connection can't attach to the WAL shared-memory until a writer is established + the DB
|
|
6
|
+
// settles, so the request times out. Without a breaker, every off-thread call in that
|
|
7
|
+
// window waits the full timeout and then falls back to a cold main-thread build.
|
|
8
|
+
//
|
|
9
|
+
// After `degradeAfter` consecutive failures the breaker OPENS for `cooldownMs`; while open,
|
|
10
|
+
// the caller SKIPS the off-thread attempt (serving cache/SWR) rather than timing out into a
|
|
11
|
+
// main build. One success CLOSES it (half-open: the first attempt after the cooldown probes
|
|
12
|
+
// the pool again). Pure + injectable clock for testing.
|
|
13
|
+
|
|
14
|
+
function createReadPoolBreaker({ degradeAfter = 2, cooldownMs = 30000 } = {}) {
|
|
15
|
+
const _degradeAfter = Math.max(1, Number(degradeAfter) || 1);
|
|
16
|
+
const _cooldownMs = Math.max(0, Number(cooldownMs) || 0);
|
|
17
|
+
let failures = 0;
|
|
18
|
+
let openUntil = 0;
|
|
19
|
+
return {
|
|
20
|
+
isOpen(now) { return Number(now) < openUntil; },
|
|
21
|
+
recordFailure(now) {
|
|
22
|
+
failures += 1;
|
|
23
|
+
if (failures >= _degradeAfter) openUntil = Number(now) + _cooldownMs;
|
|
24
|
+
return { failures, openUntil };
|
|
25
|
+
},
|
|
26
|
+
recordSuccess() { failures = 0; openUntil = 0; },
|
|
27
|
+
getState() { return { failures, openUntil, degradeAfter: _degradeAfter, cooldownMs: _cooldownMs }; },
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { createReadPoolBreaker };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Back-compat re-export. The circuit breaker is now the generic lib/readpool-breaker.js
|
|
4
|
+
// (shared by getAllSessionsData, the prompt-index compute, and future off-thread reads).
|
|
5
|
+
// `createRecentSessionsBreaker` remains as an alias so existing imports keep working.
|
|
6
|
+
|
|
7
|
+
const { createReadPoolBreaker } = require('./readpool-breaker');
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
createReadPoolBreaker,
|
|
11
|
+
createRecentSessionsBreaker: createReadPoolBreaker,
|
|
12
|
+
};
|