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
|
@@ -81,6 +81,12 @@ function buildTailscaleSetup(status, options = {}) {
|
|
|
81
81
|
return {
|
|
82
82
|
installed: true,
|
|
83
83
|
available: true,
|
|
84
|
+
// Authoritative tailnet liveness for connection-health: BackendState ('Running' = up,
|
|
85
|
+
// 'Stopped'/'NeedsLogin'/'Starting' = not connected) + the human-readable Health
|
|
86
|
+
// reasons. More reliable than Self.Online (which can be false for the local node even
|
|
87
|
+
// when healthy). Absent backend_state → treated as up (old/odd `tailscale status`).
|
|
88
|
+
backend_state: status?.BackendState || status?.backend_state || '',
|
|
89
|
+
health: Array.isArray(status?.Health) ? status.Health.slice(0, 4) : [],
|
|
84
90
|
dns_name: dnsName,
|
|
85
91
|
host,
|
|
86
92
|
origin,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function isUiRefreshActivitySource(source) {
|
|
4
|
+
const text = String(source || '').toLowerCase();
|
|
5
|
+
return text.includes('ui-refresh');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function shouldRecordProviderBusyStatusEvidence(source) {
|
|
9
|
+
return !isUiRefreshActivitySource(source);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function positiveMs(value) {
|
|
13
|
+
const n = Number(value || 0);
|
|
14
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function activeTurnLatestEvidenceMs({
|
|
18
|
+
startedAt = 0,
|
|
19
|
+
evidenceAt = 0,
|
|
20
|
+
evidenceSource = '',
|
|
21
|
+
lastActivityAt = 0,
|
|
22
|
+
lastPtyActivityAt = 0,
|
|
23
|
+
} = {}) {
|
|
24
|
+
const values = [positiveMs(startedAt), positiveMs(lastActivityAt), positiveMs(lastPtyActivityAt)];
|
|
25
|
+
if (!isUiRefreshActivitySource(evidenceSource)) values.push(positiveMs(evidenceAt));
|
|
26
|
+
return values.reduce((max, value) => Math.max(max, value), 0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
activeTurnLatestEvidenceMs,
|
|
31
|
+
isUiRefreshActivitySource,
|
|
32
|
+
shouldRecordProviderBusyStatusEvidence,
|
|
33
|
+
};
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Server-side detection and keystroke planning for interactive terminal choice
|
|
4
|
+
// menus (Claude/Codex/etc. numbered selectors). This mirrors the phone client's
|
|
5
|
+
// detection in public/m/app.js (terminalChoiceOptionFromLine / extractLiveTerminalChoice)
|
|
6
|
+
// so the two agree on what the menu is — but here it runs against the authoritative
|
|
7
|
+
// headless xterm buffer and produces the keystrokes that actually drive the agent.
|
|
8
|
+
//
|
|
9
|
+
// Why this exists: phone selection taps used to be routed through the free-form
|
|
10
|
+
// reply pipeline (session.send_message), which is blocked/swallowed when the agent
|
|
11
|
+
// is parked on a menu. CTM already drives live prompts elsewhere by writing keys to
|
|
12
|
+
// the PTY (Esc, prompt-history arrows, approval keystrokes). This module is the
|
|
13
|
+
// menu-aware equivalent: read the live options, find the highlight, and move it to
|
|
14
|
+
// the tapped option with arrow keys + Enter (with a number-key fallback).
|
|
15
|
+
|
|
16
|
+
const SELECTOR_RE = /^(?:›|❯|➜|▶|▸)\s*(.+)$/;
|
|
17
|
+
const NUMBERED_RE = /^(\d{1,2})[.)]\s+(.+)$/;
|
|
18
|
+
const CHECKBOX_PREFIX_RE = /^\[\s*([ xX✓✔])\s*\]\s*/;
|
|
19
|
+
const CHECKBOX_GLYPH_RE = /^([☐□☑☒✓✔])\s*/;
|
|
20
|
+
const SUBMIT_CONTROL_RE = /(?:^|[\s✓✔])submit\s*(?:→|$|\b)/i;
|
|
21
|
+
|
|
22
|
+
function stripAnsi(value) {
|
|
23
|
+
return String(value || '').replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizeLine(line) {
|
|
27
|
+
return stripAnsi(String(line || '')).replace(/\u00a0/g, " ").replace(/\s+$/, '');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function checkboxFromLabel(label) {
|
|
31
|
+
const raw = String(label || '').trim();
|
|
32
|
+
let match = raw.match(CHECKBOX_PREFIX_RE);
|
|
33
|
+
if (match) {
|
|
34
|
+
const marker = match[1];
|
|
35
|
+
return {
|
|
36
|
+
checkbox: true,
|
|
37
|
+
checked: /[xX✓✔]/.test(marker),
|
|
38
|
+
label: raw.slice(match[0].length).trim(),
|
|
39
|
+
rawLabel: raw,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
match = raw.match(CHECKBOX_GLYPH_RE);
|
|
43
|
+
if (match) {
|
|
44
|
+
return {
|
|
45
|
+
checkbox: true,
|
|
46
|
+
checked: /[☑☒✓✔]/.test(match[1]),
|
|
47
|
+
label: raw.slice(match[0].length).trim(),
|
|
48
|
+
rawLabel: raw,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return { checkbox: false, checked: false, label: raw, rawLabel: raw };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function fingerprintLabel(label) {
|
|
55
|
+
return checkboxFromLabel(label).label.replace(/\s+/g, ' ').trim();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Parse a single buffer line into a numbered option, capturing whether it is the
|
|
59
|
+
// currently highlighted row (prefixed by a selector glyph like ❯).
|
|
60
|
+
// A widget rendered inside a box frame prefixes (and suffixes) each row with a
|
|
61
|
+
// vertical border ("│ ❯ 1. Moderate │"). Strip surrounding box-frame chars so a
|
|
62
|
+
// boxed menu parses like a borderless one — otherwise the leading │ defeats both
|
|
63
|
+
// SELECTOR_RE and NUMBERED_RE and the whole AskUserQuestion classifies as `none`,
|
|
64
|
+
// stranding a stale approval banner over it.
|
|
65
|
+
const BOX_BORDER_PREFIX_RE = /^[│┃║▏▕|]\s*/;
|
|
66
|
+
const BOX_BORDER_SUFFIX_RE = /\s*[│┃║▏▕|]$/;
|
|
67
|
+
|
|
68
|
+
function choiceOptionFromLine(line) {
|
|
69
|
+
const raw = normalizeLine(line).trim().replace(BOX_BORDER_PREFIX_RE, '').replace(BOX_BORDER_SUFFIX_RE, '').trim();
|
|
70
|
+
if (!raw) return null;
|
|
71
|
+
const selector = raw.match(SELECTOR_RE);
|
|
72
|
+
const text = selector ? selector[1].trim() : raw;
|
|
73
|
+
const numbered = text.match(NUMBERED_RE);
|
|
74
|
+
if (!numbered) return null;
|
|
75
|
+
const parsed = checkboxFromLabel(numbered[2]);
|
|
76
|
+
const label = parsed.label;
|
|
77
|
+
if (!label) return null;
|
|
78
|
+
return {
|
|
79
|
+
value: numbered[1],
|
|
80
|
+
label,
|
|
81
|
+
rawLabel: parsed.rawLabel,
|
|
82
|
+
checkbox: parsed.checkbox,
|
|
83
|
+
checked: parsed.checked,
|
|
84
|
+
selected: !!selector,
|
|
85
|
+
text,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Window (in buffer rows) we scan above/below the highlighted row for sibling
|
|
90
|
+
// options. Claude's AskUserQuestion widget puts a description line (sometimes
|
|
91
|
+
// wrapped to two) under every option plus a divider before the trailing
|
|
92
|
+
// "Chat about this", so a tight window drops real options. 12 comfortably spans
|
|
93
|
+
// a 5-6 option menu with descriptions while staying local to the menu block.
|
|
94
|
+
// Mirrored by terminalChoiceOptionsNear in public/m/app.js.
|
|
95
|
+
const OPTION_SCAN_RADIUS = 12;
|
|
96
|
+
function optionsNear(lines, selectedRow) {
|
|
97
|
+
const options = [];
|
|
98
|
+
const start = Math.max(0, selectedRow - OPTION_SCAN_RADIUS);
|
|
99
|
+
const end = Math.min(lines.length - 1, selectedRow + OPTION_SCAN_RADIUS);
|
|
100
|
+
for (let row = start; row <= end; row += 1) {
|
|
101
|
+
const option = choiceOptionFromLine(lines[row]);
|
|
102
|
+
if (!option) continue;
|
|
103
|
+
options.push({ ...option, row });
|
|
104
|
+
}
|
|
105
|
+
return options;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// A row qualifies as the menu's selected row when it is a highlighted numbered
|
|
109
|
+
// option with at least one more numbered option nearby (so a lone "❯ 1. foo" in
|
|
110
|
+
// prose is not mistaken for a menu).
|
|
111
|
+
function isSelectorRow(lines, row) {
|
|
112
|
+
const option = choiceOptionFromLine(lines[row]);
|
|
113
|
+
if (!option || !option.selected) return false;
|
|
114
|
+
return optionsNear(lines, row).length >= 2;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Deterministic short fingerprint over the option set (value+label, in order).
|
|
118
|
+
// Mirrored byte-for-byte in public/m/app.js so client and server agree on whether
|
|
119
|
+
// the menu the user tapped still matches the live menu. FNV-1a (no Node crypto, so
|
|
120
|
+
// the browser can compute the identical value).
|
|
121
|
+
function choiceFingerprint(options) {
|
|
122
|
+
const basis = (Array.isArray(options) ? options : [])
|
|
123
|
+
.map((option) => `${option.value}:${fingerprintLabel(option.label || option.rawLabel || '')}`)
|
|
124
|
+
.join('|');
|
|
125
|
+
let hash = 0x811c9dc5;
|
|
126
|
+
for (let i = 0; i < basis.length; i += 1) {
|
|
127
|
+
hash ^= basis.charCodeAt(i);
|
|
128
|
+
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
129
|
+
}
|
|
130
|
+
return hash.toString(16).padStart(8, '0');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function hasSubmitControl(lines) {
|
|
134
|
+
return (Array.isArray(lines) ? lines : []).some((line) => SUBMIT_CONTROL_RE.test(normalizeLine(line)));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Extract the active choice menu from raw terminal text. Returns null when no
|
|
138
|
+
// numbered selection menu is present. `selectedIndex` is the ordinal of the
|
|
139
|
+
// highlighted option within `options` (not a buffer row), which is what the
|
|
140
|
+
// keystroke planner navigates against.
|
|
141
|
+
function extractNumberedChoice(lines) {
|
|
142
|
+
let selectedRow = -1;
|
|
143
|
+
for (let row = 0; row < lines.length; row += 1) {
|
|
144
|
+
if (isSelectorRow(lines, row)) { selectedRow = row; break; }
|
|
145
|
+
}
|
|
146
|
+
if (selectedRow < 0) return null;
|
|
147
|
+
const near = optionsNear(lines, selectedRow).sort((a, b) => a.row - b.row);
|
|
148
|
+
if (near.length < 2) return null;
|
|
149
|
+
|
|
150
|
+
const seen = new Set();
|
|
151
|
+
const options = [];
|
|
152
|
+
for (const option of near) {
|
|
153
|
+
const key = `${option.value}\n${option.label}`;
|
|
154
|
+
if (seen.has(key)) continue;
|
|
155
|
+
seen.add(key);
|
|
156
|
+
options.push({
|
|
157
|
+
value: option.value,
|
|
158
|
+
label: option.label,
|
|
159
|
+
rawLabel: option.rawLabel,
|
|
160
|
+
checkbox: !!option.checkbox,
|
|
161
|
+
checked: !!option.checked,
|
|
162
|
+
selected: option.selected,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
const selectedIndex = options.findIndex((option) => option.selected);
|
|
166
|
+
const requiresSubmit = options.some((option) => option.checkbox) && hasSubmitControl(lines);
|
|
167
|
+
return {
|
|
168
|
+
kind: requiresSubmit ? 'form' : 'numbered',
|
|
169
|
+
inline: false,
|
|
170
|
+
requiresSubmit,
|
|
171
|
+
submitAvailable: requiresSubmit,
|
|
172
|
+
options,
|
|
173
|
+
selectedValue: options.find((option) => option.selected)?.value || '',
|
|
174
|
+
selectedIndex,
|
|
175
|
+
fingerprint: choiceFingerprint(options),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Inline approval prompts (e.g. Gemini's "Approve? (y/n/always)" or a shell's
|
|
180
|
+
// "Overwrite? [y/N]") are un-numbered: there is no navigable list or highlight,
|
|
181
|
+
// you answer by typing a shortcut. We detect the parenthesised "/"-separated
|
|
182
|
+
// option group near the tail and turn each token into a tappable option whose
|
|
183
|
+
// value IS the keystroke to type. The keystroke planner's no-highlight path then
|
|
184
|
+
// types that token + Enter — the same thing the per-provider approval engine does.
|
|
185
|
+
const INLINE_QUESTION_RE = /(?:approve|proceed|continue|allow|overwrite|apply|replace|confirm|delete|ok to|do you want|would you like|run this|execute)\b[^?\n]*\?\s*[([]\s*([A-Za-z][A-Za-z]*(?:\s*\/\s*[A-Za-z]+)+)\s*[)\]]/i;
|
|
186
|
+
|
|
187
|
+
const INLINE_TOKEN_LABELS = {
|
|
188
|
+
y: 'Yes',
|
|
189
|
+
yes: 'Yes',
|
|
190
|
+
n: 'No',
|
|
191
|
+
no: 'No',
|
|
192
|
+
a: 'Always allow',
|
|
193
|
+
always: 'Always allow',
|
|
194
|
+
all: 'Allow all',
|
|
195
|
+
s: 'Skip',
|
|
196
|
+
skip: 'Skip',
|
|
197
|
+
q: 'Cancel',
|
|
198
|
+
quit: 'Cancel',
|
|
199
|
+
c: 'Cancel',
|
|
200
|
+
cancel: 'Cancel',
|
|
201
|
+
d: 'Show diff',
|
|
202
|
+
diff: 'Show diff',
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
function inlineTokenLabel(token) {
|
|
206
|
+
const key = token.toLowerCase();
|
|
207
|
+
if (INLINE_TOKEN_LABELS[key]) return INLINE_TOKEN_LABELS[key];
|
|
208
|
+
return token.length === 1 ? token.toUpperCase() : `${token[0].toUpperCase()}${token.slice(1)}`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function extractInlineChoice(lines) {
|
|
212
|
+
// Only consider the active prompt: scan the last few non-empty lines so a
|
|
213
|
+
// scrolled-up, already-answered prompt is never matched.
|
|
214
|
+
const nonEmpty = [];
|
|
215
|
+
for (let i = lines.length - 1; i >= 0 && nonEmpty.length < 6; i -= 1) {
|
|
216
|
+
const text = normalizeLine(lines[i]).trim();
|
|
217
|
+
if (text) nonEmpty.push(text);
|
|
218
|
+
}
|
|
219
|
+
for (const line of nonEmpty) {
|
|
220
|
+
const match = line.match(INLINE_QUESTION_RE);
|
|
221
|
+
if (!match) continue;
|
|
222
|
+
const tokens = match[1].split('/').map((token) => token.trim()).filter(Boolean);
|
|
223
|
+
if (tokens.length < 2) continue;
|
|
224
|
+
const seen = new Set();
|
|
225
|
+
const options = [];
|
|
226
|
+
for (const token of tokens) {
|
|
227
|
+
const value = token.length === 1 ? token.toLowerCase() : token;
|
|
228
|
+
if (seen.has(value)) continue;
|
|
229
|
+
seen.add(value);
|
|
230
|
+
options.push({ value, label: inlineTokenLabel(token), selected: false });
|
|
231
|
+
}
|
|
232
|
+
if (options.length < 2) continue;
|
|
233
|
+
return {
|
|
234
|
+
kind: 'inline',
|
|
235
|
+
inline: true,
|
|
236
|
+
options,
|
|
237
|
+
selectedValue: '',
|
|
238
|
+
selectedIndex: -1,
|
|
239
|
+
fingerprint: choiceFingerprint(options),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Extract the active choice prompt from raw terminal text. Returns null when no
|
|
246
|
+
// numbered selection menu is present. `selectedIndex` is the ordinal of the
|
|
247
|
+
// highlighted option within `options` (not a buffer row), which is what the
|
|
248
|
+
// keystroke planner navigates against. Inline prompts report selectedIndex -1 so
|
|
249
|
+
// the planner types the option's shortcut instead of navigating.
|
|
250
|
+
function extractTerminalChoice(text) {
|
|
251
|
+
const lines = stripAnsi(String(text || '')).split('\n');
|
|
252
|
+
return extractNumberedChoice(lines) || extractInlineChoice(lines);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function findTargetOrdinal(options, targetValue, targetLabel) {
|
|
256
|
+
const value = targetValue == null ? '' : String(targetValue).trim();
|
|
257
|
+
const label = String(targetLabel || '').trim();
|
|
258
|
+
if (value) {
|
|
259
|
+
const byValue = options.findIndex((option) => String(option.value) === value);
|
|
260
|
+
if (byValue >= 0) return byValue;
|
|
261
|
+
}
|
|
262
|
+
if (label) {
|
|
263
|
+
const byLabel = options.findIndex((option) => option.label === label);
|
|
264
|
+
if (byLabel >= 0) return byLabel;
|
|
265
|
+
}
|
|
266
|
+
return -1;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const ARROW_DOWN = '\x1b[B';
|
|
270
|
+
const ARROW_UP = '\x1b[A';
|
|
271
|
+
const ARROW_RIGHT = '\x1b[C';
|
|
272
|
+
const ENTER = '\r';
|
|
273
|
+
|
|
274
|
+
// Plan the keystrokes that move the highlight to the tapped option and confirm it.
|
|
275
|
+
// Preferred strategy is arrow-relative navigation from the live highlight (works
|
|
276
|
+
// across Claude/Codex/un-numbered menus). When the highlight can't be located we
|
|
277
|
+
// fall back to typing the option's number — the same thing CTM's approval engine
|
|
278
|
+
// does for Claude. The final frame is always Enter; callers may verify the
|
|
279
|
+
// highlight landed before sending it.
|
|
280
|
+
function planChoiceKeystrokes({ options, selectedIndex, targetValue, targetLabel } = {}) {
|
|
281
|
+
if (!Array.isArray(options) || options.length === 0) return null;
|
|
282
|
+
const targetOrdinal = findTargetOrdinal(options, targetValue, targetLabel);
|
|
283
|
+
if (targetOrdinal < 0) return null;
|
|
284
|
+
|
|
285
|
+
const hasHighlight = Number.isInteger(selectedIndex) && selectedIndex >= 0;
|
|
286
|
+
if (!hasHighlight) {
|
|
287
|
+
return {
|
|
288
|
+
strategy: 'number',
|
|
289
|
+
targetOrdinal,
|
|
290
|
+
navFrames: [String(options[targetOrdinal].value)],
|
|
291
|
+
commitFrame: ENTER,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const delta = targetOrdinal - selectedIndex;
|
|
296
|
+
const arrow = delta >= 0 ? ARROW_DOWN : ARROW_UP;
|
|
297
|
+
const navFrames = [];
|
|
298
|
+
for (let i = 0; i < Math.abs(delta); i += 1) navFrames.push(arrow);
|
|
299
|
+
return {
|
|
300
|
+
strategy: 'arrows',
|
|
301
|
+
targetOrdinal,
|
|
302
|
+
delta,
|
|
303
|
+
navFrames,
|
|
304
|
+
commitFrame: ENTER,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Normalize a label for matching a phone tap against the live menu: strip the
|
|
309
|
+
// checkbox marker, collapse whitespace, lowercase. Used by tappedOptionStillPresent.
|
|
310
|
+
function normalizeChoiceLabel(label) {
|
|
311
|
+
return checkboxFromLabel(label).label.replace(/\s+/g, ' ').trim().toLowerCase();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Reconcile a phone tap against the live menu when the full-set fingerprint
|
|
315
|
+
// disagrees. The phone parses the cleaned terminal tail (dividers/non-content
|
|
316
|
+
// rows removed) while the server parses the raw headless grid, so for the SAME
|
|
317
|
+
// live menu the two can capture a different trailing-option SET (e.g. the phone
|
|
318
|
+
// includes "Chat about this" that the server's window cut off behind a divider).
|
|
319
|
+
// A strict fingerprint match false-rejects every such tap as stale. Instead we
|
|
320
|
+
// confirm the SPECIFIC tapped option is still present in the live menu by
|
|
321
|
+
// value+label — that (together with the post-navigation closed-loop verify in
|
|
322
|
+
// the caller) is the real correctness guarantee. Returns false only when the
|
|
323
|
+
// tapped option cannot be confirmed, i.e. the menu genuinely changed.
|
|
324
|
+
function tappedOptionStillPresent(menu, targetValue, targetLabel) {
|
|
325
|
+
const options = Array.isArray(menu && menu.options) ? menu.options : [];
|
|
326
|
+
if (!options.length) return false;
|
|
327
|
+
const value = targetValue == null ? '' : String(targetValue).trim();
|
|
328
|
+
const label = normalizeChoiceLabel(targetLabel);
|
|
329
|
+
if (value) {
|
|
330
|
+
const byValue = options.find((option) => String(option.value) === value);
|
|
331
|
+
// Same slot AND same text => same option. A label that no longer matches the
|
|
332
|
+
// option at this value means the menu was replaced, so fall through to reject.
|
|
333
|
+
if (byValue) return label ? normalizeChoiceLabel(byValue.label) === label : true;
|
|
334
|
+
}
|
|
335
|
+
// No value match (e.g. the server's window excluded that ordinal): accept only
|
|
336
|
+
// an unambiguous single label match so we never drive the wrong option.
|
|
337
|
+
if (label) {
|
|
338
|
+
return options.filter((option) => normalizeChoiceLabel(option.label) === label).length === 1;
|
|
339
|
+
}
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function planFormSubmitKeystrokes(menu = {}) {
|
|
344
|
+
if (!menu.requiresSubmit && menu.kind !== 'form') return null;
|
|
345
|
+
if (!Array.isArray(menu.options) || !menu.options.some((option) => option.checked)) return null;
|
|
346
|
+
return {
|
|
347
|
+
strategy: 'form-submit',
|
|
348
|
+
frames: [ARROW_RIGHT, ENTER],
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
module.exports = {
|
|
353
|
+
stripAnsi,
|
|
354
|
+
choiceOptionFromLine,
|
|
355
|
+
extractTerminalChoice,
|
|
356
|
+
choiceFingerprint,
|
|
357
|
+
planChoiceKeystrokes,
|
|
358
|
+
planFormSubmitKeystrokes,
|
|
359
|
+
tappedOptionStillPresent,
|
|
360
|
+
ARROW_DOWN,
|
|
361
|
+
ARROW_UP,
|
|
362
|
+
ARROW_RIGHT,
|
|
363
|
+
ENTER,
|
|
364
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// xterm answers OSC 10/11/12 color queries by sending bytes back to the host.
|
|
4
|
+
// CTM must never persist those terminal-generated replies as provider output.
|
|
5
|
+
// Scope this narrowly to color reply/set payloads; leave OSC queries such as
|
|
6
|
+
// ]10;? and ordinary text containing rgb values untouched.
|
|
7
|
+
const TERMINAL_GENERATED_COLOR_REPLY_RE = /(?:\x1b\]|\u009d|\])(?:10|11|12|110|111|112);(?:rgb:[0-9a-fA-F]{1,4}\/[0-9a-fA-F]{1,4}\/[0-9a-fA-F]{1,4}|#[0-9a-fA-F]{6,12})(?:\x07|\x1b\\|\u009c|\\)?/g;
|
|
8
|
+
|
|
9
|
+
function stripLeakedTerminalGeneratedColorReplies(value) {
|
|
10
|
+
const raw = String(value || '');
|
|
11
|
+
return raw ? raw.replace(TERMINAL_GENERATED_COLOR_REPLY_RE, '') : raw;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = {
|
|
15
|
+
TERMINAL_GENERATED_COLOR_REPLY_RE,
|
|
16
|
+
stripLeakedTerminalGeneratedColorReplies,
|
|
17
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Deterministic fingerprint of a terminal's visible grid + scrollback descriptor.
|
|
3
|
+
// Used on BOTH sides: the server's authoritative headless xterm and the browser
|
|
4
|
+
// client's xterm. Equal fingerprint => client render content matches server truth.
|
|
5
|
+
//
|
|
6
|
+
// Design choices that matter for correctness:
|
|
7
|
+
// - Trailing whitespace is stripped per row: xterm pads rows to `cols` with spaces,
|
|
8
|
+
// so two buffers that differ only in pad width must still compare equal.
|
|
9
|
+
// - Dimensions (cols) are folded in: a foreign-width render IS a divergence.
|
|
10
|
+
// - scrollbackLen is folded in (not the whole scrollback — too expensive) so that
|
|
11
|
+
// gross scrollback drift (missing/duplicated blobs above the viewport) is caught.
|
|
12
|
+
// - FNV-1a 32-bit: cheap, dependency-free, stable across Node and the browser.
|
|
13
|
+
|
|
14
|
+
const FNV_OFFSET = 0x811c9dc5;
|
|
15
|
+
const FNV_PRIME = 0x01000193;
|
|
16
|
+
const EMPTY_FINGERPRINT = '0:empty';
|
|
17
|
+
|
|
18
|
+
// IMPORTANT: the browser mirror (public/js/terminal-reconciler.js) MUST keep identical logic — any change here requires a matching change there, or fingerprints diverge.
|
|
19
|
+
function fnv1a(str, seed) {
|
|
20
|
+
let h = seed >>> 0;
|
|
21
|
+
for (let i = 0; i < str.length; i++) {
|
|
22
|
+
h ^= str.charCodeAt(i);
|
|
23
|
+
h = Math.imul(h, FNV_PRIME);
|
|
24
|
+
}
|
|
25
|
+
return h >>> 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// rowStrings: array of visible-row strings (already translateToString'd on server side).
|
|
29
|
+
// meta: { cols, rows, scrollbackLen }
|
|
30
|
+
function fingerprintRows(rowStrings, meta) {
|
|
31
|
+
const cols = Number(meta && meta.cols) || 0;
|
|
32
|
+
const rows = Number(meta && meta.rows) || 0;
|
|
33
|
+
const scrollbackLen = Number(meta && meta.scrollbackLen) || 0;
|
|
34
|
+
let h = FNV_OFFSET;
|
|
35
|
+
let nonEmpty = false;
|
|
36
|
+
// Header binds dims so width drift is divergence.
|
|
37
|
+
h = fnv1a(`${cols}x${rows}|${scrollbackLen}\n`, h);
|
|
38
|
+
for (let i = 0; i < rowStrings.length; i++) {
|
|
39
|
+
const trimmed = String(rowStrings[i] == null ? '' : rowStrings[i]).replace(/\s+$/, '');
|
|
40
|
+
if (trimmed.length) nonEmpty = true;
|
|
41
|
+
// Row index is part of the hash so reordered rows differ.
|
|
42
|
+
h = fnv1a(`${i}:${trimmed}\n`, h);
|
|
43
|
+
}
|
|
44
|
+
if (!nonEmpty) return EMPTY_FINGERPRINT;
|
|
45
|
+
return `${cols}x${rows}:${h.toString(16)}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = { fingerprintRows, EMPTY_FINGERPRINT, fnv1a };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const RECENT_INPUT_ECHO_WINDOW_MS = 100;
|
|
4
|
+
const CODEX_BULK_INPUT_REDRAW_COALESCE_MS = 350;
|
|
5
|
+
const CODEX_SEMANTIC_REDRAW_FAST_MAX_BYTES = 2048;
|
|
6
|
+
// A single Codex composer keystroke echo is a small synchronized-output TUI
|
|
7
|
+
// redraw (cursor moves + one row repaint). It is far below a full-screen
|
|
8
|
+
// repaint. Flushing it at ~1ms (instead of the 8ms generic recent-input path)
|
|
9
|
+
// makes typing feel snappy without touching bulk-output coalescing.
|
|
10
|
+
const CODEX_COMPOSER_INPUT_REDRAW_FAST_MAX_BYTES = 512;
|
|
11
|
+
|
|
12
|
+
function hasTerminalControl(data) {
|
|
13
|
+
return /[\x00-\x1f\x7f\x1b]/.test(String(data || ''));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isCodexSession(session) {
|
|
17
|
+
const value = String(
|
|
18
|
+
(session && (session.agentType || session._providerId || session.type || session.cmd)) || ''
|
|
19
|
+
).toLowerCase();
|
|
20
|
+
return /\bcodex\b|\/codex(?:\s|$)|\\codex(?:\s|$)/.test(value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isCodexSemanticComposerRedraw(data) {
|
|
24
|
+
const text = String(data || '');
|
|
25
|
+
if (!text || text.length > CODEX_SEMANTIC_REDRAW_FAST_MAX_BYTES) return false;
|
|
26
|
+
if (!text.includes('\x1b[?2026h') || !text.includes('\x1b[?2026l')) return false;
|
|
27
|
+
return /\[(?:Image #[0-9]+(?::[^\]]*)?|Attached images?:[^\]]+)\]/.test(text) ||
|
|
28
|
+
/\x1b\[[0-9;]*m[$/][A-Za-z0-9][A-Za-z0-9_.:-]*/.test(text);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// A small Codex composer keystroke echo: a synchronized-output (DEC 2026)
|
|
32
|
+
// wrapped redraw under the composer-size bound. This is broader than
|
|
33
|
+
// isCodexSemanticComposerRedraw (which only matches image tokens / styled
|
|
34
|
+
// command triggers) — it covers ordinary character typing into the native
|
|
35
|
+
// composer so each keystroke echo can flush promptly instead of waiting 8ms.
|
|
36
|
+
function isCodexComposerInputRedraw(data) {
|
|
37
|
+
const text = String(data || '');
|
|
38
|
+
if (!text || text.length > CODEX_COMPOSER_INPUT_REDRAW_FAST_MAX_BYTES) return false;
|
|
39
|
+
return text.includes('\x1b[?2026h') && text.includes('\x1b[?2026l');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isRecentInput(session, now) {
|
|
43
|
+
const ts = Number(session && (session._lastInputTs || session.lastUserInputAt) || 0);
|
|
44
|
+
return !!ts && (now - ts) >= 0 && (now - ts) < RECENT_INPUT_ECHO_WINDOW_MS;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function terminalOutputFlushDelay(data, session, now = Date.now()) {
|
|
48
|
+
const text = String(data || '');
|
|
49
|
+
const len = text.length;
|
|
50
|
+
if (len < 4) return 1;
|
|
51
|
+
|
|
52
|
+
const recentInput = isRecentInput(session, now);
|
|
53
|
+
const hasControl = hasTerminalControl(text);
|
|
54
|
+
const bulkInputUntil = Number(session && session._codexComposerBulkInputUntil || 0);
|
|
55
|
+
if (hasControl && bulkInputUntil > now) {
|
|
56
|
+
return Math.max(32, Math.min(CODEX_BULK_INPUT_REDRAW_COALESCE_MS, bulkInputUntil - now));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Plain local shell echoes should stay near-instant. ANSI/TUI redraw frames
|
|
60
|
+
// need a short coalescing window; otherwise each key can fan out into many
|
|
61
|
+
// websocket/browser writes and make real typing feel worse.
|
|
62
|
+
if (recentInput && !hasControl && len <= 10) return 1;
|
|
63
|
+
if (recentInput && hasControl && isCodexSession(session) && isCodexSemanticComposerRedraw(text)) return 1;
|
|
64
|
+
// Ordinary Codex composer keystroke echo: small synchronized-output redraw on
|
|
65
|
+
// recent input. Flush near-instant so native typing feels snappy. Bounded by
|
|
66
|
+
// CODEX_COMPOSER_INPUT_REDRAW_FAST_MAX_BYTES so bulk frames never qualify.
|
|
67
|
+
if (recentInput && hasControl && isCodexSession(session) && isCodexComposerInputRedraw(text)) return 1;
|
|
68
|
+
if (recentInput && len < 512) return 8;
|
|
69
|
+
|
|
70
|
+
return len < 256 ? 8 : 32;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
RECENT_INPUT_ECHO_WINDOW_MS,
|
|
75
|
+
CODEX_BULK_INPUT_REDRAW_COALESCE_MS,
|
|
76
|
+
CODEX_SEMANTIC_REDRAW_FAST_MAX_BYTES,
|
|
77
|
+
CODEX_COMPOSER_INPUT_REDRAW_FAST_MAX_BYTES,
|
|
78
|
+
hasTerminalControl,
|
|
79
|
+
isCodexSession,
|
|
80
|
+
isCodexSemanticComposerRedraw,
|
|
81
|
+
isCodexComposerInputRedraw,
|
|
82
|
+
isRecentInput,
|
|
83
|
+
terminalOutputFlushDelay,
|
|
84
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const TIMESTAMP_FIELDS = ['timestamp', 'created_at', 'createdAt', 'time', 'ts'];
|
|
4
|
+
const SEQUENCE_FIELDS = [
|
|
5
|
+
'message_index',
|
|
6
|
+
'messageIndex',
|
|
7
|
+
'index',
|
|
8
|
+
'seq',
|
|
9
|
+
'sequence',
|
|
10
|
+
'_mergeIndex',
|
|
11
|
+
'_promptSourceIndex',
|
|
12
|
+
'_sourceLine',
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
function firstPresentTimestamp(item) {
|
|
16
|
+
if (!item || typeof item !== 'object') return item;
|
|
17
|
+
for (const field of TIMESTAMP_FIELDS) {
|
|
18
|
+
if (item[field] != null && item[field] !== '') return item[field];
|
|
19
|
+
}
|
|
20
|
+
const data = item.data && typeof item.data === 'object' ? item.data : null;
|
|
21
|
+
if (data) {
|
|
22
|
+
for (const field of TIMESTAMP_FIELDS) {
|
|
23
|
+
if (data[field] != null && data[field] !== '') return data[field];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeEpochMs(value) {
|
|
30
|
+
if (!Number.isFinite(value)) return null;
|
|
31
|
+
if (value <= 0) return null;
|
|
32
|
+
// Accept Unix seconds when an upstream provider emits 10-digit epochs.
|
|
33
|
+
if (value >= 1_000_000_000 && value < 10_000_000_000) return value * 1000;
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function timelineTimestampMs(itemOrTimestamp) {
|
|
38
|
+
const raw = firstPresentTimestamp(itemOrTimestamp);
|
|
39
|
+
if (typeof raw === 'number') return normalizeEpochMs(raw);
|
|
40
|
+
if (typeof raw === 'bigint') {
|
|
41
|
+
const value = Number(raw);
|
|
42
|
+
return normalizeEpochMs(value);
|
|
43
|
+
}
|
|
44
|
+
const text = String(raw || '').trim();
|
|
45
|
+
if (!text) return null;
|
|
46
|
+
if (/^[+-]?\d+(?:\.\d+)?$/.test(text)) {
|
|
47
|
+
const numeric = Number(text);
|
|
48
|
+
const normalized = normalizeEpochMs(numeric);
|
|
49
|
+
if (normalized != null) return normalized;
|
|
50
|
+
}
|
|
51
|
+
const parsed = Date.parse(text);
|
|
52
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function timelineSequenceValue(item) {
|
|
56
|
+
if (!item || typeof item !== 'object') return null;
|
|
57
|
+
for (const field of SEQUENCE_FIELDS) {
|
|
58
|
+
const value = Number(item[field]);
|
|
59
|
+
if (Number.isFinite(value)) return value;
|
|
60
|
+
}
|
|
61
|
+
const data = item.data && typeof item.data === 'object' ? item.data : null;
|
|
62
|
+
if (data) {
|
|
63
|
+
for (const field of SEQUENCE_FIELDS) {
|
|
64
|
+
const value = Number(data[field]);
|
|
65
|
+
if (Number.isFinite(value)) return value;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function compareTimelineMessages(a, b) {
|
|
72
|
+
const ams = timelineTimestampMs(a);
|
|
73
|
+
const bms = timelineTimestampMs(b);
|
|
74
|
+
if (ams != null && bms != null && ams !== bms) return ams - bms;
|
|
75
|
+
if (ams != null && bms == null) return -1;
|
|
76
|
+
if (ams == null && bms != null) return 1;
|
|
77
|
+
|
|
78
|
+
const aMergeSource = Number(a?._mergeSourceOrder);
|
|
79
|
+
const bMergeSource = Number(b?._mergeSourceOrder);
|
|
80
|
+
if (Number.isFinite(aMergeSource) && Number.isFinite(bMergeSource) && aMergeSource !== bMergeSource) {
|
|
81
|
+
return aMergeSource - bMergeSource;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const aSeq = timelineSequenceValue(a);
|
|
85
|
+
const bSeq = timelineSequenceValue(b);
|
|
86
|
+
if (aSeq != null && bSeq != null && aSeq !== bSeq) return aSeq - bSeq;
|
|
87
|
+
if (aSeq != null && bSeq == null) return -1;
|
|
88
|
+
if (aSeq == null && bSeq != null) return 1;
|
|
89
|
+
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function sortTimelineMessages(messages) {
|
|
94
|
+
return (Array.isArray(messages) ? messages : [])
|
|
95
|
+
.map((message, index) => ({ message, index }))
|
|
96
|
+
.sort((a, b) => compareTimelineMessages(a.message, b.message) || a.index - b.index)
|
|
97
|
+
.map((entry) => entry.message);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function sortTimelineMessagesInPlace(messages) {
|
|
101
|
+
if (!Array.isArray(messages) || messages.length < 2) return messages;
|
|
102
|
+
const sorted = sortTimelineMessages(messages);
|
|
103
|
+
messages.splice(0, messages.length, ...sorted);
|
|
104
|
+
return messages;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function latestTimelineTimestampMs(messages) {
|
|
108
|
+
let latest = 0;
|
|
109
|
+
for (const message of Array.isArray(messages) ? messages : []) {
|
|
110
|
+
const ms = timelineTimestampMs(message);
|
|
111
|
+
if (ms != null && ms > latest) latest = ms;
|
|
112
|
+
}
|
|
113
|
+
return latest;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
compareTimelineMessages,
|
|
118
|
+
latestTimelineTimestampMs,
|
|
119
|
+
sortTimelineMessages,
|
|
120
|
+
sortTimelineMessagesInPlace,
|
|
121
|
+
timelineTimestampMs,
|
|
122
|
+
};
|