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
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Public API on window.MR:
|
|
14
14
|
* - formatMsgText(text) — markdown + tool-badge HTML
|
|
15
|
-
* - classifyMessage(m, stripped, isToolOnly) — 'key'|'normal'|'self-thought'|'summary'
|
|
15
|
+
* - classifyMessage(m, stripped, isToolOnly) — 'key'|'normal'|'self-thought'|'summary'|'walle-error'
|
|
16
16
|
* - renderReviewMsg(m, i, msgType?) — Review row HTML string
|
|
17
17
|
* - renderSelfThoughtMsg(m, i, stripped) — single-self-thought collapsed group HTML
|
|
18
18
|
* - renderSelfThoughtItem(m, i, stripped) — inner item inside a multi-step thought-group
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* - renderConversationEvent(evt) — Conversation row DOM element
|
|
25
25
|
* - createConversationTurn(evt, opts) — Conversation prompt-turn DOM
|
|
26
26
|
* - refreshConversationActivityGroup(group) — recompute grouped tool UI
|
|
27
|
+
* - refreshConversationSystemGroup(group) — recompute grouped system UI
|
|
27
28
|
*
|
|
28
29
|
* Depends on global escHtml (defined in index.html). Since the renderer
|
|
29
30
|
* functions are only invoked AFTER the page's body scripts run, escHtml
|
|
@@ -39,6 +40,219 @@
|
|
|
39
40
|
return '/api/images/file/' + encodeURIComponent(match[1]);
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
const _IMAGE_REF_EXT_RE = /\.(?:png|jpe?g|gif|webp|svg|heic|heif|bmp|tiff?)(?:[?#].*)?$/i;
|
|
44
|
+
const _IMAGE_ATTACHMENT_SAFE_ATTRS = ['href', 'src', 'alt', 'title', 'target', 'rel', 'loading', 'aria-label'];
|
|
45
|
+
const _SAFE_INLINE_IMAGE_DATA_RE = /^data:image\/(?:png|jpe?g|gif|webp|bmp);base64,[a-z0-9+/=\s]+$/i;
|
|
46
|
+
|
|
47
|
+
function _stripWrappingQuotes(value) {
|
|
48
|
+
let text = String(value || '').trim();
|
|
49
|
+
if (!text) return '';
|
|
50
|
+
if (text[0] === '<' && text[text.length - 1] === '>') text = text.slice(1, -1).trim();
|
|
51
|
+
const first = text[0];
|
|
52
|
+
const last = text[text.length - 1];
|
|
53
|
+
if ((first === '"' && last === '"') || (first === "'" && last === "'")) text = text.slice(1, -1);
|
|
54
|
+
return text.replace(/\\"/g, '"').replace(/\\'/g, "'").trim();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function _basenameFromReference(value) {
|
|
58
|
+
let text = _stripWrappingQuotes(value);
|
|
59
|
+
if (!text) return '';
|
|
60
|
+
try {
|
|
61
|
+
if (/^https?:\/\//i.test(text) || /^file:\/\//i.test(text)) text = new URL(text).pathname || text;
|
|
62
|
+
} catch {}
|
|
63
|
+
try { text = decodeURIComponent(text); } catch {}
|
|
64
|
+
return String(text || '').replace(/[?#].*$/g, '').split(/[\\/]/).filter(Boolean).pop() || '';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function _imageApiUrlForReference(value) {
|
|
68
|
+
const raw = _stripWrappingQuotes(value);
|
|
69
|
+
if (!raw) return '';
|
|
70
|
+
if (/^data:image\//i.test(raw)) return _SAFE_INLINE_IMAGE_DATA_RE.test(raw) ? raw : '';
|
|
71
|
+
if (/^\/api\/images\/file\//i.test(raw)) return raw;
|
|
72
|
+
try {
|
|
73
|
+
if (/^https?:\/\//i.test(raw)) {
|
|
74
|
+
const u = new URL(raw);
|
|
75
|
+
if (/\/api\/images\/file\//i.test(u.pathname)) return u.pathname + u.search;
|
|
76
|
+
}
|
|
77
|
+
if (/^file:\/\//i.test(raw)) {
|
|
78
|
+
const u = new URL(raw);
|
|
79
|
+
const api = _imageApiUrlForLocalPath(u.pathname || '');
|
|
80
|
+
if (api) return api;
|
|
81
|
+
}
|
|
82
|
+
} catch {}
|
|
83
|
+
const api = _imageApiUrlForLocalPath(raw);
|
|
84
|
+
if (api) return api;
|
|
85
|
+
const basename = _basenameFromReference(raw);
|
|
86
|
+
if (basename && _IMAGE_REF_EXT_RE.test(basename)) return '/api/images/file/' + encodeURIComponent(basename);
|
|
87
|
+
return '';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function _imageReferenceKey(value) {
|
|
91
|
+
const api = _imageApiUrlForReference(value);
|
|
92
|
+
if (api) return api.replace(/[?#].*$/g, '').toLowerCase();
|
|
93
|
+
return _stripWrappingQuotes(value).replace(/[?#].*$/g, '').toLowerCase();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function _labelNumber(label) {
|
|
97
|
+
const m = String(label || '').match(/\[Image #(\d+)\]/i);
|
|
98
|
+
return m ? Number(m[1]) : 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function _nextImageLabel(existing) {
|
|
102
|
+
let max = 0;
|
|
103
|
+
for (const item of existing || []) max = Math.max(max, _labelNumber(item && item.label));
|
|
104
|
+
return '[Image #' + (max + 1) + ']';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function _splitImageReferenceList(value) {
|
|
108
|
+
const text = String(value || '');
|
|
109
|
+
const refs = [];
|
|
110
|
+
const quoted = /"((?:\\.|[^"])*)"|'((?:\\.|[^'])*)'/g;
|
|
111
|
+
let match;
|
|
112
|
+
while ((match = quoted.exec(text))) refs.push(match[1] || match[2] || '');
|
|
113
|
+
if (refs.length) return refs;
|
|
114
|
+
return text.split(',').map(part => part.trim()).filter(Boolean);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function _attachmentReferenceValue(item) {
|
|
118
|
+
if (typeof item === 'string') return item;
|
|
119
|
+
if (!item || typeof item !== 'object') return '';
|
|
120
|
+
return item.url || item.previewUrl || item.preview_url || item.data || item.path || item.file_path || item.filename || item.name || '';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function _attachmentLooksImage(item) {
|
|
124
|
+
if (typeof item === 'string') return !!_imageApiUrlForReference(item);
|
|
125
|
+
if (!item || typeof item !== 'object') return false;
|
|
126
|
+
const kind = String(item.kind || item.type || item.mediaType || item.mimeType || item.mime_type || '').toLowerCase();
|
|
127
|
+
if (kind.includes('image')) return true;
|
|
128
|
+
return _IMAGE_REF_EXT_RE.test(String(_attachmentReferenceValue(item) || ''));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function _messagePlainText(message) {
|
|
132
|
+
const direct = message.text ?? message.message ?? message.content ?? '';
|
|
133
|
+
if (typeof direct === 'string') return direct;
|
|
134
|
+
if (Array.isArray(direct)) {
|
|
135
|
+
return direct.map((part) => {
|
|
136
|
+
if (typeof part === 'string') return part;
|
|
137
|
+
if (!part || typeof part !== 'object') return '';
|
|
138
|
+
return part.text || part.content || part.message || '';
|
|
139
|
+
}).filter(Boolean).join('\n');
|
|
140
|
+
}
|
|
141
|
+
if (direct && typeof direct === 'object') return direct.text || direct.message || '';
|
|
142
|
+
return '';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function _addImageAttachment(out, seen, value, label, source) {
|
|
146
|
+
const ref = _stripWrappingQuotes(value);
|
|
147
|
+
const src = _imageApiUrlForReference(ref);
|
|
148
|
+
if (!src) return null;
|
|
149
|
+
const key = _imageReferenceKey(ref) || src;
|
|
150
|
+
if (seen.has(key)) return null;
|
|
151
|
+
seen.add(key);
|
|
152
|
+
const item = {
|
|
153
|
+
label: label || _nextImageLabel(out),
|
|
154
|
+
src,
|
|
155
|
+
href: src,
|
|
156
|
+
filename: _basenameFromReference(ref) || _basenameFromReference(src),
|
|
157
|
+
source: source || 'text',
|
|
158
|
+
};
|
|
159
|
+
out.push(item);
|
|
160
|
+
return item;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
MR.extractImageAttachments = function (messageOrText) {
|
|
164
|
+
const message = messageOrText && typeof messageOrText === 'object' ? messageOrText : { text: messageOrText };
|
|
165
|
+
const text = _messagePlainText(message);
|
|
166
|
+
const out = [];
|
|
167
|
+
const seen = new Set();
|
|
168
|
+
|
|
169
|
+
const structuredSources = [
|
|
170
|
+
message.attachments,
|
|
171
|
+
message.images,
|
|
172
|
+
message.imageRefs,
|
|
173
|
+
message.image_refs,
|
|
174
|
+
message.data && message.data.attachments,
|
|
175
|
+
message.data && message.data.images,
|
|
176
|
+
message.data && message.data.imageRefs,
|
|
177
|
+
message.data && message.data.image_refs,
|
|
178
|
+
];
|
|
179
|
+
for (const rawList of structuredSources) {
|
|
180
|
+
let list = rawList;
|
|
181
|
+
if (typeof list === 'string') {
|
|
182
|
+
try { list = JSON.parse(list); } catch { list = []; }
|
|
183
|
+
}
|
|
184
|
+
if (!Array.isArray(list)) continue;
|
|
185
|
+
for (let idx = 0; idx < list.length; idx += 1) {
|
|
186
|
+
const item = list[idx];
|
|
187
|
+
if (!_attachmentLooksImage(item)) continue;
|
|
188
|
+
_addImageAttachment(out, seen, _attachmentReferenceValue(item), item.label || item.token || '', 'structured');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const contentBlocks = Array.isArray(message.contentBlocks)
|
|
193
|
+
? message.contentBlocks
|
|
194
|
+
: (Array.isArray(message.data && message.data.contentBlocks) ? message.data.contentBlocks : []);
|
|
195
|
+
for (let idx = 0; idx < contentBlocks.length; idx += 1) {
|
|
196
|
+
const block = contentBlocks[idx] || {};
|
|
197
|
+
if (!_attachmentLooksImage(block) && !/image/i.test(String(block.type || ''))) continue;
|
|
198
|
+
_addImageAttachment(out, seen, _attachmentReferenceValue(block), block.label || '', 'content-block');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
text.replace(/\[(Image #\d+)\s*:\s*("((?:\\.|[^"])*)"|'((?:\\.|[^'])*)'|([^\]]+))\]/gi, (_full, label, _wrapped, dq, sq, bare) => {
|
|
202
|
+
_addImageAttachment(out, seen, dq || sq || bare || '', '[' + label.replace(/^\[|\]$/g, '') + ']', 'text-token');
|
|
203
|
+
return _full;
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
text.replace(/\[(Attached images?)\s*:\s*([^\]]+)\]/gi, (_full, label, body) => {
|
|
207
|
+
const refs = _splitImageReferenceList(body);
|
|
208
|
+
for (const ref of refs) _addImageAttachment(out, seen, ref, '', label.toLowerCase());
|
|
209
|
+
return _full;
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return out;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
MR.stripImageAttachmentMetadata = function (text) {
|
|
216
|
+
let out = String(text || '');
|
|
217
|
+
out = out.replace(/\[(Image #\d+)\s*:\s*("((?:\\.|[^"])*)"|'((?:\\.|[^'])*)'|([^\]]+))\]/gi, (_full, label) => '[' + label.replace(/^\[|\]$/g, '') + ']');
|
|
218
|
+
out = out.replace(/[ \t]*\[(Attached images?)\s*:\s*[^\]]+\][ \t]*/gi, ' ');
|
|
219
|
+
out = out.replace(/[ \t]*\[Image:\s*source:\s*(?:"[^"]+"|'[^']+'|[^\]]+)\][ \t]*/gi, ' ');
|
|
220
|
+
out = out.replace(/[ \t]*\[Image:\s*original\s+\d+(?:\.\d+)?x\d+(?:\.\d+)?,\s*displayed at\s+\d+(?:\.\d+)?x\d+(?:\.\d+)?\.\s*Multiply coordinates by\s+[0-9.]+\s+to map to original image\.?\][ \t]*/gi, ' ');
|
|
221
|
+
return out.replace(/[ \t]{2,}/g, ' ').replace(/[ \t]+(\r?\n)/g, '$1').replace(/(\r?\n)[ \t]+/g, '$1').trim();
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
MR.renderImageAttachmentsHtml = function (messageOrAttachments) {
|
|
225
|
+
const attachments = Array.isArray(messageOrAttachments)
|
|
226
|
+
? messageOrAttachments
|
|
227
|
+
: MR.extractImageAttachments(messageOrAttachments);
|
|
228
|
+
if (!attachments.length) return '';
|
|
229
|
+
return '<div class="msg-attachment-strip" aria-label="Image attachments">'
|
|
230
|
+
+ attachments.map((att, idx) => {
|
|
231
|
+
const label = att.label || '[Image #' + (idx + 1) + ']';
|
|
232
|
+
const name = att.filename || label;
|
|
233
|
+
return '<a class="msg-image-attachment" href="' + escHtml(att.href || att.src) + '" target="_blank" rel="noopener noreferrer" title="' + escHtml(name) + '">'
|
|
234
|
+
+ '<img src="' + escHtml(att.src) + '" alt="' + escHtml(label + (name && name !== label ? ' ' + name : '')) + '" loading="lazy">'
|
|
235
|
+
+ '<span>' + escHtml(label) + '</span>'
|
|
236
|
+
+ '</a>';
|
|
237
|
+
}).join('')
|
|
238
|
+
+ '</div>';
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
function _messageTextForDisplay(text) {
|
|
242
|
+
return MR.stripImageAttachmentMetadata(text);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function _appendReviewAttachmentsHtml(message) {
|
|
246
|
+
const html = MR.renderImageAttachmentsHtml(message);
|
|
247
|
+
return html ? html : '';
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function _formatDisplayTextHtml(text, emptyHtml) {
|
|
251
|
+
const displayText = _messageTextForDisplay(text);
|
|
252
|
+
if (displayText) return MR.formatMsgText(displayText);
|
|
253
|
+
return emptyHtml || '';
|
|
254
|
+
}
|
|
255
|
+
|
|
42
256
|
function _normalizeMarkdownImageSources(text) {
|
|
43
257
|
return String(text || '').replace(/!\[([^\]]*)\]\(([^)\s]+|<[^>]+>)\)/g, (all, alt, src) => {
|
|
44
258
|
const apiUrl = _imageApiUrlForLocalPath(src);
|
|
@@ -609,12 +823,521 @@
|
|
|
609
823
|
return "if(event.key==='Enter'||event.key===' '){event.preventDefault();this.classList.toggle('expanded')}";
|
|
610
824
|
}
|
|
611
825
|
|
|
826
|
+
// ------------------------------------------------------------------
|
|
827
|
+
// Structured-capture metadata (parse-time capture of tool calls/results,
|
|
828
|
+
// reasoning, patches, compaction — see lib/structured-capture.js).
|
|
829
|
+
// ------------------------------------------------------------------
|
|
830
|
+
|
|
831
|
+
// Normalized reader: messages carry `metadata`, conversation events carry
|
|
832
|
+
// `data.metadata`. Returns the metadata object only when it is a structured
|
|
833
|
+
// v1+ capture (has kind + version) — legacy rows return null and take the
|
|
834
|
+
// text pipeline unchanged.
|
|
835
|
+
MR.messageMeta = function (mOrEvt) {
|
|
836
|
+
if (!mOrEvt || typeof mOrEvt !== 'object') return null;
|
|
837
|
+
const candidates = [mOrEvt.metadata, mOrEvt.data && mOrEvt.data.metadata];
|
|
838
|
+
for (const meta of candidates) {
|
|
839
|
+
if (meta && typeof meta === 'object' && typeof meta.kind === 'string' && Number(meta.v) >= 1) return meta;
|
|
840
|
+
}
|
|
841
|
+
return null;
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
// Kinds the v1 schema emits. A kind outside this set is format drift —
|
|
845
|
+
// rendered as an inspectable raw-JSON card, never silently dropped.
|
|
846
|
+
const _KNOWN_META_KINDS = new Set([
|
|
847
|
+
'reasoning', 'tool_call', 'tool_result', 'shell', 'patch',
|
|
848
|
+
'web_search', 'compact_boundary', 'compact_summary',
|
|
849
|
+
]);
|
|
850
|
+
|
|
851
|
+
// Build the HTML-string card for a structured message, or '' when the kind
|
|
852
|
+
// has no card yet (it then falls through to the legacy text pipeline).
|
|
853
|
+
// BOTH render paths use this — renderReviewMsg returns the string and
|
|
854
|
+
// renderConversationEvent hydrates the same string via _cardElFromHtml —
|
|
855
|
+
// so Review/Conversation parity holds by construction.
|
|
856
|
+
MR.renderMetaCardHtml = function (m, i) {
|
|
857
|
+
const meta = MR.messageMeta(m);
|
|
858
|
+
if (!meta) return '';
|
|
859
|
+
if (meta.kind === 'unknown' || !_KNOWN_META_KINDS.has(meta.kind)) {
|
|
860
|
+
return MR.renderUnknownKindHtml(m, i, meta);
|
|
861
|
+
}
|
|
862
|
+
if (_TOOL_CARD_KINDS.has(meta.kind)) return MR.renderToolCardHtml(m, null, i);
|
|
863
|
+
if (meta.kind === 'tool_result') return MR.renderToolCardHtml(null, m, i);
|
|
864
|
+
if (meta.kind === 'reasoning') return MR.renderThinkingHtml(m, i, meta);
|
|
865
|
+
if (meta.kind === 'compact_boundary') return MR.renderCompactBoundaryHtml(m, i, meta);
|
|
866
|
+
if (meta.kind === 'compact_summary') return MR.renderCompactSummaryHtml(m, i, meta);
|
|
867
|
+
return '';
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
// ------------------------------------------------------------------
|
|
871
|
+
// Reasoning (thinking) pills + compaction divider/summary.
|
|
872
|
+
// ------------------------------------------------------------------
|
|
873
|
+
function _fmtTokens(n) {
|
|
874
|
+
const value = Number(n);
|
|
875
|
+
if (!Number.isFinite(value) || value <= 0) return '';
|
|
876
|
+
if (value >= 1_000_000) return (value / 1_000_000).toFixed(1).replace(/\.0$/, '') + 'M';
|
|
877
|
+
if (value >= 1000) return Math.round(value / 1000) + 'k';
|
|
878
|
+
return String(value);
|
|
879
|
+
}
|
|
880
|
+
MR._fmtTokens = _fmtTokens;
|
|
881
|
+
|
|
882
|
+
// Per-turn token accounting from assistant message.usage metadata (captured
|
|
883
|
+
// at parse time). The chip counts what the turn CONSUMED: input + output +
|
|
884
|
+
// cache writes (cache reads shown in the breakdown title only).
|
|
885
|
+
function _usageFromMessage(m) {
|
|
886
|
+
const usage = m && m.metadata && typeof m.metadata === 'object' ? m.metadata.usage : null;
|
|
887
|
+
return usage && typeof usage === 'object' ? usage : null;
|
|
888
|
+
}
|
|
889
|
+
MR.usageTokensTotal = function (usage) {
|
|
890
|
+
if (!usage || typeof usage !== 'object') return 0;
|
|
891
|
+
return (Number(usage.input_tokens) || 0)
|
|
892
|
+
+ (Number(usage.output_tokens) || 0)
|
|
893
|
+
+ (Number(usage.cache_creation_input_tokens) || 0);
|
|
894
|
+
};
|
|
895
|
+
MR.usageTitle = function (usage) {
|
|
896
|
+
if (!usage || typeof usage !== 'object') return '';
|
|
897
|
+
const parts = [];
|
|
898
|
+
if (usage.input_tokens) parts.push('in ' + _fmtTokens(usage.input_tokens));
|
|
899
|
+
if (usage.output_tokens) parts.push('out ' + _fmtTokens(usage.output_tokens));
|
|
900
|
+
if (usage.cache_creation_input_tokens) parts.push('cache write ' + _fmtTokens(usage.cache_creation_input_tokens));
|
|
901
|
+
if (usage.cache_read_input_tokens) parts.push('cache read ' + _fmtTokens(usage.cache_read_input_tokens));
|
|
902
|
+
return parts.join(' · ');
|
|
903
|
+
};
|
|
904
|
+
MR._usageFromMessage = _usageFromMessage;
|
|
905
|
+
|
|
906
|
+
MR.renderThinkingHtml = function (m, i, meta) {
|
|
907
|
+
const time = m.timestamp ? new Date(m.timestamp).toLocaleString() : '';
|
|
908
|
+
if (meta.encrypted) {
|
|
909
|
+
return '<div class="review-msg thinking-block encrypted" data-msg-idx="' + escHtml(String(i)) + '">'
|
|
910
|
+
+ '<div class="msg-header">'
|
|
911
|
+
+ '<span class="msg-role">Thinking</span>'
|
|
912
|
+
+ '<span class="thought-preview">(encrypted by the provider)</span>'
|
|
913
|
+
+ '<span class="msg-time">' + escHtml(time) + '</span>'
|
|
914
|
+
+ '</div>'
|
|
915
|
+
+ '</div>';
|
|
916
|
+
}
|
|
917
|
+
const body = _textAfterMarkerLine(m.text);
|
|
918
|
+
const firstLine = body.split('\n').find(l => l.trim()) || '';
|
|
919
|
+
return '<div class="review-msg thinking-block"' + MR.rawJsonAttrHtml(m, meta)
|
|
920
|
+
+ ' onclick="if(!MR.hasTextSelectionInside||!MR.hasTextSelectionInside(this)){this.classList.toggle(\'expanded\')}"'
|
|
921
|
+
+ ' onkeydown="' + _toggleOnKeydownHandler() + '" role="button" tabindex="0" data-msg-idx="' + escHtml(String(i)) + '">'
|
|
922
|
+
+ '<div class="msg-header">'
|
|
923
|
+
+ '<span class="msg-role"><span class="thought-chevron">▶</span>Thinking</span>'
|
|
924
|
+
+ '<span class="thought-preview">' + escHtml(_previewFromText(firstLine, 90)) + '</span>'
|
|
925
|
+
+ MR.rawJsonButtonHtml()
|
|
926
|
+
+ '<span class="msg-time">' + escHtml(time) + '</span>'
|
|
927
|
+
+ '</div>'
|
|
928
|
+
+ '<div class="msg-text" onclick="event.stopPropagation()">' + MR.formatMsgText(body) + '</div>'
|
|
929
|
+
+ (meta.truncated ? '<div class="msg-truncation-note">(truncated)</div>' : '')
|
|
930
|
+
+ '</div>';
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
MR.renderCompactBoundaryHtml = function (m, i, meta) {
|
|
934
|
+
const parts = [];
|
|
935
|
+
if (meta.trigger) parts.push(meta.trigger);
|
|
936
|
+
const tokens = _fmtTokens(meta.preTokens);
|
|
937
|
+
if (tokens) parts.push(tokens + ' tokens');
|
|
938
|
+
const label = 'Context compacted' + (parts.length ? ' — ' + parts.join(', ') : '');
|
|
939
|
+
return '<div class="compact-divider" role="separator" data-msg-idx="' + escHtml(String(i)) + '">'
|
|
940
|
+
+ '<span class="compact-divider-line"></span>'
|
|
941
|
+
+ '<span class="compact-divider-label">' + escHtml(label) + '</span>'
|
|
942
|
+
+ '<span class="compact-divider-line"></span>'
|
|
943
|
+
+ '</div>';
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
MR.renderCompactSummaryHtml = function (m, i, meta) {
|
|
947
|
+
const time = m.timestamp ? new Date(m.timestamp).toLocaleString() : '';
|
|
948
|
+
const preview = _previewFromText(m.text, 90);
|
|
949
|
+
return '<div class="review-msg skill-body compact-summary"' + MR.rawJsonAttrHtml(m, meta)
|
|
950
|
+
+ ' onclick="if(!MR.hasTextSelectionInside||!MR.hasTextSelectionInside(this)){this.classList.toggle(\'expanded\')}"'
|
|
951
|
+
+ ' onkeydown="' + _toggleOnKeydownHandler() + '" role="button" tabindex="0" data-msg-idx="' + escHtml(String(i)) + '">'
|
|
952
|
+
+ '<div class="msg-header">'
|
|
953
|
+
+ '<span class="msg-role"><span class="thought-chevron">▶</span>Compacted context</span>'
|
|
954
|
+
+ '<span class="thought-preview">' + escHtml(preview) + '</span>'
|
|
955
|
+
+ MR.rawJsonButtonHtml()
|
|
956
|
+
+ '<span class="msg-time">' + escHtml(time) + '</span>'
|
|
957
|
+
+ '</div>'
|
|
958
|
+
+ '<div class="msg-text" onclick="event.stopPropagation()">' + MR.formatMsgText(m.text) + '</div>'
|
|
959
|
+
+ (meta.truncated ? '<div class="msg-truncation-note">(truncated)</div>' : '')
|
|
960
|
+
+ '</div>';
|
|
961
|
+
};
|
|
962
|
+
|
|
963
|
+
// ------------------------------------------------------------------
|
|
964
|
+
// Tool cards: one collapsible card per tool/shell/patch/web-search call,
|
|
965
|
+
// its result filled in (paired by metadata.callId at render time on the
|
|
966
|
+
// Review path, live via fillToolCardResult on the streaming path).
|
|
967
|
+
// ------------------------------------------------------------------
|
|
968
|
+
const _TOOL_CARD_KINDS = new Set(['tool_call', 'shell', 'patch', 'web_search']);
|
|
969
|
+
|
|
970
|
+
MR.prettifyToolName = function (name) {
|
|
971
|
+
const raw = String(name || '').trim();
|
|
972
|
+
const mcp = raw.match(/^mcp__([^_]+(?:_[^_]+)*?)__(.+)$/);
|
|
973
|
+
if (mcp) return mcp[1] + ': ' + mcp[2];
|
|
974
|
+
return raw || 'tool';
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
function _toolCardTitle(meta) {
|
|
978
|
+
switch (meta.kind) {
|
|
979
|
+
case 'shell': return 'Shell';
|
|
980
|
+
case 'patch': return 'Patch';
|
|
981
|
+
case 'web_search': return 'Web search';
|
|
982
|
+
default: return MR.prettifyToolName(meta.tool);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function _toolCardPreview(meta) {
|
|
987
|
+
if (meta.kind === 'shell') return _previewFromText(meta.command || '', 90);
|
|
988
|
+
if (meta.kind === 'web_search') return _previewFromText(meta.query || '', 90);
|
|
989
|
+
if (meta.kind === 'patch') return _previewFromText((meta.files || []).join(', ') || meta.filePath || '', 90);
|
|
990
|
+
let args = String(meta.argsPreview || '');
|
|
991
|
+
try {
|
|
992
|
+
const parsed = JSON.parse(args);
|
|
993
|
+
if (parsed && typeof parsed === 'object') {
|
|
994
|
+
const salient = parsed.command || parsed.file_path || parsed.filePath || parsed.pattern || parsed.path || parsed.url || parsed.query;
|
|
995
|
+
if (salient != null) args = Array.isArray(salient) ? salient.join(' ') : String(salient);
|
|
996
|
+
}
|
|
997
|
+
} catch { /* keep raw */ }
|
|
998
|
+
return _previewFromText(args, 90);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// Patch/tool text fallbacks carry a marker first line ("[Patch] a.js",
|
|
1002
|
+
// "[Tool result: Bash]") — the body shows the content after it.
|
|
1003
|
+
function _textAfterMarkerLine(text) {
|
|
1004
|
+
const s = String(text || '');
|
|
1005
|
+
if (!/^\[[^\]]*\]/.test(s)) return s;
|
|
1006
|
+
const nl = s.indexOf('\n');
|
|
1007
|
+
return nl === -1 ? '' : s.slice(nl + 1);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
function _toolCardInputText(meta, m) {
|
|
1011
|
+
if (meta.kind === 'shell') return meta.command || '';
|
|
1012
|
+
if (meta.kind === 'web_search') return meta.query || '';
|
|
1013
|
+
if (meta.kind === 'patch') return _textAfterMarkerLine(m && m.text);
|
|
1014
|
+
let args = String(meta.argsPreview || '');
|
|
1015
|
+
if (!meta.truncated) {
|
|
1016
|
+
try { args = JSON.stringify(JSON.parse(args), null, 2); } catch { /* keep raw */ }
|
|
1017
|
+
}
|
|
1018
|
+
return args;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// Patch input body: real diff when the renderer is available (step: diff
|
|
1022
|
+
// renderer), <pre> fallback otherwise.
|
|
1023
|
+
function _toolCardInputHtml(meta, m) {
|
|
1024
|
+
if (meta.kind === 'patch' && typeof MR.renderPatchDiffHtml === 'function') {
|
|
1025
|
+
const diff = MR.renderPatchDiffHtml(meta, m);
|
|
1026
|
+
if (diff) return diff;
|
|
1027
|
+
}
|
|
1028
|
+
const input = _toolCardInputText(meta, m);
|
|
1029
|
+
if (!input) return '';
|
|
1030
|
+
return '<div class="tool-card-section tool-card-input">'
|
|
1031
|
+
+ '<div class="tool-card-section-label">Input</div>'
|
|
1032
|
+
+ '<pre><code>' + escHtml(input) + '</code></pre>'
|
|
1033
|
+
+ (meta.truncated ? '<div class="msg-truncation-note">(truncated)</div>' : '')
|
|
1034
|
+
+ '</div>';
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
function _toolResultStatus(resultMeta) {
|
|
1038
|
+
if (!resultMeta) return { status: 'pending', label: '' };
|
|
1039
|
+
const exitCode = Number(resultMeta.exitCode);
|
|
1040
|
+
const isError = resultMeta.isError === true || (Number.isFinite(exitCode) && exitCode !== 0);
|
|
1041
|
+
const parts = [];
|
|
1042
|
+
if (Number.isFinite(exitCode) && exitCode !== 0) parts.push('exit ' + exitCode);
|
|
1043
|
+
else if (isError) parts.push('error');
|
|
1044
|
+
const durationMs = Number(resultMeta.durationMs);
|
|
1045
|
+
if (Number.isFinite(durationMs) && durationMs > 0) {
|
|
1046
|
+
parts.push(durationMs >= 1000 ? (durationMs / 1000).toFixed(1) + 's' : durationMs + 'ms');
|
|
1047
|
+
}
|
|
1048
|
+
return { status: isError ? 'error' : 'ok', label: parts.join(' · ') };
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function _toolResultOutputHtml(resultMsg) {
|
|
1052
|
+
if (!resultMsg) return '';
|
|
1053
|
+
const meta = MR.messageMeta(resultMsg) || {};
|
|
1054
|
+
const output = _textAfterMarkerLine(resultMsg.text);
|
|
1055
|
+
if (typeof MR.renderStructuredPatchHtml === 'function' && Array.isArray(meta.structuredPatch) && meta.structuredPatch.length) {
|
|
1056
|
+
const diff = MR.renderStructuredPatchHtml(meta);
|
|
1057
|
+
if (diff) {
|
|
1058
|
+
return '<div class="tool-card-section tool-card-output">'
|
|
1059
|
+
+ '<div class="tool-card-section-label">Result</div>' + diff
|
|
1060
|
+
+ (meta.truncated ? '<div class="msg-truncation-note">(truncated)</div>' : '')
|
|
1061
|
+
+ '</div>';
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
if (!output) return '';
|
|
1065
|
+
return '<div class="tool-card-section tool-card-output">'
|
|
1066
|
+
+ '<div class="tool-card-section-label">Output</div>'
|
|
1067
|
+
+ '<pre><code>' + escHtml(output) + '</code></pre>'
|
|
1068
|
+
+ (meta.truncated ? '<div class="msg-truncation-note">(truncated)</div>' : '')
|
|
1069
|
+
+ '</div>';
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// call / result are messages ({text, metadata}); either may be null
|
|
1073
|
+
// (call-only = in-flight or interrupted; result-only = orphaned replay).
|
|
1074
|
+
MR.renderToolCardHtml = function (call, result, i) {
|
|
1075
|
+
const callMeta = call ? (MR.messageMeta(call) || {}) : null;
|
|
1076
|
+
const resultMeta = result ? (MR.messageMeta(result) || {}) : null;
|
|
1077
|
+
const meta = callMeta || { kind: 'tool_call', tool: resultMeta && resultMeta.tool };
|
|
1078
|
+
const anchor = call || result || {};
|
|
1079
|
+
const time = anchor.timestamp ? new Date(anchor.timestamp).toLocaleString() : '';
|
|
1080
|
+
const callId = (callMeta && callMeta.callId) || (resultMeta && resultMeta.callId) || '';
|
|
1081
|
+
const { status, label } = _toolResultStatus(resultMeta);
|
|
1082
|
+
const title = _toolCardTitle(meta);
|
|
1083
|
+
const preview = callMeta ? _toolCardPreview(callMeta) : _previewFromText(_textAfterMarkerLine(result && result.text), 90);
|
|
1084
|
+
return '<div class="review-msg tool-card tool-kind-' + escHtml(meta.kind) + '"'
|
|
1085
|
+
+ (callId ? ' data-call-id="' + escHtml(callId) + '"' : '')
|
|
1086
|
+
+ ' data-status="' + status + '"'
|
|
1087
|
+
+ MR.rawJsonAttrHtml(anchor, callMeta || resultMeta || {})
|
|
1088
|
+
+ ' onclick="if(!MR.hasTextSelectionInside||!MR.hasTextSelectionInside(this)){this.classList.toggle(\'expanded\')}"'
|
|
1089
|
+
+ ' onkeydown="' + _toggleOnKeydownHandler() + '" role="button" tabindex="0" data-msg-idx="' + escHtml(String(i)) + '">'
|
|
1090
|
+
+ '<div class="msg-header">'
|
|
1091
|
+
+ '<span class="msg-role"><span class="thought-chevron">▶</span><span class="tool-card-name">' + escHtml(title) + '</span></span>'
|
|
1092
|
+
+ '<span class="thought-preview">' + escHtml(preview) + '</span>'
|
|
1093
|
+
+ '<span class="tool-card-status">' + escHtml(label) + '</span>'
|
|
1094
|
+
+ MR.rawJsonButtonHtml()
|
|
1095
|
+
+ '<span class="msg-time">' + escHtml(time) + '</span>'
|
|
1096
|
+
+ '</div>'
|
|
1097
|
+
+ '<div class="msg-text tool-card-body" onclick="event.stopPropagation()">'
|
|
1098
|
+
+ (callMeta ? _toolCardInputHtml(callMeta, call) : '')
|
|
1099
|
+
+ _toolResultOutputHtml(result)
|
|
1100
|
+
+ '</div>'
|
|
1101
|
+
+ '</div>';
|
|
1102
|
+
};
|
|
1103
|
+
|
|
1104
|
+
// ------------------------------------------------------------------
|
|
1105
|
+
// Diff rendering: apply_patch text (*** Begin Patch grammar) and Claude
|
|
1106
|
+
// structuredPatch hunks → per-file collapsible diff tables. Reuses the
|
|
1107
|
+
// code-review diff CSS (.cr-diff-table / .cr-diff-line add|del — linked on
|
|
1108
|
+
// both pages via reviews.css); the JS is intentionally NOT shared with
|
|
1109
|
+
// reviews.js, which is entangled with CR state (comments, context expand).
|
|
1110
|
+
// ------------------------------------------------------------------
|
|
1111
|
+
|
|
1112
|
+
// "*** Begin Patch" grammar → [{path, op, movedTo, hunks:[{header, lines:[{type,text}]}]}]
|
|
1113
|
+
MR.parseApplyPatchText = function (text) {
|
|
1114
|
+
const raw = String(text || '');
|
|
1115
|
+
const start = raw.indexOf('*** Begin Patch');
|
|
1116
|
+
if (start === -1) return [];
|
|
1117
|
+
const lines = raw.slice(start).split('\n');
|
|
1118
|
+
const files = [];
|
|
1119
|
+
let file = null;
|
|
1120
|
+
let hunk = null;
|
|
1121
|
+
const openHunk = (header) => {
|
|
1122
|
+
if (!file) return;
|
|
1123
|
+
hunk = { header: header || '', lines: [] };
|
|
1124
|
+
file.hunks.push(hunk);
|
|
1125
|
+
};
|
|
1126
|
+
for (const line of lines) {
|
|
1127
|
+
const fileStart = line.match(/^\*\*\* (Update|Add|Delete) File: (.+)$/);
|
|
1128
|
+
if (fileStart) {
|
|
1129
|
+
file = {
|
|
1130
|
+
path: fileStart[2].trim(),
|
|
1131
|
+
op: fileStart[1].toLowerCase(),
|
|
1132
|
+
movedTo: '',
|
|
1133
|
+
hunks: [],
|
|
1134
|
+
};
|
|
1135
|
+
files.push(file);
|
|
1136
|
+
hunk = null;
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
if (!file) continue;
|
|
1140
|
+
const moveTo = line.match(/^\*\*\* Move to: (.+)$/);
|
|
1141
|
+
if (moveTo) { file.movedTo = moveTo[1].trim(); continue; }
|
|
1142
|
+
if (/^\*\*\* End Patch/.test(line) || /^\*\*\* Begin Patch/.test(line)) { file = null; hunk = null; continue; }
|
|
1143
|
+
if (/^@@/.test(line)) { openHunk(line.replace(/^@@\s?/, '').trim()); continue; }
|
|
1144
|
+
const first = line[0];
|
|
1145
|
+
if (first === '+' || first === '-' || first === ' ') {
|
|
1146
|
+
if (!hunk) openHunk('');
|
|
1147
|
+
hunk.lines.push({
|
|
1148
|
+
type: first === '+' ? 'add' : (first === '-' ? 'del' : 'ctx'),
|
|
1149
|
+
text: line.slice(1),
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
return files.filter(f => f.hunks.length || f.op === 'delete');
|
|
1154
|
+
};
|
|
1155
|
+
|
|
1156
|
+
// Claude toolUseResult.structuredPatch (jsdiff hunks) → the same file shape.
|
|
1157
|
+
MR.normalizeStructuredPatch = function (meta) {
|
|
1158
|
+
const hunksIn = meta && Array.isArray(meta.structuredPatch) ? meta.structuredPatch : [];
|
|
1159
|
+
if (!hunksIn.length) return [];
|
|
1160
|
+
const hunks = hunksIn.map(h => ({
|
|
1161
|
+
header: `-${h.oldStart},${h.oldLines} +${h.newStart},${h.newLines}`,
|
|
1162
|
+
oldStart: Number(h.oldStart) || 0,
|
|
1163
|
+
newStart: Number(h.newStart) || 0,
|
|
1164
|
+
lines: (Array.isArray(h.lines) ? h.lines : []).map(l => {
|
|
1165
|
+
const s = String(l);
|
|
1166
|
+
const first = s[0];
|
|
1167
|
+
return {
|
|
1168
|
+
type: first === '+' ? 'add' : (first === '-' ? 'del' : 'ctx'),
|
|
1169
|
+
text: first === '+' || first === '-' || first === ' ' ? s.slice(1) : s,
|
|
1170
|
+
};
|
|
1171
|
+
}),
|
|
1172
|
+
}));
|
|
1173
|
+
return [{ path: String(meta.filePath || ''), op: 'update', movedTo: '', hunks }];
|
|
1174
|
+
};
|
|
1175
|
+
|
|
1176
|
+
MR.renderDiffHtml = function (files) {
|
|
1177
|
+
const list = Array.isArray(files) ? files : [];
|
|
1178
|
+
if (!list.length) return '';
|
|
1179
|
+
return list.map(file => {
|
|
1180
|
+
let adds = 0;
|
|
1181
|
+
let dels = 0;
|
|
1182
|
+
const rows = [];
|
|
1183
|
+
for (const hunk of file.hunks || []) {
|
|
1184
|
+
if (hunk.header) {
|
|
1185
|
+
rows.push('<tr class="cr-diff-hunk-header"><td colspan="4">@@ ' + escHtml(hunk.header) + ' @@</td></tr>');
|
|
1186
|
+
}
|
|
1187
|
+
let oldNum = Number(hunk.oldStart) || 0;
|
|
1188
|
+
let newNum = Number(hunk.newStart) || 0;
|
|
1189
|
+
const hasNums = oldNum > 0 || newNum > 0;
|
|
1190
|
+
for (const line of hunk.lines || []) {
|
|
1191
|
+
const cls = line.type === 'add' ? ' add' : (line.type === 'del' ? ' del' : '');
|
|
1192
|
+
if (line.type === 'add') adds++;
|
|
1193
|
+
if (line.type === 'del') dels++;
|
|
1194
|
+
let oldCell = '';
|
|
1195
|
+
let newCell = '';
|
|
1196
|
+
if (hasNums) {
|
|
1197
|
+
if (line.type !== 'add') oldCell = String(oldNum++);
|
|
1198
|
+
if (line.type !== 'del') newCell = String(newNum++);
|
|
1199
|
+
}
|
|
1200
|
+
rows.push('<tr class="cr-diff-line' + cls + '">'
|
|
1201
|
+
+ '<td class="cr-line-num">' + oldCell + '</td>'
|
|
1202
|
+
+ '<td class="cr-line-num">' + newCell + '</td>'
|
|
1203
|
+
+ '<td class="cr-line-prefix">' + (line.type === 'add' ? '+' : (line.type === 'del' ? '−' : '')) + '</td>'
|
|
1204
|
+
+ '<td class="cr-line-content">' + escHtml(line.text) + '</td>'
|
|
1205
|
+
+ '</tr>');
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
const stat = (adds ? '+' + adds : '') + (adds && dels ? ' ' : '') + (dels ? '−' + dels : '');
|
|
1209
|
+
const pathLabel = file.path + (file.movedTo ? ' → ' + file.movedTo : '');
|
|
1210
|
+
return '<div class="mr-diff-file">'
|
|
1211
|
+
+ '<div class="mr-diff-file-header" onclick="this.parentElement.classList.toggle(\'collapsed\');event.stopPropagation()">'
|
|
1212
|
+
+ '<span class="mr-diff-op ' + escHtml(file.op || 'update') + '">' + escHtml(file.op || 'update') + '</span>'
|
|
1213
|
+
+ '<span class="mr-diff-path">' + escHtml(pathLabel) + '</span>'
|
|
1214
|
+
+ (stat ? '<span class="mr-diff-stat">' + escHtml(stat) + '</span>' : '')
|
|
1215
|
+
+ '</div>'
|
|
1216
|
+
+ (rows.length ? '<table class="cr-diff-table">' + rows.join('') + '</table>' : '')
|
|
1217
|
+
+ '</div>';
|
|
1218
|
+
}).join('');
|
|
1219
|
+
};
|
|
1220
|
+
|
|
1221
|
+
// Tool-card hooks (declared in the tool-card section): patch-kind input and
|
|
1222
|
+
// structuredPatch results render as real diffs.
|
|
1223
|
+
MR.renderPatchDiffHtml = function (meta, m) {
|
|
1224
|
+
const files = MR.parseApplyPatchText(_textAfterMarkerLine(m && m.text));
|
|
1225
|
+
if (!files.length) return '';
|
|
1226
|
+
return '<div class="tool-card-section tool-card-input">'
|
|
1227
|
+
+ '<div class="tool-card-section-label">Patch</div>'
|
|
1228
|
+
+ MR.renderDiffHtml(files)
|
|
1229
|
+
+ (meta && meta.truncated ? '<div class="msg-truncation-note">(truncated)</div>' : '')
|
|
1230
|
+
+ '</div>';
|
|
1231
|
+
};
|
|
1232
|
+
|
|
1233
|
+
MR.renderStructuredPatchHtml = function (meta) {
|
|
1234
|
+
return MR.renderDiffHtml(MR.normalizeStructuredPatch(meta));
|
|
1235
|
+
};
|
|
1236
|
+
|
|
1237
|
+
// Live fill-in: a tool_result event lands for a card already in the DOM
|
|
1238
|
+
// (matched on data-call-id by the streaming append path).
|
|
1239
|
+
MR.fillToolCardResult = function (cardEl, resultMsg) {
|
|
1240
|
+
if (!cardEl || !resultMsg) return false;
|
|
1241
|
+
const resultMeta = MR.messageMeta(resultMsg) || {};
|
|
1242
|
+
const { status, label } = _toolResultStatus(resultMeta);
|
|
1243
|
+
cardEl.setAttribute('data-status', status);
|
|
1244
|
+
const statusEl = cardEl.querySelector('.tool-card-status');
|
|
1245
|
+
if (statusEl) statusEl.textContent = label;
|
|
1246
|
+
const body = cardEl.querySelector('.tool-card-body');
|
|
1247
|
+
if (body) {
|
|
1248
|
+
const existing = body.querySelector('.tool-card-output');
|
|
1249
|
+
if (existing) existing.remove();
|
|
1250
|
+
const outputHtml = _toolResultOutputHtml(resultMsg);
|
|
1251
|
+
if (outputHtml) {
|
|
1252
|
+
const el = MR._cardElFromHtml(outputHtml);
|
|
1253
|
+
if (el) body.appendChild(el);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
return true;
|
|
1257
|
+
};
|
|
1258
|
+
|
|
1259
|
+
MR._cardElFromHtml = function (html) {
|
|
1260
|
+
const trimmed = String(html || '').trim();
|
|
1261
|
+
if (!trimmed) return null;
|
|
1262
|
+
const tpl = document.createElement('template');
|
|
1263
|
+
// Trusted structural HTML from our own card renderers; message bodies
|
|
1264
|
+
// inside it have already been escaped/sanitized (escHtml/formatMsgText).
|
|
1265
|
+
tpl.innerHTML = trimmed;
|
|
1266
|
+
return tpl.content.firstElementChild;
|
|
1267
|
+
};
|
|
1268
|
+
|
|
1269
|
+
// Hover-revealed {} button + lazy <pre> with the message's JSON. The JSON
|
|
1270
|
+
// rides in data-raw-json (escaped attribute); textContent assignment means
|
|
1271
|
+
// no sanitization concerns on display.
|
|
1272
|
+
MR.rawJsonAttrHtml = function (m, meta) {
|
|
1273
|
+
const payload = { role: m.role, text: m.text, timestamp: m.timestamp, metadata: meta };
|
|
1274
|
+
try { return ' data-raw-json="' + escHtml(JSON.stringify(payload)) + '"'; } catch { return ''; }
|
|
1275
|
+
};
|
|
1276
|
+
|
|
1277
|
+
MR.rawJsonButtonHtml = function () {
|
|
1278
|
+
return '<button class="msg-raw-btn" title="Show raw JSON" '
|
|
1279
|
+
+ 'onclick="MR.toggleRawJson(this);event.stopPropagation()">{}</button>';
|
|
1280
|
+
};
|
|
1281
|
+
|
|
1282
|
+
MR.toggleRawJson = function (btn) {
|
|
1283
|
+
const row = btn && btn.closest ? btn.closest('[data-raw-json]') : null;
|
|
1284
|
+
if (!row) return;
|
|
1285
|
+
let pre = row.querySelector('.msg-raw-json');
|
|
1286
|
+
if (!pre) {
|
|
1287
|
+
pre = document.createElement('pre');
|
|
1288
|
+
pre.className = 'msg-raw-json';
|
|
1289
|
+
let raw = row.getAttribute('data-raw-json') || '';
|
|
1290
|
+
try { raw = JSON.stringify(JSON.parse(raw), null, 2); } catch { /* show as-is */ }
|
|
1291
|
+
pre.textContent = raw;
|
|
1292
|
+
row.appendChild(pre);
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
pre.classList.toggle('hidden');
|
|
1296
|
+
};
|
|
1297
|
+
|
|
1298
|
+
MR.renderUnknownKindHtml = function (m, i, meta) {
|
|
1299
|
+
const time = m.timestamp ? new Date(m.timestamp).toLocaleString() : '';
|
|
1300
|
+
const kindLabel = String(meta.kindHint || meta.kind || 'unknown');
|
|
1301
|
+
const raw = typeof meta.raw === 'string' && meta.raw ? meta.raw : JSON.stringify(meta);
|
|
1302
|
+
return '<div class="review-msg local-cmd unknown-kind"' + MR.rawJsonAttrHtml(m, meta)
|
|
1303
|
+
+ ' onclick="this.classList.toggle(\'expanded\')" onkeydown="' + _toggleOnKeydownHandler() + '" role="button" tabindex="0" data-msg-idx="' + escHtml(String(i)) + '">'
|
|
1304
|
+
+ '<div class="msg-header">'
|
|
1305
|
+
+ '<span class="msg-role"><span class="thought-chevron">▶</span>Unrecorded event</span>'
|
|
1306
|
+
+ '<span class="thought-preview">' + escHtml('kind: ' + kindLabel) + '</span>'
|
|
1307
|
+
+ MR.rawJsonButtonHtml()
|
|
1308
|
+
+ '<span class="msg-time">' + escHtml(time) + '</span>'
|
|
1309
|
+
+ '</div>'
|
|
1310
|
+
+ '<div class="msg-text"><pre><code>' + escHtml(raw) + '</code></pre>'
|
|
1311
|
+
+ (meta.truncated ? '<div class="msg-truncation-note">(truncated)</div>' : '')
|
|
1312
|
+
+ '</div>'
|
|
1313
|
+
+ '</div>';
|
|
1314
|
+
};
|
|
1315
|
+
|
|
612
1316
|
// ------------------------------------------------------------------
|
|
613
1317
|
// Message classification (was index.html classifyMessage).
|
|
614
1318
|
// ------------------------------------------------------------------
|
|
1319
|
+
function _isSubagentPromptMessage(m) {
|
|
1320
|
+
const metadata = m && m.metadata && typeof m.metadata === 'object' ? m.metadata : {};
|
|
1321
|
+
return m && m.role === 'system' && metadata.sourceKind === 'subagent' && metadata.originalRole === 'user';
|
|
1322
|
+
}
|
|
1323
|
+
|
|
615
1324
|
MR.classifyMessage = function (m, stripped, isToolOnly) {
|
|
616
1325
|
const text = m.text || '';
|
|
1326
|
+
// Structured-capture rows: meta-first, before all text heuristics. Kinds
|
|
1327
|
+
// whose card renderer hasn't landed yet fall through to the legacy
|
|
1328
|
+
// pipeline (their text fallback renders exactly like pre-capture rows).
|
|
1329
|
+
const meta = MR.messageMeta(m);
|
|
1330
|
+
if (meta) {
|
|
1331
|
+
if (meta.kind === 'unknown' || !_KNOWN_META_KINDS.has(meta.kind)) return 'unknown-kind';
|
|
1332
|
+
if (_TOOL_CARD_KINDS.has(meta.kind)) return 'tool-card';
|
|
1333
|
+
if (meta.kind === 'tool_result') return 'tool-card-result';
|
|
1334
|
+
if (meta.kind === 'reasoning') return 'thinking';
|
|
1335
|
+
if (meta.kind === 'compact_boundary') return 'compact-boundary';
|
|
1336
|
+
if (meta.kind === 'compact_summary') return 'compact-summary';
|
|
1337
|
+
}
|
|
617
1338
|
if ((m.role === 'assistant' || m.role === 'system') && MR.isCodexOperationalWarning(text)) return 'codex-warning';
|
|
1339
|
+
if (m.role === 'system' && /^\[Wall-E (?:error|aborted)\]/i.test(text)) return 'walle-error';
|
|
1340
|
+
if (_isSubagentPromptMessage(m)) return 'subagent-prompt';
|
|
618
1341
|
// System role messages (tool results, task notifications) — treat as self-thought
|
|
619
1342
|
if (m.role === 'system') return 'self-thought';
|
|
620
1343
|
// User-role messages that are actually slash commands or injected skill
|
|
@@ -691,8 +1414,10 @@
|
|
|
691
1414
|
}
|
|
692
1415
|
|
|
693
1416
|
function _isPromptItem(item) {
|
|
694
|
-
if (!item || !item.m
|
|
1417
|
+
if (!item || !item.m) return false;
|
|
695
1418
|
const text = String(item.m.text || '').trim();
|
|
1419
|
+
if (item.msgType === 'subagent-prompt') return !!text;
|
|
1420
|
+
if (item.m.role !== 'user') return false;
|
|
696
1421
|
if (!text && item.msgType !== 'command') return false;
|
|
697
1422
|
return item.msgType === 'key' || item.msgType === 'command';
|
|
698
1423
|
}
|
|
@@ -712,8 +1437,11 @@
|
|
|
712
1437
|
let detail = 0;
|
|
713
1438
|
for (const item of responses) {
|
|
714
1439
|
if (item.m.role === 'assistant') assistant++;
|
|
715
|
-
|
|
716
|
-
|
|
1440
|
+
// Tool cards count once each (their text fallback may or may not carry
|
|
1441
|
+
// a [Tool: …] token, so counting the card avoids double/zero counts).
|
|
1442
|
+
if (item.msgType === 'tool-card') tools += 1;
|
|
1443
|
+
else tools += _toolNamesFromText(item.m.text).length;
|
|
1444
|
+
if (item.msgType === 'self-thought' || item.msgType === 'skill-body' || item.msgType === 'local-cmd' || item.msgType === 'tool-result' || item.msgType === 'tool-card-result' || item.msgType === 'summary') detail++;
|
|
717
1445
|
}
|
|
718
1446
|
return { assistant, tools, detail, total: responses.length };
|
|
719
1447
|
}
|
|
@@ -739,6 +1467,19 @@
|
|
|
739
1467
|
if (counts.tools) badges.push(counts.tools + ' tool' + (counts.tools === 1 ? '' : 's'));
|
|
740
1468
|
if (counts.detail) badges.push(counts.detail + ' detail' + (counts.detail === 1 ? '' : 's'));
|
|
741
1469
|
if (!counts.total && turn.type !== 'setup') badges.push('no response yet');
|
|
1470
|
+
// Token chip: sum assistant usage metadata across the turn's responses.
|
|
1471
|
+
let usageTotal = 0;
|
|
1472
|
+
const usageSum = { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 };
|
|
1473
|
+
for (const item of turn.responses || []) {
|
|
1474
|
+
const usage = _usageFromMessage(item.m);
|
|
1475
|
+
if (!usage) continue;
|
|
1476
|
+
usageTotal += MR.usageTokensTotal(usage);
|
|
1477
|
+
for (const key of Object.keys(usageSum)) usageSum[key] += Number(usage[key]) || 0;
|
|
1478
|
+
}
|
|
1479
|
+
if (usageTotal > 0) {
|
|
1480
|
+
badges.push('<span class="prompt-turn-badge token-chip" title="' + escHtml(MR.usageTitle(usageSum)) + '">'
|
|
1481
|
+
+ escHtml(_fmtTokens(usageTotal) + ' tok') + '</span>');
|
|
1482
|
+
}
|
|
742
1483
|
const alert = _turnAlert(turn);
|
|
743
1484
|
if (alert) badges.push('<span class="prompt-turn-badge prompt-turn-alert ' + alert.level + '">' + escHtml(alert.label) + '</span>');
|
|
744
1485
|
return badges.map(b => {
|
|
@@ -751,18 +1492,24 @@
|
|
|
751
1492
|
const m = item.m || {};
|
|
752
1493
|
const time = m.timestamp ? new Date(m.timestamp).toLocaleString() : '';
|
|
753
1494
|
const cmd = item.msgType === 'command' ? MR.parseCommandInvocation(m.text) : null;
|
|
1495
|
+
const isSubagentPrompt = item.msgType === 'subagent-prompt';
|
|
1496
|
+
const displayText = cmd ? (cmd.args || '') : (m.text || '');
|
|
1497
|
+
const attachmentHtml = _appendReviewAttachmentsHtml(Object.assign({}, m, { text: displayText }));
|
|
754
1498
|
const bodyHtml = cmd
|
|
755
|
-
? (
|
|
756
|
-
:
|
|
1499
|
+
? _formatDisplayTextHtml(displayText, attachmentHtml ? '<span class="msg-empty">(image attachment)</span>' : '<span class="msg-empty">(no arguments)</span>')
|
|
1500
|
+
: _formatDisplayTextHtml(displayText, attachmentHtml ? '<span class="msg-empty">(image attachment)</span>' : '');
|
|
757
1501
|
const badge = cmd ? '<span class="msg-cmd-badge" title="Slash command">/' + escHtml(cmd.name) + '</span>' : '';
|
|
758
1502
|
const parentUuid = m.parentUuid ? ' data-parent-uuid="' + escHtml(m.parentUuid) + '"' : '';
|
|
759
|
-
|
|
1503
|
+
const roleClass = isSubagentPrompt ? 'subagent-prompt' : 'user';
|
|
1504
|
+
const roleLabel = isSubagentPrompt ? (m.agentLabel || m.roleLabel || 'Subagent prompt') : 'You';
|
|
1505
|
+
return '<div class="review-msg ' + roleClass + ' key-msg prompt-turn-prompt"' + parentUuid + '>'
|
|
760
1506
|
+ '<div class="msg-header">'
|
|
761
|
-
+ '<span class="msg-role">
|
|
1507
|
+
+ '<span class="msg-role">' + escHtml(roleLabel) + '</span>'
|
|
762
1508
|
+ badge
|
|
763
1509
|
+ '<span class="msg-time">' + escHtml(time) + '</span>'
|
|
764
1510
|
+ '</div>'
|
|
765
1511
|
+ '<div class="msg-text" data-msg-idx="' + item.i + '">' + bodyHtml + '</div>'
|
|
1512
|
+
+ attachmentHtml
|
|
766
1513
|
+ '</div>';
|
|
767
1514
|
}
|
|
768
1515
|
|
|
@@ -839,12 +1586,16 @@
|
|
|
839
1586
|
// ------------------------------------------------------------------
|
|
840
1587
|
|
|
841
1588
|
MR.renderReviewMsg = function (m, i, msgType) {
|
|
1589
|
+
const metaCard = MR.renderMetaCardHtml(m, i);
|
|
1590
|
+
if (metaCard) return metaCard;
|
|
842
1591
|
const time = m.timestamp ? new Date(m.timestamp).toLocaleString() : '';
|
|
843
|
-
const
|
|
1592
|
+
const displayText = _messageTextForDisplay(m.text || '');
|
|
1593
|
+
const attachmentHtml = _appendReviewAttachmentsHtml(Object.assign({}, m, { text: m.text || '' }));
|
|
1594
|
+
const textHtml = _formatDisplayTextHtml(m.text || '', attachmentHtml ? '<span class="msg-empty">(image attachment)</span>' : '');
|
|
844
1595
|
const hasRenderedText = !!String(textHtml || '').trim();
|
|
845
|
-
const needsCollapse =
|
|
846
|
-
const stripped =
|
|
847
|
-
const isToolOnly = stripped.length === 0 && /\[Tool: /.test(
|
|
1596
|
+
const needsCollapse = displayText.length > 1500 || displayText.split('\n').length > 30;
|
|
1597
|
+
const stripped = displayText.replace(/\[Tool: [^\]]+\]/g, '').trim();
|
|
1598
|
+
const isToolOnly = stripped.length === 0 && /\[Tool: /.test(displayText);
|
|
848
1599
|
if (!msgType) msgType = MR.classifyMessage(m, stripped, isToolOnly);
|
|
849
1600
|
|
|
850
1601
|
if (msgType === 'command') {
|
|
@@ -852,7 +1603,9 @@
|
|
|
852
1603
|
// after `/name`. Render as a real user message but with a small badge
|
|
853
1604
|
// identifying the command — the badge replaces the raw envelope clutter.
|
|
854
1605
|
const cmd = MR.parseCommandInvocation(m.text) || { name: '', args: '' };
|
|
855
|
-
const
|
|
1606
|
+
const cmdMessage = Object.assign({}, m, { text: cmd.args || '' });
|
|
1607
|
+
const cmdAttachmentHtml = _appendReviewAttachmentsHtml(cmdMessage);
|
|
1608
|
+
const argsHtml = _formatDisplayTextHtml(cmd.args || '', cmdAttachmentHtml ? '<span class="msg-empty">(image attachment)</span>' : '<span class="msg-empty">(no arguments)</span>');
|
|
856
1609
|
return '<div class="review-msg user key-msg command-invocation">'
|
|
857
1610
|
+ '<div class="msg-header">'
|
|
858
1611
|
+ '<span class="msg-role">You</span>'
|
|
@@ -860,6 +1613,7 @@
|
|
|
860
1613
|
+ '<span class="msg-time">' + escHtml(time) + '</span>'
|
|
861
1614
|
+ '</div>'
|
|
862
1615
|
+ '<div class="msg-text" data-msg-idx="' + i + '">' + argsHtml + '</div>'
|
|
1616
|
+
+ cmdAttachmentHtml
|
|
863
1617
|
+ '</div>';
|
|
864
1618
|
}
|
|
865
1619
|
|
|
@@ -916,15 +1670,17 @@
|
|
|
916
1670
|
+ '</div>';
|
|
917
1671
|
}
|
|
918
1672
|
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1673
|
+
const isWalleError = msgType === 'walle-error';
|
|
1674
|
+
const isKey = msgType === 'key' || isWalleError;
|
|
1675
|
+
if (!hasRenderedText && !isToolOnly && !attachmentHtml) return '';
|
|
1676
|
+
const roleLabel = isWalleError ? 'Wall-E Error' : (m.role === 'user' ? 'You' : (m.agentLabel || m.roleLabel || 'Claude'));
|
|
1677
|
+
return '<div class="review-msg ' + m.role + (isToolOnly ? ' tool-only' : '') + (isKey ? ' key-msg' : '') + (isWalleError ? ' walle-error-msg' : '') + '">'
|
|
923
1678
|
+ '<div class="msg-header">'
|
|
924
1679
|
+ '<span class="msg-role">' + escHtml(roleLabel) + '</span>'
|
|
925
1680
|
+ '<span class="msg-time">' + escHtml(time) + '</span>'
|
|
926
1681
|
+ '</div>'
|
|
927
1682
|
+ '<div class="msg-text' + (needsCollapse ? ' collapsed' : '') + '" data-msg-idx="' + i + '">' + textHtml + '</div>'
|
|
1683
|
+
+ attachmentHtml
|
|
928
1684
|
+ (needsCollapse ? '<button class="msg-expand visible" onclick="toggleMsgExpand(this)">Show more</button>' : '')
|
|
929
1685
|
+ '</div>';
|
|
930
1686
|
};
|
|
@@ -964,6 +1720,32 @@
|
|
|
964
1720
|
|
|
965
1721
|
MR.groupMessages = function (messages) {
|
|
966
1722
|
const groups = [];
|
|
1723
|
+
// Tool-call ↔ tool-result pairing by metadata.callId: a result claims
|
|
1724
|
+
// the FIRST unclaimed call with its id; claimed results are skipped at
|
|
1725
|
+
// their own index (they render inside the call's card). Results can be
|
|
1726
|
+
// non-adjacent (parallel tool calls interleave).
|
|
1727
|
+
const resultIdxByCallId = new Map();
|
|
1728
|
+
const claimedResultIdxs = new Set();
|
|
1729
|
+
for (let ri = 0; ri < messages.length; ri += 1) {
|
|
1730
|
+
const rMeta = MR.messageMeta(messages[ri]);
|
|
1731
|
+
if (rMeta && rMeta.kind === 'tool_result' && rMeta.callId && !resultIdxByCallId.has(rMeta.callId)) {
|
|
1732
|
+
resultIdxByCallId.set(rMeta.callId, ri);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
let sessionWarningItems = null;
|
|
1736
|
+
let sessionWarningsInserted = false;
|
|
1737
|
+
const collectSessionWarnings = () => {
|
|
1738
|
+
if (sessionWarningItems) return sessionWarningItems;
|
|
1739
|
+
sessionWarningItems = [];
|
|
1740
|
+
for (let wi = 0; wi < messages.length; wi += 1) {
|
|
1741
|
+
const wm = messages[wi];
|
|
1742
|
+
const ws = wm.text.replace(/\[Tool: [^\]]+\]/g, '').trim();
|
|
1743
|
+
const wto = ws.length === 0 && /\[Tool: /.test(wm.text);
|
|
1744
|
+
if (MR.classifyMessage(wm, ws, wto) !== 'codex-warning') continue;
|
|
1745
|
+
sessionWarningItems.push({ m: wm, i: typeof wm._reviewIdx === 'number' ? wm._reviewIdx : wi, stripped: ws, isToolOnly: wto });
|
|
1746
|
+
}
|
|
1747
|
+
return sessionWarningItems;
|
|
1748
|
+
};
|
|
967
1749
|
let i = 0;
|
|
968
1750
|
while (i < messages.length) {
|
|
969
1751
|
const m = messages[i];
|
|
@@ -972,17 +1754,29 @@
|
|
|
972
1754
|
const isToolOnly = stripped.length === 0 && /\[Tool: /.test(m.text);
|
|
973
1755
|
const msgType = MR.classifyMessage(m, stripped, isToolOnly);
|
|
974
1756
|
|
|
975
|
-
if (msgType === '
|
|
976
|
-
const
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1757
|
+
if (msgType === 'tool-card') {
|
|
1758
|
+
const callMeta = MR.messageMeta(m);
|
|
1759
|
+
let result = null;
|
|
1760
|
+
const resultIdx = callMeta && callMeta.callId ? resultIdxByCallId.get(callMeta.callId) : undefined;
|
|
1761
|
+
if (resultIdx !== undefined && resultIdx > i && !claimedResultIdxs.has(resultIdx)) {
|
|
1762
|
+
claimedResultIdxs.add(resultIdx);
|
|
1763
|
+
const rm = messages[resultIdx];
|
|
1764
|
+
result = { m: rm, i: typeof rm._reviewIdx === 'number' ? rm._reviewIdx : resultIdx };
|
|
1765
|
+
}
|
|
1766
|
+
groups.push({ type: 'tool-card', call: { m, i: idx }, result });
|
|
1767
|
+
i++;
|
|
1768
|
+
} else if (msgType === 'tool-card-result') {
|
|
1769
|
+
if (!claimedResultIdxs.has(i)) {
|
|
1770
|
+
// Orphaned result (call outside this page/window) — completed card.
|
|
1771
|
+
groups.push({ type: 'tool-card', call: null, result: { m, i: idx } });
|
|
984
1772
|
}
|
|
985
|
-
|
|
1773
|
+
i++;
|
|
1774
|
+
} else if (msgType === 'codex-warning') {
|
|
1775
|
+
if (!sessionWarningsInserted) {
|
|
1776
|
+
groups.push({ type: 'codex-warning-group', items: collectSessionWarnings() });
|
|
1777
|
+
sessionWarningsInserted = true;
|
|
1778
|
+
}
|
|
1779
|
+
i++;
|
|
986
1780
|
} else if (msgType === 'self-thought') {
|
|
987
1781
|
const groupItems = [];
|
|
988
1782
|
while (i < messages.length) {
|
|
@@ -1006,6 +1800,9 @@
|
|
|
1006
1800
|
if (g.type === 'message') {
|
|
1007
1801
|
return MR.renderReviewMsg(g.m, g.i, g.msgType);
|
|
1008
1802
|
}
|
|
1803
|
+
if (g.type === 'tool-card') {
|
|
1804
|
+
return MR.renderToolCardHtml(g.call && g.call.m, g.result && g.result.m, g.call ? g.call.i : g.result.i);
|
|
1805
|
+
}
|
|
1009
1806
|
if (g.type === 'codex-warning-group') {
|
|
1010
1807
|
return MR.renderCodexWarningGroup(g.items || []);
|
|
1011
1808
|
}
|
|
@@ -1185,6 +1982,125 @@
|
|
|
1185
1982
|
return group;
|
|
1186
1983
|
};
|
|
1187
1984
|
|
|
1985
|
+
function _createSystemActivityItemRow(evt, time) {
|
|
1986
|
+
const text = _conversationEventTextValue(evt);
|
|
1987
|
+
const row = document.createElement('div');
|
|
1988
|
+
row.className = 'review-msg system system-activity-item';
|
|
1989
|
+
if (evt?.data?.parentUuid) row.dataset.parentUuid = evt.data.parentUuid;
|
|
1990
|
+
if (time) row.dataset.time = time;
|
|
1991
|
+
row.dataset.systemPreview = _summaryPreview(text) || 'No details recorded';
|
|
1992
|
+
|
|
1993
|
+
const header = document.createElement('div');
|
|
1994
|
+
header.className = 'msg-header';
|
|
1995
|
+
const role = document.createElement('span');
|
|
1996
|
+
role.className = 'msg-role';
|
|
1997
|
+
role.textContent = 'System';
|
|
1998
|
+
const preview = document.createElement('span');
|
|
1999
|
+
preview.className = 'thought-preview';
|
|
2000
|
+
preview.textContent = row.dataset.systemPreview;
|
|
2001
|
+
header.appendChild(role);
|
|
2002
|
+
header.appendChild(preview);
|
|
2003
|
+
if (time) {
|
|
2004
|
+
const t = document.createElement('span');
|
|
2005
|
+
t.className = 'msg-time';
|
|
2006
|
+
t.textContent = time;
|
|
2007
|
+
header.appendChild(t);
|
|
2008
|
+
}
|
|
2009
|
+
row.appendChild(header);
|
|
2010
|
+
|
|
2011
|
+
const body = document.createElement('div');
|
|
2012
|
+
body.className = 'msg-text';
|
|
2013
|
+
if (text) {
|
|
2014
|
+
const formatted = MR.formatMsgText(_messageTextForDisplay(text));
|
|
2015
|
+
body.innerHTML = (typeof DOMPurify !== 'undefined') ? DOMPurify.sanitize(formatted) : formatted;
|
|
2016
|
+
} else {
|
|
2017
|
+
const empty = document.createElement('span');
|
|
2018
|
+
empty.className = 'msg-empty';
|
|
2019
|
+
empty.textContent = '(no details recorded)';
|
|
2020
|
+
body.appendChild(empty);
|
|
2021
|
+
}
|
|
2022
|
+
row.appendChild(body);
|
|
2023
|
+
return row;
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
function _isLowValueSystemSummary(evt, text, toolUses) {
|
|
2027
|
+
if (String(text || '').trim()) return false;
|
|
2028
|
+
if (Array.isArray(toolUses) && toolUses.length) return false;
|
|
2029
|
+
const data = evt && evt.data && typeof evt.data === 'object' ? evt.data : {};
|
|
2030
|
+
const content = data.content ?? evt?.content;
|
|
2031
|
+
if (content == null) return true;
|
|
2032
|
+
return !_conversationContentToText(content).trim();
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
MR.refreshConversationSystemGroup = function (group) {
|
|
2036
|
+
if (!group || !group.querySelector) return;
|
|
2037
|
+
const items = group.querySelector('.thought-group-items');
|
|
2038
|
+
if (!items) return;
|
|
2039
|
+
const rows = Array.from(items.children || []);
|
|
2040
|
+
const countEl = group.querySelector('.thought-group-count');
|
|
2041
|
+
if (countEl) {
|
|
2042
|
+
const eventCount = rows.length;
|
|
2043
|
+
countEl.textContent = '— ' + eventCount + ' event' + (eventCount === 1 ? '' : 's');
|
|
2044
|
+
}
|
|
2045
|
+
const previews = [];
|
|
2046
|
+
for (const row of rows) {
|
|
2047
|
+
const p = row.dataset?.systemPreview || '';
|
|
2048
|
+
if (p && !previews.includes(p)) previews.push(p);
|
|
2049
|
+
}
|
|
2050
|
+
const previewEl = group.querySelector('.thought-group-preview');
|
|
2051
|
+
if (previewEl) {
|
|
2052
|
+
previewEl.textContent = previews.length === 1 ? previews[0] : (previews[0] || 'No details recorded');
|
|
2053
|
+
}
|
|
2054
|
+
const timeEl = group.querySelector('.thought-group-time');
|
|
2055
|
+
if (timeEl) {
|
|
2056
|
+
const f = group.dataset.firstTime || rows[0]?.dataset?.time || '';
|
|
2057
|
+
const l = group.dataset.lastTime || rows[rows.length - 1]?.dataset?.time || '';
|
|
2058
|
+
timeEl.textContent = (f && l && f !== l) ? f + ' – ' + l : (l || f);
|
|
2059
|
+
}
|
|
2060
|
+
};
|
|
2061
|
+
|
|
2062
|
+
MR.createConversationSystemGroup = function (evt, time) {
|
|
2063
|
+
const group = document.createElement('div');
|
|
2064
|
+
group.className = 'thought-group conv-system-group system-activity-group';
|
|
2065
|
+
group.dataset.firstTime = time || '';
|
|
2066
|
+
group.dataset.lastTime = time || '';
|
|
2067
|
+
group.addEventListener('click', () => group.classList.toggle('expanded'));
|
|
2068
|
+
|
|
2069
|
+
const header = document.createElement('div');
|
|
2070
|
+
header.className = 'thought-group-header';
|
|
2071
|
+
const label = document.createElement('span');
|
|
2072
|
+
label.className = 'thought-group-label';
|
|
2073
|
+
const chev = document.createElement('span');
|
|
2074
|
+
chev.className = 'thought-chevron';
|
|
2075
|
+
chev.textContent = '▶';
|
|
2076
|
+
label.appendChild(chev);
|
|
2077
|
+
const labelText = document.createElement('span');
|
|
2078
|
+
labelText.textContent = 'System';
|
|
2079
|
+
label.appendChild(labelText);
|
|
2080
|
+
const count = document.createElement('span');
|
|
2081
|
+
count.className = 'thought-group-count';
|
|
2082
|
+
count.textContent = '— 1 event';
|
|
2083
|
+
label.appendChild(count);
|
|
2084
|
+
|
|
2085
|
+
const preview = document.createElement('span');
|
|
2086
|
+
preview.className = 'thought-group-preview';
|
|
2087
|
+
preview.textContent = 'No details recorded';
|
|
2088
|
+
const tEl = document.createElement('span');
|
|
2089
|
+
tEl.className = 'thought-group-time';
|
|
2090
|
+
tEl.textContent = time || '';
|
|
2091
|
+
header.appendChild(label);
|
|
2092
|
+
header.appendChild(preview);
|
|
2093
|
+
header.appendChild(tEl);
|
|
2094
|
+
|
|
2095
|
+
const items = document.createElement('div');
|
|
2096
|
+
items.className = 'thought-group-items';
|
|
2097
|
+
items.appendChild(_createSystemActivityItemRow(evt, time));
|
|
2098
|
+
group.appendChild(header);
|
|
2099
|
+
group.appendChild(items);
|
|
2100
|
+
MR.refreshConversationSystemGroup(group);
|
|
2101
|
+
return group;
|
|
2102
|
+
};
|
|
2103
|
+
|
|
1188
2104
|
// ------------------------------------------------------------------
|
|
1189
2105
|
// Conversation-tab renderers — return DOM elements with event
|
|
1190
2106
|
// listeners attached directly. Used inside the active session view
|
|
@@ -1236,6 +2152,10 @@
|
|
|
1236
2152
|
|
|
1237
2153
|
MR.refreshConversationActivityGroup = function (group) {
|
|
1238
2154
|
if (!group || !group.querySelector) return;
|
|
2155
|
+
if (_isConversationCombinedActivityGroup(group)) {
|
|
2156
|
+
MR.refreshConversationCombinedActivityGroup(group);
|
|
2157
|
+
return;
|
|
2158
|
+
}
|
|
1239
2159
|
const items = group.querySelector('.thought-group-items');
|
|
1240
2160
|
if (!items) return;
|
|
1241
2161
|
const rows = Array.from(items.children || []);
|
|
@@ -1281,14 +2201,150 @@
|
|
|
1281
2201
|
// 'summary'). In the unit-test harness, the envelope shape is
|
|
1282
2202
|
// `{ type: 'stream-event', data: { type: 'assistant', ... } }`. Look
|
|
1283
2203
|
// at evt.type first, fall back to evt.data.type.
|
|
1284
|
-
const _ROLES = ['user', 'assistant', 'tool_result', 'summary'];
|
|
2204
|
+
const _ROLES = ['user', 'assistant', 'tool_result', 'summary', 'system'];
|
|
2205
|
+
|
|
2206
|
+
function _conversationContentToText(content) {
|
|
2207
|
+
if (content == null) return '';
|
|
2208
|
+
if (typeof content === 'string') return content;
|
|
2209
|
+
if (Array.isArray(content)) {
|
|
2210
|
+
return content.map((part) => {
|
|
2211
|
+
if (!part) return '';
|
|
2212
|
+
if (typeof part === 'string') return part;
|
|
2213
|
+
if (part.type === 'text' || part.type === 'input_text') return part.text || part.content || '';
|
|
2214
|
+
if (part.type === 'tool_result') return part.content || part.text || '';
|
|
2215
|
+
if (part.type === 'tool_use') return '[Tool: ' + (part.name || 'tool') + ']';
|
|
2216
|
+
if (part.text || part.content || part.message) return part.text || part.content || part.message;
|
|
2217
|
+
return '';
|
|
2218
|
+
}).filter(Boolean).join('\n');
|
|
2219
|
+
}
|
|
2220
|
+
if (content && typeof content === 'object') {
|
|
2221
|
+
if (content.text || content.content || content.message) return _conversationContentToText(content.text || content.content || content.message);
|
|
2222
|
+
try { return JSON.stringify(content); } catch { return String(content); }
|
|
2223
|
+
}
|
|
2224
|
+
return String(content || '');
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
function _conversationEventTextValue(evt) {
|
|
2228
|
+
const data = evt && evt.data && typeof evt.data === 'object' ? evt.data : {};
|
|
2229
|
+
return _conversationContentToText(
|
|
2230
|
+
data.text ?? data.message ?? data.content ?? evt?.text ?? evt?.message ?? evt?.content ?? ''
|
|
2231
|
+
).trim();
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
function _stripSystemReminderText(text) {
|
|
2235
|
+
return String(text || '')
|
|
2236
|
+
.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/gi, '')
|
|
2237
|
+
.replace(/<\/?system-reminder[^>]*>/gi, '')
|
|
2238
|
+
.trim();
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
function _isSystemOnlyUserEvent(evt, text) {
|
|
2242
|
+
const data = evt && evt.data && typeof evt.data === 'object' ? evt.data : {};
|
|
2243
|
+
if (data.system === true || data.isSystem === true || data.role === 'system') return true;
|
|
2244
|
+
const content = data.content ?? evt?.content;
|
|
2245
|
+
if (Array.isArray(content) && content.some(part => part && (part.type === 'tool_result' || part.type === 'tool_use'))) return true;
|
|
2246
|
+
const raw = String(text || '');
|
|
2247
|
+
if (!raw) return true;
|
|
2248
|
+
if (raw.includes('<system-reminder>') && !_stripSystemReminderText(raw)) return true;
|
|
2249
|
+
if (raw.includes('<task-notification>')) return true;
|
|
2250
|
+
if (raw.includes('toolu_') && raw.includes('.output')) return true;
|
|
2251
|
+
return false;
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
function _summaryPreview(text) {
|
|
2255
|
+
const value = String(text || '').replace(/\s+/g, ' ').trim();
|
|
2256
|
+
return value.length > 110 ? value.slice(0, 110) + '…' : value;
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
function _wireSummaryToggle(row, header) {
|
|
2260
|
+
if (!row || !header) return;
|
|
2261
|
+
const setExpanded = (expanded) => {
|
|
2262
|
+
row.classList.toggle('expanded', !!expanded);
|
|
2263
|
+
header.setAttribute('aria-expanded', expanded ? 'true' : 'false');
|
|
2264
|
+
};
|
|
2265
|
+
header.setAttribute('role', 'button');
|
|
2266
|
+
header.setAttribute('tabindex', '0');
|
|
2267
|
+
header.setAttribute('aria-expanded', row.classList.contains('expanded') ? 'true' : 'false');
|
|
2268
|
+
const toggle = () => setExpanded(!row.classList.contains('expanded'));
|
|
2269
|
+
header.addEventListener('click', toggle);
|
|
2270
|
+
header.addEventListener('keydown', (ev) => {
|
|
2271
|
+
if (ev.key === 'Enter' || ev.key === ' ') {
|
|
2272
|
+
ev.preventDefault();
|
|
2273
|
+
toggle();
|
|
2274
|
+
}
|
|
2275
|
+
});
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
function _buildConversationSummaryRow(evt, time, opts) {
|
|
2279
|
+
opts = opts || {};
|
|
2280
|
+
const text = _conversationEventTextValue(evt);
|
|
2281
|
+
const row = document.createElement('div');
|
|
2282
|
+
row.className = 'review-msg ' + (opts.roleClass || 'system') + ' summary';
|
|
2283
|
+
if (evt?.data?.parentUuid) row.dataset.parentUuid = evt.data.parentUuid;
|
|
2284
|
+
|
|
2285
|
+
const header = document.createElement('div');
|
|
2286
|
+
header.className = 'msg-header';
|
|
2287
|
+
const role = document.createElement('span');
|
|
2288
|
+
role.className = 'msg-role';
|
|
2289
|
+
const chev = document.createElement('span');
|
|
2290
|
+
chev.className = 'thought-chevron';
|
|
2291
|
+
chev.textContent = '▶';
|
|
2292
|
+
role.appendChild(chev);
|
|
2293
|
+
const label = document.createElement('span');
|
|
2294
|
+
label.textContent = opts.label || 'System';
|
|
2295
|
+
role.appendChild(label);
|
|
2296
|
+
const previewText = _summaryPreview(text) || '(no details recorded)';
|
|
2297
|
+
const preview = document.createElement('span');
|
|
2298
|
+
preview.className = 'thought-preview';
|
|
2299
|
+
preview.textContent = previewText;
|
|
2300
|
+
role.appendChild(preview);
|
|
2301
|
+
header.appendChild(role);
|
|
2302
|
+
|
|
2303
|
+
if (time) {
|
|
2304
|
+
const t = document.createElement('span');
|
|
2305
|
+
t.className = 'msg-time';
|
|
2306
|
+
t.textContent = time;
|
|
2307
|
+
header.appendChild(t);
|
|
2308
|
+
}
|
|
2309
|
+
row.appendChild(header);
|
|
2310
|
+
|
|
2311
|
+
const body = document.createElement('div');
|
|
2312
|
+
body.className = 'msg-text';
|
|
2313
|
+
if (text) {
|
|
2314
|
+
body.innerHTML = MR.formatMsgText(_messageTextForDisplay(text));
|
|
2315
|
+
} else {
|
|
2316
|
+
const empty = document.createElement('span');
|
|
2317
|
+
empty.className = 'msg-empty';
|
|
2318
|
+
empty.textContent = '(no details recorded)';
|
|
2319
|
+
body.appendChild(empty);
|
|
2320
|
+
}
|
|
2321
|
+
row.appendChild(body);
|
|
2322
|
+
_wireSummaryToggle(row, header);
|
|
2323
|
+
return row;
|
|
2324
|
+
}
|
|
2325
|
+
|
|
1285
2326
|
MR.renderConversationEvent = function (evt) {
|
|
1286
|
-
|
|
2327
|
+
// Structured-capture rows render the SAME HTML string the Review page
|
|
2328
|
+
// gets (renderMetaCardHtml), hydrated into a DOM element — parity by
|
|
2329
|
+
// construction. Falls through when the kind has no card renderer.
|
|
2330
|
+
const evtMeta = MR.messageMeta(evt);
|
|
2331
|
+
if (evtMeta) {
|
|
2332
|
+
const metaMsg = {
|
|
2333
|
+
role: (evt.data && evt.data.type) || evt.type || 'system',
|
|
2334
|
+
text: _conversationEventTextValue(evt),
|
|
2335
|
+
timestamp: evt.timestamp || (evt.data && evt.data.timestamp) || '',
|
|
2336
|
+
metadata: evtMeta,
|
|
2337
|
+
};
|
|
2338
|
+
const card = MR._cardElFromHtml(MR.renderMetaCardHtml(metaMsg, evt.data && evt.data.msgIdx || 0));
|
|
2339
|
+
if (card) return card;
|
|
2340
|
+
}
|
|
2341
|
+
const text = _conversationEventTextValue(evt);
|
|
1287
2342
|
const toolUses = evt.data?.toolUses || [];
|
|
1288
2343
|
|
|
1289
2344
|
let evType = _ROLES.includes(evt.type) ? evt.type : null;
|
|
1290
2345
|
if (!evType && _ROLES.includes(evt.data?.type)) evType = evt.data.type;
|
|
1291
2346
|
if (!evType) return null;
|
|
2347
|
+
if (evType === 'system') evType = 'summary';
|
|
1292
2348
|
|
|
1293
2349
|
// Phase 4 reconciliation: empty user events used to be dropped in
|
|
1294
2350
|
// Conversation (legacy `if (!text) return null`), but Review classifies
|
|
@@ -1301,27 +2357,14 @@
|
|
|
1301
2357
|
const time = evt.timestamp ? new Date(evt.timestamp).toLocaleString() : '';
|
|
1302
2358
|
|
|
1303
2359
|
if (evType === 'user') {
|
|
1304
|
-
if (
|
|
2360
|
+
if (_isSystemOnlyUserEvent(evt, text)) {
|
|
1305
2361
|
// System-only user message (tool_result blocks, hooks, etc.) —
|
|
1306
2362
|
// render as a compact summary row that the user can expand if
|
|
1307
2363
|
// they want to inspect. Matches Review's classifier output.
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
if (evt.data?.parentUuid) div.dataset.parentUuid = evt.data.parentUuid;
|
|
1311
|
-
const header = document.createElement('div');
|
|
1312
|
-
header.className = 'msg-header';
|
|
1313
|
-
const role = document.createElement('span');
|
|
1314
|
-
role.className = 'msg-role';
|
|
1315
|
-
role.textContent = '(system message)';
|
|
1316
|
-
header.appendChild(role);
|
|
1317
|
-
if (time) {
|
|
1318
|
-
const t = document.createElement('span');
|
|
1319
|
-
t.className = 'msg-time';
|
|
1320
|
-
t.textContent = time;
|
|
1321
|
-
header.appendChild(t);
|
|
2364
|
+
if (_isLowValueSystemSummary(evt, text, toolUses)) {
|
|
2365
|
+
return MR.createConversationSystemGroup(evt, time);
|
|
1322
2366
|
}
|
|
1323
|
-
|
|
1324
|
-
return div;
|
|
2367
|
+
return _buildConversationSummaryRow(evt, time, { roleClass: 'user', label: 'System' });
|
|
1325
2368
|
}
|
|
1326
2369
|
// Slash-command invocation — extract args; render with a /cmd badge.
|
|
1327
2370
|
const cmd = MR.parseCommandInvocation(text);
|
|
@@ -1337,8 +2380,9 @@
|
|
|
1337
2380
|
// Insert the badge between role and time so layout matches Review.
|
|
1338
2381
|
header.insertBefore(badge, header.querySelector('.msg-time') || null);
|
|
1339
2382
|
div.appendChild(header);
|
|
1340
|
-
|
|
1341
|
-
|
|
2383
|
+
const cmdMessage = Object.assign({}, evt.data || {}, { text: cmd.args || '' });
|
|
2384
|
+
if (cmd.args || MR.extractImageAttachments(cmdMessage).length) {
|
|
2385
|
+
_appendTextWithExpand(div, cmd.args, cmdMessage, { emptyHtml: '<span class="msg-empty">(image attachment)</span>' });
|
|
1342
2386
|
} else {
|
|
1343
2387
|
const empty = document.createElement('div');
|
|
1344
2388
|
empty.className = 'msg-text';
|
|
@@ -1381,7 +2425,7 @@
|
|
|
1381
2425
|
}
|
|
1382
2426
|
div.appendChild(header);
|
|
1383
2427
|
// Body uses DOMPurify (defense-in-depth around marked output).
|
|
1384
|
-
_appendTextWithExpand(div, text);
|
|
2428
|
+
_appendTextWithExpand(div, text, evt.data || evt);
|
|
1385
2429
|
return div;
|
|
1386
2430
|
}
|
|
1387
2431
|
// Local command output — same compact-collapsible treatment.
|
|
@@ -1469,7 +2513,7 @@
|
|
|
1469
2513
|
div.className = 'review-msg user';
|
|
1470
2514
|
if (evt.data?.parentUuid) div.dataset.parentUuid = evt.data.parentUuid;
|
|
1471
2515
|
div.appendChild(_makeHeader('You', time));
|
|
1472
|
-
if (!_appendTextWithExpand(div, text)) return null;
|
|
2516
|
+
if (!_appendTextWithExpand(div, text, evt.data || evt)) return null;
|
|
1473
2517
|
return div;
|
|
1474
2518
|
}
|
|
1475
2519
|
|
|
@@ -1596,10 +2640,15 @@
|
|
|
1596
2640
|
const div = document.createElement('div');
|
|
1597
2641
|
div.className = 'review-msg assistant';
|
|
1598
2642
|
if (evt.data?.parentUuid) div.dataset.parentUuid = evt.data.parentUuid;
|
|
2643
|
+
const rowUsage = evt.data?.metadata?.usage;
|
|
2644
|
+
if (rowUsage && MR.usageTokensTotal(rowUsage) > 0) {
|
|
2645
|
+
div.dataset.usageTokens = String(MR.usageTokensTotal(rowUsage));
|
|
2646
|
+
div.dataset.usageTitle = MR.usageTitle(rowUsage);
|
|
2647
|
+
}
|
|
1599
2648
|
const roleLabel = 'Assistant' + (evt.data?.model ? ' (' + evt.data.model + ')' : '');
|
|
1600
2649
|
div.appendChild(_makeHeader(roleLabel, time));
|
|
1601
2650
|
if (text) {
|
|
1602
|
-
const hasTextBody = _appendTextWithExpand(div, text);
|
|
2651
|
+
const hasTextBody = _appendTextWithExpand(div, text, evt.data || evt);
|
|
1603
2652
|
if (!hasTextBody && !toolUses.length) return null;
|
|
1604
2653
|
}
|
|
1605
2654
|
else if (!toolUses.length) return null;
|
|
@@ -1674,14 +2723,10 @@
|
|
|
1674
2723
|
}
|
|
1675
2724
|
|
|
1676
2725
|
if (evType === 'summary') {
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
body.className = 'msg-text';
|
|
1682
|
-
body.textContent = evt.data?.text || '';
|
|
1683
|
-
div.appendChild(body);
|
|
1684
|
-
return div;
|
|
2726
|
+
if (_isLowValueSystemSummary(evt, text, toolUses)) {
|
|
2727
|
+
return MR.createConversationSystemGroup(evt, time);
|
|
2728
|
+
}
|
|
2729
|
+
return _buildConversationSummaryRow(evt, time, { roleClass: 'system', label: 'System' });
|
|
1685
2730
|
}
|
|
1686
2731
|
|
|
1687
2732
|
return null;
|
|
@@ -1694,13 +2739,14 @@
|
|
|
1694
2739
|
}
|
|
1695
2740
|
|
|
1696
2741
|
function _conversationEventText(evt) {
|
|
1697
|
-
return
|
|
2742
|
+
return _conversationEventTextValue(evt);
|
|
1698
2743
|
}
|
|
1699
2744
|
|
|
1700
2745
|
MR.isConversationPromptEvent = function (evt) {
|
|
1701
2746
|
if (_conversationEventType(evt) !== 'user') return false;
|
|
1702
2747
|
const text = _conversationEventText(evt);
|
|
1703
2748
|
if (!text) return false;
|
|
2749
|
+
if (_isSystemOnlyUserEvent(evt, text)) return false;
|
|
1704
2750
|
if (MR.parseSkillBody(text) || MR.parseLocalCommand(text) || MR.parseToolResult(text)) return false;
|
|
1705
2751
|
return true;
|
|
1706
2752
|
};
|
|
@@ -1728,6 +2774,98 @@
|
|
|
1728
2774
|
return el.classList.contains('conv-warning-group') && el.classList.contains('codex-warning-group');
|
|
1729
2775
|
}
|
|
1730
2776
|
|
|
2777
|
+
function _isConversationSystemGroup(el) {
|
|
2778
|
+
if (!el || !el.classList) return false;
|
|
2779
|
+
return el.classList.contains('conv-system-group') && el.classList.contains('system-activity-group');
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
function _isConversationCombinedActivityGroup(el) {
|
|
2783
|
+
if (!el || !el.classList) return false;
|
|
2784
|
+
return el.classList.contains('conv-activity-group') && el.classList.contains('combined-activity-group');
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
function _isFoldableOperationalActivityGroup(el) {
|
|
2788
|
+
return _isConversationCombinedActivityGroup(el) || _isConversationToolActivityGroup(el) || _isConversationSystemGroup(el);
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
function _setConversationActivityGroupLabel(group, text) {
|
|
2792
|
+
if (!group || !group.querySelector) return;
|
|
2793
|
+
const label = group.querySelector('.thought-group-label');
|
|
2794
|
+
if (!label) return;
|
|
2795
|
+
let target = label.querySelector('[data-activity-label]');
|
|
2796
|
+
if (!target) {
|
|
2797
|
+
target = Array.from(label.children || []).find((child) => {
|
|
2798
|
+
if (!child || !child.classList) return false;
|
|
2799
|
+
return !child.classList.contains('thought-chevron') && !child.classList.contains('thought-group-count');
|
|
2800
|
+
});
|
|
2801
|
+
}
|
|
2802
|
+
if (!target && typeof document !== 'undefined') {
|
|
2803
|
+
target = document.createElement('span');
|
|
2804
|
+
target.setAttribute('data-activity-label', 'true');
|
|
2805
|
+
const count = label.querySelector('.thought-group-count');
|
|
2806
|
+
label.insertBefore(target, count || null);
|
|
2807
|
+
}
|
|
2808
|
+
if (target) {
|
|
2809
|
+
target.setAttribute('data-activity-label', 'true');
|
|
2810
|
+
target.textContent = text;
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
function _markConversationCombinedActivityGroup(group) {
|
|
2815
|
+
if (!group || !group.classList) return group;
|
|
2816
|
+
group.classList.add('conv-activity-group');
|
|
2817
|
+
group.classList.add('combined-activity-group');
|
|
2818
|
+
group.classList.add('tool-activity-group');
|
|
2819
|
+
group.dataset.activityKind = 'combined';
|
|
2820
|
+
_setConversationActivityGroupLabel(group, 'Activity');
|
|
2821
|
+
return group;
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
MR.refreshConversationCombinedActivityGroup = function (group) {
|
|
2825
|
+
if (!group || !group.querySelector) return;
|
|
2826
|
+
_markConversationCombinedActivityGroup(group);
|
|
2827
|
+
const items = group.querySelector('.thought-group-items');
|
|
2828
|
+
if (!items) return;
|
|
2829
|
+
const rows = Array.from(items.children || []);
|
|
2830
|
+
const countEl = group.querySelector('.thought-group-count');
|
|
2831
|
+
if (countEl) {
|
|
2832
|
+
const itemCount = rows.length;
|
|
2833
|
+
countEl.textContent = '— ' + itemCount + ' item' + (itemCount === 1 ? '' : 's');
|
|
2834
|
+
}
|
|
2835
|
+
const toolNames = [];
|
|
2836
|
+
const previews = [];
|
|
2837
|
+
let systemCount = 0;
|
|
2838
|
+
let toolResultCount = 0;
|
|
2839
|
+
for (const row of rows) {
|
|
2840
|
+
if (!row || !row.classList) continue;
|
|
2841
|
+
if (row.classList.contains('system-activity-item')) systemCount += 1;
|
|
2842
|
+
if (row.classList.contains('tool-result-item')) toolResultCount += 1;
|
|
2843
|
+
const rawTools = row.dataset?.toolNames || '';
|
|
2844
|
+
if (rawTools) {
|
|
2845
|
+
for (const name of rawTools.split(',')) {
|
|
2846
|
+
const clean = name.trim();
|
|
2847
|
+
if (clean) toolNames.push(clean);
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
const preview = row.dataset?.activityPreview || row.dataset?.systemPreview || '';
|
|
2851
|
+
if (preview && preview !== 'No details recorded') previews.push(preview);
|
|
2852
|
+
}
|
|
2853
|
+
const summaryParts = [];
|
|
2854
|
+
const toolSummary = _summarizeNames(toolNames);
|
|
2855
|
+
if (toolSummary) summaryParts.push(toolSummary);
|
|
2856
|
+
if (!toolSummary && toolResultCount) summaryParts.push(toolResultCount + ' tool result' + (toolResultCount === 1 ? '' : 's'));
|
|
2857
|
+
if (systemCount) summaryParts.push(systemCount + ' system event' + (systemCount === 1 ? '' : 's'));
|
|
2858
|
+
if (!summaryParts.length && previews.length) summaryParts.push(previews[0]);
|
|
2859
|
+
const previewEl = group.querySelector('.thought-group-preview');
|
|
2860
|
+
if (previewEl) previewEl.textContent = summaryParts.join(' · ') || 'Activity';
|
|
2861
|
+
const timeEl = group.querySelector('.thought-group-time');
|
|
2862
|
+
if (timeEl) {
|
|
2863
|
+
const f = group.dataset.firstTime || rows[0]?.dataset?.time || '';
|
|
2864
|
+
const l = group.dataset.lastTime || rows[rows.length - 1]?.dataset?.time || '';
|
|
2865
|
+
timeEl.textContent = (f && l && f !== l) ? f + ' – ' + l : (l || f);
|
|
2866
|
+
}
|
|
2867
|
+
};
|
|
2868
|
+
|
|
1731
2869
|
function _nodeWithin(root, node) {
|
|
1732
2870
|
if (!root || !node) return false;
|
|
1733
2871
|
const el = node.nodeType === 1 ? node : node.parentNode;
|
|
@@ -1741,9 +2879,21 @@
|
|
|
1741
2879
|
return _nodeWithin(root, sel.anchorNode) || _nodeWithin(root, sel.focusNode);
|
|
1742
2880
|
};
|
|
1743
2881
|
|
|
2882
|
+
MR.isPromptTurnPromptTextTarget = function (ev, headerEl) {
|
|
2883
|
+
if (!ev || !headerEl || !ev.target || !ev.target.closest) return false;
|
|
2884
|
+
const textEl = ev.target.closest('.prompt-turn-prompt .msg-text');
|
|
2885
|
+
return !!(textEl && headerEl.contains(textEl));
|
|
2886
|
+
};
|
|
2887
|
+
|
|
1744
2888
|
MR.shouldKeepPromptTextSelection = function (ev, headerEl) {
|
|
2889
|
+
if (!MR.isPromptTurnPromptTextTarget(ev, headerEl)) return false;
|
|
2890
|
+
return MR.hasTextSelectionInside(headerEl);
|
|
2891
|
+
};
|
|
2892
|
+
|
|
2893
|
+
MR.shouldIgnorePromptTurnHeaderToggle = function (ev, headerEl) {
|
|
1745
2894
|
if (!ev || !headerEl || !ev.target || !ev.target.closest) return false;
|
|
1746
|
-
if (
|
|
2895
|
+
if (ev.target.closest('a,button,input,textarea,select')) return true;
|
|
2896
|
+
if (MR.shouldKeepPromptTextSelection(ev, headerEl)) return true;
|
|
1747
2897
|
return MR.hasTextSelectionInside(headerEl);
|
|
1748
2898
|
};
|
|
1749
2899
|
|
|
@@ -1754,31 +2904,41 @@
|
|
|
1754
2904
|
let targetKind = null;
|
|
1755
2905
|
let merged = false;
|
|
1756
2906
|
for (const child of Array.from(body.children || [])) {
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
2907
|
+
if (!_isFoldableOperationalActivityGroup(child)) {
|
|
2908
|
+
if (_isConversationWarningGroup(child)) {
|
|
2909
|
+
target = null;
|
|
2910
|
+
targetKind = null;
|
|
2911
|
+
}
|
|
1762
2912
|
continue;
|
|
1763
2913
|
}
|
|
1764
|
-
|
|
2914
|
+
const childKind = _isConversationCombinedActivityGroup(child)
|
|
2915
|
+
? 'activity'
|
|
2916
|
+
: (_isConversationToolActivityGroup(child) ? 'tool' : 'system');
|
|
2917
|
+
if (!target) {
|
|
1765
2918
|
target = child;
|
|
1766
|
-
targetKind =
|
|
2919
|
+
targetKind = childKind;
|
|
1767
2920
|
continue;
|
|
1768
2921
|
}
|
|
1769
2922
|
const targetItems = target.querySelector('.thought-group-items');
|
|
1770
2923
|
const childItems = child.querySelector('.thought-group-items');
|
|
1771
2924
|
if (!targetItems || !childItems) continue;
|
|
2925
|
+
const shouldPromote = targetKind === 'activity' || childKind === 'activity' || childKind !== targetKind;
|
|
1772
2926
|
const keepExpanded = target.classList.contains('expanded') || child.classList.contains('expanded');
|
|
1773
2927
|
while (childItems.firstChild) targetItems.appendChild(childItems.firstChild);
|
|
1774
2928
|
const childLast = child.dataset && child.dataset.lastTime;
|
|
1775
2929
|
if (childLast) target.dataset.lastTime = childLast;
|
|
1776
2930
|
if (keepExpanded && target.classList && !target.classList.contains('expanded')) target.classList.add('expanded');
|
|
1777
2931
|
child.remove();
|
|
2932
|
+
if (shouldPromote) {
|
|
2933
|
+
_markConversationCombinedActivityGroup(target);
|
|
2934
|
+
targetKind = 'activity';
|
|
2935
|
+
}
|
|
1778
2936
|
merged = true;
|
|
1779
2937
|
}
|
|
1780
|
-
if (target &&
|
|
1781
|
-
MR.
|
|
2938
|
+
if (target && _isConversationCombinedActivityGroup(target) && typeof MR.refreshConversationCombinedActivityGroup === 'function') {
|
|
2939
|
+
MR.refreshConversationCombinedActivityGroup(target);
|
|
2940
|
+
} else if (target && _isConversationSystemGroup(target) && typeof MR.refreshConversationSystemGroup === 'function') {
|
|
2941
|
+
MR.refreshConversationSystemGroup(target);
|
|
1782
2942
|
} else if (target && typeof MR.refreshConversationActivityGroup === 'function') {
|
|
1783
2943
|
MR.refreshConversationActivityGroup(target);
|
|
1784
2944
|
}
|
|
@@ -1810,13 +2970,29 @@
|
|
|
1810
2970
|
if (responseBlocks) appendBadge(responseBlocks + ' detail' + (responseBlocks === 1 ? '' : 's'));
|
|
1811
2971
|
else appendBadge('no response yet');
|
|
1812
2972
|
if (toolBlocks) appendBadge(toolBlocks + ' tool block' + (toolBlocks === 1 ? '' : 's'));
|
|
2973
|
+
// Token chip: assistant rows stamp data-usage-tokens at render time.
|
|
2974
|
+
// (Guarded: rendering test harnesses stub the DOM without selectors.)
|
|
2975
|
+
let usageTotal = 0;
|
|
2976
|
+
const usageTitles = [];
|
|
2977
|
+
let usageEls = [];
|
|
2978
|
+
try { usageEls = Array.from(body.querySelectorAll('[data-usage-tokens]') || []); } catch { usageEls = []; }
|
|
2979
|
+
for (const el of usageEls) {
|
|
2980
|
+
usageTotal += Number(el.dataset && el.dataset.usageTokens) || 0;
|
|
2981
|
+
if (el.dataset && el.dataset.usageTitle) usageTitles.push(el.dataset.usageTitle);
|
|
2982
|
+
}
|
|
2983
|
+
if (usageTotal > 0) {
|
|
2984
|
+
const chip = document.createElement('span');
|
|
2985
|
+
chip.className = 'prompt-turn-badge token-chip';
|
|
2986
|
+
chip.textContent = _fmtTokens(usageTotal) + ' tok';
|
|
2987
|
+
chip.title = usageTitles.join('\n');
|
|
2988
|
+
meta.appendChild(chip);
|
|
2989
|
+
}
|
|
1813
2990
|
};
|
|
1814
2991
|
|
|
1815
2992
|
function _wirePromptTurnHeader(turnEl, header) {
|
|
1816
2993
|
if (!turnEl || !header) return;
|
|
1817
2994
|
const toggle = (ev) => {
|
|
1818
|
-
if (
|
|
1819
|
-
if (MR.shouldKeepPromptTextSelection(ev, header)) return;
|
|
2995
|
+
if (MR.shouldIgnorePromptTurnHeaderToggle(ev, header)) return;
|
|
1820
2996
|
MR.setPromptTurnExpanded(turnEl, !turnEl.classList.contains('expanded'));
|
|
1821
2997
|
};
|
|
1822
2998
|
header.addEventListener('click', toggle);
|
|
@@ -1904,19 +3080,31 @@
|
|
|
1904
3080
|
// The body uses the same 1500-char / 30-line threshold as Review so the
|
|
1905
3081
|
// two views feel identical for long messages — closes the 3000-char hard
|
|
1906
3082
|
// truncate gap that Conversation had pre-Phase-2.
|
|
1907
|
-
function _appendTextWithExpand(host, text) {
|
|
1908
|
-
|
|
3083
|
+
function _appendTextWithExpand(host, text, message, opts) {
|
|
3084
|
+
opts = opts || {};
|
|
3085
|
+
const displayText = _messageTextForDisplay(text);
|
|
3086
|
+
const attachmentMessage = Object.assign({}, message || {}, { text: text || '' });
|
|
3087
|
+
const attachmentHtml = MR.renderImageAttachmentsHtml(attachmentMessage);
|
|
3088
|
+
const needsCollapse = displayText.length > 1500 || displayText.split('\n').length > 30;
|
|
1909
3089
|
// formatMsgText already runs DOMPurify; we re-sanitize at the assignment
|
|
1910
3090
|
// site to match Review's defense-in-depth pattern. No untrusted HTML
|
|
1911
3091
|
// ever reaches innerHTML without sanitization.
|
|
3092
|
+
const rendered = displayText ? MR.formatMsgText(displayText) : (opts.emptyHtml || '');
|
|
1912
3093
|
const safe = (typeof DOMPurify !== 'undefined')
|
|
1913
|
-
? DOMPurify.sanitize(
|
|
1914
|
-
:
|
|
3094
|
+
? DOMPurify.sanitize(rendered)
|
|
3095
|
+
: rendered;
|
|
1915
3096
|
const body = document.createElement('div');
|
|
1916
3097
|
body.className = 'msg-text' + (needsCollapse ? ' collapsed' : '');
|
|
1917
3098
|
body.innerHTML = safe;
|
|
1918
|
-
if (!_hasMeaningfulMarkdownContent(body)) return false;
|
|
3099
|
+
if (!_hasMeaningfulMarkdownContent(body) && !attachmentHtml) return false;
|
|
1919
3100
|
host.appendChild(body);
|
|
3101
|
+
if (attachmentHtml) {
|
|
3102
|
+
const template = document.createElement('template');
|
|
3103
|
+
template.innerHTML = (typeof DOMPurify !== 'undefined')
|
|
3104
|
+
? DOMPurify.sanitize(attachmentHtml, { ADD_ATTR: _IMAGE_ATTACHMENT_SAFE_ATTRS })
|
|
3105
|
+
: attachmentHtml;
|
|
3106
|
+
while (template.content.firstChild) host.appendChild(template.content.firstChild);
|
|
3107
|
+
}
|
|
1920
3108
|
if (needsCollapse) {
|
|
1921
3109
|
const btn = document.createElement('button');
|
|
1922
3110
|
btn.className = 'msg-expand visible';
|