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,93 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Local-only Node.js version guard for the CTM + Wall-E monorepo.
|
|
3
|
+
//
|
|
4
|
+
// Why: the daemon, /ctm-dev, and skill subprocesses share a single compiled
|
|
5
|
+
// better-sqlite3 / sqlite-vec / tree-sitter binary, which is built for ONE Node
|
|
6
|
+
// major (ABI). Launching under a different major corrupts that binary and
|
|
7
|
+
// crash-loops the daemon ("MCP never became ready" + skill-failure storm).
|
|
8
|
+
//
|
|
9
|
+
// This guard activates ONLY inside the owner's repo, identified by a
|
|
10
|
+
// `.node-version` pin file sitting beside `wall-e/` and `claude-task-manager/`.
|
|
11
|
+
// In a shipped create-walle install the pin is excluded from the package
|
|
12
|
+
// (see create-walle/build.sh), so findPinRoot() returns null and this is a
|
|
13
|
+
// silent no-op — it never constrains other users' Node version.
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
// Walk up from startDir until we find the monorepo root: a directory that has a
|
|
19
|
+
// `.node-version` pin AND both project dirs. The triple condition prevents us
|
|
20
|
+
// from hijacking an unrelated `.node-version` somewhere above a user's install.
|
|
21
|
+
function findPinRoot(startDir) {
|
|
22
|
+
let dir = startDir;
|
|
23
|
+
for (;;) {
|
|
24
|
+
if (
|
|
25
|
+
fs.existsSync(path.join(dir, '.node-version')) &&
|
|
26
|
+
fs.existsSync(path.join(dir, 'wall-e')) &&
|
|
27
|
+
fs.existsSync(path.join(dir, 'claude-task-manager'))
|
|
28
|
+
) {
|
|
29
|
+
return dir;
|
|
30
|
+
}
|
|
31
|
+
const parent = path.dirname(dir);
|
|
32
|
+
if (parent === dir) return null;
|
|
33
|
+
dir = parent;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readPin(root) {
|
|
38
|
+
try {
|
|
39
|
+
return fs.readFileSync(path.join(root, '.node-version'), 'utf8').replace(/[v\s]/g, '');
|
|
40
|
+
} catch {
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Assert the running Node matches the pinned MAJOR (the ABI-critical part — a
|
|
46
|
+
// patch bump within the line keeps the same NODE_MODULE_VERSION). On mismatch:
|
|
47
|
+
// hard-fail with remediation (default) or throw when opts.throwInstead is set.
|
|
48
|
+
// Returns {pinned:false} as a no-op when there is no pin (shipped / non-owner).
|
|
49
|
+
function assertPinnedNode(opts = {}) {
|
|
50
|
+
const startDir = opts.startDir || __dirname;
|
|
51
|
+
const root = findPinRoot(startDir);
|
|
52
|
+
if (!root) return { pinned: false };
|
|
53
|
+
const pin = readPin(root);
|
|
54
|
+
if (!pin) return { pinned: false };
|
|
55
|
+
|
|
56
|
+
const pinnedMajor = pin.split('.')[0];
|
|
57
|
+
const runningMajor = process.versions.node.split('.')[0];
|
|
58
|
+
if (runningMajor === pinnedMajor) {
|
|
59
|
+
return { pinned: true, ok: true, root, pin, running: process.versions.node };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (opts.throwInstead) {
|
|
63
|
+
const err = new Error(
|
|
64
|
+
`Node version mismatch: pinned v${pin} (major ${pinnedMajor}), running v${process.versions.node}`
|
|
65
|
+
);
|
|
66
|
+
err.code = 'NODE_PIN_MISMATCH';
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const resolver = path.join(root, 'bin', 'node-bin.sh');
|
|
71
|
+
const devSh = path.join(root, 'bin', 'dev.sh');
|
|
72
|
+
process.stderr.write(
|
|
73
|
+
[
|
|
74
|
+
'',
|
|
75
|
+
' ✖ Wrong Node.js version for CTM + Wall-E.',
|
|
76
|
+
` pinned : v${pin} (ABI line ${pinnedMajor}.x)`,
|
|
77
|
+
` running: v${process.versions.node} (NODE_MODULE_VERSION ${process.versions.modules})`,
|
|
78
|
+
'',
|
|
79
|
+
' The shared native modules (better-sqlite3, sqlite-vec, tree-sitter) are',
|
|
80
|
+
' compiled for ONE Node major. Launching under another major corrupts that',
|
|
81
|
+
' binary and crash-loops the daemon. Start through the pinned launcher:',
|
|
82
|
+
'',
|
|
83
|
+
` "$(bash ${resolver})" <script> # resolves the pinned node`,
|
|
84
|
+
` bash ${devSh} # isolated dev instance`,
|
|
85
|
+
` fnm use ${pin} | nvm use ${pin} # then re-run`,
|
|
86
|
+
'',
|
|
87
|
+
].join('\n') + '\n'
|
|
88
|
+
);
|
|
89
|
+
process.exit(78); // EX_CONFIG
|
|
90
|
+
return { pinned: true, ok: false }; // unreachable
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = { assertPinnedNode, findPinRoot, readPin };
|
|
@@ -12,9 +12,177 @@
|
|
|
12
12
|
// Cost: two property writes per wrapped call. Negligible.
|
|
13
13
|
|
|
14
14
|
let _current = null;
|
|
15
|
+
let _lastCompleted = null;
|
|
15
16
|
const _debug = process.env.CTM_PERF_DEBUG === '1';
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
// Perf diagnostic console output ([perf-lag] / [freeze-probe]) is OFF by default — once the hot
|
|
19
|
+
// paths are tuned, the per-block log lines are noise. The rolling stats are STILL recorded (always
|
|
20
|
+
// queryable at GET /api/diagnostics/event-loop), and CRITICAL freezes (>= PERF_CRITICAL_MS) are
|
|
21
|
+
// ALWAYS logged so a genuine multi-second stall is never silently swallowed. Flip CTM_PERF_LOGS=1
|
|
22
|
+
// (or CTM_PERF_DEBUG=1) to restore full verbose perf logging while debugging.
|
|
23
|
+
const PERF_CRITICAL_MS = (() => {
|
|
24
|
+
const n = Number(process.env.CTM_PERF_CRITICAL_MS);
|
|
25
|
+
return Number.isFinite(n) && n > 0 ? n : 2000;
|
|
26
|
+
})();
|
|
27
|
+
// Re-read the env each call so the flag is runtime-toggleable (and testable) — no restart needed
|
|
28
|
+
// to flip verbose perf logging on/off. Cheap: a couple of process.env property reads.
|
|
29
|
+
function perfLogsEnabled() {
|
|
30
|
+
return process.env.CTM_PERF_LOGS === '1' || process.env.CTM_PERF_DEBUG === '1';
|
|
31
|
+
}
|
|
32
|
+
function shouldLogPerf(durMs) { return perfLogsEnabled() || Number(durMs) >= PERF_CRITICAL_MS; }
|
|
33
|
+
|
|
34
|
+
// Ring buffer of recently-completed spans. `getOp()` only names the single op active
|
|
35
|
+
// (or last completed within 1s) when a [perf-lag] block fires — but a freeze is often
|
|
36
|
+
// the SUM of several sync chunks running back-to-back after awaits, the last of which
|
|
37
|
+
// is `(unknown)`. This trail ("what ran in the last few seconds, and for how long")
|
|
38
|
+
// turns an anonymous block into an attributable one. Cheap: one push per span over a
|
|
39
|
+
// small threshold, capped length.
|
|
40
|
+
const _RECENT_SPAN_MIN_MS = (() => {
|
|
41
|
+
const n = Number(process.env.CTM_PERF_SPAN_MIN_MS);
|
|
42
|
+
return Number.isFinite(n) && n >= 0 ? n : 20;
|
|
43
|
+
})();
|
|
44
|
+
const _RECENT_SPANS_MAX = 24;
|
|
45
|
+
const _recentSpans = []; // {name, dur, end}
|
|
46
|
+
function _recordSpan(name, dur) {
|
|
47
|
+
if (dur < _RECENT_SPAN_MIN_MS) return;
|
|
48
|
+
_recentSpans.push({ name, dur, end: Date.now() });
|
|
49
|
+
if (_recentSpans.length > _RECENT_SPANS_MAX) _recentSpans.shift();
|
|
50
|
+
}
|
|
51
|
+
// Most-recent-first list of spans that ended within maxAgeMs, plus their summed time
|
|
52
|
+
// grouped by name. Read only on the slow path (when a lag line is being emitted).
|
|
53
|
+
function getRecentSpans({ maxAgeMs = 3000, limit = 8 } = {}) {
|
|
54
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
55
|
+
const recent = [];
|
|
56
|
+
const byName = new Map();
|
|
57
|
+
for (let i = _recentSpans.length - 1; i >= 0; i--) {
|
|
58
|
+
const s = _recentSpans[i];
|
|
59
|
+
if (s.end < cutoff) break;
|
|
60
|
+
if (recent.length < limit) recent.push(s);
|
|
61
|
+
byName.set(s.name, (byName.get(s.name) || 0) + s.dur);
|
|
62
|
+
}
|
|
63
|
+
const top = Array.from(byName.entries())
|
|
64
|
+
.sort((a, b) => b[1] - a[1])
|
|
65
|
+
.slice(0, limit)
|
|
66
|
+
.map(([name, ms]) => ({ name, ms }));
|
|
67
|
+
return { recent, top };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Phase 5: continuous event-loop budget. The lag detector feeds every block here so a
|
|
71
|
+
// rolling window of "blocked ms attributed by op" is queryable (GET /api/diagnostics/
|
|
72
|
+
// event-loop) instead of only grep-able after the fact. Cumulative-since-reset (?reset=1
|
|
73
|
+
// for windowed sampling), matching the write-lock diagnostics. Blocks >= the sleep
|
|
74
|
+
// threshold (laptop suspend → a giant wall-clock gap, not a real freeze) are bucketed
|
|
75
|
+
// separately so they don't pollute the named op stats.
|
|
76
|
+
const _SLEEP_BLOCK_MS = (() => {
|
|
77
|
+
const n = Number(process.env.CTM_SLEEP_BLOCK_MS);
|
|
78
|
+
return Number.isFinite(n) && n > 0 ? n : 20000;
|
|
79
|
+
})();
|
|
80
|
+
let _blockSince = Date.now();
|
|
81
|
+
let _blockCount = 0;
|
|
82
|
+
let _blockSumMs = 0;
|
|
83
|
+
let _blockMaxMs = 0;
|
|
84
|
+
let _blockUnknownMs = 0;
|
|
85
|
+
let _sleepCount = 0;
|
|
86
|
+
let _sleepMs = 0;
|
|
87
|
+
const _blockByOp = new Map(); // name -> { count, sumMs, maxMs }
|
|
88
|
+
|
|
89
|
+
// GC-pause attribution. A major GC on CTM's multi-GB heap can pause the loop
|
|
90
|
+
// 100-400ms, and the lag detector blames that pause on whatever synchronous op
|
|
91
|
+
// happened to be running (pty:processData, scrollback-snapshot, ...) — producing
|
|
92
|
+
// scattered ~400ms blocks with no single fixable cause. Observing GC directly
|
|
93
|
+
// lets the diagnostic answer "was that block GC or real CPU?": compare gc.maxMs
|
|
94
|
+
// to maxBlockMs (close → the big block was a GC pause, not code to optimize).
|
|
95
|
+
// Read-only: receiving GC entries does not change GC behavior. The observer is
|
|
96
|
+
// cheap (V8 emits these regardless; the callback only bumps counters). Disable
|
|
97
|
+
// with CTM_TRACK_GC=0 if the observer itself is ever suspected.
|
|
98
|
+
let _gcCount = 0;
|
|
99
|
+
let _gcMs = 0;
|
|
100
|
+
let _gcMaxMs = 0;
|
|
101
|
+
const _gcByKind = new Map(); // kind -> { count, ms, maxMs }
|
|
102
|
+
// performance GC entry `kind` flags (perf_hooks NODE_PERFORMANCE_GC_*).
|
|
103
|
+
const _GC_KIND = { 1: 'minor', 2: 'major', 4: 'incremental', 8: 'weakcb' };
|
|
104
|
+
let _gcObserver = null;
|
|
105
|
+
function _startGcObserver() {
|
|
106
|
+
if (_gcObserver || process.env.CTM_TRACK_GC === '0') return;
|
|
107
|
+
try {
|
|
108
|
+
const { PerformanceObserver } = require('perf_hooks');
|
|
109
|
+
_gcObserver = new PerformanceObserver((list) => {
|
|
110
|
+
for (const entry of list.getEntries()) {
|
|
111
|
+
const ms = Number(entry.duration) || 0;
|
|
112
|
+
_gcCount += 1;
|
|
113
|
+
_gcMs += ms;
|
|
114
|
+
if (ms > _gcMaxMs) _gcMaxMs = ms;
|
|
115
|
+
const flag = entry.detail ? entry.detail.kind : entry.kind;
|
|
116
|
+
const kind = _GC_KIND[flag] || 'gc';
|
|
117
|
+
const e = _gcByKind.get(kind) || { count: 0, ms: 0, maxMs: 0 };
|
|
118
|
+
e.count += 1; e.ms += ms; if (ms > e.maxMs) e.maxMs = ms;
|
|
119
|
+
_gcByKind.set(kind, e);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
_gcObserver.observe({ entryTypes: ['gc'], buffered: false });
|
|
123
|
+
if (typeof _gcObserver.unref === 'function') _gcObserver.unref();
|
|
124
|
+
} catch { _gcObserver = null; }
|
|
125
|
+
}
|
|
126
|
+
_startGcObserver();
|
|
127
|
+
function recordEventLoopBlock(name, blockedMs) {
|
|
128
|
+
const ms = Math.max(0, Number(blockedMs) || 0);
|
|
129
|
+
if (ms <= 0) return;
|
|
130
|
+
if (ms >= _SLEEP_BLOCK_MS) { _sleepCount += 1; _sleepMs += ms; return; }
|
|
131
|
+
_blockCount += 1;
|
|
132
|
+
_blockSumMs += ms;
|
|
133
|
+
if (ms > _blockMaxMs) _blockMaxMs = ms;
|
|
134
|
+
const key = String(name || '(unknown)');
|
|
135
|
+
if (key.includes('(unknown)')) _blockUnknownMs += ms;
|
|
136
|
+
const e = _blockByOp.get(key) || { count: 0, sumMs: 0, maxMs: 0 };
|
|
137
|
+
e.count += 1; e.sumMs += ms; if (ms > e.maxMs) e.maxMs = ms;
|
|
138
|
+
_blockByOp.set(key, e);
|
|
139
|
+
}
|
|
140
|
+
function getEventLoopStats({ reset = false } = {}) {
|
|
141
|
+
const now = Date.now();
|
|
142
|
+
const windowMs = Math.max(1, now - _blockSince);
|
|
143
|
+
const topOps = Array.from(_blockByOp.entries())
|
|
144
|
+
.map(([name, e]) => ({ name, count: e.count, sumMs: e.sumMs, maxMs: e.maxMs }))
|
|
145
|
+
.sort((a, b) => b.sumMs - a.sumMs)
|
|
146
|
+
.slice(0, 15);
|
|
147
|
+
const gcByKind = Array.from(_gcByKind.entries())
|
|
148
|
+
.map(([kind, e]) => ({ kind, count: e.count, ms: Math.round(e.ms), maxMs: Math.round(e.maxMs) }))
|
|
149
|
+
.sort((a, b) => b.ms - a.ms);
|
|
150
|
+
const snap = {
|
|
151
|
+
windowMs,
|
|
152
|
+
blocks: _blockCount,
|
|
153
|
+
totalBlockedMs: _blockSumMs,
|
|
154
|
+
maxBlockMs: _blockMaxMs,
|
|
155
|
+
busyPct: Math.round(1000 * _blockSumMs / windowMs) / 10, // % of wall-clock spent in >threshold blocks
|
|
156
|
+
unknownMs: _blockUnknownMs,
|
|
157
|
+
unknownSharePct: _blockSumMs > 0 ? Math.round(100 * _blockUnknownMs / _blockSumMs) : 0,
|
|
158
|
+
suspectedSleep: { count: _sleepCount, ms: _sleepMs },
|
|
159
|
+
topOps,
|
|
160
|
+
// GC pauses over the same window. Compare gc.maxMs to maxBlockMs: if they're
|
|
161
|
+
// close, the worst block was a GC pause (heap pressure), not a sync op to
|
|
162
|
+
// optimize. enabled:false means the observer was turned off (CTM_TRACK_GC=0).
|
|
163
|
+
gc: {
|
|
164
|
+
enabled: !!_gcObserver,
|
|
165
|
+
count: _gcCount,
|
|
166
|
+
ms: Math.round(_gcMs),
|
|
167
|
+
maxMs: Math.round(_gcMaxMs),
|
|
168
|
+
byKind: gcByKind,
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
if (reset) {
|
|
172
|
+
_blockSince = now; _blockCount = 0; _blockSumMs = 0; _blockMaxMs = 0;
|
|
173
|
+
_blockUnknownMs = 0; _sleepCount = 0; _sleepMs = 0; _blockByOp.clear();
|
|
174
|
+
_gcCount = 0; _gcMs = 0; _gcMaxMs = 0; _gcByKind.clear();
|
|
175
|
+
}
|
|
176
|
+
return snap;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function getOp() {
|
|
180
|
+
if (_current) return _current;
|
|
181
|
+
if (_lastCompleted && Date.now() - _lastCompleted.end <= 1000) {
|
|
182
|
+
return { name: `${_lastCompleted.name} (last completed)`, start: _lastCompleted.start };
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
18
186
|
|
|
19
187
|
function eventLoopBlockMs(deltaMs, intervalMs = 100) {
|
|
20
188
|
return Math.max(0, Number(deltaMs || 0) - Number(intervalMs || 0));
|
|
@@ -44,17 +212,27 @@ function wrap(name, fn) {
|
|
|
44
212
|
if (_debug && Date.now() - t0 > 100) console.warn(`[perf] ${name} threw after ${Date.now() - t0}ms`);
|
|
45
213
|
throw e;
|
|
46
214
|
}
|
|
215
|
+
// Synchronous-prefix duration: the time fn() ran on the loop before returning. For an
|
|
216
|
+
// async function this is the only part that BLOCKED the event loop — the awaited time
|
|
217
|
+
// afterward is I/O wait, not a block. Recording wall-clock for async spans made the
|
|
218
|
+
// recent-spans trail over-attribute (e.g. an async health tick showed "1243ms" that was
|
|
219
|
+
// mostly network wait, not a freeze). Record the sync prefix so the trail is faithful.
|
|
220
|
+
const syncDur = Date.now() - t0;
|
|
47
221
|
// Promise/thenable: the synchronous prefix is done. Clear the tag now so
|
|
48
222
|
// unrelated timers do not inherit a stale "active op" while this awaits I/O.
|
|
49
223
|
if (result && typeof result.then === 'function') {
|
|
50
224
|
_current = prev;
|
|
225
|
+
_recordSpan(name, syncDur); // only the sync prefix counts as a loop block
|
|
51
226
|
return result.finally(() => {
|
|
52
|
-
const dur = Date.now() - t0;
|
|
53
|
-
if (
|
|
227
|
+
const dur = Date.now() - t0; // wall-clock — for the "(last completed)" attribution only
|
|
228
|
+
if (dur > 100) _lastCompleted = { name, start: t0, end: Date.now(), dur };
|
|
229
|
+
if (_debug && dur > 100) console.warn(`[perf] ${name} took ${dur}ms (incl await)`);
|
|
54
230
|
});
|
|
55
231
|
}
|
|
56
|
-
// Sync: restore immediately and pass through.
|
|
57
|
-
const dur =
|
|
232
|
+
// Sync: restore immediately and pass through. The whole call blocked the loop.
|
|
233
|
+
const dur = syncDur;
|
|
234
|
+
if (dur > 100) _lastCompleted = { name, start: t0, end: Date.now(), dur };
|
|
235
|
+
_recordSpan(name, dur);
|
|
58
236
|
_current = prev;
|
|
59
237
|
if (_debug && dur > 100) console.warn(`[perf] ${name} took ${dur}ms`);
|
|
60
238
|
return result;
|
|
@@ -64,4 +242,62 @@ function wrap(name, fn) {
|
|
|
64
242
|
// Back-compat alias — both behave identically now (smart sync/async detection).
|
|
65
243
|
const wrapAsync = wrap;
|
|
66
244
|
|
|
67
|
-
|
|
245
|
+
/**
|
|
246
|
+
* Run a synchronous block as a named span and return its result.
|
|
247
|
+
*
|
|
248
|
+
* Why this exists: `wrap()` tags an async function only for its synchronous
|
|
249
|
+
* prefix, then clears the tag at the first `await`. So a heavy synchronous
|
|
250
|
+
* CHUNK that runs *after* an await (a per-row DB loop, a big JSON.parse) is
|
|
251
|
+
* invisible — the event-loop-lag detector fires in a later timer tick and sees
|
|
252
|
+
* `(unknown)`. Wrapping that chunk with `runSync('name', () => {...})` records
|
|
253
|
+
* it into `_lastCompleted`, so `getOp()` can attribute the block within 1s.
|
|
254
|
+
* Use around whole chunks, not per loop iteration (one closure alloc per call).
|
|
255
|
+
*/
|
|
256
|
+
function runSync(name, fn) {
|
|
257
|
+
return wrap(name, fn)();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Default freeze-probe threshold (ms). A single synchronous span over this bound
|
|
261
|
+
// is a user-perceptible stall on a single-threaded server. Overridable per call.
|
|
262
|
+
const FREEZE_PROBE_MS = (() => {
|
|
263
|
+
const n = Number(process.env.CTM_FREEZE_PROBE_MS);
|
|
264
|
+
return Number.isFinite(n) && n > 0 ? n : 150;
|
|
265
|
+
})();
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Run a synchronous block as a named span AND emit a one-line `[freeze-probe]`
|
|
269
|
+
* diagnostic when it exceeds a threshold. Builds on runSync() (so the span is
|
|
270
|
+
* still attributed to getOp()), but additionally logs WHY the loop froze with
|
|
271
|
+
* caller-supplied context — turning the next freeze into a self-describing log
|
|
272
|
+
* entry instead of an anonymous `(unknown)` lag block.
|
|
273
|
+
*
|
|
274
|
+
* @param {string} name span name (also the freeze-probe `where`)
|
|
275
|
+
* @param {() => any} fn the synchronous work
|
|
276
|
+
* @param {object} [opts]
|
|
277
|
+
* @param {number} [opts.thresholdMs] override the default threshold
|
|
278
|
+
* @param {() => object} [opts.context] lazily-built context object, only invoked
|
|
279
|
+
* on the slow path (so the common fast path pays nothing). Keys are logged
|
|
280
|
+
* as `k=v` pairs.
|
|
281
|
+
*/
|
|
282
|
+
function runSyncProbed(name, fn, opts = {}) {
|
|
283
|
+
const t0 = Date.now();
|
|
284
|
+
try {
|
|
285
|
+
return wrap(name, fn)();
|
|
286
|
+
} finally {
|
|
287
|
+
const dur = Date.now() - t0;
|
|
288
|
+
const threshold = Number(opts.thresholdMs) > 0 ? Number(opts.thresholdMs) : FREEZE_PROBE_MS;
|
|
289
|
+
if (dur >= threshold && shouldLogPerf(dur)) {
|
|
290
|
+
let ctxStr = '';
|
|
291
|
+
try {
|
|
292
|
+
const ctx = typeof opts.context === 'function' ? opts.context() : null;
|
|
293
|
+
if (ctx && typeof ctx === 'object') {
|
|
294
|
+
ctxStr = Object.keys(ctx).map((k) => `${k}=${ctx[k]}`).join(' ');
|
|
295
|
+
}
|
|
296
|
+
} catch { /* context build must never break the probe */ }
|
|
297
|
+
// eslint-disable-next-line no-console
|
|
298
|
+
console.warn(`[freeze-probe] ${name} blocked ${dur}ms${ctxStr ? ' ' + ctxStr : ''}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
module.exports = { eventLoopBlockMs, getOp, getRecentSpans, recordEventLoopBlock, getEventLoopStats, shouldReportLag, wrap, wrapAsync, runSync, runSyncProbed, FREEZE_PROBE_MS, perfLogsEnabled, shouldLogPerf, PERF_CRITICAL_MS };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Match a detected approval command against the user's Permission Manager rules
|
|
4
|
+
// (the `perm_rules` table — Claude-syntax "Bash(node:*)" allow/deny entries).
|
|
5
|
+
//
|
|
6
|
+
// These rules otherwise only configure Claude Code's own settings.json, so they
|
|
7
|
+
// don't apply to Codex/other providers and the shadow approver never honored
|
|
8
|
+
// them. This lets the approver respect the user's explicit allow/deny across ALL
|
|
9
|
+
// providers: an allow match → auto-approve (skip the verifier); a deny match →
|
|
10
|
+
// escalate. An allow rule marked always_ask is treated as "ask" (no auto-approve).
|
|
11
|
+
|
|
12
|
+
// Parse a "Tool(spec)" rule into { tool, wildcard|prefix|exact }.
|
|
13
|
+
function parseRule(rule) {
|
|
14
|
+
const raw = String(rule || '').trim();
|
|
15
|
+
const m = raw.match(/^([A-Za-z_][\w-]*)\((.*)\)$/);
|
|
16
|
+
if (!m) {
|
|
17
|
+
// Bare tool name (e.g. "Read") = the whole tool is covered.
|
|
18
|
+
return raw ? { tool: raw, wildcard: true } : null;
|
|
19
|
+
}
|
|
20
|
+
const tool = m[1];
|
|
21
|
+
const spec = m[2].trim();
|
|
22
|
+
if (spec === '' || spec === '*') return { tool, wildcard: true };
|
|
23
|
+
if (spec.endsWith(':*')) return { tool, prefix: spec.slice(0, -2).trim() };
|
|
24
|
+
return { tool, exact: spec };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Reduce a displayed tool header ("⏺ Bash command", "Bash(...)", "Edit") to a
|
|
28
|
+
// bare tool word ("Bash", "Edit").
|
|
29
|
+
function toolOf(toolName) {
|
|
30
|
+
const t = String(toolName || '').replace(/^[^A-Za-z]+/, '').trim();
|
|
31
|
+
return (t.split(/[\s(]/)[0] || '');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Does `command` fall under a parsed rule's spec? Prefix matches require a word
|
|
35
|
+
// boundary so "Bash(node:*)" does not match "nodejs ...".
|
|
36
|
+
function commandMatchesSpec(command, parsed) {
|
|
37
|
+
if (!parsed) return false;
|
|
38
|
+
if (parsed.wildcard) return true;
|
|
39
|
+
const cmd = String(command || '').trim();
|
|
40
|
+
if (parsed.exact != null) return cmd === parsed.exact;
|
|
41
|
+
const p = parsed.prefix;
|
|
42
|
+
if (!p) return false;
|
|
43
|
+
if (cmd === p) return true;
|
|
44
|
+
if (cmd.startsWith(p)) {
|
|
45
|
+
// Boundary: the next char must not continue a word, so "node" does not match
|
|
46
|
+
// "nodejs" but "node -e require" still matches "node -e require(...)".
|
|
47
|
+
const next = cmd.charAt(p.length);
|
|
48
|
+
return next === '' || !/[A-Za-z0-9_]/.test(next);
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Evaluate a command against the user's perm_rules.
|
|
54
|
+
// Returns { action: 'deny', rule } | { action: 'allow', rule } | null.
|
|
55
|
+
// Deny wins over allow. allow rules with always_ask are skipped (the user wants
|
|
56
|
+
// to keep being asked).
|
|
57
|
+
function matchPermission({ toolName, command } = {}, rules = []) {
|
|
58
|
+
const tool = toolOf(toolName);
|
|
59
|
+
const looksBash = /^bash$/i.test(tool) || /\bbash\b/i.test(String(toolName || ''));
|
|
60
|
+
let allowHit = null;
|
|
61
|
+
for (const r of rules || []) {
|
|
62
|
+
const parsed = parseRule(r.rule);
|
|
63
|
+
if (!parsed) continue;
|
|
64
|
+
const ruleTool = String(parsed.tool || '').toLowerCase();
|
|
65
|
+
const toolMatch = ruleTool === tool.toLowerCase() || (looksBash && ruleTool === 'bash');
|
|
66
|
+
if (!toolMatch) continue;
|
|
67
|
+
if (!commandMatchesSpec(command, parsed)) continue;
|
|
68
|
+
if (r.list_type === 'deny') return { action: 'deny', rule: r.rule };
|
|
69
|
+
if (r.list_type === 'allow' && !(r.always_ask)) {
|
|
70
|
+
if (!allowHit) allowHit = { action: 'allow', rule: r.rule };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return allowHit;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = { parseRule, toolOf, commandMatchesSpec, matchPermission };
|
|
@@ -19,12 +19,26 @@ function readJsonFile(filePath) {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
async function readJsonFileAsync(filePath) {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(await fs.promises.readFile(filePath, 'utf8'));
|
|
25
|
+
} catch {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
22
30
|
function writeJsonFile(filePath, data) {
|
|
23
31
|
const dir = path.dirname(filePath);
|
|
24
32
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
25
33
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
26
34
|
}
|
|
27
35
|
|
|
36
|
+
async function writeJsonFileAsync(filePath, data) {
|
|
37
|
+
const dir = path.dirname(filePath);
|
|
38
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
39
|
+
await fs.promises.writeFile(filePath, JSON.stringify(data, null, 2));
|
|
40
|
+
}
|
|
41
|
+
|
|
28
42
|
function getProjectSettingsPath(projectPath) {
|
|
29
43
|
return path.join(projectPath, '.claude', 'settings.local.json');
|
|
30
44
|
}
|
|
@@ -35,6 +49,21 @@ function isValidPermRule(rule) {
|
|
|
35
49
|
return true;
|
|
36
50
|
}
|
|
37
51
|
|
|
52
|
+
function pushSettingsRules(rules, settings, { scope, project }) {
|
|
53
|
+
const permissions = settings && settings.permissions || {};
|
|
54
|
+
for (const tool of permissions.allow || []) {
|
|
55
|
+
if (!isValidPermRule(tool)) {
|
|
56
|
+
console.warn(` Skipping invalid perm rule: ${tool}`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
rules.push({ rule: tool, listType: 'allow', scope, project });
|
|
60
|
+
}
|
|
61
|
+
for (const tool of permissions.deny || []) {
|
|
62
|
+
if (!isValidPermRule(tool)) continue;
|
|
63
|
+
rules.push({ rule: tool, listType: 'deny', scope, project });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
38
67
|
function getSettingsAllowList(settingsPath) {
|
|
39
68
|
const settings = readJsonFile(settingsPath);
|
|
40
69
|
return (settings.permissions && settings.permissions.allow) || [];
|
|
@@ -50,35 +79,40 @@ function collectJsonRules({ homeDir = process.env.HOME } = {}) {
|
|
|
50
79
|
const globalSettingsPath = getGlobalSettingsPath(homeDir);
|
|
51
80
|
const claudeJsonPath = getClaudeJsonPath(homeDir);
|
|
52
81
|
|
|
53
|
-
|
|
54
|
-
if (!isValidPermRule(tool)) {
|
|
55
|
-
console.warn(` Skipping invalid perm rule: ${tool}`);
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
rules.push({ rule: tool, listType: 'allow', scope: 'global', project: '__global__' });
|
|
59
|
-
}
|
|
60
|
-
for (const tool of getSettingsDenyList(globalSettingsPath)) {
|
|
61
|
-
if (!isValidPermRule(tool)) continue;
|
|
62
|
-
rules.push({ rule: tool, listType: 'deny', scope: 'global', project: '__global__' });
|
|
63
|
-
}
|
|
82
|
+
pushSettingsRules(rules, readJsonFile(globalSettingsPath), { scope: 'global', project: '__global__' });
|
|
64
83
|
|
|
65
84
|
const claudeJson = readJsonFile(claudeJsonPath);
|
|
66
85
|
const projectPaths = Object.keys(claudeJson.projects || {});
|
|
67
86
|
for (const projectPath of projectPaths) {
|
|
68
87
|
const settingsPath = getProjectSettingsPath(projectPath);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
for (const tool of getSettingsDenyList(settingsPath)) {
|
|
88
|
+
pushSettingsRules(rules, readJsonFile(settingsPath), { scope: 'project', project: projectPath });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for (const [projectPath, projectData] of Object.entries(claudeJson.projects || {})) {
|
|
92
|
+
const allowedTools = projectData.allowedTools || [];
|
|
93
|
+
for (const tool of allowedTools) {
|
|
77
94
|
if (!isValidPermRule(tool)) continue;
|
|
78
|
-
rules.push({ rule: tool, listType: '
|
|
95
|
+
rules.push({ rule: tool, listType: 'allow', scope: 'project', project: projectPath });
|
|
79
96
|
}
|
|
80
97
|
}
|
|
81
98
|
|
|
99
|
+
return rules;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function collectJsonRulesAsync({ homeDir = process.env.HOME } = {}) {
|
|
103
|
+
const rules = [];
|
|
104
|
+
const globalSettingsPath = getGlobalSettingsPath(homeDir);
|
|
105
|
+
const claudeJsonPath = getClaudeJsonPath(homeDir);
|
|
106
|
+
|
|
107
|
+
pushSettingsRules(rules, await readJsonFileAsync(globalSettingsPath), { scope: 'global', project: '__global__' });
|
|
108
|
+
|
|
109
|
+
const claudeJson = await readJsonFileAsync(claudeJsonPath);
|
|
110
|
+
const projectPaths = Object.keys(claudeJson.projects || {});
|
|
111
|
+
for (const projectPath of projectPaths) {
|
|
112
|
+
const settingsPath = getProjectSettingsPath(projectPath);
|
|
113
|
+
pushSettingsRules(rules, await readJsonFileAsync(settingsPath), { scope: 'project', project: projectPath });
|
|
114
|
+
}
|
|
115
|
+
|
|
82
116
|
for (const [projectPath, projectData] of Object.entries(claudeJson.projects || {})) {
|
|
83
117
|
const allowedTools = projectData.allowedTools || [];
|
|
84
118
|
for (const tool of allowedTools) {
|
|
@@ -101,6 +135,32 @@ function mergeJsonRulesIntoDb(dbApi, options = {}) {
|
|
|
101
135
|
return imported;
|
|
102
136
|
}
|
|
103
137
|
|
|
138
|
+
async function mergeJsonRulesIntoDbAsync(dbApi, options = {}) {
|
|
139
|
+
const jsonRules = await collectJsonRulesAsync(options);
|
|
140
|
+
return applyJsonRulesToDb(dbApi, jsonRules, { mergeOnly: true }).imported;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function applyJsonRulesToDb(dbApi, jsonRules, { mergeOnly = false } = {}) {
|
|
144
|
+
const rules = Array.isArray(jsonRules) ? jsonRules : [];
|
|
145
|
+
const existing = dbApi.listPermRules();
|
|
146
|
+
if (existing.length > 0 || mergeOnly) {
|
|
147
|
+
const before = existing.length;
|
|
148
|
+
for (const rule of rules) dbApi.addPermRule(rule);
|
|
149
|
+
const imported = dbApi.listPermRules().length - before;
|
|
150
|
+
if (imported > 0) {
|
|
151
|
+
console.log(` Merged ${imported} new permission rules from JSON files into DB`);
|
|
152
|
+
}
|
|
153
|
+
return { imported, mode: 'merge' };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (rules.length > 0) {
|
|
157
|
+
dbApi.bulkSetPermRules(rules);
|
|
158
|
+
console.log(` Imported ${rules.length} permission rules from JSON files into DB`);
|
|
159
|
+
return { imported: rules.length, mode: 'bulk' };
|
|
160
|
+
}
|
|
161
|
+
return { imported: 0, mode: 'empty' };
|
|
162
|
+
}
|
|
163
|
+
|
|
104
164
|
function syncDbToJsonFiles(dbApi, { homeDir = process.env.HOME } = {}) {
|
|
105
165
|
for (const rule of collectJsonRules({ homeDir })) dbApi.addPermRule(rule);
|
|
106
166
|
for (const row of dbApi.listPermRules()) {
|
|
@@ -132,6 +192,37 @@ function syncDbToJsonFiles(dbApi, { homeDir = process.env.HOME } = {}) {
|
|
|
132
192
|
}
|
|
133
193
|
}
|
|
134
194
|
|
|
195
|
+
async function syncDbToJsonFilesAsync(dbApi, { homeDir = process.env.HOME } = {}) {
|
|
196
|
+
for (const rule of await collectJsonRulesAsync({ homeDir })) dbApi.addPermRule(rule);
|
|
197
|
+
for (const row of dbApi.listPermRules()) {
|
|
198
|
+
if (!isValidPermRule(row.rule)) {
|
|
199
|
+
console.warn(` Removing invalid perm rule from DB: ${row.rule}`);
|
|
200
|
+
dbApi.removePermRule({ rule: row.rule, listType: row.list_type, project: row.project });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const byProject = dbApi.getPermRulesByProject();
|
|
205
|
+
const globalSettingsPath = getGlobalSettingsPath(homeDir);
|
|
206
|
+
const globalRules = byProject.__global__ || { allow: [], deny: [] };
|
|
207
|
+
const globalSettings = await readJsonFileAsync(globalSettingsPath);
|
|
208
|
+
if (!globalSettings.permissions) globalSettings.permissions = {};
|
|
209
|
+
globalSettings.permissions.allow = globalRules.allow.filter(isValidPermRule);
|
|
210
|
+
globalSettings.permissions.deny = globalRules.deny.filter(isValidPermRule);
|
|
211
|
+
await writeJsonFileAsync(globalSettingsPath, globalSettings);
|
|
212
|
+
|
|
213
|
+
for (const [project, lists] of Object.entries(byProject)) {
|
|
214
|
+
if (project === '__global__') continue;
|
|
215
|
+
const settingsPath = getProjectSettingsPath(project);
|
|
216
|
+
try {
|
|
217
|
+
const settings = await readJsonFileAsync(settingsPath);
|
|
218
|
+
if (!settings.permissions) settings.permissions = {};
|
|
219
|
+
settings.permissions.allow = lists.allow.filter(isValidPermRule);
|
|
220
|
+
settings.permissions.deny = lists.deny.filter(isValidPermRule);
|
|
221
|
+
await writeJsonFileAsync(settingsPath, settings);
|
|
222
|
+
} catch {}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
135
226
|
function importPermissionsToDb(dbApi, { syncBack = true, homeDir = process.env.HOME } = {}) {
|
|
136
227
|
const existing = dbApi.listPermRules();
|
|
137
228
|
if (existing.length > 0) {
|
|
@@ -148,15 +239,37 @@ function importPermissionsToDb(dbApi, { syncBack = true, homeDir = process.env.H
|
|
|
148
239
|
}
|
|
149
240
|
}
|
|
150
241
|
|
|
242
|
+
async function importPermissionsToDbAsync(dbApi, {
|
|
243
|
+
syncBack = true,
|
|
244
|
+
homeDir = process.env.HOME,
|
|
245
|
+
applyWith = null,
|
|
246
|
+
applyRulesWith = null,
|
|
247
|
+
} = {}) {
|
|
248
|
+
const rules = await collectJsonRulesAsync({ homeDir });
|
|
249
|
+
const apply = () => applyJsonRulesToDb(dbApi, rules);
|
|
250
|
+
const result = typeof applyRulesWith === 'function'
|
|
251
|
+
? await applyRulesWith(rules, apply)
|
|
252
|
+
: (typeof applyWith === 'function' ? await applyWith(apply) : apply());
|
|
253
|
+
if (syncBack) await syncDbToJsonFilesAsync(dbApi, { homeDir });
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
|
|
151
257
|
module.exports = {
|
|
258
|
+
applyJsonRulesToDb,
|
|
152
259
|
collectJsonRules,
|
|
260
|
+
collectJsonRulesAsync,
|
|
153
261
|
getClaudeJsonPath,
|
|
154
262
|
getGlobalSettingsPath,
|
|
155
263
|
getProjectSettingsPath,
|
|
156
264
|
importPermissionsToDb,
|
|
265
|
+
importPermissionsToDbAsync,
|
|
157
266
|
isValidPermRule,
|
|
158
267
|
mergeJsonRulesIntoDb,
|
|
268
|
+
mergeJsonRulesIntoDbAsync,
|
|
159
269
|
readJsonFile,
|
|
270
|
+
readJsonFileAsync,
|
|
160
271
|
syncDbToJsonFiles,
|
|
272
|
+
syncDbToJsonFilesAsync,
|
|
161
273
|
writeJsonFile,
|
|
274
|
+
writeJsonFileAsync,
|
|
162
275
|
};
|