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
|
@@ -37,6 +37,9 @@ const _streamState = {
|
|
|
37
37
|
// _pendingEvents: sessionId → [stream-event...] buffered during prime.
|
|
38
38
|
// _parentUuidSeen: sessionId → Set of parentUuids already rendered,
|
|
39
39
|
// used to dedup stream-events that overlap with primed history.
|
|
40
|
+
// _messageFingerprintSeen: sessionId → Set of semantic message keys
|
|
41
|
+
// rendered from HTTP history or live stream. This reconciles the two
|
|
42
|
+
// data planes so stream-init cannot replay rows already in the snapshot.
|
|
40
43
|
_primed: new Map(),
|
|
41
44
|
_primedTarget: new Map(),
|
|
42
45
|
_priming: new Map(),
|
|
@@ -44,6 +47,7 @@ const _streamState = {
|
|
|
44
47
|
_primeToken: new Map(),
|
|
45
48
|
_pendingEvents: new Map(),
|
|
46
49
|
_parentUuidSeen: new Map(),
|
|
50
|
+
_messageFingerprintSeen: new Map(),
|
|
47
51
|
};
|
|
48
52
|
|
|
49
53
|
function _normalizeSessionViewMode(view) {
|
|
@@ -132,7 +136,7 @@ function _tooltipPhaseLabel(phase) {
|
|
|
132
136
|
if (key === 'implementing') return 'Implementing';
|
|
133
137
|
if (key === 'investigating') return 'Investigating';
|
|
134
138
|
if (key === 'verifying') return 'Verifying';
|
|
135
|
-
if (key === 'waiting') return '
|
|
139
|
+
if (key === 'waiting') return 'Needs You';
|
|
136
140
|
if (key === 'running') return 'Running';
|
|
137
141
|
return key ? _tooltipStatusLabel(key) : 'Progress';
|
|
138
142
|
}
|
|
@@ -496,8 +500,19 @@ function renderConversationEvent(evt) {
|
|
|
496
500
|
return MR.renderConversationEvent(evt);
|
|
497
501
|
}
|
|
498
502
|
|
|
503
|
+
function _linkConversationDocumentReferences(container, root) {
|
|
504
|
+
if (!container || !root || !window.CTMDocLinks || typeof window.CTMDocLinks.linkifyElement !== 'function') return;
|
|
505
|
+
try {
|
|
506
|
+
const sessionId = container.dataset?.sessionId || '';
|
|
507
|
+
window.CTMDocLinks.linkifyElement(root, window.CTMDocLinks.contextForSession(sessionId));
|
|
508
|
+
} catch (e) {
|
|
509
|
+
console.warn('[stream-view] document linkification failed:', e && e.message ? e.message : e);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
499
513
|
// When the new element AND the last existing child are compatible collapsible
|
|
500
|
-
// operational rows (`.conv-tool-group
|
|
514
|
+
// operational rows (`.conv-tool-group`, `.conv-warning-group`, or
|
|
515
|
+
// `.conv-system-group`), fold the new
|
|
501
516
|
// one into the previous one's items list and bump the count + last-time.
|
|
502
517
|
// Result: consecutive low-value activity rows render as ONE thought-group,
|
|
503
518
|
// matching Review's `groupMessages` behavior. Returns true if merged (caller
|
|
@@ -505,12 +520,61 @@ function renderConversationEvent(evt) {
|
|
|
505
520
|
function _mergeConsecutiveToolGroups(container, newEl) {
|
|
506
521
|
const groupKind = (el) => {
|
|
507
522
|
if (!el || !el.classList) return null;
|
|
523
|
+
if (el.classList.contains('conv-activity-group')) return 'activity';
|
|
508
524
|
if (el.classList.contains('conv-tool-group')) return 'tool';
|
|
509
525
|
if (el.classList.contains('conv-warning-group')) return 'warning';
|
|
526
|
+
if (el.classList.contains('conv-system-group')) return 'system';
|
|
510
527
|
return null;
|
|
511
528
|
};
|
|
529
|
+
const foldableKind = (kind) => kind === 'activity' || kind === 'tool' || kind === 'system';
|
|
530
|
+
const markCombined = (el) => {
|
|
531
|
+
if (!el || !el.classList) return;
|
|
532
|
+
el.classList.add('conv-activity-group');
|
|
533
|
+
el.classList.add('combined-activity-group');
|
|
534
|
+
el.classList.add('tool-activity-group');
|
|
535
|
+
if (el.dataset) el.dataset.activityKind = 'combined';
|
|
536
|
+
};
|
|
537
|
+
const refreshGroup = (el, kind) => {
|
|
538
|
+
if (kind === 'activity' && typeof MR !== 'undefined' && typeof MR.refreshConversationCombinedActivityGroup === 'function') {
|
|
539
|
+
MR.refreshConversationCombinedActivityGroup(el);
|
|
540
|
+
} else if (kind === 'warning' && typeof MR !== 'undefined' && typeof MR.refreshConversationWarningGroup === 'function') {
|
|
541
|
+
MR.refreshConversationWarningGroup(el);
|
|
542
|
+
} else if (kind === 'system' && typeof MR !== 'undefined' && typeof MR.refreshConversationSystemGroup === 'function') {
|
|
543
|
+
MR.refreshConversationSystemGroup(el);
|
|
544
|
+
} else if (typeof MR !== 'undefined' && typeof MR.refreshConversationActivityGroup === 'function') {
|
|
545
|
+
MR.refreshConversationActivityGroup(el);
|
|
546
|
+
}
|
|
547
|
+
};
|
|
512
548
|
const kind = groupKind(newEl);
|
|
513
549
|
if (!kind) return false;
|
|
550
|
+
if (foldableKind(kind)) {
|
|
551
|
+
const lastChild = container.lastElementChild;
|
|
552
|
+
const lastKind = groupKind(lastChild);
|
|
553
|
+
if (!foldableKind(lastKind)) return false;
|
|
554
|
+
const targetItems = lastChild.querySelector('.thought-group-items');
|
|
555
|
+
const newItems = newEl.querySelector('.thought-group-items');
|
|
556
|
+
if (!targetItems || !newItems) return false;
|
|
557
|
+
while (newItems.firstChild) targetItems.appendChild(newItems.firstChild);
|
|
558
|
+
const newLast = newEl.dataset.lastTime || '';
|
|
559
|
+
if (newLast) lastChild.dataset.lastTime = newLast;
|
|
560
|
+
const shouldPromote = lastKind === 'activity' || kind === 'activity' || lastKind !== kind;
|
|
561
|
+
if (shouldPromote) markCombined(lastChild);
|
|
562
|
+
refreshGroup(lastChild, shouldPromote ? 'activity' : lastKind);
|
|
563
|
+
return true;
|
|
564
|
+
}
|
|
565
|
+
if (kind === 'warning') {
|
|
566
|
+
const existingWarning = container.querySelector(':scope > .conv-warning-group');
|
|
567
|
+
if (existingWarning) {
|
|
568
|
+
const targetItems = existingWarning.querySelector('.thought-group-items');
|
|
569
|
+
const newItems = newEl.querySelector('.thought-group-items');
|
|
570
|
+
if (!targetItems || !newItems) return false;
|
|
571
|
+
while (newItems.firstChild) targetItems.appendChild(newItems.firstChild);
|
|
572
|
+
const newLast = newEl.dataset.lastTime || '';
|
|
573
|
+
if (newLast) existingWarning.dataset.lastTime = newLast;
|
|
574
|
+
refreshGroup(existingWarning, 'warning');
|
|
575
|
+
return true;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
514
578
|
const lastChild = container.lastElementChild;
|
|
515
579
|
if (groupKind(lastChild) !== kind) return false;
|
|
516
580
|
const targetItems = lastChild.querySelector('.thought-group-items');
|
|
@@ -522,17 +586,18 @@ function _mergeConsecutiveToolGroups(container, newEl) {
|
|
|
522
586
|
// rows prevents a later update from replacing the whole merged group.
|
|
523
587
|
const newLast = newEl.dataset.lastTime || '';
|
|
524
588
|
if (newLast) lastChild.dataset.lastTime = newLast;
|
|
525
|
-
|
|
526
|
-
MR.refreshConversationWarningGroup(lastChild);
|
|
527
|
-
} else if (typeof MR !== 'undefined' && typeof MR.refreshConversationActivityGroup === 'function') {
|
|
528
|
-
MR.refreshConversationActivityGroup(lastChild);
|
|
529
|
-
}
|
|
589
|
+
refreshGroup(lastChild, kind);
|
|
530
590
|
return true;
|
|
531
591
|
}
|
|
532
592
|
|
|
533
593
|
function _assignConversationParentUuid(el, parentUuid) {
|
|
534
594
|
if (!el || !parentUuid) return;
|
|
535
|
-
if (el.classList && (
|
|
595
|
+
if (el.classList && (
|
|
596
|
+
el.classList.contains('conv-activity-group') ||
|
|
597
|
+
el.classList.contains('conv-tool-group') ||
|
|
598
|
+
el.classList.contains('conv-warning-group') ||
|
|
599
|
+
el.classList.contains('conv-system-group')
|
|
600
|
+
)) {
|
|
536
601
|
const items = el.querySelector('.thought-group-items');
|
|
537
602
|
const row = items && (items.lastElementChild || (items.children && items.children[items.children.length - 1]));
|
|
538
603
|
if (row && row.dataset && !row.dataset.parentUuid) row.dataset.parentUuid = parentUuid;
|
|
@@ -554,11 +619,38 @@ function _replaceConversationParentEvent(existing, newEl) {
|
|
|
554
619
|
else existing.remove();
|
|
555
620
|
return true;
|
|
556
621
|
}
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
622
|
+
const conversationGroupKind = (el) => {
|
|
623
|
+
if (!el || !el.classList) return '';
|
|
624
|
+
if (el.classList.contains('conv-activity-group')) return 'activity';
|
|
625
|
+
if (el.classList.contains('conv-warning-group')) return 'warning';
|
|
626
|
+
if (el.classList.contains('conv-system-group')) return 'system';
|
|
627
|
+
if (el.classList.contains('conv-tool-group')) return 'tool';
|
|
628
|
+
return '';
|
|
629
|
+
};
|
|
630
|
+
const refreshConversationGroup = (group, kind) => {
|
|
631
|
+
if (!group || typeof MR === 'undefined') return;
|
|
632
|
+
if (kind === 'activity' && typeof MR.refreshConversationCombinedActivityGroup === 'function') {
|
|
633
|
+
MR.refreshConversationCombinedActivityGroup(group);
|
|
634
|
+
} else if (kind === 'warning' && typeof MR.refreshConversationWarningGroup === 'function') {
|
|
635
|
+
MR.refreshConversationWarningGroup(group);
|
|
636
|
+
} else if (kind === 'system' && typeof MR.refreshConversationSystemGroup === 'function') {
|
|
637
|
+
MR.refreshConversationSystemGroup(group);
|
|
638
|
+
} else if (typeof MR.refreshConversationActivityGroup === 'function') {
|
|
639
|
+
MR.refreshConversationActivityGroup(group);
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
const compatibleActivityReplacement = (existingKind, incomingKind) => {
|
|
643
|
+
if (existingKind === incomingKind) return true;
|
|
644
|
+
if (existingKind !== 'activity') return false;
|
|
645
|
+
return incomingKind === 'tool' || incomingKind === 'system' || incomingKind === 'activity';
|
|
646
|
+
};
|
|
647
|
+
const existingGroup = existing.closest
|
|
648
|
+
? (existing.closest('.conv-activity-group') || existing.closest('.conv-tool-group') || existing.closest('.conv-warning-group') || existing.closest('.conv-system-group'))
|
|
649
|
+
: null;
|
|
650
|
+
const existingKind = conversationGroupKind(existingGroup);
|
|
651
|
+
const newKind = conversationGroupKind(newEl);
|
|
652
|
+
if (existingGroup && newEl && newEl.classList && newKind && compatibleActivityReplacement(existingKind, newKind)) {
|
|
653
|
+
const targetRow = existing.closest('.tool-activity-item') || existing.closest('.system-activity-item') || existing;
|
|
562
654
|
const targetParent = targetRow.parentNode;
|
|
563
655
|
const newItems = newEl.querySelector('.thought-group-items');
|
|
564
656
|
if (!targetParent || !newItems) return false;
|
|
@@ -566,25 +658,17 @@ function _replaceConversationParentEvent(existing, newEl) {
|
|
|
566
658
|
for (const row of rows) targetParent.insertBefore(row, targetRow);
|
|
567
659
|
targetParent.removeChild(targetRow);
|
|
568
660
|
if (newEl.dataset.lastTime) existingGroup.dataset.lastTime = newEl.dataset.lastTime;
|
|
569
|
-
|
|
570
|
-
MR.refreshConversationWarningGroup(existingGroup);
|
|
571
|
-
} else if (typeof MR !== 'undefined' && typeof MR.refreshConversationActivityGroup === 'function') {
|
|
572
|
-
MR.refreshConversationActivityGroup(existingGroup);
|
|
573
|
-
}
|
|
661
|
+
refreshConversationGroup(existingGroup, existingKind);
|
|
574
662
|
refreshTurn();
|
|
575
663
|
return true;
|
|
576
664
|
}
|
|
577
665
|
if (existingGroup && !newEl) {
|
|
578
|
-
const targetRow = existing.closest('.tool-activity-item') || existing;
|
|
666
|
+
const targetRow = existing.closest('.tool-activity-item') || existing.closest('.system-activity-item') || existing;
|
|
579
667
|
const targetParent = targetRow.parentNode;
|
|
580
668
|
if (targetParent) targetParent.removeChild(targetRow);
|
|
581
669
|
const remaining = existingGroup.querySelector('.thought-group-items')?.children?.length || 0;
|
|
582
670
|
if (remaining === 0) existingGroup.remove();
|
|
583
|
-
else
|
|
584
|
-
MR.refreshConversationWarningGroup(existingGroup);
|
|
585
|
-
} else if (typeof MR !== 'undefined' && typeof MR.refreshConversationActivityGroup === 'function') {
|
|
586
|
-
MR.refreshConversationActivityGroup(existingGroup);
|
|
587
|
-
}
|
|
671
|
+
else refreshConversationGroup(existingGroup, existingKind);
|
|
588
672
|
refreshTurn();
|
|
589
673
|
return true;
|
|
590
674
|
}
|
|
@@ -594,6 +678,96 @@ function _replaceConversationParentEvent(existing, newEl) {
|
|
|
594
678
|
return true;
|
|
595
679
|
}
|
|
596
680
|
|
|
681
|
+
function _conversationScrollNearBottom(container) {
|
|
682
|
+
if (!container) return true;
|
|
683
|
+
const remaining = Number(container.scrollHeight || 0)
|
|
684
|
+
- Number(container.scrollTop || 0)
|
|
685
|
+
- Number(container.clientHeight || 0);
|
|
686
|
+
return remaining < 100;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function _conversationScrollAnchorCandidates(container) {
|
|
690
|
+
if (!container) return [];
|
|
691
|
+
const selector = [
|
|
692
|
+
':scope > .prompt-turn',
|
|
693
|
+
':scope > .review-msg',
|
|
694
|
+
':scope > .thought-group',
|
|
695
|
+
':scope > .conversation-state',
|
|
696
|
+
':scope > .conversation-load-older',
|
|
697
|
+
':scope .prompt-turn-header',
|
|
698
|
+
':scope .review-msg',
|
|
699
|
+
':scope .thought-group',
|
|
700
|
+
':scope .tool-activity-item',
|
|
701
|
+
':scope .system-activity-item',
|
|
702
|
+
':scope .msg-text',
|
|
703
|
+
].join(', ');
|
|
704
|
+
if (typeof container.querySelectorAll === 'function') {
|
|
705
|
+
try { return Array.from(container.querySelectorAll(selector)); } catch {}
|
|
706
|
+
}
|
|
707
|
+
return Array.from(container.children || []);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function _captureConversationScrollAnchor(container) {
|
|
711
|
+
if (!container) return null;
|
|
712
|
+
const followBottom = _conversationScrollNearBottom(container);
|
|
713
|
+
const anchor = {
|
|
714
|
+
followBottom,
|
|
715
|
+
scrollTop: Number(container.scrollTop || 0),
|
|
716
|
+
scrollHeight: Number(container.scrollHeight || 0),
|
|
717
|
+
element: null,
|
|
718
|
+
topOffset: 0,
|
|
719
|
+
};
|
|
720
|
+
if (followBottom) return anchor;
|
|
721
|
+
let containerRect = null;
|
|
722
|
+
try { containerRect = container.getBoundingClientRect && container.getBoundingClientRect(); } catch {}
|
|
723
|
+
if (!containerRect) return anchor;
|
|
724
|
+
const top = Number(containerRect.top || 0);
|
|
725
|
+
const bottom = top + Number(container.clientHeight || containerRect.height || 0);
|
|
726
|
+
for (const el of _conversationScrollAnchorCandidates(container)) {
|
|
727
|
+
if (!el || typeof el.getBoundingClientRect !== 'function') continue;
|
|
728
|
+
let rect = null;
|
|
729
|
+
try { rect = el.getBoundingClientRect(); } catch {}
|
|
730
|
+
if (!rect || Number(rect.height || 0) <= 0) continue;
|
|
731
|
+
if (Number(rect.bottom || 0) <= top + 2) continue;
|
|
732
|
+
if (Number(rect.top || 0) >= bottom - 2) continue;
|
|
733
|
+
anchor.element = el;
|
|
734
|
+
anchor.topOffset = Number(rect.top || 0) - top;
|
|
735
|
+
break;
|
|
736
|
+
}
|
|
737
|
+
return anchor;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function _restoreConversationScrollAnchor(container, anchor) {
|
|
741
|
+
if (!container || !anchor) return;
|
|
742
|
+
if (anchor.followBottom) {
|
|
743
|
+
container.scrollTop = container.scrollHeight;
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
const fallback = () => { container.scrollTop = anchor.scrollTop; };
|
|
747
|
+
const el = anchor.element;
|
|
748
|
+
if (!el || el.isConnected === false || typeof el.getBoundingClientRect !== 'function') {
|
|
749
|
+
fallback();
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
let containerRect = null;
|
|
753
|
+
let rect = null;
|
|
754
|
+
try {
|
|
755
|
+
containerRect = container.getBoundingClientRect && container.getBoundingClientRect();
|
|
756
|
+
rect = el.getBoundingClientRect();
|
|
757
|
+
} catch {}
|
|
758
|
+
if (!containerRect || !rect) {
|
|
759
|
+
fallback();
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
const nextOffset = Number(rect.top || 0) - Number(containerRect.top || 0);
|
|
763
|
+
const delta = nextOffset - Number(anchor.topOffset || 0);
|
|
764
|
+
if (Number.isFinite(delta) && Math.abs(delta) > 0.5) {
|
|
765
|
+
container.scrollTop = Number(container.scrollTop || 0) + delta;
|
|
766
|
+
} else {
|
|
767
|
+
container.scrollTop = anchor.scrollTop;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
597
771
|
function _isPromptTurnContainer(container) {
|
|
598
772
|
return !!(container && container.dataset && (container.dataset.turnMode === 'conversation' || container.dataset.turnMode === 'review'));
|
|
599
773
|
}
|
|
@@ -691,6 +865,117 @@ function _removePromptTurnEmpty(body) {
|
|
|
691
865
|
}
|
|
692
866
|
}
|
|
693
867
|
|
|
868
|
+
function _conversationPromptTextKey(text) {
|
|
869
|
+
return String(text || '').replace(/\s+/g, ' ').trim();
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function _conversationEventTimestampMs(evt) {
|
|
873
|
+
const raw = evt && evt.timestamp;
|
|
874
|
+
if (typeof raw === 'number' && Number.isFinite(raw)) return raw;
|
|
875
|
+
if (typeof raw === 'string' && /^\d{10,}$/.test(raw.trim())) {
|
|
876
|
+
const numeric = Number(raw.trim());
|
|
877
|
+
if (Number.isFinite(numeric)) return numeric;
|
|
878
|
+
}
|
|
879
|
+
const parsed = Date.parse(raw || '');
|
|
880
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function _conversationEventRole(evt) {
|
|
884
|
+
const role = String(evt?.data?.type || evt?.eventType || evt?.type || evt?.role || '').trim();
|
|
885
|
+
return role === 'user' || role === 'assistant' ? role : '';
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function _conversationEventTextKey(evt) {
|
|
889
|
+
return _conversationPromptTextKey(evt?.data?.text ?? evt?.text ?? evt?.message ?? '');
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
function _conversationEventFingerprint(evt) {
|
|
893
|
+
const role = _conversationEventRole(evt);
|
|
894
|
+
const text = _conversationEventTextKey(evt);
|
|
895
|
+
if (!role || !text) return '';
|
|
896
|
+
const ts = _conversationEventTimestampMs(evt);
|
|
897
|
+
if (!ts) return '';
|
|
898
|
+
return [role, String(ts), text].join('\u001f');
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function _conversationMessageFingerprintSet(sessionId, create) {
|
|
902
|
+
const id = String(sessionId || '').trim();
|
|
903
|
+
if (!id) return null;
|
|
904
|
+
let seen = _streamState._messageFingerprintSeen.get(id);
|
|
905
|
+
if (!seen && create) {
|
|
906
|
+
seen = new Set();
|
|
907
|
+
_streamState._messageFingerprintSeen.set(id, seen);
|
|
908
|
+
}
|
|
909
|
+
return seen || null;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function _markConversationEventSeen(sessionId, evt) {
|
|
913
|
+
const fingerprint = _conversationEventFingerprint(evt);
|
|
914
|
+
if (!fingerprint) return false;
|
|
915
|
+
const seen = _conversationMessageFingerprintSet(sessionId, true);
|
|
916
|
+
if (!seen) return false;
|
|
917
|
+
seen.add(fingerprint);
|
|
918
|
+
return true;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
function _conversationEventAlreadySeen(sessionId, evt) {
|
|
922
|
+
const fingerprint = _conversationEventFingerprint(evt);
|
|
923
|
+
if (!fingerprint) return false;
|
|
924
|
+
const seen = _conversationMessageFingerprintSet(sessionId, false);
|
|
925
|
+
return !!(seen && seen.has(fingerprint));
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function _conversationStampPromptTurn(turn, evt) {
|
|
929
|
+
if (!turn || !turn.dataset || !evt || !MR || typeof MR.isConversationPromptEvent !== 'function') return;
|
|
930
|
+
if (!MR.isConversationPromptEvent(evt)) return;
|
|
931
|
+
const key = _conversationEventTextKey(evt);
|
|
932
|
+
if (key) turn.dataset.promptTextKey = key;
|
|
933
|
+
const ts = _conversationEventTimestampMs(evt);
|
|
934
|
+
if (ts) turn.dataset.promptTimestampMs = String(ts);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
function _conversationPromptTurnTextKey(turn) {
|
|
938
|
+
if (!turn || !turn.querySelector) return '';
|
|
939
|
+
const existing = turn.dataset && turn.dataset.promptTextKey;
|
|
940
|
+
if (existing) return existing;
|
|
941
|
+
const textEl = turn.querySelector('.prompt-turn-prompt .msg-text');
|
|
942
|
+
return _conversationPromptTextKey(textEl ? textEl.textContent : '');
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
function _conversationPromptTurnTimestampMs(turn) {
|
|
946
|
+
if (!turn || !turn.dataset) return 0;
|
|
947
|
+
const stored = Number(turn.dataset.promptTimestampMs || 0);
|
|
948
|
+
if (Number.isFinite(stored) && stored > 0) return stored;
|
|
949
|
+
return 0;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function _findDuplicateConversationPromptTurn(container, evt) {
|
|
953
|
+
if (!_isPromptTurnContainer(container)) return null;
|
|
954
|
+
if (!MR || typeof MR.isConversationPromptEvent !== 'function' || !MR.isConversationPromptEvent(evt)) return null;
|
|
955
|
+
const key = _conversationEventTextKey(evt);
|
|
956
|
+
if (!key) return null;
|
|
957
|
+
const ts = _conversationEventTimestampMs(evt);
|
|
958
|
+
const children = Array.from(container.children || []);
|
|
959
|
+
let checkedPromptTurns = 0;
|
|
960
|
+
for (let i = children.length - 1; i >= 0 && checkedPromptTurns < 6; i -= 1) {
|
|
961
|
+
const turn = children[i];
|
|
962
|
+
if (!turn || !turn.classList || !turn.classList.contains('prompt-turn')) continue;
|
|
963
|
+
if (turn.classList.contains('setup-turn')) continue;
|
|
964
|
+
checkedPromptTurns += 1;
|
|
965
|
+
if (_conversationPromptTurnTextKey(turn) !== key) continue;
|
|
966
|
+
const existingTs = _conversationPromptTurnTimestampMs(turn);
|
|
967
|
+
if (ts && existingTs) {
|
|
968
|
+
if (Math.abs(ts - existingTs) <= 5000) return turn;
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
// Missing timestamps only happen in legacy/import fallback paths. Keep this
|
|
972
|
+
// conservative: dedup only the current tail prompt so a user can still send
|
|
973
|
+
// the same short command again later.
|
|
974
|
+
if (checkedPromptTurns === 1) return turn;
|
|
975
|
+
}
|
|
976
|
+
return null;
|
|
977
|
+
}
|
|
978
|
+
|
|
694
979
|
function _ensureResponseTurn(container, opts) {
|
|
695
980
|
let turn = _latestPromptTurn(container);
|
|
696
981
|
if (turn) return turn;
|
|
@@ -705,23 +990,101 @@ function _ensureResponseTurn(container, opts) {
|
|
|
705
990
|
|
|
706
991
|
function _renderConversationNodeForEvent(evt, opts) {
|
|
707
992
|
if (MR && typeof MR.isConversationPromptEvent === 'function' && MR.isConversationPromptEvent(evt)) {
|
|
708
|
-
|
|
993
|
+
const turn = MR.createConversationTurn(evt, { expanded: !!(opts && opts.expandPrompt) });
|
|
994
|
+
_conversationStampPromptTurn(turn, evt);
|
|
995
|
+
return turn;
|
|
709
996
|
}
|
|
710
997
|
return renderConversationEvent(evt);
|
|
711
998
|
}
|
|
712
999
|
|
|
1000
|
+
// A streaming assistant turn can be rendered once as a stale-cache PARTIAL (the large-session
|
|
1001
|
+
// stale-cache path serves it without a parentUuid, so the row has no data-parent-uuid) and then
|
|
1002
|
+
// again as the live FINAL. Their text differs (partial vs final), so neither the parentUuid match
|
|
1003
|
+
// nor the exact-fingerprint dedup in _applyStreamEvent catches it and the final appends a
|
|
1004
|
+
// duplicate. When the turn's last assistant text row is a long PREFIX of (or equal to) the
|
|
1005
|
+
// incoming assistant text, they are the same streamed turn: keep the longer copy in its slot
|
|
1006
|
+
// instead of stacking a duplicate. Mirrors the server-side messagesRepresentSameEvent prefix rule.
|
|
1007
|
+
const _STREAM_PREFIX_DEDUP_MIN = 200;
|
|
1008
|
+
function _conversationRowRawText(el) {
|
|
1009
|
+
return el && typeof el._convRawText === 'string' ? el._convRawText : '';
|
|
1010
|
+
}
|
|
1011
|
+
function _supersedeStreamingAssistantPrefix(body, newEl, newText, evt) {
|
|
1012
|
+
if (!body || !newEl || !newEl.classList
|
|
1013
|
+
|| !newEl.classList.contains('assistant') || newEl.classList.contains('tool-only')) return null;
|
|
1014
|
+
const prev = body.lastElementChild;
|
|
1015
|
+
if (!prev || !prev.classList || !prev.classList.contains('review-msg')
|
|
1016
|
+
|| !prev.classList.contains('assistant') || prev.classList.contains('tool-only')) return null;
|
|
1017
|
+
const a = _conversationRowRawText(prev);
|
|
1018
|
+
const b = String(newText || '');
|
|
1019
|
+
if (a.length < _STREAM_PREFIX_DEDUP_MIN || b.length < _STREAM_PREFIX_DEDUP_MIN) return null;
|
|
1020
|
+
const shorter = a.length <= b.length ? a : b;
|
|
1021
|
+
const longer = a.length <= b.length ? b : a;
|
|
1022
|
+
if (!longer.startsWith(shorter)) return null; // not the same streamed turn — keep both
|
|
1023
|
+
const keep = b.length >= a.length ? newEl : prev;
|
|
1024
|
+
if (keep === newEl) _replaceConversationParentEvent(prev, newEl); // newEl takes the partial's slot
|
|
1025
|
+
keep._convRawText = longer;
|
|
1026
|
+
if (evt && evt.data && evt.data.parentUuid) _assignConversationParentUuid(keep, evt.data.parentUuid);
|
|
1027
|
+
return keep;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
const _TOOL_CARD_EVENT_KINDS = new Set(['tool_call', 'shell', 'patch', 'web_search']);
|
|
1031
|
+
|
|
1032
|
+
function _findToolCardByCallId(scopeEl, callId) {
|
|
1033
|
+
if (!scopeEl || !callId || typeof scopeEl.querySelector !== 'function') return null;
|
|
1034
|
+
const escaped = (window.CSS && CSS.escape) ? CSS.escape(callId) : callId.replace(/"/g, '\\"');
|
|
1035
|
+
return scopeEl.querySelector('.tool-card[data-call-id="' + escaped + '"]');
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// Structured tool events against the live DOM:
|
|
1039
|
+
// - a tool_result fills its call's card IN PLACE (matched on data-call-id) —
|
|
1040
|
+
// no new node;
|
|
1041
|
+
// - a replayed tool_call/shell/patch whose card already exists dedups to it.
|
|
1042
|
+
// Returns the affected card element, or null to continue the normal path.
|
|
1043
|
+
function _applyStructuredToolEvent(scopeEl, evt) {
|
|
1044
|
+
if (!MR || typeof MR.messageMeta !== 'function') return null;
|
|
1045
|
+
const meta = MR.messageMeta(evt);
|
|
1046
|
+
if (!meta || !meta.callId) return null;
|
|
1047
|
+
const card = _findToolCardByCallId(scopeEl, meta.callId);
|
|
1048
|
+
if (!card) return null;
|
|
1049
|
+
if (meta.kind === 'tool_result') {
|
|
1050
|
+
// Fill only a pending card; a replayed result against an already-filled
|
|
1051
|
+
// card is a duplicate event — dedup to the existing node.
|
|
1052
|
+
if (card.getAttribute('data-status') === 'pending' && typeof MR.fillToolCardResult === 'function') {
|
|
1053
|
+
MR.fillToolCardResult(card, {
|
|
1054
|
+
text: (evt.data && evt.data.text) || evt.text || '',
|
|
1055
|
+
metadata: meta,
|
|
1056
|
+
timestamp: evt.timestamp,
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
return card;
|
|
1060
|
+
}
|
|
1061
|
+
if (_TOOL_CARD_EVENT_KINDS.has(meta.kind)) return card; // dedup replayed call
|
|
1062
|
+
return null;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
713
1065
|
function _appendConversationEventToTurns(container, evt, opts) {
|
|
714
1066
|
opts = opts || {};
|
|
715
1067
|
if (!container || !MR || typeof MR.isConversationPromptEvent !== 'function') return null;
|
|
716
1068
|
if (MR.isConversationPromptEvent(evt)) {
|
|
717
1069
|
if (opts.collapseExisting) _collapsePromptTurns(container);
|
|
718
1070
|
const turn = MR.createConversationTurn(evt, { expanded: !!opts.expandPrompt });
|
|
1071
|
+
_conversationStampPromptTurn(turn, evt);
|
|
719
1072
|
_removeConversationState(container);
|
|
720
1073
|
container.appendChild(turn);
|
|
1074
|
+
_linkConversationDocumentReferences(container, turn);
|
|
721
1075
|
if (typeof MR.refreshPromptTurnMeta === 'function') MR.refreshPromptTurnMeta(turn);
|
|
722
1076
|
return turn;
|
|
723
1077
|
}
|
|
724
1078
|
|
|
1079
|
+
const filledCard = _applyStructuredToolEvent(container, evt);
|
|
1080
|
+
if (filledCard) {
|
|
1081
|
+
if (typeof MR.refreshPromptTurnMeta === 'function') {
|
|
1082
|
+
const cardTurn = filledCard.closest ? filledCard.closest('.prompt-turn') : null;
|
|
1083
|
+
if (cardTurn) MR.refreshPromptTurnMeta(cardTurn);
|
|
1084
|
+
}
|
|
1085
|
+
return filledCard;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
725
1088
|
const el = renderConversationEvent(evt);
|
|
726
1089
|
if (!el) return null;
|
|
727
1090
|
_removeConversationState(container);
|
|
@@ -729,8 +1092,18 @@ function _appendConversationEventToTurns(container, evt, opts) {
|
|
|
729
1092
|
const body = MR.getPromptTurnBody(turn);
|
|
730
1093
|
if (!body) return null;
|
|
731
1094
|
_removePromptTurnEmpty(body);
|
|
1095
|
+
const rawText = (evt.data && evt.data.text) || evt.text || '';
|
|
1096
|
+
el._convRawText = rawText;
|
|
732
1097
|
if (evt.data?.parentUuid) _assignConversationParentUuid(el, evt.data.parentUuid);
|
|
733
|
-
|
|
1098
|
+
const superseded = _supersedeStreamingAssistantPrefix(body, el, rawText, evt);
|
|
1099
|
+
if (superseded) {
|
|
1100
|
+
_linkConversationDocumentReferences(container, body);
|
|
1101
|
+
if (typeof MR.refreshPromptTurnMeta === 'function') MR.refreshPromptTurnMeta(turn);
|
|
1102
|
+
return superseded;
|
|
1103
|
+
}
|
|
1104
|
+
const merged = _mergeConsecutiveToolGroups(body, el);
|
|
1105
|
+
if (!merged) body.appendChild(el);
|
|
1106
|
+
_linkConversationDocumentReferences(container, merged ? body : el);
|
|
734
1107
|
if (typeof MR.refreshPromptTurnMeta === 'function') MR.refreshPromptTurnMeta(turn);
|
|
735
1108
|
return el;
|
|
736
1109
|
}
|
|
@@ -738,6 +1111,7 @@ function _appendConversationEventToTurns(container, evt, opts) {
|
|
|
738
1111
|
function populateConversationView(container, events) {
|
|
739
1112
|
container.textContent = '';
|
|
740
1113
|
if (container.dataset) container.dataset.turnMode = container.dataset.turnMode || 'conversation';
|
|
1114
|
+
const sessionId = container.dataset?.sessionId || '';
|
|
741
1115
|
if (!events || events.length === 0) {
|
|
742
1116
|
_renderConversationState(container, 'No transcript messages found for this session.');
|
|
743
1117
|
container.scrollTop = container.scrollHeight;
|
|
@@ -745,15 +1119,26 @@ function populateConversationView(container, events) {
|
|
|
745
1119
|
}
|
|
746
1120
|
for (const evt of events) {
|
|
747
1121
|
if (_isPromptTurnContainer(container)) {
|
|
748
|
-
_appendConversationEventToTurns(container, evt, { expandPrompt: false, collapseExisting: false, expandSetup: false })
|
|
1122
|
+
if (_appendConversationEventToTurns(container, evt, { expandPrompt: false, collapseExisting: false, expandSetup: false })) {
|
|
1123
|
+
_markConversationEventSeen(sessionId, evt);
|
|
1124
|
+
}
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1127
|
+
if (_applyStructuredToolEvent(container, evt)) {
|
|
1128
|
+
_markConversationEventSeen(sessionId, evt);
|
|
749
1129
|
continue;
|
|
750
1130
|
}
|
|
751
1131
|
const el = renderConversationEvent(evt);
|
|
752
1132
|
if (!el) continue;
|
|
753
1133
|
if (evt.data?.parentUuid) _assignConversationParentUuid(el, evt.data.parentUuid);
|
|
754
|
-
if (_mergeConsecutiveToolGroups(container, el))
|
|
1134
|
+
if (_mergeConsecutiveToolGroups(container, el)) {
|
|
1135
|
+
_markConversationEventSeen(sessionId, evt);
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
755
1138
|
container.appendChild(el);
|
|
1139
|
+
_markConversationEventSeen(sessionId, evt);
|
|
756
1140
|
}
|
|
1141
|
+
_linkConversationDocumentReferences(container, container);
|
|
757
1142
|
if (_isPromptTurnContainer(container)) _refreshLatestPromptTurnStatus(container.dataset?.sessionId || '', container);
|
|
758
1143
|
container.scrollTop = container.scrollHeight;
|
|
759
1144
|
}
|
|
@@ -912,12 +1297,37 @@ let _streamStatusRefreshInFlight = null;
|
|
|
912
1297
|
let _streamStatusLastRefreshAt = 0;
|
|
913
1298
|
let _streamStatusRenderTimer = null;
|
|
914
1299
|
|
|
1300
|
+
function _clearStreamRestoreStateFallback(s) {
|
|
1301
|
+
if (!s) return;
|
|
1302
|
+
s._restoreDeferredStarting = false;
|
|
1303
|
+
s._restoreStarting = false;
|
|
1304
|
+
s._restoreResuming = false;
|
|
1305
|
+
s._restoreStatus = '';
|
|
1306
|
+
if (s.meta) {
|
|
1307
|
+
s.meta.restoreStarting = false;
|
|
1308
|
+
s.meta.restore_starting = false;
|
|
1309
|
+
s.meta.restoreResuming = false;
|
|
1310
|
+
s.meta.restore_resuming = false;
|
|
1311
|
+
s.meta.restoreStatus = '';
|
|
1312
|
+
s.meta.restore_status = '';
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
function _clearStreamRestoreState(s, root) {
|
|
1317
|
+
if (root && typeof root._ctmClearSessionRestoreState === 'function') {
|
|
1318
|
+
root._ctmClearSessionRestoreState(s);
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
_clearStreamRestoreStateFallback(s);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
915
1324
|
function applyStreamStatus(msg) {
|
|
916
1325
|
const status = msg && (msg.newStatus || msg.status);
|
|
917
1326
|
const ctmId = msg && (msg.ctmSessionId || msg.sessionId);
|
|
918
1327
|
if (!ctmId || !status || typeof state === 'undefined' || !state.sessions) return false;
|
|
919
1328
|
const s = state.sessions.get(ctmId);
|
|
920
1329
|
if (!s) return false;
|
|
1330
|
+
const root = typeof window !== 'undefined' ? window : globalThis;
|
|
921
1331
|
|
|
922
1332
|
const beforeEffectiveStatus = typeof getSessionStatus === 'function'
|
|
923
1333
|
? getSessionStatus(s).cls
|
|
@@ -928,8 +1338,11 @@ function applyStreamStatus(msg) {
|
|
|
928
1338
|
const normalizedStatus = typeof normalizeLiveSessionStatus === 'function'
|
|
929
1339
|
? normalizeLiveSessionStatus(status)
|
|
930
1340
|
: String(status || '').toLowerCase();
|
|
1341
|
+
const serverReady = !!(root && root._ctmState && root._ctmState._serverReady);
|
|
1342
|
+
if (normalizedStatus === 'running' || (serverReady && normalizedStatus && normalizedStatus !== 'resuming')) {
|
|
1343
|
+
_clearStreamRestoreState(s, root);
|
|
1344
|
+
}
|
|
931
1345
|
if (normalizedStatus && normalizedStatus !== 'running') {
|
|
932
|
-
const root = typeof window !== 'undefined' ? window : globalThis;
|
|
933
1346
|
const heldNonRunningStatus = root && typeof root._ctmRecordClientCodexPendingNonRunning === 'function'
|
|
934
1347
|
? root._ctmRecordClientCodexPendingNonRunning(
|
|
935
1348
|
s,
|
|
@@ -1037,10 +1450,17 @@ if (_streamStatusPollTimer && typeof _streamStatusPollTimer.unref === 'function'
|
|
|
1037
1450
|
|
|
1038
1451
|
// --- View Toggle ---
|
|
1039
1452
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1453
|
+
// The Terminal/Conversation toggle only makes sense when the agent has a
|
|
1454
|
+
// structured transcript (Claude/Codex/Wall-E). Plain shells and terminal-only
|
|
1455
|
+
// agents have no conversation view, so the toggle would be a dead control.
|
|
1456
|
+
function _sessionHasConversationView(sessionId) {
|
|
1457
|
+
const caps = (typeof window !== 'undefined' && typeof window._ctmAgentCapsForSession === 'function')
|
|
1458
|
+
? window._ctmAgentCapsForSession(sessionId)
|
|
1459
|
+
: null;
|
|
1460
|
+
return !caps || caps.structuredTranscript !== false;
|
|
1461
|
+
}
|
|
1043
1462
|
|
|
1463
|
+
function _buildViewToggleEl(sessionId) {
|
|
1044
1464
|
const toggle = document.createElement('div');
|
|
1045
1465
|
toggle.className = 'stream-view-toggle';
|
|
1046
1466
|
toggle.setAttribute('role', 'group');
|
|
@@ -1064,22 +1484,48 @@ function createViewToggle(sessionId) {
|
|
|
1064
1484
|
convBtn.setAttribute('aria-label', 'Show structured conversation');
|
|
1065
1485
|
convBtn.innerHTML = '<span class="stream-view-icon" aria-hidden="true">C</span><span class="stream-view-label">Conversation</span>';
|
|
1066
1486
|
|
|
1067
|
-
termBtn.onclick = () => {
|
|
1068
|
-
|
|
1069
|
-
};
|
|
1070
|
-
|
|
1071
|
-
convBtn.onclick = () => {
|
|
1072
|
-
setSessionView(sessionId, 'conversation');
|
|
1073
|
-
};
|
|
1487
|
+
termBtn.onclick = () => { setSessionView(sessionId, 'terminal'); };
|
|
1488
|
+
convBtn.onclick = () => { setSessionView(sessionId, 'conversation'); };
|
|
1074
1489
|
|
|
1075
1490
|
toggle.appendChild(termBtn);
|
|
1076
1491
|
toggle.appendChild(convBtn);
|
|
1077
|
-
controls.appendChild(toggle);
|
|
1078
|
-
controls.appendChild(_buildExportMenu(sessionId));
|
|
1079
1492
|
_setViewButtonState([termBtn, convBtn], _streamState.activeView.get(sessionId) || getPreferredSessionView());
|
|
1493
|
+
return toggle;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
function createViewToggle(sessionId) {
|
|
1497
|
+
const controls = document.createElement('div');
|
|
1498
|
+
controls.className = 'stream-view-controls';
|
|
1499
|
+
if (_sessionHasConversationView(sessionId)) controls.appendChild(_buildViewToggleEl(sessionId));
|
|
1500
|
+
controls.appendChild(_buildExportMenu(sessionId));
|
|
1080
1501
|
return controls;
|
|
1081
1502
|
}
|
|
1082
1503
|
|
|
1504
|
+
// Reconcile the Term/Conv toggle's presence after the agent type resolves. A
|
|
1505
|
+
// freshly-restored session can be classified as 'shell' before its agentType is
|
|
1506
|
+
// known, so createViewToggle skips the toggle and nothing rebuilds the toolbar —
|
|
1507
|
+
// leaving a Claude/Codex session permanently without Term/Conv. Add the toggle in
|
|
1508
|
+
// place (before the ⋯ menu) once the session is known to have a conversation
|
|
1509
|
+
// view; drop a stale one otherwise. Located via the toolbar's data-session-id so
|
|
1510
|
+
// it works whether the toolbar is in its pane or merged onto the tab strip.
|
|
1511
|
+
function syncSessionViewToggle(sessionId) {
|
|
1512
|
+
if (typeof document === 'undefined' || !sessionId) return;
|
|
1513
|
+
const esc = (typeof window !== 'undefined' && window.CSS && CSS.escape) ? CSS.escape(sessionId) : sessionId;
|
|
1514
|
+
const toolbar = document.querySelector(`.session-toolbar[data-session-id="${esc}"]`);
|
|
1515
|
+
const controls = toolbar ? toolbar.querySelector('.stream-view-controls') : null;
|
|
1516
|
+
if (!controls) return;
|
|
1517
|
+
const wantToggle = _sessionHasConversationView(sessionId);
|
|
1518
|
+
const existing = controls.querySelector('.stream-view-toggle');
|
|
1519
|
+
if (wantToggle && !existing) {
|
|
1520
|
+
const exportMenu = controls.querySelector('.stream-export-menu');
|
|
1521
|
+
controls.insertBefore(_buildViewToggleEl(sessionId), exportMenu || null);
|
|
1522
|
+
_syncViewToggle(sessionId);
|
|
1523
|
+
} else if (!wantToggle && existing) {
|
|
1524
|
+
existing.remove();
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
if (typeof window !== 'undefined') window.syncSessionViewToggle = syncSessionViewToggle;
|
|
1528
|
+
|
|
1083
1529
|
function _setViewButtonState(buttons, view) {
|
|
1084
1530
|
buttons.forEach((btn) => {
|
|
1085
1531
|
const active = btn.dataset.view === view;
|
|
@@ -1090,10 +1536,13 @@ function _setViewButtonState(buttons, view) {
|
|
|
1090
1536
|
|
|
1091
1537
|
function _syncViewToggle(sessionId) {
|
|
1092
1538
|
const view = _streamState.activeView.get(sessionId) || 'terminal';
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1539
|
+
// Select by data-session-id globally rather than scoping to the session's
|
|
1540
|
+
// .term-container: in the merged-row layout the toggle is relocated out of
|
|
1541
|
+
// the container into #tabbar-session-controls, so a container-scoped query
|
|
1542
|
+
// finds zero buttons and the active highlight never updates. Each button
|
|
1543
|
+
// carries data-session-id, so this matches it wherever it lives (container,
|
|
1544
|
+
// merged tab-strip slot, or a split pane).
|
|
1545
|
+
const buttons = document.querySelectorAll(`.stream-view-btn[data-view][data-session-id="${CSS.escape(sessionId)}"]`);
|
|
1097
1546
|
_setViewButtonState(buttons, view);
|
|
1098
1547
|
}
|
|
1099
1548
|
|
|
@@ -1105,8 +1554,18 @@ function _latestAnswerUiEnabled() {
|
|
|
1105
1554
|
window._ctmIsLatestAnswerUiEnabled();
|
|
1106
1555
|
}
|
|
1107
1556
|
|
|
1557
|
+
function _finalSummaryUiEnabled() {
|
|
1558
|
+
return typeof window !== 'undefined' &&
|
|
1559
|
+
typeof window._ctmIsFinalSummaryUiEnabled === 'function' &&
|
|
1560
|
+
window._ctmIsFinalSummaryUiEnabled();
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
function _latestAnswerHydrationEnabled() {
|
|
1564
|
+
return _latestAnswerUiEnabled() || _finalSummaryUiEnabled();
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1108
1567
|
function _hydrateLatestAnswerFromConversationMessages(sessionId, messages, source, opts) {
|
|
1109
|
-
if (!
|
|
1568
|
+
if (!_latestAnswerHydrationEnabled()) return false;
|
|
1110
1569
|
if (typeof window === 'undefined' || typeof window._ctmHydrateLatestAnswerFromMessages !== 'function') return false;
|
|
1111
1570
|
try {
|
|
1112
1571
|
return window._ctmHydrateLatestAnswerFromMessages(sessionId, messages, {
|
|
@@ -1120,7 +1579,7 @@ function _hydrateLatestAnswerFromConversationMessages(sessionId, messages, sourc
|
|
|
1120
1579
|
}
|
|
1121
1580
|
|
|
1122
1581
|
async function _primeLatestAnswerForTerminal(sessionId, opts) {
|
|
1123
|
-
if (!
|
|
1582
|
+
if (!_latestAnswerHydrationEnabled()) return null;
|
|
1124
1583
|
opts = opts || {};
|
|
1125
1584
|
const now = Date.now();
|
|
1126
1585
|
const cached = _latestAnswerTerminalPrime.get(sessionId);
|
|
@@ -1178,9 +1637,36 @@ function _buildExportMenu(sessionId) {
|
|
|
1178
1637
|
menu.className = 'stream-export-menu-items';
|
|
1179
1638
|
menu.setAttribute('role', 'menu');
|
|
1180
1639
|
|
|
1640
|
+
// The dropdown is position:fixed (viewport-relative) so it escapes the
|
|
1641
|
+
// #tabbar { overflow:hidden } clip when the controls are merged onto the tab
|
|
1642
|
+
// strip. Anchor it under the trigger, right-aligned, clamped to the viewport.
|
|
1643
|
+
const positionMenu = () => {
|
|
1644
|
+
const rect = trigger.getBoundingClientRect();
|
|
1645
|
+
const margin = 8;
|
|
1646
|
+
menu.style.visibility = 'hidden';
|
|
1647
|
+
menu.style.top = '0px';
|
|
1648
|
+
menu.style.left = '0px';
|
|
1649
|
+
const w = menu.offsetWidth || 168;
|
|
1650
|
+
const h = menu.offsetHeight || 0;
|
|
1651
|
+
const left = Math.max(margin, Math.min(rect.right - w, window.innerWidth - w - margin));
|
|
1652
|
+
let top = rect.bottom + 4;
|
|
1653
|
+
if (top + h > window.innerHeight - margin) {
|
|
1654
|
+
top = Math.max(margin, window.innerHeight - h - margin);
|
|
1655
|
+
}
|
|
1656
|
+
menu.style.left = left + 'px';
|
|
1657
|
+
menu.style.top = top + 'px';
|
|
1658
|
+
menu.style.visibility = '';
|
|
1659
|
+
};
|
|
1660
|
+
|
|
1661
|
+
let reposition = null;
|
|
1181
1662
|
const closeMenu = () => {
|
|
1182
1663
|
container.classList.remove('open');
|
|
1183
1664
|
trigger.setAttribute('aria-expanded', 'false');
|
|
1665
|
+
if (reposition) {
|
|
1666
|
+
window.removeEventListener('scroll', reposition, true);
|
|
1667
|
+
window.removeEventListener('resize', reposition);
|
|
1668
|
+
reposition = null;
|
|
1669
|
+
}
|
|
1184
1670
|
};
|
|
1185
1671
|
|
|
1186
1672
|
const addSection = (label) => {
|
|
@@ -1224,6 +1710,16 @@ function _buildExportMenu(sessionId) {
|
|
|
1224
1710
|
}
|
|
1225
1711
|
};
|
|
1226
1712
|
|
|
1713
|
+
addSection('Session');
|
|
1714
|
+
addItem('📋 Copy command', () => {
|
|
1715
|
+
const sess = (typeof state !== 'undefined' && state.sessions) ? state.sessions.get(sessionId) : null;
|
|
1716
|
+
const cwd = (sess && ((sess.meta && sess.meta.cwd) || sess.cwd)) || '';
|
|
1717
|
+
if (typeof window.copySessionCommand === 'function') window.copySessionCommand(sessionId, cwd);
|
|
1718
|
+
});
|
|
1719
|
+
addItem('⬚ Split right', () => { if (typeof window.splitFocusedPane === 'function') window.splitFocusedPane('h'); });
|
|
1720
|
+
addItem('⬚ Split down', () => { if (typeof window.splitFocusedPane === 'function') window.splitFocusedPane('v'); });
|
|
1721
|
+
addDivider();
|
|
1722
|
+
|
|
1227
1723
|
addSection('Review');
|
|
1228
1724
|
addItem('📝 Transcript', () => {
|
|
1229
1725
|
const caps = (typeof window !== 'undefined' && typeof window._ctmAgentCapsForSession === 'function')
|
|
@@ -1291,6 +1787,14 @@ function _buildExportMenu(sessionId) {
|
|
|
1291
1787
|
const open = !container.classList.contains('open');
|
|
1292
1788
|
container.classList.toggle('open', open);
|
|
1293
1789
|
trigger.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
1790
|
+
if (open) {
|
|
1791
|
+
positionMenu();
|
|
1792
|
+
reposition = () => { if (container.classList.contains('open')) positionMenu(); };
|
|
1793
|
+
window.addEventListener('scroll', reposition, true);
|
|
1794
|
+
window.addEventListener('resize', reposition);
|
|
1795
|
+
} else {
|
|
1796
|
+
closeMenu();
|
|
1797
|
+
}
|
|
1294
1798
|
};
|
|
1295
1799
|
document.addEventListener('click', closeMenu);
|
|
1296
1800
|
|
|
@@ -1328,7 +1832,12 @@ function setSessionView(sessionId, view, opts) {
|
|
|
1328
1832
|
if (xtermEl) xtermEl.style.display = '';
|
|
1329
1833
|
if (convView) convView.style.display = 'none';
|
|
1330
1834
|
if (typeof _renderCodexFinalPanel === 'function') _renderCodexFinalPanel(sessionId);
|
|
1331
|
-
if (
|
|
1835
|
+
if (_latestAnswerHydrationEnabled()) _primeLatestAnswerForTerminal(sessionId);
|
|
1836
|
+
if (!opts.skipTerminalRestore &&
|
|
1837
|
+
typeof window !== 'undefined' &&
|
|
1838
|
+
typeof window._ctmEnsureTerminalRestoreForVisibleView === 'function') {
|
|
1839
|
+
window._ctmEnsureTerminalRestoreForVisibleView(sessionId, { reason: 'switch-to-terminal-view' });
|
|
1840
|
+
}
|
|
1332
1841
|
if (window._ws) unsubscribeFromStream(window._ws, sessionId);
|
|
1333
1842
|
}
|
|
1334
1843
|
return view;
|
|
@@ -1353,6 +1862,8 @@ function _applyStreamEvent(sessionId, container, msg) {
|
|
|
1353
1862
|
const refreshLatestPromptStatus = () => {
|
|
1354
1863
|
if (_isPromptTurnContainer(container)) _refreshLatestPromptTurnStatus(sessionId, container);
|
|
1355
1864
|
};
|
|
1865
|
+
const scrollAnchor = _captureConversationScrollAnchor(container);
|
|
1866
|
+
const restoreScrollAnchor = () => _restoreConversationScrollAnchor(container, scrollAnchor);
|
|
1356
1867
|
|
|
1357
1868
|
// Explicit update (server signaled _update=true): replace the row with
|
|
1358
1869
|
// this parentUuid.
|
|
@@ -1364,11 +1875,14 @@ function _applyStreamEvent(sessionId, container, msg) {
|
|
|
1364
1875
|
: renderConversationEvent(msg);
|
|
1365
1876
|
if (newEl) {
|
|
1366
1877
|
_assignConversationParentUuid(newEl, parentUuid);
|
|
1878
|
+
_linkConversationDocumentReferences(container, newEl);
|
|
1367
1879
|
_replaceConversationParentEvent(existing, newEl);
|
|
1880
|
+
_markConversationEventSeen(sessionId, msg);
|
|
1368
1881
|
} else {
|
|
1369
1882
|
_replaceConversationParentEvent(existing, null); // Event became empty after update
|
|
1370
1883
|
}
|
|
1371
1884
|
refreshLatestPromptStatus();
|
|
1885
|
+
restoreScrollAnchor();
|
|
1372
1886
|
return;
|
|
1373
1887
|
}
|
|
1374
1888
|
}
|
|
@@ -1385,9 +1899,12 @@ function _applyStreamEvent(sessionId, container, msg) {
|
|
|
1385
1899
|
: renderConversationEvent(msg);
|
|
1386
1900
|
if (newEl) {
|
|
1387
1901
|
_assignConversationParentUuid(newEl, parentUuid);
|
|
1902
|
+
_linkConversationDocumentReferences(container, newEl);
|
|
1388
1903
|
_replaceConversationParentEvent(existing, newEl);
|
|
1904
|
+
_markConversationEventSeen(sessionId, msg);
|
|
1389
1905
|
}
|
|
1390
1906
|
refreshLatestPromptStatus();
|
|
1907
|
+
restoreScrollAnchor();
|
|
1391
1908
|
return;
|
|
1392
1909
|
}
|
|
1393
1910
|
}
|
|
@@ -1399,14 +1916,37 @@ function _applyStreamEvent(sessionId, container, msg) {
|
|
|
1399
1916
|
: renderConversationEvent(msg);
|
|
1400
1917
|
if (newEl) {
|
|
1401
1918
|
_assignConversationParentUuid(newEl, parentUuid);
|
|
1919
|
+
_linkConversationDocumentReferences(container, newEl);
|
|
1402
1920
|
_replaceConversationParentEvent(existing, newEl);
|
|
1403
1921
|
seen.add(parentUuid);
|
|
1922
|
+
_markConversationEventSeen(sessionId, msg);
|
|
1404
1923
|
}
|
|
1405
1924
|
refreshLatestPromptStatus();
|
|
1925
|
+
restoreScrollAnchor();
|
|
1406
1926
|
return;
|
|
1407
1927
|
}
|
|
1408
1928
|
}
|
|
1409
1929
|
|
|
1930
|
+
if (_conversationEventAlreadySeen(sessionId, msg)) {
|
|
1931
|
+
if (parentUuid) seen.add(parentUuid);
|
|
1932
|
+
refreshLatestPromptStatus();
|
|
1933
|
+
restoreScrollAnchor();
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
const duplicatePromptTurn = _findDuplicateConversationPromptTurn(container, msg);
|
|
1938
|
+
if (duplicatePromptTurn) {
|
|
1939
|
+
_conversationStampPromptTurn(duplicatePromptTurn, msg);
|
|
1940
|
+
if (parentUuid) {
|
|
1941
|
+
if (!duplicatePromptTurn.dataset?.parentUuid) _assignConversationParentUuid(duplicatePromptTurn, parentUuid);
|
|
1942
|
+
seen.add(parentUuid);
|
|
1943
|
+
}
|
|
1944
|
+
_markConversationEventSeen(sessionId, msg);
|
|
1945
|
+
refreshLatestPromptStatus();
|
|
1946
|
+
restoreScrollAnchor();
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1410
1950
|
const el = _isPromptTurnContainer(container)
|
|
1411
1951
|
? _appendConversationEventToTurns(container, msg, { expandPrompt: false, collapseExisting: true, expandSetup: true })
|
|
1412
1952
|
: renderConversationEvent(msg);
|
|
@@ -1415,6 +1955,7 @@ function _applyStreamEvent(sessionId, container, msg) {
|
|
|
1415
1955
|
_assignConversationParentUuid(el, parentUuid);
|
|
1416
1956
|
if (seen) seen.add(parentUuid);
|
|
1417
1957
|
}
|
|
1958
|
+
_markConversationEventSeen(sessionId, msg);
|
|
1418
1959
|
// If this is a tool-only thought-group AND the last rendered child is also
|
|
1419
1960
|
// a tool-only thought-group, fold this one's single step into the existing
|
|
1420
1961
|
// group instead of appending a new wrapper. Matches Review's grouping.
|
|
@@ -1425,6 +1966,7 @@ function _applyStreamEvent(sessionId, container, msg) {
|
|
|
1425
1966
|
} else {
|
|
1426
1967
|
container.appendChild(el);
|
|
1427
1968
|
}
|
|
1969
|
+
_linkConversationDocumentReferences(container, container);
|
|
1428
1970
|
// Phase 4 reconciliation: respect the global "Hide tool calls" toggle
|
|
1429
1971
|
// for newly-streamed tool-only rows so the user's preference applies
|
|
1430
1972
|
// immediately to live updates, not just to the next full re-render.
|
|
@@ -1432,9 +1974,9 @@ function _applyStreamEvent(sessionId, container, msg) {
|
|
|
1432
1974
|
const cb = document.getElementById('hide-tool-msgs');
|
|
1433
1975
|
if (cb && cb.checked) el.style.display = 'none';
|
|
1434
1976
|
}
|
|
1977
|
+
_pruneConversationTurns(container);
|
|
1435
1978
|
refreshLatestPromptStatus();
|
|
1436
|
-
|
|
1437
|
-
if (atBottom) container.scrollTop = container.scrollHeight;
|
|
1979
|
+
restoreScrollAnchor();
|
|
1438
1980
|
}
|
|
1439
1981
|
|
|
1440
1982
|
// Prime the conversation view with the full cached history from
|
|
@@ -1445,12 +1987,10 @@ function _applyStreamEvent(sessionId, container, msg) {
|
|
|
1445
1987
|
// the same promise. If priming has already completed, returns immediately.
|
|
1446
1988
|
// Call this on stream-init; stream-events arriving during the HTTP fetch
|
|
1447
1989
|
// are buffered in _streamState._pendingEvents and flushed after prime.
|
|
1448
|
-
// Pagination support for chat-style conversation loading.
|
|
1449
|
-
//
|
|
1450
|
-
//
|
|
1451
|
-
|
|
1452
|
-
// gap-reports/viewer-2026-04-23.md item D.
|
|
1453
|
-
const CONVERSATION_PAGE_SIZE = 200;
|
|
1990
|
+
// Pagination support for chat-style conversation loading. The server returns
|
|
1991
|
+
// complete semantic turns for this view so pages never start inside a long
|
|
1992
|
+
// tool-heavy turn without the owning prompt.
|
|
1993
|
+
const CONVERSATION_TURN_PAGE_SIZE = 20;
|
|
1454
1994
|
|
|
1455
1995
|
function _conversationViewHasRenderableContent(convView) {
|
|
1456
1996
|
if (!convView || !convView.children || convView.children.length === 0) return false;
|
|
@@ -1489,24 +2029,54 @@ function _primeStillCurrent(sessionId, token, convView) {
|
|
|
1489
2029
|
}
|
|
1490
2030
|
|
|
1491
2031
|
function _cleanSystemXml(t) {
|
|
1492
|
-
return t
|
|
2032
|
+
return String(t || '')
|
|
1493
2033
|
.replace(/<([a-z][a-z0-9]*(?:-[a-z0-9]+)+)[^>]*>[\s\S]*?<\/\1>/gi, '')
|
|
1494
2034
|
.replace(/<\/?[a-z][a-z0-9]*(?:-[a-z0-9]+)+[^>]*>/gi, '')
|
|
1495
2035
|
.trim();
|
|
1496
2036
|
}
|
|
1497
2037
|
|
|
2038
|
+
function _messageTextForConversationEvent(message) {
|
|
2039
|
+
if (!message || typeof message !== 'object') return '';
|
|
2040
|
+
const direct = message.text ?? message.content ?? message.message ?? '';
|
|
2041
|
+
if (typeof direct === 'string') return direct;
|
|
2042
|
+
if (Array.isArray(direct)) {
|
|
2043
|
+
return direct.map(part => {
|
|
2044
|
+
if (typeof part === 'string') return part;
|
|
2045
|
+
if (!part || typeof part !== 'object') return '';
|
|
2046
|
+
return part.text || part.content || part.message || '';
|
|
2047
|
+
}).filter(Boolean).join('\n');
|
|
2048
|
+
}
|
|
2049
|
+
if (direct && typeof direct === 'object') return direct.text || direct.content || direct.message || '';
|
|
2050
|
+
return '';
|
|
2051
|
+
}
|
|
2052
|
+
|
|
1498
2053
|
function _messagesToEvents(messages) {
|
|
1499
2054
|
return messages
|
|
1500
|
-
.filter(m => m.role === 'user' || m.role === 'assistant')
|
|
1501
|
-
.map(m =>
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
2055
|
+
.filter(m => m.role === 'user' || m.role === 'assistant' || m.role === 'system')
|
|
2056
|
+
.map(m => {
|
|
2057
|
+
const rawText = _messageTextForConversationEvent(m);
|
|
2058
|
+
const cleanedUserText = m.role === 'user' ? _cleanSystemXml(rawText) : rawText;
|
|
2059
|
+
const isSystemOnlyUser = m.role === 'user' && rawText && !cleanedUserText;
|
|
2060
|
+
const type = (m.role === 'system' || isSystemOnlyUser) ? 'summary' : m.role;
|
|
2061
|
+
const data = {
|
|
2062
|
+
text: type === 'summary' ? rawText : cleanedUserText,
|
|
1505
2063
|
model: m.model || '',
|
|
1506
2064
|
parentUuid: m.parentUuid,
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
|
|
2065
|
+
};
|
|
2066
|
+
if (m.image_refs) data.image_refs = m.image_refs;
|
|
2067
|
+
if (m.imageRefs) data.imageRefs = m.imageRefs;
|
|
2068
|
+
if (m.attachments) data.attachments = m.attachments;
|
|
2069
|
+
if (m.images) data.images = m.images;
|
|
2070
|
+
if (m.contentBlocks) data.contentBlocks = m.contentBlocks;
|
|
2071
|
+
// Structured-capture rows (tool cards, reasoning, dividers) and subagent
|
|
2072
|
+
// tags need their metadata on the event or MR.messageMeta can't see it.
|
|
2073
|
+
if (m.metadata) data.metadata = m.metadata;
|
|
2074
|
+
return {
|
|
2075
|
+
type,
|
|
2076
|
+
data,
|
|
2077
|
+
timestamp: m.timestamp,
|
|
2078
|
+
};
|
|
2079
|
+
});
|
|
1510
2080
|
}
|
|
1511
2081
|
|
|
1512
2082
|
function _renderConversationState(container, text, kind = 'empty') {
|
|
@@ -1564,13 +2134,15 @@ function _removeConversationState(container) {
|
|
|
1564
2134
|
|
|
1565
2135
|
function _normalizeConversationPage(body) {
|
|
1566
2136
|
if (Array.isArray(body)) {
|
|
1567
|
-
return { messages: body, total: body.length, has_more: false, next_offset: body.length };
|
|
2137
|
+
return { messages: body, total: body.length, has_more: false, next_offset: body.length, page_kind: 'messages', turn_count: 0 };
|
|
1568
2138
|
}
|
|
1569
2139
|
return {
|
|
1570
2140
|
messages: Array.isArray(body?.messages) ? body.messages : [],
|
|
1571
2141
|
total: typeof body?.total === 'number' ? body.total : 0,
|
|
1572
2142
|
has_more: !!body?.has_more,
|
|
1573
2143
|
next_offset: typeof body?.next_offset === 'number' ? body.next_offset : 0,
|
|
2144
|
+
page_kind: body?.page_kind || 'messages',
|
|
2145
|
+
turn_count: typeof body?.turn_count === 'number' ? body.turn_count : 0,
|
|
1574
2146
|
partial: !!body?.partial,
|
|
1575
2147
|
};
|
|
1576
2148
|
}
|
|
@@ -1634,8 +2206,8 @@ function _conversationLookupCandidates(sessionId) {
|
|
|
1634
2206
|
}
|
|
1635
2207
|
|
|
1636
2208
|
/**
|
|
1637
|
-
* Fetch one chat-style page of
|
|
1638
|
-
*
|
|
2209
|
+
* Fetch one chat-style page of turns. offset=0 yields the most recent
|
|
2210
|
+
* CONVERSATION_TURN_PAGE_SIZE turns. Backend returns either an array
|
|
1639
2211
|
* (legacy/unpaginated response for other callers) or a structured page:
|
|
1640
2212
|
* { messages, total, has_more, next_offset, partial? }
|
|
1641
2213
|
*/
|
|
@@ -1645,7 +2217,7 @@ async function _fetchConversationPage(sessionId, offset, opts) {
|
|
|
1645
2217
|
let firstEmptyPage = null;
|
|
1646
2218
|
for (const candidate of candidates) {
|
|
1647
2219
|
let url = `/api/session/messages?id=${encodeURIComponent(candidate.id)}`
|
|
1648
|
-
+ `&offset=${offset}&limit=${
|
|
2220
|
+
+ `&offset=${offset}&limit=${CONVERSATION_TURN_PAGE_SIZE}&mode=turns`;
|
|
1649
2221
|
if (candidate.projectEntry) url += `&project=${encodeURIComponent(candidate.projectEntry)}`;
|
|
1650
2222
|
if (opts.fresh && offset === 0) url += '&nocache=1';
|
|
1651
2223
|
const resp = await fetch(url, { cache: 'no-store' });
|
|
@@ -1664,18 +2236,56 @@ function _ensureLoadOlderBar(convView, sessionId) {
|
|
|
1664
2236
|
bar.className = 'conversation-load-older';
|
|
1665
2237
|
bar.type = 'button';
|
|
1666
2238
|
bar.style.cssText = 'display:none;margin:0 auto 8px;padding:6px 14px;border:1px solid var(--conversation-load-border, var(--border, #333));background:var(--conversation-load-bg, var(--surface-2, #1a1a2e));color:var(--conversation-load-fg, var(--fg-dim, #999));border-radius:6px;font-size:11px;cursor:pointer;';
|
|
1667
|
-
bar.textContent = `Load ${
|
|
2239
|
+
bar.textContent = `Load ${CONVERSATION_TURN_PAGE_SIZE} older turns`;
|
|
1668
2240
|
bar.onclick = () => _loadOlder(sessionId, convView);
|
|
1669
2241
|
convView.insertBefore(bar, convView.firstChild);
|
|
2242
|
+
// Auto-load when the bar scrolls into view (button stays as the fallback
|
|
2243
|
+
// affordance). bar.disabled already provides re-entrancy guarding.
|
|
2244
|
+
if (typeof IntersectionObserver === 'function') {
|
|
2245
|
+
const observer = new IntersectionObserver((entries) => {
|
|
2246
|
+
for (const entry of entries) {
|
|
2247
|
+
if (!entry.isIntersecting) continue;
|
|
2248
|
+
if (convView.dataset.hasMore !== '1' || bar.disabled || bar.style.display === 'none') continue;
|
|
2249
|
+
_loadOlder(sessionId, convView);
|
|
2250
|
+
}
|
|
2251
|
+
}, { root: convView, threshold: 0 });
|
|
2252
|
+
observer.observe(bar);
|
|
2253
|
+
}
|
|
1670
2254
|
return bar;
|
|
1671
2255
|
}
|
|
1672
2256
|
|
|
2257
|
+
// Soft DOM cap: a very long live session accumulates turns without bound.
|
|
2258
|
+
// Above the cap, prune the OLDEST rendered turns and re-arm the load-older
|
|
2259
|
+
// bar at the recomputed offset — scroll-up refetches them (turn pages are
|
|
2260
|
+
// O(limit) on the server), which replaces height-placeholder bookkeeping.
|
|
2261
|
+
// Only prunes while the user is following the bottom, so it can never yank
|
|
2262
|
+
// the viewport from under someone reading history.
|
|
2263
|
+
const MAX_RENDERED_TURNS = 400;
|
|
2264
|
+
function _pruneConversationTurns(convView) {
|
|
2265
|
+
if (!convView || typeof convView.querySelectorAll !== 'function') return;
|
|
2266
|
+
if (!_isPromptTurnContainer(convView)) return;
|
|
2267
|
+
if (!_conversationScrollNearBottom(convView)) return;
|
|
2268
|
+
const turns = convView.querySelectorAll(':scope > .prompt-turn:not(.setup-turn)');
|
|
2269
|
+
const excess = turns.length - MAX_RENDERED_TURNS;
|
|
2270
|
+
if (excess <= 0) return;
|
|
2271
|
+
for (let i = 0; i < excess; i++) turns[i].remove();
|
|
2272
|
+
const nextOffset = Math.max(0, (parseInt(convView.dataset.nextOffset || '0', 10) || 0) - excess);
|
|
2273
|
+
convView.dataset.nextOffset = String(nextOffset);
|
|
2274
|
+
convView.dataset.hasMore = '1';
|
|
2275
|
+
const bar = convView.querySelector(':scope > .conversation-load-older');
|
|
2276
|
+
if (bar) {
|
|
2277
|
+
bar.textContent = 'Load older turns';
|
|
2278
|
+
bar.style.display = 'block';
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
|
|
1673
2282
|
function _updateLoadOlderBar(convView, page) {
|
|
1674
2283
|
const bar = convView.querySelector(':scope > .conversation-load-older');
|
|
1675
2284
|
if (!bar) return;
|
|
1676
2285
|
if (page && page.has_more) {
|
|
1677
2286
|
const remaining = Math.max(0, page.total - page.next_offset);
|
|
1678
|
-
|
|
2287
|
+
const unit = page.page_kind === 'turns' ? 'turns' : 'messages';
|
|
2288
|
+
bar.textContent = `Load older ${unit} (${remaining} remaining)`;
|
|
1679
2289
|
bar.style.display = 'block';
|
|
1680
2290
|
} else {
|
|
1681
2291
|
bar.style.display = 'none';
|
|
@@ -1711,18 +2321,22 @@ async function _loadOlder(sessionId, convView) {
|
|
|
1711
2321
|
while (pageHost.firstChild) frag.appendChild(pageHost.firstChild);
|
|
1712
2322
|
// Prepend below the bar so the bar stays at the very top.
|
|
1713
2323
|
convView.insertBefore(frag, bar.nextSibling);
|
|
2324
|
+
_linkConversationDocumentReferences(convView, convView);
|
|
1714
2325
|
const newScrollHeight = convView.scrollHeight;
|
|
1715
2326
|
convView.scrollTop = prevScrollTop + (newScrollHeight - prevScrollHeight);
|
|
1716
2327
|
// Merge new parentUuids into the dedup set so live events for primed
|
|
1717
2328
|
// turns still find and replace their older-page counterparts.
|
|
1718
2329
|
const seen = _streamState._parentUuidSeen.get(sessionId) || new Set();
|
|
1719
|
-
for (const e of events)
|
|
2330
|
+
for (const e of events) {
|
|
2331
|
+
if (e.data?.parentUuid) seen.add(e.data.parentUuid);
|
|
2332
|
+
_markConversationEventSeen(sessionId, e);
|
|
2333
|
+
}
|
|
1720
2334
|
_streamState._parentUuidSeen.set(sessionId, seen);
|
|
1721
2335
|
_updateLoadOlderBar(convView, page);
|
|
1722
2336
|
_refreshLatestPromptTurnStatus(sessionId, convView);
|
|
1723
2337
|
} catch (e) {
|
|
1724
2338
|
console.error('[stream-view] load older failed:', e && e.message);
|
|
1725
|
-
bar.textContent = `Load ${
|
|
2339
|
+
bar.textContent = `Load ${CONVERSATION_TURN_PAGE_SIZE} older turns`;
|
|
1726
2340
|
} finally {
|
|
1727
2341
|
bar.disabled = false;
|
|
1728
2342
|
}
|
|
@@ -1742,6 +2356,7 @@ async function _primeConversationView(sessionId, convView, opts) {
|
|
|
1742
2356
|
const token = _nextPrimeToken(sessionId);
|
|
1743
2357
|
_streamState._primed.delete(sessionId);
|
|
1744
2358
|
_streamState._primedTarget.delete(sessionId);
|
|
2359
|
+
_streamState._messageFingerprintSeen.delete(sessionId);
|
|
1745
2360
|
_setConversationBusy(convView, true);
|
|
1746
2361
|
_renderConversationLoading(convView);
|
|
1747
2362
|
|
|
@@ -1836,6 +2451,7 @@ function _resetPrimingState(sessionId) {
|
|
|
1836
2451
|
_streamState._primingTarget.delete(sessionId);
|
|
1837
2452
|
_streamState._pendingEvents.delete(sessionId);
|
|
1838
2453
|
_streamState._parentUuidSeen.delete(sessionId);
|
|
2454
|
+
_streamState._messageFingerprintSeen.delete(sessionId);
|
|
1839
2455
|
}
|
|
1840
2456
|
|
|
1841
2457
|
// --- Tooltip hover binding ---
|
|
@@ -1963,6 +2579,8 @@ if (typeof module !== 'undefined' && module.exports) {
|
|
|
1963
2579
|
renderConversationEvent,
|
|
1964
2580
|
populateConversationView,
|
|
1965
2581
|
_mergeConsecutiveToolGroups,
|
|
2582
|
+
_captureConversationScrollAnchor,
|
|
2583
|
+
_restoreConversationScrollAnchor,
|
|
1966
2584
|
_streamState,
|
|
1967
2585
|
getPreferredSessionView,
|
|
1968
2586
|
setPreferredSessionView,
|