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
|
@@ -8,11 +8,16 @@ window.WalleSession = (function() {
|
|
|
8
8
|
|
|
9
9
|
var TRANSCRIPT_ATTACH_DEBOUNCE_MS = 120;
|
|
10
10
|
var TRANSCRIPT_LIVE_ECHO_SKIP_MS = 1500;
|
|
11
|
+
var HISTORY_RETRY_AFTER_MS = 2500;
|
|
11
12
|
var COMPOSER_HEIGHT_STORAGE_KEY = 'ctm.walle.composerHeightPx';
|
|
12
13
|
var COMPOSER_HEIGHT_STEP_PX = 16;
|
|
13
14
|
var COMPOSER_HEIGHT_DEFAULT_PX = 44;
|
|
14
15
|
var COMPOSER_HEIGHT_MIN_PX = 44;
|
|
15
16
|
var COMPOSER_HEIGHT_MAX_PX = 420;
|
|
17
|
+
var MESSAGE_RENDER_SYNC_LIMIT = 60;
|
|
18
|
+
var MESSAGE_RENDER_BATCH_SIZE = 24;
|
|
19
|
+
var MESSAGE_RENDER_FRAME_BUDGET_MS = 8;
|
|
20
|
+
var _cancelConfirmationOpen = false;
|
|
16
21
|
|
|
17
22
|
function loadSavedComposerHeight() {
|
|
18
23
|
try {
|
|
@@ -40,7 +45,7 @@ window.WalleSession = (function() {
|
|
|
40
45
|
// ---------- state helper ----------
|
|
41
46
|
function initialModelFromSession(s) {
|
|
42
47
|
var meta = (s && s.meta) || {};
|
|
43
|
-
var model = meta.model_id || meta.model || '';
|
|
48
|
+
var model = normalizeSessionModelId(meta.model_id || meta.model || '');
|
|
44
49
|
return {
|
|
45
50
|
model: model,
|
|
46
51
|
registryId: meta.model_registry_id || meta.modelRegistryId || '',
|
|
@@ -73,10 +78,31 @@ window.WalleSession = (function() {
|
|
|
73
78
|
inputHistory: [],
|
|
74
79
|
inputHistoryIdx: -1,
|
|
75
80
|
inputDraft: '',
|
|
81
|
+
composerDraft: {
|
|
82
|
+
value: '',
|
|
83
|
+
selectionStart: 0,
|
|
84
|
+
selectionEnd: 0,
|
|
85
|
+
focused: false,
|
|
86
|
+
updatedAt: 0
|
|
87
|
+
},
|
|
76
88
|
inputAttachments: [],
|
|
89
|
+
queueState: null,
|
|
90
|
+
workState: null,
|
|
91
|
+
workDrawerOpen: false,
|
|
92
|
+
isQueueingMessage: false,
|
|
77
93
|
composerPreviewOpen: false,
|
|
78
94
|
isPreparingLocation: false,
|
|
79
|
-
|
|
95
|
+
historyStatus: s.needsAttach ? 'loading' : (s._walleHistoryLoaded ? 'loaded' : 'idle'),
|
|
96
|
+
historyRequestedAt: s._walleHistoryRequestedAt || 0,
|
|
97
|
+
historyLoadedAt: s._walleHistoryLoadedAt || 0,
|
|
98
|
+
historyLoadReason: '',
|
|
99
|
+
composerHeightPx: loadSavedComposerHeight(),
|
|
100
|
+
branches: {},
|
|
101
|
+
branchActive: {},
|
|
102
|
+
editingMessageIndex: -1,
|
|
103
|
+
_branchHistoryLoaded: false,
|
|
104
|
+
_branchHistoryAuthoritative: false,
|
|
105
|
+
_branchActiveHistory: []
|
|
80
106
|
};
|
|
81
107
|
} else if (!s.walleState._modelHydrated) {
|
|
82
108
|
s.walleState.selectedModel = s.walleState.selectedModel || initialModel.model;
|
|
@@ -94,9 +120,33 @@ window.WalleSession = (function() {
|
|
|
94
120
|
if (!Array.isArray(s.walleState.inputHistory)) s.walleState.inputHistory = [];
|
|
95
121
|
if (typeof s.walleState.inputHistoryIdx !== 'number') s.walleState.inputHistoryIdx = -1;
|
|
96
122
|
if (typeof s.walleState.inputDraft !== 'string') s.walleState.inputDraft = '';
|
|
123
|
+
if (!s.walleState.composerDraft || typeof s.walleState.composerDraft !== 'object') {
|
|
124
|
+
s.walleState.composerDraft = { value: '', selectionStart: 0, selectionEnd: 0, focused: false, updatedAt: 0 };
|
|
125
|
+
}
|
|
126
|
+
if (typeof s.walleState.composerDraft.value !== 'string') s.walleState.composerDraft.value = '';
|
|
127
|
+
if (typeof s.walleState.composerDraft.selectionStart !== 'number') s.walleState.composerDraft.selectionStart = 0;
|
|
128
|
+
if (typeof s.walleState.composerDraft.selectionEnd !== 'number') s.walleState.composerDraft.selectionEnd = s.walleState.composerDraft.selectionStart;
|
|
129
|
+
if (typeof s.walleState.composerDraft.focused !== 'boolean') s.walleState.composerDraft.focused = false;
|
|
130
|
+
if (typeof s.walleState.composerDraft.updatedAt !== 'number') s.walleState.composerDraft.updatedAt = 0;
|
|
97
131
|
if (!Array.isArray(s.walleState.inputAttachments)) s.walleState.inputAttachments = [];
|
|
132
|
+
if (typeof s.walleState.isQueueingMessage !== 'boolean') s.walleState.isQueueingMessage = false;
|
|
133
|
+
if (s.walleState.queueState && typeof s.walleState.queueState !== 'object') s.walleState.queueState = null;
|
|
134
|
+
if (s.walleState.workState && typeof s.walleState.workState !== 'object') s.walleState.workState = null;
|
|
135
|
+
if (typeof s.walleState.workDrawerOpen !== 'boolean') s.walleState.workDrawerOpen = false;
|
|
136
|
+
if (!s.walleState.branches || typeof s.walleState.branches !== 'object') s.walleState.branches = {};
|
|
137
|
+
if (!s.walleState.branchActive || typeof s.walleState.branchActive !== 'object') s.walleState.branchActive = {};
|
|
138
|
+
if (typeof s.walleState.editingMessageIndex !== 'number') s.walleState.editingMessageIndex = -1;
|
|
139
|
+
if (!Array.isArray(s.walleState._branchActiveHistory)) s.walleState._branchActiveHistory = [];
|
|
140
|
+
if (typeof s.walleState._branchHistoryLoaded !== 'boolean') s.walleState._branchHistoryLoaded = false;
|
|
141
|
+
if (typeof s.walleState._branchHistoryAuthoritative !== 'boolean') s.walleState._branchHistoryAuthoritative = false;
|
|
98
142
|
if (typeof s.walleState.composerPreviewOpen !== 'boolean') s.walleState.composerPreviewOpen = false;
|
|
99
143
|
if (typeof s.walleState.isPreparingLocation !== 'boolean') s.walleState.isPreparingLocation = false;
|
|
144
|
+
if (!s.walleState.historyStatus) {
|
|
145
|
+
s.walleState.historyStatus = s.needsAttach ? 'loading' : (s._walleHistoryLoaded ? 'loaded' : 'idle');
|
|
146
|
+
}
|
|
147
|
+
if (typeof s.walleState.historyRequestedAt !== 'number') s.walleState.historyRequestedAt = s._walleHistoryRequestedAt || 0;
|
|
148
|
+
if (typeof s.walleState.historyLoadedAt !== 'number') s.walleState.historyLoadedAt = s._walleHistoryLoadedAt || 0;
|
|
149
|
+
if (typeof s.walleState.historyLoadReason !== 'string') s.walleState.historyLoadReason = '';
|
|
100
150
|
if (typeof s.walleState.selectedModelRegistryId !== 'string') s.walleState.selectedModelRegistryId = '';
|
|
101
151
|
if (typeof s.walleState.selectedModelProviderType !== 'string') s.walleState.selectedModelProviderType = '';
|
|
102
152
|
if ((s.meta && (s.meta.model_pinned === true || s.meta.modelPinned === true)) && s.walleState.selectedModel) {
|
|
@@ -108,6 +158,383 @@ window.WalleSession = (function() {
|
|
|
108
158
|
return s.walleState;
|
|
109
159
|
}
|
|
110
160
|
|
|
161
|
+
function walleBranchSessionId(id) {
|
|
162
|
+
var s = state.sessions.get(id);
|
|
163
|
+
var meta = (s && s.meta) || {};
|
|
164
|
+
return meta.chatSessionId
|
|
165
|
+
|| meta.chat_session_id
|
|
166
|
+
|| meta.agentSessionId
|
|
167
|
+
|| meta.agent_session_id
|
|
168
|
+
|| meta.walleChatSessionId
|
|
169
|
+
|| meta.walle_chat_session_id
|
|
170
|
+
|| ('walle-' + id);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function cloneJsonSafe(value, fallback) {
|
|
174
|
+
if (value == null) return fallback;
|
|
175
|
+
try {
|
|
176
|
+
return JSON.parse(JSON.stringify(value));
|
|
177
|
+
} catch (_) {
|
|
178
|
+
return fallback;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Branch snapshots are persisted/transmitted as JSON, so they must not carry
|
|
183
|
+
// base64 image payloads: a single screenshot can be hundreds of KB, and a
|
|
184
|
+
// snapshot serializes the whole conversation across every branch version.
|
|
185
|
+
// Strip the heavy `data` field and keep only the lightweight reference fields
|
|
186
|
+
// — the renderer displays from `url` (renderAttachmentStrip) and the server
|
|
187
|
+
// rehydrates the base64 from disk by path/url/filename on resend
|
|
188
|
+
// (_hydrateWalleAttachment). The image lives in the images dir regardless.
|
|
189
|
+
var BRANCH_ATTACHMENT_REF_FIELDS = [
|
|
190
|
+
'type', 'name', 'filename', 'label', 'url', 'path', 'file_path', 'id',
|
|
191
|
+
'mediaType', 'mimeType', 'imageWidth', 'imageHeight',
|
|
192
|
+
'originalWidth', 'originalHeight', 'resizedForProvider'
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
function branchSafeAttachments(attachments) {
|
|
196
|
+
return (Array.isArray(attachments) ? attachments : []).map(function(att) {
|
|
197
|
+
if (!att || typeof att !== 'object') return att;
|
|
198
|
+
var out = {};
|
|
199
|
+
BRANCH_ATTACHMENT_REF_FIELDS.forEach(function(key) {
|
|
200
|
+
if (att[key] != null) out[key] = att[key];
|
|
201
|
+
});
|
|
202
|
+
return out;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function normalizeBranchMessage(msg) {
|
|
207
|
+
if (!msg || typeof msg !== 'object') return null;
|
|
208
|
+
var role = String(msg.role || '').trim();
|
|
209
|
+
if (!role) return null;
|
|
210
|
+
var content = msg.content != null ? msg.content : msg.text;
|
|
211
|
+
var out = {
|
|
212
|
+
role: role,
|
|
213
|
+
content: typeof content === 'string' ? content : String(content || ''),
|
|
214
|
+
timestamp: msg.timestamp || msg.created_at || msg.createdAt || Date.now()
|
|
215
|
+
};
|
|
216
|
+
var model = msg.model_id || msg.model || '';
|
|
217
|
+
if (model) out.model = model;
|
|
218
|
+
var latency = msg.latency_ms != null ? msg.latency_ms : msg.latencyMs;
|
|
219
|
+
if (latency != null) out.latency_ms = latency;
|
|
220
|
+
if (Array.isArray(msg.toolCalls)) out.toolCalls = cloneJsonSafe(msg.toolCalls, []);
|
|
221
|
+
else if (Array.isArray(msg.tool_calls)) out.toolCalls = cloneJsonSafe(msg.tool_calls, []);
|
|
222
|
+
if (Array.isArray(msg.attachments)) out.attachments = branchSafeAttachments(msg.attachments);
|
|
223
|
+
if (msg.metadata) out.metadata = cloneJsonSafe(msg.metadata, msg.metadata);
|
|
224
|
+
if (msg.liveActivity) out.liveActivity = true;
|
|
225
|
+
if (msg.agentLabel) out.agentLabel = msg.agentLabel;
|
|
226
|
+
return out;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function cloneBranchMessage(msg) {
|
|
230
|
+
return normalizeBranchMessage(msg) || {
|
|
231
|
+
role: '',
|
|
232
|
+
content: '',
|
|
233
|
+
timestamp: Date.now()
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function normalizeBranchTail(tail) {
|
|
238
|
+
return (Array.isArray(tail) ? tail : [])
|
|
239
|
+
.map(normalizeBranchMessage)
|
|
240
|
+
.filter(function(msg) { return !!msg && !!msg.role; });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function branchMessageTimestampMs(msg) {
|
|
244
|
+
if (!msg) return 0;
|
|
245
|
+
var value = msg.timestamp || msg.created_at || msg.createdAt || 0;
|
|
246
|
+
if (typeof value === 'number') return Number.isFinite(value) ? value : 0;
|
|
247
|
+
var parsed = Date.parse(String(value || ''));
|
|
248
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function branchAttachmentFingerprint(att) {
|
|
252
|
+
if (!att || typeof att !== 'object') return '';
|
|
253
|
+
return [
|
|
254
|
+
att.label || '',
|
|
255
|
+
att.filename || att.name || '',
|
|
256
|
+
att.url || '',
|
|
257
|
+
att.path || att.file_path || '',
|
|
258
|
+
att.id || ''
|
|
259
|
+
].join('\u001f');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function branchMessageFingerprint(msg) {
|
|
263
|
+
if (!msg || !msg.role) return '';
|
|
264
|
+
var attachments = (Array.isArray(msg.attachments) ? msg.attachments : [])
|
|
265
|
+
.map(branchAttachmentFingerprint)
|
|
266
|
+
.filter(Boolean)
|
|
267
|
+
.sort()
|
|
268
|
+
.join('\u001e');
|
|
269
|
+
return [
|
|
270
|
+
String(msg.role || ''),
|
|
271
|
+
String(msg.content || ''),
|
|
272
|
+
String(branchMessageTimestampMs(msg) || ''),
|
|
273
|
+
attachments
|
|
274
|
+
].join('\u001f');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function reconcileBranchHistoryWithDurable(branchHistory, durableHistory, snapshotSavedAtMs) {
|
|
278
|
+
var branch = normalizeBranchTail(branchHistory);
|
|
279
|
+
var durable = normalizeBranchTail(durableHistory);
|
|
280
|
+
if (!branch.length) return durable;
|
|
281
|
+
if (!durable.length) return branch;
|
|
282
|
+
|
|
283
|
+
var branchFingerprints = {};
|
|
284
|
+
branch.forEach(function(msg) {
|
|
285
|
+
var fp = branchMessageFingerprint(msg);
|
|
286
|
+
if (fp) branchFingerprints[fp] = true;
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
var lastDurableMatch = -1;
|
|
290
|
+
for (var b = branch.length - 1; b >= 0 && lastDurableMatch < 0; b--) {
|
|
291
|
+
var branchFp = branchMessageFingerprint(branch[b]);
|
|
292
|
+
if (!branchFp) continue;
|
|
293
|
+
for (var d = durable.length - 1; d >= 0; d--) {
|
|
294
|
+
if (branchMessageFingerprint(durable[d]) === branchFp) {
|
|
295
|
+
lastDurableMatch = d;
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
var watermark = Number(snapshotSavedAtMs || 0) || 0;
|
|
302
|
+
var appendStart = lastDurableMatch >= 0 ? lastDurableMatch + 1 : 0;
|
|
303
|
+
var appended = [];
|
|
304
|
+
for (var i = appendStart; i < durable.length; i++) {
|
|
305
|
+
var candidate = durable[i];
|
|
306
|
+
var fp = branchMessageFingerprint(candidate);
|
|
307
|
+
if (fp && branchFingerprints[fp]) continue;
|
|
308
|
+
if (watermark > 0) {
|
|
309
|
+
var ts = branchMessageTimestampMs(candidate);
|
|
310
|
+
// New snapshots carry a save watermark. Do not resurrect old durable
|
|
311
|
+
// transcript tails that a branch edit/delete intentionally abandoned.
|
|
312
|
+
if (ts > 0 && ts <= watermark) continue;
|
|
313
|
+
} else if (lastDurableMatch < 0) {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
appended.push(cloneBranchMessage(candidate));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return branch.map(cloneBranchMessage).concat(appended);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function normalizeBranchMap(branches) {
|
|
323
|
+
var out = {};
|
|
324
|
+
if (!branches || typeof branches !== 'object') return out;
|
|
325
|
+
Object.keys(branches).forEach(function(key) {
|
|
326
|
+
var idx = parseInt(key, 10);
|
|
327
|
+
if (!Number.isFinite(idx) || idx < 0) return;
|
|
328
|
+
var versions = Array.isArray(branches[key]) ? branches[key] : [];
|
|
329
|
+
var normalized = versions.map(normalizeBranchTail).filter(function(tail) { return tail.length > 0; });
|
|
330
|
+
if (normalized.length) out[String(idx)] = normalized;
|
|
331
|
+
});
|
|
332
|
+
return out;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function activeHistorySnapshot(ws) {
|
|
336
|
+
return ((ws && ws.messages) || []).map(cloneBranchMessage).filter(function(msg) { return !!msg.role; });
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function branchKeys(ws) {
|
|
340
|
+
return Object.keys((ws && ws.branches) || {})
|
|
341
|
+
.map(function(key) { return parseInt(key, 10); })
|
|
342
|
+
.filter(function(idx) { return Number.isFinite(idx); })
|
|
343
|
+
.sort(function(a, b) { return a - b; });
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function hasBranchSnapshot(ws) {
|
|
347
|
+
return branchKeys(ws).length > 0 || !!(ws && ws._branchHistoryAuthoritative);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function refreshActiveBranchSlots(ws) {
|
|
351
|
+
if (!ws || !ws.branches) return;
|
|
352
|
+
branchKeys(ws).forEach(function(splitIdx) {
|
|
353
|
+
if (splitIdx >= ws.messages.length) {
|
|
354
|
+
delete ws.branches[String(splitIdx)];
|
|
355
|
+
delete ws.branchActive[String(splitIdx)];
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
var branches = ws.branches[String(splitIdx)];
|
|
359
|
+
if (!Array.isArray(branches) || !branches.length) return;
|
|
360
|
+
var active = Number(ws.branchActive[String(splitIdx)] || 0);
|
|
361
|
+
if (!Number.isFinite(active) || active < 0 || active >= branches.length) active = 0;
|
|
362
|
+
branches[active] = ws.messages.slice(splitIdx).map(cloneBranchMessage);
|
|
363
|
+
ws.branchActive[String(splitIdx)] = active;
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function clearBranchesAfter(ws, splitIdx, inclusive) {
|
|
368
|
+
if (!ws || !ws.branches) return;
|
|
369
|
+
branchKeys(ws).forEach(function(key) {
|
|
370
|
+
if (inclusive ? key >= splitIdx : key > splitIdx) {
|
|
371
|
+
delete ws.branches[String(key)];
|
|
372
|
+
delete ws.branchActive[String(key)];
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function markBranchHistoryAuthoritative(ws) {
|
|
378
|
+
if (!ws) return;
|
|
379
|
+
ws._branchHistoryAuthoritative = true;
|
|
380
|
+
ws._branchActiveHistory = activeHistorySnapshot(ws);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function applyBranchHistoryIfAuthoritative(ws, durableHistory) {
|
|
384
|
+
if (!ws || !ws._branchHistoryAuthoritative || !Array.isArray(ws._branchActiveHistory)) return false;
|
|
385
|
+
ws.messages = reconcileBranchHistoryWithDurable(
|
|
386
|
+
ws._branchActiveHistory,
|
|
387
|
+
durableHistory,
|
|
388
|
+
ws._branchSnapshotSavedAtMs
|
|
389
|
+
);
|
|
390
|
+
ws.messageCount = ws.messages.length;
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function branchApiUrl(sessionId) {
|
|
395
|
+
return '/api/wall-e/chat/branches?session_id=' + encodeURIComponent(sessionId || 'default');
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function saveWalleBranchSnapshot(id) {
|
|
399
|
+
var ws = getState(id);
|
|
400
|
+
if (!ws) return Promise.resolve(false);
|
|
401
|
+
refreshActiveBranchSlots(ws);
|
|
402
|
+
markBranchHistoryAuthoritative(ws);
|
|
403
|
+
var payload = {
|
|
404
|
+
session_id: walleBranchSessionId(id),
|
|
405
|
+
branches: ws.branches || {},
|
|
406
|
+
active: ws.branchActive || {},
|
|
407
|
+
history: activeHistorySnapshot(ws),
|
|
408
|
+
updated_at_ms: Date.now()
|
|
409
|
+
};
|
|
410
|
+
var previousSave = ws._branchSavePromise || Promise.resolve();
|
|
411
|
+
ws._branchSavePromise = previousSave.catch(function() {}).then(function() {
|
|
412
|
+
return fetch('/api/wall-e/chat/branches', {
|
|
413
|
+
method: 'POST',
|
|
414
|
+
headers: { 'Content-Type': 'application/json' },
|
|
415
|
+
body: JSON.stringify(payload)
|
|
416
|
+
});
|
|
417
|
+
}).then(function(resp) {
|
|
418
|
+
if (!resp.ok) {
|
|
419
|
+
// Surface the server's error message (e.g. a 413 "Body too large")
|
|
420
|
+
// instead of a bare status — the body carries { error, code }.
|
|
421
|
+
return resp.json().catch(function() { return null; }).then(function(body) {
|
|
422
|
+
var detail = body && body.error ? body.error : 'HTTP ' + resp.status;
|
|
423
|
+
throw new Error('Wall-E branch save failed: ' + detail);
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
ws._branchHistoryLoaded = true;
|
|
427
|
+
ws._branchActiveHistory = payload.history;
|
|
428
|
+
ws._branchSnapshotSavedAtMs = payload.updated_at_ms;
|
|
429
|
+
return true;
|
|
430
|
+
}).catch(function(err) {
|
|
431
|
+
console.warn('[walle-session] branch snapshot save failed:', err && err.message || err);
|
|
432
|
+
if (typeof toast === 'function') {
|
|
433
|
+
toast('Could not save Wall-E branch history: ' + (err && err.message ? err.message : err), { type: 'error', duration: 5000 });
|
|
434
|
+
}
|
|
435
|
+
return false;
|
|
436
|
+
});
|
|
437
|
+
return ws._branchSavePromise;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function applyLoadedBranchSnapshot(id, data) {
|
|
441
|
+
var ws = getState(id);
|
|
442
|
+
if (!ws || !data || typeof data !== 'object') return false;
|
|
443
|
+
var branches = normalizeBranchMap(data.branches || {});
|
|
444
|
+
var active = data.active && typeof data.active === 'object' ? data.active : {};
|
|
445
|
+
var history = normalizeBranchTail(data.history || []);
|
|
446
|
+
var snapshotSavedAtMs = Number(data.updated_at_ms || data.updatedAtMs || data.saved_at_ms || data.savedAtMs || 0) || 0;
|
|
447
|
+
if (!history.length && Object.keys(branches).length === 0) {
|
|
448
|
+
ws._branchHistoryLoaded = true;
|
|
449
|
+
if (data.exists === true) {
|
|
450
|
+
ws.branches = {};
|
|
451
|
+
ws.branchActive = {};
|
|
452
|
+
ws._branchActiveHistory = [];
|
|
453
|
+
ws._branchHistoryAuthoritative = false;
|
|
454
|
+
ws._branchSnapshotSavedAtMs = snapshotSavedAtMs;
|
|
455
|
+
}
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
ws.branches = branches;
|
|
459
|
+
ws.branchActive = {};
|
|
460
|
+
Object.keys(active).forEach(function(key) {
|
|
461
|
+
var n = Number(active[key]);
|
|
462
|
+
if (Number.isFinite(n)) ws.branchActive[String(parseInt(key, 10))] = n;
|
|
463
|
+
});
|
|
464
|
+
if (history.length) {
|
|
465
|
+
ws.messages = reconcileBranchHistoryWithDurable(history, activeHistorySnapshot(ws), snapshotSavedAtMs);
|
|
466
|
+
ws.messageCount = ws.messages.length;
|
|
467
|
+
ws._branchActiveHistory = ws.messages.map(cloneBranchMessage);
|
|
468
|
+
ws._branchHistoryAuthoritative = true;
|
|
469
|
+
} else {
|
|
470
|
+
ws._branchActiveHistory = activeHistorySnapshot(ws);
|
|
471
|
+
ws._branchHistoryAuthoritative = branchKeys(ws).length > 0;
|
|
472
|
+
}
|
|
473
|
+
ws._branchSnapshotSavedAtMs = snapshotSavedAtMs;
|
|
474
|
+
ws._branchHistoryLoaded = true;
|
|
475
|
+
return history.length > 0 || branchKeys(ws).length > 0;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function ensureWalleBranchSnapshotLoaded(id) {
|
|
479
|
+
var ws = getState(id);
|
|
480
|
+
if (!ws || ws._branchLoadStarted) return;
|
|
481
|
+
ws._branchLoadStarted = true;
|
|
482
|
+
fetch(branchApiUrl(walleBranchSessionId(id)), { cache: 'no-store' })
|
|
483
|
+
.then(function(resp) {
|
|
484
|
+
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
|
485
|
+
return resp.json();
|
|
486
|
+
})
|
|
487
|
+
.then(function(json) {
|
|
488
|
+
var changed = applyLoadedBranchSnapshot(id, json && (json.data || json));
|
|
489
|
+
var latest = getState(id);
|
|
490
|
+
if (changed && latest && document.getElementById('walle-session-' + id)) {
|
|
491
|
+
latest.renderPending = true;
|
|
492
|
+
if (!isWalleSessionVisible(id)) return;
|
|
493
|
+
var composerSnapshot = captureComposerState(id);
|
|
494
|
+
renderSession(id);
|
|
495
|
+
restoreComposerState(id, composerSnapshot);
|
|
496
|
+
}
|
|
497
|
+
})
|
|
498
|
+
.catch(function(err) {
|
|
499
|
+
console.warn('[walle-session] branch snapshot load failed:', err && err.message || err);
|
|
500
|
+
})
|
|
501
|
+
.finally(function() {
|
|
502
|
+
var latest = getState(id);
|
|
503
|
+
if (latest) latest._branchHistoryLoaded = true;
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function messageContentForContext(msg) {
|
|
508
|
+
if (!msg) return '';
|
|
509
|
+
if (typeof msg.content === 'string') return msg.content;
|
|
510
|
+
if (typeof msg.text === 'string') return msg.text;
|
|
511
|
+
return String(msg.content || '');
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function contextMessagesForModel(ws, outboundText) {
|
|
515
|
+
var messages = ((ws && ws.messages) || [])
|
|
516
|
+
.map(function(msg) {
|
|
517
|
+
var role = String(msg && msg.role || '').trim();
|
|
518
|
+
if (role !== 'user' && role !== 'assistant') return null;
|
|
519
|
+
var out = {
|
|
520
|
+
role: role,
|
|
521
|
+
content: messageContentForContext(msg)
|
|
522
|
+
};
|
|
523
|
+
if (Array.isArray(msg.attachments) && msg.attachments.length) out.attachments = cloneJsonSafe(msg.attachments, []);
|
|
524
|
+
return out;
|
|
525
|
+
})
|
|
526
|
+
.filter(function(msg) { return !!msg && (!!msg.content || (msg.attachments && msg.attachments.length)); });
|
|
527
|
+
if (outboundText && messages.length) {
|
|
528
|
+
for (var i = messages.length - 1; i >= 0; i--) {
|
|
529
|
+
if (messages[i].role === 'user') {
|
|
530
|
+
messages[i].content = outboundText;
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return messages.slice(-60);
|
|
536
|
+
}
|
|
537
|
+
|
|
111
538
|
var _walleModelRegistryCache = null;
|
|
112
539
|
var _walleModelRegistryLoaded = false;
|
|
113
540
|
var _walleModelRegistryPromise = null;
|
|
@@ -497,15 +924,104 @@ window.WalleSession = (function() {
|
|
|
497
924
|
{ label: 'Find a bug', prompt: 'Run the test suite and help me debug whatever fails.' },
|
|
498
925
|
{ label: 'Paste a screenshot', prompt: 'I will paste a screenshot — tell me what is wrong with the UI.' }
|
|
499
926
|
];
|
|
927
|
+
function _hasRenderableMessages(ws) {
|
|
928
|
+
return !!(ws && ((ws.messageCount || 0) > 0 || (ws.messages && ws.messages.length)));
|
|
929
|
+
}
|
|
930
|
+
function historyStatusForSession(id, ws) {
|
|
931
|
+
var s = state.sessions.get(id);
|
|
932
|
+
if (_hasRenderableMessages(ws)) return 'loaded';
|
|
933
|
+
if (ws && ws.historyStatus === 'error') return 'error';
|
|
934
|
+
if ((ws && ws.historyStatus === 'loaded') || (s && s._walleHistoryLoaded)) return 'loaded';
|
|
935
|
+
if ((ws && ws.historyStatus === 'loading') ||
|
|
936
|
+
(s && (s.needsAttach || s._walleHistoryLoading || (s._walleHistoryRequested && !s._walleHistoryLoaded)))) {
|
|
937
|
+
return 'loading';
|
|
938
|
+
}
|
|
939
|
+
return 'idle';
|
|
940
|
+
}
|
|
941
|
+
function markHistoryLoading(id, reason) {
|
|
942
|
+
var s = state.sessions.get(id);
|
|
943
|
+
var ws = getState(id);
|
|
944
|
+
if (!s || !ws) return false;
|
|
945
|
+
ws.historyRequestSeq = (Number(ws.historyRequestSeq || 0) || 0) + 1;
|
|
946
|
+
ws.historyStatus = 'loading';
|
|
947
|
+
ws.historyRequestedAt = Date.now();
|
|
948
|
+
ws.historyLoadReason = reason || 'attach';
|
|
949
|
+
ws.historyRequestId = ws.historyRequestSeq;
|
|
950
|
+
s._walleHistoryRequested = true;
|
|
951
|
+
s._walleHistoryRequestedAt = ws.historyRequestedAt;
|
|
952
|
+
s._walleHistoryRequestSeq = ws.historyRequestSeq;
|
|
953
|
+
s._walleHistoryLoading = true;
|
|
954
|
+
s._walleHistoryLoaded = false;
|
|
955
|
+
var messagesArea = document.getElementById('walle-messages-' + id);
|
|
956
|
+
if (messagesArea && !_hasRenderableMessages(ws)) {
|
|
957
|
+
_renderEmptyStateChipsIfNeeded(id, messagesArea);
|
|
958
|
+
}
|
|
959
|
+
updateHeaderStats(id);
|
|
960
|
+
return true;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
function shouldRequestHistory(id, opts) {
|
|
964
|
+
opts = opts || {};
|
|
965
|
+
var s = state.sessions.get(id);
|
|
966
|
+
var ws = getState(id);
|
|
967
|
+
if (!s || !ws) return false;
|
|
968
|
+
if (opts.force === true) return true;
|
|
969
|
+
if (_hasRenderableMessages(ws)) return false;
|
|
970
|
+
if (ws.historyStatus === 'loaded' || s._walleHistoryLoaded) return false;
|
|
971
|
+
var requested = !!(s._walleHistoryRequested || ws.historyStatus === 'loading' || s._walleHistoryLoading);
|
|
972
|
+
if (!requested) return true;
|
|
973
|
+
var requestedAt = Number(ws.historyRequestedAt || s._walleHistoryRequestedAt || 0);
|
|
974
|
+
if (!requestedAt) return true;
|
|
975
|
+
return Date.now() - requestedAt >= HISTORY_RETRY_AFTER_MS;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function requestHistory(id, reason, opts) {
|
|
979
|
+
opts = opts || {};
|
|
980
|
+
if (!shouldRequestHistory(id, opts)) return false;
|
|
981
|
+
var s = state.sessions.get(id);
|
|
982
|
+
var ws = getState(id);
|
|
983
|
+
if (!s || !ws) return false;
|
|
984
|
+
s.needsAttach = false;
|
|
985
|
+
if (!markHistoryLoading(id, reason || 'walle-history-request')) return false;
|
|
986
|
+
send({
|
|
987
|
+
type: 'walle-history-request',
|
|
988
|
+
id: id,
|
|
989
|
+
reason: reason || 'walle-history-request',
|
|
990
|
+
historyRequestId: ws.historyRequestId,
|
|
991
|
+
limit: opts.limit || 500,
|
|
992
|
+
noCache: opts.noCache === true,
|
|
993
|
+
});
|
|
994
|
+
return true;
|
|
995
|
+
}
|
|
996
|
+
function markHistoryLoaded(id) {
|
|
997
|
+
var s = state.sessions.get(id);
|
|
998
|
+
var ws = getState(id);
|
|
999
|
+
if (!s || !ws) return false;
|
|
1000
|
+
ws.historyStatus = 'loaded';
|
|
1001
|
+
ws.historyLoadedAt = Date.now();
|
|
1002
|
+
ws.historyLoadReason = '';
|
|
1003
|
+
s._walleHistoryRequested = true;
|
|
1004
|
+
s._walleHistoryLoading = false;
|
|
1005
|
+
s._walleHistoryLoaded = true;
|
|
1006
|
+
s._walleHistoryLoadedAt = ws.historyLoadedAt;
|
|
1007
|
+
return true;
|
|
1008
|
+
}
|
|
500
1009
|
function _renderEmptyStateChipsIfNeeded(id, messagesArea) {
|
|
501
1010
|
if (!messagesArea) return;
|
|
502
1011
|
var ws = getState(id);
|
|
503
1012
|
if (!ws) return;
|
|
504
1013
|
// Don't render if there are already messages in this session.
|
|
505
|
-
if ((ws
|
|
1014
|
+
if (_hasRenderableMessages(ws)) {
|
|
1015
|
+
_removeEmptyAndLoadingState(messagesArea);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
var historyStatus = historyStatusForSession(id, ws);
|
|
1019
|
+
if (historyStatus === 'loading') {
|
|
506
1020
|
_removeEmptyStateChips(messagesArea);
|
|
1021
|
+
_renderHistoryLoadingState(messagesArea);
|
|
507
1022
|
return;
|
|
508
1023
|
}
|
|
1024
|
+
_removeHistoryLoadingState(messagesArea);
|
|
509
1025
|
if (messagesArea.querySelector('.walle-empty-state')) return;
|
|
510
1026
|
var wrap = document.createElement('div');
|
|
511
1027
|
wrap.className = 'walle-empty-state';
|
|
@@ -535,11 +1051,58 @@ window.WalleSession = (function() {
|
|
|
535
1051
|
wrap.appendChild(chips);
|
|
536
1052
|
messagesArea.appendChild(wrap);
|
|
537
1053
|
}
|
|
1054
|
+
function _renderHistoryLoadingState(messagesArea) {
|
|
1055
|
+
if (!messagesArea || messagesArea.querySelector('.walle-history-loading-state')) return;
|
|
1056
|
+
var wrap = document.createElement('div');
|
|
1057
|
+
wrap.className = 'walle-history-loading-state';
|
|
1058
|
+
wrap.setAttribute('role', 'status');
|
|
1059
|
+
wrap.setAttribute('aria-live', 'polite');
|
|
1060
|
+
|
|
1061
|
+
var badge = document.createElement('div');
|
|
1062
|
+
badge.className = 'walle-history-loading-badge';
|
|
1063
|
+
var spinner = document.createElement('span');
|
|
1064
|
+
spinner.className = 'walle-history-loading-spinner';
|
|
1065
|
+
spinner.setAttribute('aria-hidden', 'true');
|
|
1066
|
+
badge.appendChild(spinner);
|
|
1067
|
+
var badgeText = document.createElement('span');
|
|
1068
|
+
badgeText.textContent = 'Loading';
|
|
1069
|
+
badge.appendChild(badgeText);
|
|
1070
|
+
wrap.appendChild(badge);
|
|
1071
|
+
|
|
1072
|
+
var title = document.createElement('div');
|
|
1073
|
+
title.className = 'walle-history-loading-title';
|
|
1074
|
+
title.textContent = 'Rehydrating Wall-E session history';
|
|
1075
|
+
wrap.appendChild(title);
|
|
1076
|
+
|
|
1077
|
+
var copy = document.createElement('div');
|
|
1078
|
+
copy.className = 'walle-history-loading-copy';
|
|
1079
|
+
copy.textContent = 'Messages are being replayed after restart. You can keep typing while CTM catches up.';
|
|
1080
|
+
wrap.appendChild(copy);
|
|
1081
|
+
|
|
1082
|
+
var skeleton = document.createElement('div');
|
|
1083
|
+
skeleton.className = 'walle-history-loading-skeleton';
|
|
1084
|
+
for (var i = 0; i < 3; i++) {
|
|
1085
|
+
var row = document.createElement('span');
|
|
1086
|
+
row.className = 'walle-history-loading-line';
|
|
1087
|
+
skeleton.appendChild(row);
|
|
1088
|
+
}
|
|
1089
|
+
wrap.appendChild(skeleton);
|
|
1090
|
+
messagesArea.appendChild(wrap);
|
|
1091
|
+
}
|
|
1092
|
+
function _removeHistoryLoadingState(messagesArea) {
|
|
1093
|
+
if (!messagesArea) return;
|
|
1094
|
+
var ex = messagesArea.querySelector('.walle-history-loading-state');
|
|
1095
|
+
if (ex && ex.parentNode) ex.parentNode.removeChild(ex);
|
|
1096
|
+
}
|
|
538
1097
|
function _removeEmptyStateChips(messagesArea) {
|
|
539
1098
|
if (!messagesArea) return;
|
|
540
1099
|
var ex = messagesArea.querySelector('.walle-empty-state');
|
|
541
1100
|
if (ex && ex.parentNode) ex.parentNode.removeChild(ex);
|
|
542
1101
|
}
|
|
1102
|
+
function _removeEmptyAndLoadingState(messagesArea) {
|
|
1103
|
+
_removeEmptyStateChips(messagesArea);
|
|
1104
|
+
_removeHistoryLoadingState(messagesArea);
|
|
1105
|
+
}
|
|
543
1106
|
|
|
544
1107
|
function tokenParam() {
|
|
545
1108
|
return state && state.token ? '&token=' + encodeURIComponent(state.token) : '';
|
|
@@ -607,10 +1170,17 @@ window.WalleSession = (function() {
|
|
|
607
1170
|
ws.inputDraft = '';
|
|
608
1171
|
}
|
|
609
1172
|
|
|
610
|
-
function canRecallHistory(textarea, key) {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
1173
|
+
function canRecallHistory(textarea, key, browsingHistory) {
|
|
1174
|
+
if (!textarea) return false;
|
|
1175
|
+
var value = textarea.value || '';
|
|
1176
|
+
var start = typeof textarea.selectionStart === 'number' ? textarea.selectionStart : value.length;
|
|
1177
|
+
var end = typeof textarea.selectionEnd === 'number' ? textarea.selectionEnd : start;
|
|
1178
|
+
if (start !== end) return false;
|
|
1179
|
+
var atStart = start === 0;
|
|
1180
|
+
var atEnd = end === value.length;
|
|
1181
|
+
var singleLine = value.indexOf('\n') < 0;
|
|
1182
|
+
if (key === 'ArrowUp') return atStart || (atEnd && (singleLine || browsingHistory));
|
|
1183
|
+
if (key === 'ArrowDown') return atEnd;
|
|
614
1184
|
return false;
|
|
615
1185
|
}
|
|
616
1186
|
|
|
@@ -678,18 +1248,17 @@ window.WalleSession = (function() {
|
|
|
678
1248
|
if (!isFinite(minHeight) || minHeight <= 0) minHeight = 44;
|
|
679
1249
|
if (!isFinite(maxHeight) || maxHeight <= 0) maxHeight = 260;
|
|
680
1250
|
textarea.style.height = 'auto';
|
|
1251
|
+
var preferredHeight = null;
|
|
1252
|
+
var effectiveMaxHeight = maxHeight;
|
|
681
1253
|
if (ws && ws.composerHeightPx != null) {
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
textarea.classList.toggle('is-scrollable', fixedScrollable);
|
|
686
|
-
textarea.style.overflowY = fixedScrollable ? 'auto' : 'hidden';
|
|
687
|
-
if (id) syncComposerResizeHandle(id, textarea);
|
|
688
|
-
return;
|
|
1254
|
+
preferredHeight = clampComposerHeight(id, textarea, ws.composerHeightPx);
|
|
1255
|
+
var bounds = composerHeightBounds(id, textarea);
|
|
1256
|
+
effectiveMaxHeight = Math.min(bounds.max, Math.max(maxHeight, preferredHeight));
|
|
689
1257
|
}
|
|
690
|
-
var next = Math.max(minHeight, Math.min(textarea.scrollHeight,
|
|
1258
|
+
var next = Math.max(minHeight, Math.min(textarea.scrollHeight, effectiveMaxHeight));
|
|
1259
|
+
if (preferredHeight != null) next = Math.min(effectiveMaxHeight, Math.max(preferredHeight, next));
|
|
691
1260
|
textarea.style.height = next + 'px';
|
|
692
|
-
var scrollable = textarea.scrollHeight >
|
|
1261
|
+
var scrollable = textarea.scrollHeight > next + 1;
|
|
693
1262
|
textarea.classList.toggle('is-scrollable', scrollable);
|
|
694
1263
|
textarea.style.overflowY = scrollable ? 'auto' : 'hidden';
|
|
695
1264
|
if (id) syncComposerResizeHandle(id, textarea);
|
|
@@ -720,7 +1289,68 @@ window.WalleSession = (function() {
|
|
|
720
1289
|
}
|
|
721
1290
|
}
|
|
722
1291
|
|
|
1292
|
+
function snapshotComposerTextarea(textarea) {
|
|
1293
|
+
if (!textarea) return null;
|
|
1294
|
+
var value = textarea.value || '';
|
|
1295
|
+
var start = typeof textarea.selectionStart === 'number' ? textarea.selectionStart : value.length;
|
|
1296
|
+
var end = typeof textarea.selectionEnd === 'number' ? textarea.selectionEnd : start;
|
|
1297
|
+
start = Math.max(0, Math.min(start, value.length));
|
|
1298
|
+
end = Math.max(0, Math.min(end, value.length));
|
|
1299
|
+
return {
|
|
1300
|
+
value: value,
|
|
1301
|
+
selectionStart: start,
|
|
1302
|
+
selectionEnd: end,
|
|
1303
|
+
focused: document.activeElement === textarea,
|
|
1304
|
+
updatedAt: Date.now()
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
function cloneComposerSnapshot(snapshot) {
|
|
1309
|
+
if (!snapshot || typeof snapshot !== 'object') return null;
|
|
1310
|
+
var value = typeof snapshot.value === 'string' ? snapshot.value : '';
|
|
1311
|
+
var start = typeof snapshot.selectionStart === 'number' ? snapshot.selectionStart : value.length;
|
|
1312
|
+
var end = typeof snapshot.selectionEnd === 'number' ? snapshot.selectionEnd : start;
|
|
1313
|
+
start = Math.max(0, Math.min(start, value.length));
|
|
1314
|
+
end = Math.max(0, Math.min(end, value.length));
|
|
1315
|
+
return {
|
|
1316
|
+
value: value,
|
|
1317
|
+
selectionStart: start,
|
|
1318
|
+
selectionEnd: end,
|
|
1319
|
+
focused: snapshot.focused === true,
|
|
1320
|
+
updatedAt: typeof snapshot.updatedAt === 'number' ? snapshot.updatedAt : Date.now()
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
function rememberComposerSnapshot(id, snapshot) {
|
|
1325
|
+
var ws = getState(id);
|
|
1326
|
+
var normalized = cloneComposerSnapshot(snapshot);
|
|
1327
|
+
if (!ws || !normalized) return normalized;
|
|
1328
|
+
ws.composerDraft = normalized;
|
|
1329
|
+
return normalized;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
function rememberComposerTextarea(id, textarea) {
|
|
1333
|
+
if (!textarea) return null;
|
|
1334
|
+
return rememberComposerSnapshot(id, snapshotComposerTextarea(textarea));
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
function storedComposerSnapshot(id) {
|
|
1338
|
+
var ws = getState(id);
|
|
1339
|
+
return ws ? cloneComposerSnapshot(ws.composerDraft) : null;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
function clearComposerDraft(id, textarea) {
|
|
1343
|
+
var snapshot = { value: '', selectionStart: 0, selectionEnd: 0, focused: !!(textarea && document.activeElement === textarea), updatedAt: Date.now() };
|
|
1344
|
+
rememberComposerSnapshot(id, snapshot);
|
|
1345
|
+
var ws = getState(id);
|
|
1346
|
+
if (ws) {
|
|
1347
|
+
ws.inputHistoryIdx = -1;
|
|
1348
|
+
ws.inputDraft = '';
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
|
|
723
1352
|
function syncComposerChrome(id, textarea) {
|
|
1353
|
+
rememberComposerTextarea(id, textarea);
|
|
724
1354
|
resizeComposerTextarea(textarea);
|
|
725
1355
|
syncComposerPreview(id);
|
|
726
1356
|
}
|
|
@@ -734,7 +1364,7 @@ window.WalleSession = (function() {
|
|
|
734
1364
|
handle.setAttribute('aria-label', 'Resize Wall-E composer');
|
|
735
1365
|
handle.setAttribute('aria-controls', 'walle-input-' + id);
|
|
736
1366
|
handle.tabIndex = 0;
|
|
737
|
-
handle.title = 'Drag to
|
|
1367
|
+
handle.title = 'Drag to set the minimum composer height. Double-click to reset.';
|
|
738
1368
|
var grip = document.createElement('span');
|
|
739
1369
|
grip.className = 'walle-composer-resize-grip';
|
|
740
1370
|
grip.setAttribute('aria-hidden', 'true');
|
|
@@ -811,28 +1441,25 @@ window.WalleSession = (function() {
|
|
|
811
1441
|
var root = document.getElementById('walle-session-' + id);
|
|
812
1442
|
var textarea = root && root.querySelector ? root.querySelector('.walle-input') : null;
|
|
813
1443
|
if (!textarea) return null;
|
|
814
|
-
return
|
|
815
|
-
value: textarea.value || '',
|
|
816
|
-
selectionStart: textarea.selectionStart || 0,
|
|
817
|
-
selectionEnd: textarea.selectionEnd || textarea.selectionStart || 0,
|
|
818
|
-
focused: document.activeElement === textarea,
|
|
819
|
-
};
|
|
1444
|
+
return rememberComposerTextarea(id, textarea);
|
|
820
1445
|
}
|
|
821
1446
|
|
|
822
1447
|
function restoreComposerState(id, snapshot) {
|
|
823
|
-
|
|
1448
|
+
var restore = cloneComposerSnapshot(snapshot) || storedComposerSnapshot(id);
|
|
1449
|
+
if (!restore) return;
|
|
824
1450
|
var root = document.getElementById('walle-session-' + id);
|
|
825
1451
|
var textarea = root && root.querySelector ? root.querySelector('.walle-input') : null;
|
|
826
1452
|
if (!textarea) return;
|
|
827
|
-
textarea.value =
|
|
1453
|
+
textarea.value = restore.value || '';
|
|
828
1454
|
syncComposerChrome(id, textarea);
|
|
829
1455
|
try {
|
|
830
1456
|
textarea.setSelectionRange(
|
|
831
|
-
Math.min(
|
|
832
|
-
Math.min(
|
|
1457
|
+
Math.min(restore.selectionStart || 0, textarea.value.length),
|
|
1458
|
+
Math.min(restore.selectionEnd || restore.selectionStart || 0, textarea.value.length)
|
|
833
1459
|
);
|
|
834
1460
|
} catch (_) {}
|
|
835
|
-
|
|
1461
|
+
rememberComposerTextarea(id, textarea);
|
|
1462
|
+
if (restore.focused) {
|
|
836
1463
|
try { textarea.focus(); } catch (_) {}
|
|
837
1464
|
}
|
|
838
1465
|
}
|
|
@@ -847,8 +1474,10 @@ window.WalleSession = (function() {
|
|
|
847
1474
|
function recallInputHistory(id, textarea, dir) {
|
|
848
1475
|
var ws = getState(id);
|
|
849
1476
|
if (!ws || !textarea || !Array.isArray(ws.inputHistory) || ws.inputHistory.length === 0) return false;
|
|
1477
|
+
var browsingHistory = ws.inputHistoryIdx !== -1;
|
|
850
1478
|
if (dir < 0) {
|
|
851
|
-
if (!canRecallHistory(textarea, 'ArrowUp')) return false;
|
|
1479
|
+
if (!canRecallHistory(textarea, 'ArrowUp', browsingHistory)) return false;
|
|
1480
|
+
if (!browsingHistory && (textarea.value || '').length > 0) return false;
|
|
852
1481
|
if (ws.inputHistoryIdx === -1) {
|
|
853
1482
|
ws.inputDraft = textarea.value;
|
|
854
1483
|
ws.inputHistoryIdx = 0;
|
|
@@ -858,7 +1487,7 @@ window.WalleSession = (function() {
|
|
|
858
1487
|
setTextareaValue(textarea, ws.inputHistory[ws.inputHistoryIdx] || '');
|
|
859
1488
|
return true;
|
|
860
1489
|
}
|
|
861
|
-
if (!canRecallHistory(textarea, 'ArrowDown') || ws.inputHistoryIdx === -1) return false;
|
|
1490
|
+
if (!canRecallHistory(textarea, 'ArrowDown', browsingHistory) || ws.inputHistoryIdx === -1) return false;
|
|
862
1491
|
ws.inputHistoryIdx--;
|
|
863
1492
|
if (ws.inputHistoryIdx < 0) {
|
|
864
1493
|
ws.inputHistoryIdx = -1;
|
|
@@ -870,6 +1499,14 @@ window.WalleSession = (function() {
|
|
|
870
1499
|
return true;
|
|
871
1500
|
}
|
|
872
1501
|
|
|
1502
|
+
function shouldProtectDraftHistoryArrow(id, textarea, key) {
|
|
1503
|
+
var ws = getState(id);
|
|
1504
|
+
if (!ws || !textarea || !Array.isArray(ws.inputHistory) || ws.inputHistory.length === 0) return false;
|
|
1505
|
+
if (ws.inputHistoryIdx !== -1) return false;
|
|
1506
|
+
if (!(textarea.value || '').length) return false;
|
|
1507
|
+
return canRecallHistory(textarea, key, false);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
873
1510
|
function resetInputHistoryBrowse(ws) {
|
|
874
1511
|
if (!ws) return;
|
|
875
1512
|
ws.inputHistoryIdx = -1;
|
|
@@ -904,6 +1541,131 @@ window.WalleSession = (function() {
|
|
|
904
1541
|
});
|
|
905
1542
|
}
|
|
906
1543
|
|
|
1544
|
+
function isWalleSessionVisible(id) {
|
|
1545
|
+
var root = document.getElementById('walle-session-' + id);
|
|
1546
|
+
if (state && state.activeTab === id) return true;
|
|
1547
|
+
if (root && root.classList && root.classList.contains('active')) return true;
|
|
1548
|
+
try {
|
|
1549
|
+
if (typeof isSessionVisibleInSplit === 'function' && isSessionVisibleInSplit(id)) return true;
|
|
1550
|
+
} catch (_) {}
|
|
1551
|
+
return false;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
function nextFrame(fn) {
|
|
1555
|
+
if (typeof requestAnimationFrame === 'function') return requestAnimationFrame(fn);
|
|
1556
|
+
return setTimeout(fn, 16);
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
function cancelFrame(handle) {
|
|
1560
|
+
if (!handle) return;
|
|
1561
|
+
try {
|
|
1562
|
+
if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(handle);
|
|
1563
|
+
else clearTimeout(handle);
|
|
1564
|
+
} catch (_) {}
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
function cancelMessageRender(ws) {
|
|
1568
|
+
if (!ws) return;
|
|
1569
|
+
ws.messageRenderEpoch = Number(ws.messageRenderEpoch || 0) + 1;
|
|
1570
|
+
cancelFrame(ws.messageRenderFrame);
|
|
1571
|
+
ws.messageRenderFrame = 0;
|
|
1572
|
+
ws.renderingMessages = false;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
function finalizeMessageRender(id, ws, messagesArea, textarea, epoch) {
|
|
1576
|
+
if (!ws || Number(ws.messageRenderEpoch || 0) !== epoch) return;
|
|
1577
|
+
ws.messageRenderFrame = 0;
|
|
1578
|
+
ws.renderingMessages = false;
|
|
1579
|
+
ws.renderPending = false;
|
|
1580
|
+
if (messagesArea) messagesArea.dataset.walleRenderState = 'complete';
|
|
1581
|
+
syncPromptMessageDomIndices(id);
|
|
1582
|
+
updatePromptNav(id);
|
|
1583
|
+
renderWorkBar(id);
|
|
1584
|
+
renderQueuePreview(id);
|
|
1585
|
+
renderComposerAttachments(id);
|
|
1586
|
+
if (textarea) syncComposerChrome(id, textarea);
|
|
1587
|
+
if (messagesArea && isWalleSessionVisible(id)) scrollToBottom(messagesArea);
|
|
1588
|
+
focusInput(id);
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
function renderMessagesIncrementally(id, ws, messagesArea, textarea) {
|
|
1592
|
+
if (!ws || !messagesArea) return;
|
|
1593
|
+
cancelMessageRender(ws);
|
|
1594
|
+
messagesArea.dataset.lastDate = '';
|
|
1595
|
+
messagesArea.dataset.walleRenderState = 'idle';
|
|
1596
|
+
|
|
1597
|
+
var messages = Array.isArray(ws.messages) ? ws.messages.slice() : [];
|
|
1598
|
+
if (!messages.length) {
|
|
1599
|
+
finalizeMessageRender(id, ws, messagesArea, textarea, Number(ws.messageRenderEpoch || 0));
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
var epoch = Number(ws.messageRenderEpoch || 0) + 1;
|
|
1604
|
+
ws.messageRenderEpoch = epoch;
|
|
1605
|
+
ws.renderPending = false;
|
|
1606
|
+
ws.renderingMessages = messages.length > MESSAGE_RENDER_SYNC_LIMIT;
|
|
1607
|
+
|
|
1608
|
+
var index = 0;
|
|
1609
|
+
var renderOne = function() {
|
|
1610
|
+
renderMessage(messagesArea, messages[index], index);
|
|
1611
|
+
index += 1;
|
|
1612
|
+
};
|
|
1613
|
+
|
|
1614
|
+
if (messages.length <= MESSAGE_RENDER_SYNC_LIMIT) {
|
|
1615
|
+
while (index < messages.length) renderOne();
|
|
1616
|
+
finalizeMessageRender(id, ws, messagesArea, textarea, epoch);
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
messagesArea.dataset.walleRenderState = 'rendering';
|
|
1621
|
+
var appendBatch = function() {
|
|
1622
|
+
if (!ws || Number(ws.messageRenderEpoch || 0) !== epoch || !messagesArea.isConnected) return;
|
|
1623
|
+
if (!isWalleSessionVisible(id)) {
|
|
1624
|
+
ws.messageRenderFrame = 0;
|
|
1625
|
+
ws.renderingMessages = false;
|
|
1626
|
+
ws.renderPending = true;
|
|
1627
|
+
messagesArea.dataset.walleRenderState = 'paused';
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
var started = (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now();
|
|
1632
|
+
var rendered = 0;
|
|
1633
|
+
while (index < messages.length && rendered < MESSAGE_RENDER_BATCH_SIZE) {
|
|
1634
|
+
renderOne();
|
|
1635
|
+
rendered += 1;
|
|
1636
|
+
var now = (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now();
|
|
1637
|
+
if (now - started >= MESSAGE_RENDER_FRAME_BUDGET_MS) break;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
if (messagesArea && isWalleSessionVisible(id)) scrollToBottom(messagesArea);
|
|
1641
|
+
if (index < messages.length) {
|
|
1642
|
+
ws.messageRenderFrame = nextFrame(appendBatch);
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
finalizeMessageRender(id, ws, messagesArea, textarea, epoch);
|
|
1646
|
+
// Re-attach inline queued-prompt bubbles after a full message re-render.
|
|
1647
|
+
renderPendingQueueBubbles(id);
|
|
1648
|
+
};
|
|
1649
|
+
|
|
1650
|
+
appendBatch();
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
function messageRenderNeedsRestart(id) {
|
|
1654
|
+
var ws = getState(id);
|
|
1655
|
+
return !!(ws && ws.renderPending);
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
function restartVisibleMessageRenderIfBusy(id, ws) {
|
|
1659
|
+
if (!ws || !ws.renderingMessages) return false;
|
|
1660
|
+
ws.renderPending = true;
|
|
1661
|
+
if (isWalleSessionVisible(id)) {
|
|
1662
|
+
var composerSnapshot = captureComposerState(id);
|
|
1663
|
+
renderSession(id);
|
|
1664
|
+
restoreComposerState(id, composerSnapshot);
|
|
1665
|
+
}
|
|
1666
|
+
return true;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
907
1669
|
function isComposerTextTarget(target) {
|
|
908
1670
|
if (!target || !target.closest) return false;
|
|
909
1671
|
return !!target.closest('input, textarea, select, button, a, [contenteditable="true"], [contenteditable=""]');
|
|
@@ -1147,6 +1909,10 @@ window.WalleSession = (function() {
|
|
|
1147
1909
|
|
|
1148
1910
|
function redirectComposerKeydown(id, e) {
|
|
1149
1911
|
if (!e || isComposerTextTarget(e.target)) return;
|
|
1912
|
+
if (e.key === 'Escape') {
|
|
1913
|
+
confirmCancelActiveRun(id, e);
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1150
1916
|
if (isMacCtrlVPasteShortcut(e)) {
|
|
1151
1917
|
var pasteContainer = document.getElementById('walle-session-' + id);
|
|
1152
1918
|
var pasteTextarea = pasteContainer && pasteContainer.querySelector('.walle-input');
|
|
@@ -1230,8 +1996,43 @@ window.WalleSession = (function() {
|
|
|
1230
1996
|
var a = attachments[i] || {};
|
|
1231
1997
|
var thumb = document.createElement('button');
|
|
1232
1998
|
thumb.type = 'button';
|
|
1233
|
-
thumb.className = 'walle-attachment-thumb';
|
|
1234
1999
|
var label = a.label || ('[Image #' + (i + 1) + ']');
|
|
2000
|
+
if (a.pending) {
|
|
2001
|
+
// Optimistic placeholder while the image resizes+compresses.
|
|
2002
|
+
thumb.className = 'walle-attachment-thumb walle-attachment-thumb--pending';
|
|
2003
|
+
thumb.disabled = true;
|
|
2004
|
+
thumb.title = label + ' — compressing…';
|
|
2005
|
+
var spinner = document.createElement('span');
|
|
2006
|
+
spinner.className = 'walle-attachment-spinner';
|
|
2007
|
+
spinner.setAttribute('aria-hidden', 'true');
|
|
2008
|
+
thumb.appendChild(spinner);
|
|
2009
|
+
var pendingToken = document.createElement('span');
|
|
2010
|
+
pendingToken.className = 'walle-attachment-token';
|
|
2011
|
+
pendingToken.textContent = 'compressing…';
|
|
2012
|
+
thumb.appendChild(pendingToken);
|
|
2013
|
+
if (editable) {
|
|
2014
|
+
var pendingRemove = document.createElement('span');
|
|
2015
|
+
pendingRemove.className = 'walle-attachment-remove';
|
|
2016
|
+
pendingRemove.textContent = '×';
|
|
2017
|
+
pendingRemove.title = 'Remove image';
|
|
2018
|
+
pendingRemove.addEventListener('click', (function(idx) {
|
|
2019
|
+
return function(e) {
|
|
2020
|
+
e.preventDefault();
|
|
2021
|
+
e.stopPropagation();
|
|
2022
|
+
var ws = getState(id);
|
|
2023
|
+
if (ws && Array.isArray(ws.inputAttachments)) {
|
|
2024
|
+
ws.inputAttachments.splice(idx, 1);
|
|
2025
|
+
renderComposerAttachments(id);
|
|
2026
|
+
updateSendButton(id, !!ws.isGenerating);
|
|
2027
|
+
}
|
|
2028
|
+
};
|
|
2029
|
+
})(i));
|
|
2030
|
+
thumb.appendChild(pendingRemove);
|
|
2031
|
+
}
|
|
2032
|
+
strip.appendChild(thumb);
|
|
2033
|
+
continue;
|
|
2034
|
+
}
|
|
2035
|
+
thumb.className = 'walle-attachment-thumb';
|
|
1235
2036
|
thumb.title = label + ' — click to preview, right-click for path or URL';
|
|
1236
2037
|
thumb.dataset.index = String(i);
|
|
1237
2038
|
var img = document.createElement('img');
|
|
@@ -1326,20 +2127,50 @@ window.WalleSession = (function() {
|
|
|
1326
2127
|
});
|
|
1327
2128
|
}
|
|
1328
2129
|
|
|
2130
|
+
function hasPendingAttachments(ws) {
|
|
2131
|
+
return !!(ws && Array.isArray(ws.inputAttachments)
|
|
2132
|
+
&& ws.inputAttachments.some(function(a) { return a && a.pending; }));
|
|
2133
|
+
}
|
|
2134
|
+
|
|
1329
2135
|
async function attachImageBlob(id, blob, filename) {
|
|
1330
2136
|
var ws = getState(id);
|
|
1331
2137
|
if (!ws || !blob) return;
|
|
1332
|
-
var
|
|
1333
|
-
var
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
2138
|
+
var label = nextAttachmentLabel(ws);
|
|
2139
|
+
var safeName = sanitizeImageFilename(filename, extensionForMime(blob.type));
|
|
2140
|
+
// Show an optimistic "compressing…" chip the instant the image is pasted and
|
|
2141
|
+
// gate send until it's resized+compressed (a large screenshot takes ~0.5-1s),
|
|
2142
|
+
// so a fast Enter can't fire the message before the attachment is attached.
|
|
2143
|
+
var pendingKey = 'pending-' + Date.now() + '-' + Math.floor(Math.random() * 1e9);
|
|
2144
|
+
ws.inputAttachments.push({
|
|
2145
|
+
type: 'image', label: label, name: safeName,
|
|
2146
|
+
mediaType: blob.type || 'image/png', pending: true, _pendingKey: pendingKey,
|
|
2147
|
+
});
|
|
2148
|
+
insertAttachmentToken(id, label);
|
|
1341
2149
|
renderComposerAttachments(id);
|
|
2150
|
+
updateSendButton(id, !!ws.isGenerating);
|
|
1342
2151
|
focusInput(id);
|
|
2152
|
+
try {
|
|
2153
|
+
var dataUrl = await readBlobAsDataUrl(blob);
|
|
2154
|
+
var img = await uploadAttachmentBlob(blob, filename);
|
|
2155
|
+
img.label = label;
|
|
2156
|
+
img.type = 'image';
|
|
2157
|
+
img.name = img.filename || safeName;
|
|
2158
|
+
img.mediaType = blob.type || 'image/png';
|
|
2159
|
+
img.data = dataUrl;
|
|
2160
|
+
img.pending = false;
|
|
2161
|
+
// Replace the placeholder in place. If the user removed it while it was
|
|
2162
|
+
// processing, honor that and don't re-add the image.
|
|
2163
|
+
var idx = ws.inputAttachments.findIndex(function(a) { return a && a._pendingKey === pendingKey; });
|
|
2164
|
+
if (idx >= 0) ws.inputAttachments[idx] = img;
|
|
2165
|
+
renderComposerAttachments(id);
|
|
2166
|
+
} catch (err) {
|
|
2167
|
+
var failIdx = ws.inputAttachments.findIndex(function(a) { return a && a._pendingKey === pendingKey; });
|
|
2168
|
+
if (failIdx >= 0) ws.inputAttachments.splice(failIdx, 1);
|
|
2169
|
+
renderComposerAttachments(id);
|
|
2170
|
+
throw err;
|
|
2171
|
+
} finally {
|
|
2172
|
+
updateSendButton(id, !!ws.isGenerating);
|
|
2173
|
+
}
|
|
1343
2174
|
}
|
|
1344
2175
|
|
|
1345
2176
|
async function handlePaste(id, e) {
|
|
@@ -1484,7 +2315,9 @@ window.WalleSession = (function() {
|
|
|
1484
2315
|
function renderSession(id) {
|
|
1485
2316
|
var s = state.sessions.get(id);
|
|
1486
2317
|
if (!s) return;
|
|
2318
|
+
var composerSnapshot = captureComposerState(id);
|
|
1487
2319
|
var ws = getState(id);
|
|
2320
|
+
var initialComposerSnapshot = composerSnapshot || storedComposerSnapshot(id);
|
|
1488
2321
|
|
|
1489
2322
|
var container = s.container || document.getElementById('walle-session-' + id);
|
|
1490
2323
|
if (!container) return;
|
|
@@ -1505,7 +2338,9 @@ window.WalleSession = (function() {
|
|
|
1505
2338
|
info.className = 'session-info';
|
|
1506
2339
|
var title = document.createElement('div');
|
|
1507
2340
|
title.className = 'session-title';
|
|
1508
|
-
title.textContent =
|
|
2341
|
+
title.textContent = (typeof activeSessionDisplayLabel === 'function'
|
|
2342
|
+
? activeSessionDisplayLabel(s, id)
|
|
2343
|
+
: (s.meta?.displayTitle || s.meta?.aiTitle || s.meta?.title || s.meta?.label)) || 'Wall-E';
|
|
1509
2344
|
info.appendChild(title);
|
|
1510
2345
|
|
|
1511
2346
|
var meta = document.createElement('div');
|
|
@@ -1513,7 +2348,24 @@ window.WalleSession = (function() {
|
|
|
1513
2348
|
meta.id = 'walle-meta-' + id;
|
|
1514
2349
|
var cwd = s.meta?.cwd || '';
|
|
1515
2350
|
var msgCount = ws.messageCount || 0;
|
|
1516
|
-
|
|
2351
|
+
// Working directory is editable: clicking it opens an inline input that
|
|
2352
|
+
// sends `update-session-cwd`. A Wall-E chat session is bound to the dir it
|
|
2353
|
+
// was created in; when the user wants work to happen elsewhere (e.g. "build
|
|
2354
|
+
// it under ~/ws/sl-web, don't touch ~/ws/octo") the model otherwise has to
|
|
2355
|
+
// prefix every run_shell with `cd <target>`, which trips the `cd * -> ask`
|
|
2356
|
+
// permission rule on every command. Setting the cwd here points the coding
|
|
2357
|
+
// runtime (effectiveCwd) at the intended dir so no `cd` prefix is needed.
|
|
2358
|
+
var cwdEl = renderSessionCwdControl(id, cwd);
|
|
2359
|
+
meta.appendChild(cwdEl);
|
|
2360
|
+
var countText = document.createElement('span');
|
|
2361
|
+
countText.className = 'session-meta-count';
|
|
2362
|
+
countText.style.marginLeft = cwd ? '8px' : '0';
|
|
2363
|
+
if (historyStatusForSession(id, ws) === 'loading' && msgCount === 0) {
|
|
2364
|
+
countText.textContent = 'Loading messages...';
|
|
2365
|
+
} else {
|
|
2366
|
+
countText.textContent = msgCount + ' messages';
|
|
2367
|
+
}
|
|
2368
|
+
meta.appendChild(countText);
|
|
1517
2369
|
info.appendChild(meta);
|
|
1518
2370
|
header.appendChild(info);
|
|
1519
2371
|
|
|
@@ -1606,6 +2458,11 @@ window.WalleSession = (function() {
|
|
|
1606
2458
|
messagesArea.id = 'walle-messages-' + id;
|
|
1607
2459
|
messagesArea.tabIndex = -1;
|
|
1608
2460
|
|
|
2461
|
+
var workbarSlot = document.createElement('div');
|
|
2462
|
+
workbarSlot.className = 'walle-workbar-slot';
|
|
2463
|
+
workbarSlot.id = 'walle-workbar-' + id;
|
|
2464
|
+
workbarSlot.style.display = 'none';
|
|
2465
|
+
|
|
1609
2466
|
// Input bar
|
|
1610
2467
|
var inputBar = document.createElement('div');
|
|
1611
2468
|
inputBar.className = 'walle-input-bar';
|
|
@@ -1613,6 +2470,11 @@ window.WalleSession = (function() {
|
|
|
1613
2470
|
var inputStack = document.createElement('div');
|
|
1614
2471
|
inputStack.className = 'walle-input-stack';
|
|
1615
2472
|
|
|
2473
|
+
var queuePreview = document.createElement('div');
|
|
2474
|
+
queuePreview.className = 'walle-queue-preview';
|
|
2475
|
+
queuePreview.id = 'walle-queue-preview-' + id;
|
|
2476
|
+
inputStack.appendChild(queuePreview);
|
|
2477
|
+
|
|
1616
2478
|
var attachmentPreview = document.createElement('div');
|
|
1617
2479
|
attachmentPreview.className = 'walle-composer-attachments';
|
|
1618
2480
|
attachmentPreview.id = 'walle-attachments-' + id;
|
|
@@ -1628,6 +2490,9 @@ window.WalleSession = (function() {
|
|
|
1628
2490
|
textarea.id = 'walle-input-' + id;
|
|
1629
2491
|
textarea.placeholder = 'Message Wall-E... paste images here';
|
|
1630
2492
|
textarea.rows = 1;
|
|
2493
|
+
if (initialComposerSnapshot && initialComposerSnapshot.value) {
|
|
2494
|
+
textarea.value = initialComposerSnapshot.value;
|
|
2495
|
+
}
|
|
1631
2496
|
textarea.dataset.skillAgent = 'walle';
|
|
1632
2497
|
textarea.dataset.skillCwd = s.meta?.cwd || s.meta?.project_path || s.meta?.project || '';
|
|
1633
2498
|
textarea.dataset.skillMode = 'walle-composer';
|
|
@@ -1649,23 +2514,22 @@ window.WalleSession = (function() {
|
|
|
1649
2514
|
e.preventDefault();
|
|
1650
2515
|
return;
|
|
1651
2516
|
}
|
|
1652
|
-
|
|
1653
|
-
if (e.key === 'Escape') {
|
|
1654
|
-
var ws = getState(id);
|
|
1655
|
-
if (ws && ws.isGenerating) {
|
|
2517
|
+
if (shouldProtectDraftHistoryArrow(id, textarea, e.key)) {
|
|
1656
2518
|
e.preventDefault();
|
|
1657
|
-
|
|
2519
|
+
rememberComposerTextarea(id, textarea);
|
|
2520
|
+
return;
|
|
1658
2521
|
}
|
|
1659
|
-
|
|
2522
|
+
}
|
|
2523
|
+
if (e.key === 'Escape') {
|
|
2524
|
+
confirmCancelActiveRun(id, e);
|
|
2525
|
+
return;
|
|
1660
2526
|
}
|
|
1661
2527
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
1662
|
-
//
|
|
1663
|
-
//
|
|
2528
|
+
// Shift+Enter inserts a newline. ⌘/Ctrl+Enter = "send now" (interrupt a
|
|
2529
|
+
// busy run and send next); plain Enter = send, or queue when busy.
|
|
1664
2530
|
e.preventDefault();
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
e.preventDefault();
|
|
1668
|
-
sendMessage(id);
|
|
2531
|
+
if (e.metaKey || e.ctrlKey) sendNow(id);
|
|
2532
|
+
else sendMessage(id);
|
|
1669
2533
|
}
|
|
1670
2534
|
});
|
|
1671
2535
|
textarea.addEventListener('input', function() {
|
|
@@ -1677,7 +2541,11 @@ window.WalleSession = (function() {
|
|
|
1677
2541
|
pe.checkSlashTriggerAtCursor(textarea);
|
|
1678
2542
|
}
|
|
1679
2543
|
});
|
|
2544
|
+
textarea.addEventListener('keyup', function() { rememberComposerTextarea(id, textarea); });
|
|
2545
|
+
textarea.addEventListener('click', function() { rememberComposerTextarea(id, textarea); });
|
|
2546
|
+
textarea.addEventListener('select', function() { rememberComposerTextarea(id, textarea); });
|
|
1680
2547
|
textarea.addEventListener('blur', function() {
|
|
2548
|
+
rememberComposerTextarea(id, textarea);
|
|
1681
2549
|
var pe = promptEditorApi();
|
|
1682
2550
|
if (!pe || typeof pe.hideSlashPicker !== 'function') return;
|
|
1683
2551
|
setTimeout(function() {
|
|
@@ -1716,13 +2584,14 @@ window.WalleSession = (function() {
|
|
|
1716
2584
|
// send key in Sprint 1). Mac users see ⌘; everyone else sees Ctrl.
|
|
1717
2585
|
var isMac = (navigator.platform || '').toUpperCase().indexOf('MAC') >= 0;
|
|
1718
2586
|
var sendChord = isMac ? '⌘' : 'Ctrl';
|
|
1719
|
-
hints.textContent = 'Enter
|
|
2587
|
+
hints.textContent = 'Enter to send (queues while busy) · ' + sendChord + '+Enter to send now · Shift+Enter for newline · Up/Down for history · ' + sendChord + '+B/I/K · paste images to attach';
|
|
1720
2588
|
|
|
1721
2589
|
// Assemble — clear and rebuild. Sprint 2: only one chrome row now
|
|
1722
2590
|
// (the merged header). Reclaims ~36px of vertical space.
|
|
1723
2591
|
while (container.firstChild) container.removeChild(container.firstChild);
|
|
1724
2592
|
container.appendChild(header);
|
|
1725
2593
|
container.appendChild(messagesArea);
|
|
2594
|
+
container.appendChild(workbarSlot);
|
|
1726
2595
|
container.appendChild(createComposerResizeHandle(id));
|
|
1727
2596
|
container.appendChild(inputBar);
|
|
1728
2597
|
container.appendChild(hints);
|
|
@@ -1742,18 +2611,16 @@ window.WalleSession = (function() {
|
|
|
1742
2611
|
});
|
|
1743
2612
|
}
|
|
1744
2613
|
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
renderMessage(messagesArea, ws.messages[i]);
|
|
1748
|
-
}
|
|
1749
|
-
syncPromptMessageDomIndices(id);
|
|
1750
|
-
updatePromptNav(id);
|
|
2614
|
+
renderWorkBar(id);
|
|
2615
|
+
renderQueuePreview(id);
|
|
1751
2616
|
renderComposerAttachments(id);
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
focusInput(id);
|
|
2617
|
+
restoreComposerState(id, initialComposerSnapshot);
|
|
2618
|
+
updateSendButton(id, !!ws.isGenerating);
|
|
2619
|
+
if (!initialComposerSnapshot || !initialComposerSnapshot.focused) focusInput(id);
|
|
2620
|
+
renderMessagesIncrementally(id, ws, messagesArea, textarea);
|
|
1755
2621
|
|
|
1756
2622
|
loadWalleModels().then(function() { syncWalleModelButtons(id); }).catch(function() {});
|
|
2623
|
+
ensureWalleBranchSnapshotLoaded(id);
|
|
1757
2624
|
}
|
|
1758
2625
|
|
|
1759
2626
|
// ---------- date divider ----------
|
|
@@ -1778,18 +2645,169 @@ window.WalleSession = (function() {
|
|
|
1778
2645
|
container.appendChild(divider);
|
|
1779
2646
|
}
|
|
1780
2647
|
|
|
2648
|
+
function renderBranchNav(id, messageIndex) {
|
|
2649
|
+
var ws = getState(id);
|
|
2650
|
+
var branches = ws && ws.branches ? ws.branches[String(messageIndex)] : null;
|
|
2651
|
+
if (!Array.isArray(branches) || branches.length <= 1) return null;
|
|
2652
|
+
var active = Number(ws.branchActive[String(messageIndex)] || 0);
|
|
2653
|
+
if (!Number.isFinite(active) || active < 0 || active >= branches.length) active = 0;
|
|
2654
|
+
var nav = document.createElement('span');
|
|
2655
|
+
nav.className = 'walle-branch-nav';
|
|
2656
|
+
|
|
2657
|
+
var prev = document.createElement('button');
|
|
2658
|
+
prev.type = 'button';
|
|
2659
|
+
prev.className = 'walle-branch-btn';
|
|
2660
|
+
prev.textContent = '<';
|
|
2661
|
+
prev.title = 'Previous prompt version';
|
|
2662
|
+
prev.setAttribute('aria-label', 'Previous prompt version');
|
|
2663
|
+
prev.disabled = active <= 0;
|
|
2664
|
+
prev.addEventListener('click', function(e) {
|
|
2665
|
+
e.preventDefault();
|
|
2666
|
+
e.stopPropagation();
|
|
2667
|
+
switchBranch(id, messageIndex, -1);
|
|
2668
|
+
});
|
|
2669
|
+
nav.appendChild(prev);
|
|
2670
|
+
|
|
2671
|
+
var label = document.createElement('span');
|
|
2672
|
+
label.className = 'walle-branch-label';
|
|
2673
|
+
label.textContent = (active + 1) + '/' + branches.length;
|
|
2674
|
+
label.title = 'Prompt versions';
|
|
2675
|
+
nav.appendChild(label);
|
|
2676
|
+
|
|
2677
|
+
var next = document.createElement('button');
|
|
2678
|
+
next.type = 'button';
|
|
2679
|
+
next.className = 'walle-branch-btn';
|
|
2680
|
+
next.textContent = '>';
|
|
2681
|
+
next.title = 'Next prompt version';
|
|
2682
|
+
next.setAttribute('aria-label', 'Next prompt version');
|
|
2683
|
+
next.disabled = active >= branches.length - 1;
|
|
2684
|
+
next.addEventListener('click', function(e) {
|
|
2685
|
+
e.preventDefault();
|
|
2686
|
+
e.stopPropagation();
|
|
2687
|
+
switchBranch(id, messageIndex, 1);
|
|
2688
|
+
});
|
|
2689
|
+
nav.appendChild(next);
|
|
2690
|
+
return nav;
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
function renderUserMessageActions(id, messageIndex) {
|
|
2694
|
+
var wrap = document.createElement('span');
|
|
2695
|
+
wrap.className = 'walle-msg-actions';
|
|
2696
|
+
var branchNav = renderBranchNav(id, messageIndex);
|
|
2697
|
+
if (branchNav) wrap.appendChild(branchNav);
|
|
2698
|
+
|
|
2699
|
+
var edit = document.createElement('button');
|
|
2700
|
+
edit.type = 'button';
|
|
2701
|
+
edit.className = 'walle-msg-action';
|
|
2702
|
+
edit.textContent = 'Edit';
|
|
2703
|
+
edit.title = 'Edit and resend this prompt';
|
|
2704
|
+
edit.setAttribute('aria-label', 'Edit and resend prompt');
|
|
2705
|
+
edit.addEventListener('click', function(e) {
|
|
2706
|
+
e.preventDefault();
|
|
2707
|
+
e.stopPropagation();
|
|
2708
|
+
editMessage(id, messageIndex);
|
|
2709
|
+
});
|
|
2710
|
+
wrap.appendChild(edit);
|
|
2711
|
+
|
|
2712
|
+
var del = document.createElement('button');
|
|
2713
|
+
del.type = 'button';
|
|
2714
|
+
del.className = 'walle-msg-action danger';
|
|
2715
|
+
del.textContent = 'Delete';
|
|
2716
|
+
del.title = 'Delete this prompt and everything after it';
|
|
2717
|
+
del.setAttribute('aria-label', 'Delete prompt and truncate history');
|
|
2718
|
+
del.addEventListener('click', function(e) {
|
|
2719
|
+
e.preventDefault();
|
|
2720
|
+
e.stopPropagation();
|
|
2721
|
+
deleteFromPrompt(id, messageIndex);
|
|
2722
|
+
});
|
|
2723
|
+
wrap.appendChild(del);
|
|
2724
|
+
return wrap;
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2727
|
+
function renderUserEditForm(id, messageIndex, msg) {
|
|
2728
|
+
var wrap = document.createElement('div');
|
|
2729
|
+
wrap.className = 'walle-edit-card';
|
|
2730
|
+
|
|
2731
|
+
var textarea = document.createElement('textarea');
|
|
2732
|
+
textarea.className = 'walle-edit-input';
|
|
2733
|
+
textarea.value = msg.content || '';
|
|
2734
|
+
textarea.rows = Math.min(10, Math.max(3, String(textarea.value || '').split('\n').length + 1));
|
|
2735
|
+
textarea.addEventListener('keydown', function(e) {
|
|
2736
|
+
if (e.key === 'Escape') {
|
|
2737
|
+
e.preventDefault();
|
|
2738
|
+
cancelEdit(id);
|
|
2739
|
+
} else if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
|
2740
|
+
e.preventDefault();
|
|
2741
|
+
submitEdit(id, messageIndex);
|
|
2742
|
+
}
|
|
2743
|
+
});
|
|
2744
|
+
wrap.appendChild(textarea);
|
|
2745
|
+
|
|
2746
|
+
var hint = document.createElement('div');
|
|
2747
|
+
hint.className = 'walle-edit-hint';
|
|
2748
|
+
hint.textContent = 'Resending creates a new version. Later messages stay available in the version switcher.';
|
|
2749
|
+
wrap.appendChild(hint);
|
|
2750
|
+
|
|
2751
|
+
var actions = document.createElement('div');
|
|
2752
|
+
actions.className = 'walle-edit-actions';
|
|
2753
|
+
var sendBtn = document.createElement('button');
|
|
2754
|
+
sendBtn.type = 'button';
|
|
2755
|
+
sendBtn.className = 'walle-session-action-btn primary';
|
|
2756
|
+
sendBtn.textContent = 'Resend';
|
|
2757
|
+
sendBtn.addEventListener('click', function(e) {
|
|
2758
|
+
e.preventDefault();
|
|
2759
|
+
submitEdit(id, messageIndex);
|
|
2760
|
+
});
|
|
2761
|
+
actions.appendChild(sendBtn);
|
|
2762
|
+
|
|
2763
|
+
var cancelBtn = document.createElement('button');
|
|
2764
|
+
cancelBtn.type = 'button';
|
|
2765
|
+
cancelBtn.className = 'walle-session-action-btn';
|
|
2766
|
+
cancelBtn.textContent = 'Cancel';
|
|
2767
|
+
cancelBtn.addEventListener('click', function(e) {
|
|
2768
|
+
e.preventDefault();
|
|
2769
|
+
cancelEdit(id);
|
|
2770
|
+
});
|
|
2771
|
+
actions.appendChild(cancelBtn);
|
|
2772
|
+
wrap.appendChild(actions);
|
|
2773
|
+
return wrap;
|
|
2774
|
+
}
|
|
2775
|
+
|
|
1781
2776
|
// ---------- renderMessage ----------
|
|
1782
|
-
function renderMessage(container, msg) {
|
|
2777
|
+
function renderMessage(container, msg, messageIndex) {
|
|
1783
2778
|
var sessionId = '';
|
|
1784
2779
|
if (container && container.id && container.id.indexOf('walle-messages-') === 0) {
|
|
1785
2780
|
sessionId = container.id.slice('walle-messages-'.length);
|
|
1786
2781
|
}
|
|
1787
|
-
|
|
1788
|
-
|
|
2782
|
+
if (msg && msg.liveActivity) {
|
|
2783
|
+
var livePanel = renderLiveActivity(msg.toolCalls || []);
|
|
2784
|
+
if (livePanel) {
|
|
2785
|
+
_removeEmptyAndLoadingState(container);
|
|
2786
|
+
_maybeAppendDateDivider(container, msg);
|
|
2787
|
+
livePanel.dataset.walleRole = msg.role || 'system';
|
|
2788
|
+
if (typeof messageIndex === 'number') livePanel.dataset.walleMessageIndex = String(messageIndex);
|
|
2789
|
+
container.appendChild(livePanel);
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
// Durable approval card (persisted permission_request part): render it as the
|
|
2794
|
+
// same card the live stream shows, so a reload keeps a pending approval
|
|
2795
|
+
// actionable (and an answered one shown as resolved).
|
|
2796
|
+
if (msg && msg.metadata && msg.metadata.permission && msg.metadata.permission.permId) {
|
|
2797
|
+
_removeEmptyAndLoadingState(container);
|
|
2798
|
+
_maybeAppendDateDivider(container, msg);
|
|
2799
|
+
var histPermCard = renderPermissionCardForHistory(sessionId, msg.metadata.permission);
|
|
2800
|
+
if (typeof messageIndex === 'number') histPermCard.dataset.walleMessageIndex = String(messageIndex);
|
|
2801
|
+
container.appendChild(histPermCard);
|
|
2802
|
+
return;
|
|
2803
|
+
}
|
|
2804
|
+
// Sprint 3: any time a real message lands, drop the empty/loading state.
|
|
2805
|
+
_removeEmptyAndLoadingState(container);
|
|
1789
2806
|
_maybeAppendDateDivider(container, msg);
|
|
1790
2807
|
var el = document.createElement('div');
|
|
1791
2808
|
el.className = 'walle-msg';
|
|
1792
2809
|
el.dataset.walleRole = msg.role || '';
|
|
2810
|
+
if (typeof messageIndex === 'number') el.dataset.walleMessageIndex = String(messageIndex);
|
|
1793
2811
|
|
|
1794
2812
|
var avatar = document.createElement('div');
|
|
1795
2813
|
avatar.className = 'walle-msg-avatar ' + (msg.role === 'user' ? 'user' : 'assistant');
|
|
@@ -1839,6 +2857,10 @@ window.WalleSession = (function() {
|
|
|
1839
2857
|
hdr.appendChild(lat);
|
|
1840
2858
|
}
|
|
1841
2859
|
|
|
2860
|
+
if (msg.role === 'user' && sessionId && typeof messageIndex === 'number') {
|
|
2861
|
+
hdr.appendChild(renderUserMessageActions(sessionId, messageIndex));
|
|
2862
|
+
}
|
|
2863
|
+
|
|
1842
2864
|
body.appendChild(hdr);
|
|
1843
2865
|
|
|
1844
2866
|
// Tool activity (before body text). While a response is running, progress
|
|
@@ -1854,6 +2876,14 @@ window.WalleSession = (function() {
|
|
|
1854
2876
|
body.appendChild(renderAttachmentStrip(sessionId, msg.attachments, false));
|
|
1855
2877
|
}
|
|
1856
2878
|
|
|
2879
|
+
var wsForEdit = sessionId ? getState(sessionId) : null;
|
|
2880
|
+
if (msg.role === 'user' && wsForEdit && wsForEdit.editingMessageIndex === messageIndex) {
|
|
2881
|
+
body.appendChild(renderUserEditForm(sessionId, messageIndex, msg));
|
|
2882
|
+
el.appendChild(body);
|
|
2883
|
+
container.appendChild(el);
|
|
2884
|
+
return;
|
|
2885
|
+
}
|
|
2886
|
+
|
|
1857
2887
|
// Message body — rendered as sanitized markdown
|
|
1858
2888
|
var content = document.createElement('div');
|
|
1859
2889
|
content.className = 'walle-msg-body';
|
|
@@ -1880,10 +2910,14 @@ window.WalleSession = (function() {
|
|
|
1880
2910
|
card.className = 'walle-tool-card';
|
|
1881
2911
|
card.dataset.expanded = 'false';
|
|
1882
2912
|
|
|
1883
|
-
var header = document.createElement('
|
|
2913
|
+
var header = document.createElement('button');
|
|
2914
|
+
header.type = 'button';
|
|
1884
2915
|
header.className = 'walle-tool-header';
|
|
2916
|
+
header.setAttribute('aria-expanded', 'false');
|
|
1885
2917
|
header.onclick = function() {
|
|
1886
|
-
|
|
2918
|
+
var expanded = card.dataset.expanded !== 'true';
|
|
2919
|
+
card.dataset.expanded = expanded ? 'true' : 'false';
|
|
2920
|
+
header.setAttribute('aria-expanded', expanded ? 'true' : 'false');
|
|
1887
2921
|
};
|
|
1888
2922
|
|
|
1889
2923
|
var arrow = document.createElement('span');
|
|
@@ -1930,10 +2964,7 @@ window.WalleSession = (function() {
|
|
|
1930
2964
|
|
|
1931
2965
|
card.appendChild(header);
|
|
1932
2966
|
|
|
1933
|
-
|
|
1934
|
-
output.className = 'walle-tool-output';
|
|
1935
|
-
output.textContent = _formatToolOutput(tc.output);
|
|
1936
|
-
card.appendChild(output);
|
|
2967
|
+
card.appendChild(renderToolDetail(tc));
|
|
1937
2968
|
|
|
1938
2969
|
return card;
|
|
1939
2970
|
}
|
|
@@ -1977,6 +3008,162 @@ window.WalleSession = (function() {
|
|
|
1977
3008
|
return '';
|
|
1978
3009
|
}
|
|
1979
3010
|
|
|
3011
|
+
function toolPrimaryLabel(toolName) {
|
|
3012
|
+
var name = String(toolName || '').toLowerCase();
|
|
3013
|
+
if (name === 'run_shell' || name === 'shell' || name === 'bash') return 'Command';
|
|
3014
|
+
if (name === 'browser_screenshot') return 'URL';
|
|
3015
|
+
if (name === 'read_file' || name === 'read' || name === 'cat') return 'Path';
|
|
3016
|
+
if (name === 'glob') return 'Pattern';
|
|
3017
|
+
if (name === 'grep_files' || name === 'grep') return 'Search';
|
|
3018
|
+
if (name === 'think') return 'Thought';
|
|
3019
|
+
return 'Input';
|
|
3020
|
+
}
|
|
3021
|
+
|
|
3022
|
+
function fullScalarText(value) {
|
|
3023
|
+
if (value == null) return '';
|
|
3024
|
+
if (typeof value === 'string') return value;
|
|
3025
|
+
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
3026
|
+
return '';
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
function toolPrimaryText(tc) {
|
|
3030
|
+
if (!tc) return '';
|
|
3031
|
+
var toolName = String(tc.name || tc.tool || '').toLowerCase();
|
|
3032
|
+
var args = tc.args != null ? tc.args : tc.input;
|
|
3033
|
+
if (args == null) return '';
|
|
3034
|
+
if (typeof args === 'string' || typeof args === 'number' || typeof args === 'boolean') return String(args);
|
|
3035
|
+
if (typeof args !== 'object') return '';
|
|
3036
|
+
try {
|
|
3037
|
+
if (toolName === 'run_shell' || toolName === 'shell' || toolName === 'bash') {
|
|
3038
|
+
if (args.command) return String(args.command);
|
|
3039
|
+
if (Array.isArray(args.cmd)) return args.cmd.join(' ');
|
|
3040
|
+
}
|
|
3041
|
+
if (toolName === 'browser_screenshot') {
|
|
3042
|
+
return fullScalarText(args.url || args.href || args.target);
|
|
3043
|
+
}
|
|
3044
|
+
if (toolName === 'read_file' || toolName === 'read' || toolName === 'cat') {
|
|
3045
|
+
return fullScalarText(args.path || args.file_path);
|
|
3046
|
+
}
|
|
3047
|
+
if (toolName === 'glob') {
|
|
3048
|
+
return fullScalarText(args.pattern || args.path);
|
|
3049
|
+
}
|
|
3050
|
+
if (toolName === 'grep_files' || toolName === 'grep') {
|
|
3051
|
+
var p = fullScalarText(args.pattern || args.query);
|
|
3052
|
+
var loc = fullScalarText(args.path || args.glob);
|
|
3053
|
+
if (p && loc) return '"' + p + '" in ' + loc;
|
|
3054
|
+
return p || loc;
|
|
3055
|
+
}
|
|
3056
|
+
if (toolName === 'edit_file' || toolName === 'write_file' || toolName === 'edit' || toolName === 'write') {
|
|
3057
|
+
return fullScalarText(args.path || args.file_path);
|
|
3058
|
+
}
|
|
3059
|
+
if (toolName === 'list_directory' || toolName === 'ls') {
|
|
3060
|
+
return fullScalarText(args.path);
|
|
3061
|
+
}
|
|
3062
|
+
if (toolName === 'think') {
|
|
3063
|
+
return fullScalarText(args.thought || args.text);
|
|
3064
|
+
}
|
|
3065
|
+
if (toolName === 'search_memories') {
|
|
3066
|
+
return fullScalarText(args.query);
|
|
3067
|
+
}
|
|
3068
|
+
} catch (_) {
|
|
3069
|
+
return '';
|
|
3070
|
+
}
|
|
3071
|
+
return '';
|
|
3072
|
+
}
|
|
3073
|
+
|
|
3074
|
+
function formatToolArgs(args) {
|
|
3075
|
+
if (args == null) return '';
|
|
3076
|
+
if (typeof args === 'string') return args;
|
|
3077
|
+
if (typeof args === 'number' || typeof args === 'boolean') return String(args);
|
|
3078
|
+
try {
|
|
3079
|
+
return JSON.stringify(args, null, 2);
|
|
3080
|
+
} catch (_) {
|
|
3081
|
+
return String(args || '');
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
function appendToolDetailSection(parent, labelText, value, opts) {
|
|
3086
|
+
var text = value == null ? '' : String(value);
|
|
3087
|
+
if (!text.trim()) return null;
|
|
3088
|
+
opts = opts || {};
|
|
3089
|
+
var section = document.createElement('div');
|
|
3090
|
+
section.className = 'walle-tool-detail-section';
|
|
3091
|
+
|
|
3092
|
+
var label = document.createElement('div');
|
|
3093
|
+
label.className = 'walle-tool-detail-label';
|
|
3094
|
+
label.textContent = labelText;
|
|
3095
|
+
section.appendChild(label);
|
|
3096
|
+
|
|
3097
|
+
var pre = document.createElement('pre');
|
|
3098
|
+
pre.className = 'walle-tool-detail-pre' + (opts.output ? ' output' : '');
|
|
3099
|
+
pre.textContent = opts.output ? _formatToolOutput(text) : text;
|
|
3100
|
+
section.appendChild(pre);
|
|
3101
|
+
|
|
3102
|
+
parent.appendChild(section);
|
|
3103
|
+
return section;
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
function renderToolDetail(tc) {
|
|
3107
|
+
var detail = document.createElement('div');
|
|
3108
|
+
detail.className = 'walle-tool-detail';
|
|
3109
|
+
|
|
3110
|
+
var meta = document.createElement('div');
|
|
3111
|
+
meta.className = 'walle-tool-detail-meta';
|
|
3112
|
+
|
|
3113
|
+
var statusChip = document.createElement('span');
|
|
3114
|
+
statusChip.className = 'walle-tool-detail-chip ' + (tc && tc.status ? tc.status : 'working');
|
|
3115
|
+
statusChip.textContent = tc && tc.status === 'error'
|
|
3116
|
+
? 'Error'
|
|
3117
|
+
: (tc && tc.status === 'done' ? 'Done' : 'Running');
|
|
3118
|
+
meta.appendChild(statusChip);
|
|
3119
|
+
|
|
3120
|
+
var name = tc && (tc.name || tc.tool) ? displayToolName(tc.name || tc.tool) : '';
|
|
3121
|
+
if (name) {
|
|
3122
|
+
var nameChip = document.createElement('span');
|
|
3123
|
+
nameChip.className = 'walle-tool-detail-chip name';
|
|
3124
|
+
nameChip.textContent = name;
|
|
3125
|
+
meta.appendChild(nameChip);
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
var duration = _extractDurationLabel(tc);
|
|
3129
|
+
if (duration) {
|
|
3130
|
+
var durationChip = document.createElement('span');
|
|
3131
|
+
durationChip.className = 'walle-tool-detail-chip';
|
|
3132
|
+
durationChip.textContent = duration;
|
|
3133
|
+
meta.appendChild(durationChip);
|
|
3134
|
+
}
|
|
3135
|
+
detail.appendChild(meta);
|
|
3136
|
+
|
|
3137
|
+
var summaryText = (tc && (tc.summary || tc.result_summary)) || '';
|
|
3138
|
+
if (summaryText) {
|
|
3139
|
+
var summary = document.createElement('div');
|
|
3140
|
+
summary.className = 'walle-tool-detail-summary';
|
|
3141
|
+
summary.textContent = summaryText;
|
|
3142
|
+
detail.appendChild(summary);
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
var toolName = tc && (tc.name || tc.tool) || '';
|
|
3146
|
+
var primary = toolPrimaryText(tc);
|
|
3147
|
+
appendToolDetailSection(detail, toolPrimaryLabel(toolName), primary);
|
|
3148
|
+
|
|
3149
|
+
var args = tc ? (tc.args != null ? tc.args : tc.input) : null;
|
|
3150
|
+
var argsText = formatToolArgs(args);
|
|
3151
|
+
if (argsText && argsText !== primary) appendToolDetailSection(detail, 'Arguments', argsText);
|
|
3152
|
+
|
|
3153
|
+
var output = tc && (tc.output || tc.result || tc.error);
|
|
3154
|
+
appendToolDetailSection(detail, tc && tc.status === 'error' ? 'Error' : 'Output', output, { output: true });
|
|
3155
|
+
|
|
3156
|
+
if (!detail.querySelector('.walle-tool-detail-section') && !summaryText) {
|
|
3157
|
+
var empty = document.createElement('div');
|
|
3158
|
+
empty.className = 'walle-tool-detail-empty';
|
|
3159
|
+
empty.textContent = 'No detailed payload was recorded for this activity.';
|
|
3160
|
+
detail.appendChild(empty);
|
|
3161
|
+
}
|
|
3162
|
+
|
|
3163
|
+
addCopyButtonsToBlocks(detail);
|
|
3164
|
+
return detail;
|
|
3165
|
+
}
|
|
3166
|
+
|
|
1980
3167
|
function isRenderableToolCall(tc) {
|
|
1981
3168
|
if (!tc) return false;
|
|
1982
3169
|
var toolName = tc.name || tc.tool || '';
|
|
@@ -2041,43 +3228,75 @@ window.WalleSession = (function() {
|
|
|
2041
3228
|
var maxRows = 6;
|
|
2042
3229
|
var start = Math.max(0, visible.length - maxRows);
|
|
2043
3230
|
if (start > 0) {
|
|
2044
|
-
var more = document.createElement('
|
|
3231
|
+
var more = document.createElement('button');
|
|
3232
|
+
more.type = 'button';
|
|
2045
3233
|
more.className = 'walle-live-activity-more';
|
|
2046
|
-
more.textContent = '+' + start + ' earlier';
|
|
3234
|
+
more.textContent = '+' + start + ' earlier · Show';
|
|
2047
3235
|
list.appendChild(more);
|
|
3236
|
+
|
|
3237
|
+
var hiddenList = document.createElement('div');
|
|
3238
|
+
hiddenList.className = 'walle-live-activity-hidden';
|
|
3239
|
+
hiddenList.hidden = true;
|
|
3240
|
+
for (var h = 0; h < start; h++) hiddenList.appendChild(renderLiveActivityItem(visible[h]));
|
|
3241
|
+
more.onclick = function() {
|
|
3242
|
+
var showing = hiddenList.hidden;
|
|
3243
|
+
hiddenList.hidden = !showing;
|
|
3244
|
+
more.textContent = showing ? 'Hide earlier' : '+' + start + ' earlier · Show';
|
|
3245
|
+
};
|
|
3246
|
+
list.appendChild(hiddenList);
|
|
2048
3247
|
}
|
|
2049
3248
|
for (var i = start; i < visible.length; i++) {
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
3249
|
+
list.appendChild(renderLiveActivityItem(visible[i]));
|
|
3250
|
+
}
|
|
3251
|
+
panel.appendChild(list);
|
|
3252
|
+
return panel;
|
|
3253
|
+
}
|
|
2054
3254
|
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
3255
|
+
function renderLiveActivityItem(tc) {
|
|
3256
|
+
var status = (tc && tc.status) || 'working';
|
|
3257
|
+
var item = document.createElement('details');
|
|
3258
|
+
item.className = 'walle-live-activity-item ' + status;
|
|
2058
3259
|
|
|
2059
|
-
|
|
2060
|
-
|
|
3260
|
+
var row = document.createElement('summary');
|
|
3261
|
+
row.className = 'walle-live-activity-row ' + status;
|
|
3262
|
+
var label = displayToolName((tc && (tc.name || tc.tool)) || 'tool') || 'activity';
|
|
3263
|
+
var preview = toolPreviewText(tc);
|
|
3264
|
+
row.title = preview ? label + ': ' + preview : label;
|
|
3265
|
+
row.setAttribute('aria-label', 'Show details for ' + label + (preview ? ': ' + preview : ''));
|
|
2061
3266
|
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
var text = document.createElement('span');
|
|
2068
|
-
text.className = 'walle-live-activity-text';
|
|
2069
|
-
text.textContent = toolPreviewText(tc);
|
|
2070
|
-
main.appendChild(text);
|
|
2071
|
-
row.appendChild(main);
|
|
3267
|
+
var arrow = document.createElement('span');
|
|
3268
|
+
arrow.className = 'walle-live-activity-arrow';
|
|
3269
|
+
arrow.textContent = '\u25B6';
|
|
3270
|
+
row.appendChild(arrow);
|
|
2072
3271
|
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
3272
|
+
var dot = document.createElement('span');
|
|
3273
|
+
dot.className = 'walle-live-activity-dot';
|
|
3274
|
+
row.appendChild(dot);
|
|
3275
|
+
|
|
3276
|
+
var main = document.createElement('div');
|
|
3277
|
+
main.className = 'walle-live-activity-main';
|
|
3278
|
+
|
|
3279
|
+
var name = document.createElement('span');
|
|
3280
|
+
name.className = 'walle-live-activity-name';
|
|
3281
|
+
name.textContent = label;
|
|
3282
|
+
main.appendChild(name);
|
|
3283
|
+
|
|
3284
|
+
var text = document.createElement('span');
|
|
3285
|
+
text.className = 'walle-live-activity-text';
|
|
3286
|
+
text.textContent = preview;
|
|
3287
|
+
main.appendChild(text);
|
|
3288
|
+
row.appendChild(main);
|
|
3289
|
+
|
|
3290
|
+
var meta = document.createElement('span');
|
|
3291
|
+
meta.className = 'walle-live-activity-meta';
|
|
3292
|
+
meta.textContent = toolStatusLabel(tc);
|
|
3293
|
+
row.appendChild(meta);
|
|
3294
|
+
item.appendChild(row);
|
|
3295
|
+
|
|
3296
|
+
var detail = renderToolDetail(tc);
|
|
3297
|
+
detail.classList.add('walle-live-activity-detail');
|
|
3298
|
+
item.appendChild(detail);
|
|
3299
|
+
return item;
|
|
2081
3300
|
}
|
|
2082
3301
|
|
|
2083
3302
|
function normalizeToolCall(tc) {
|
|
@@ -2345,6 +3564,75 @@ window.WalleSession = (function() {
|
|
|
2345
3564
|
}
|
|
2346
3565
|
|
|
2347
3566
|
// ---------- permission card ----------
|
|
3567
|
+
// Editable working-directory control for the session header. Clicking the
|
|
3568
|
+
// path opens an inline input; committing sends `update-session-cwd` (the same
|
|
3569
|
+
// message the worktree-create flow uses), which updates session.cwd so the
|
|
3570
|
+
// next coding turn's effectiveCwd points there — no `cd <target>` prefix that
|
|
3571
|
+
// would trip the `cd * -> ask` permission rule on every command.
|
|
3572
|
+
function renderSessionCwdControl(sessionId, cwd) {
|
|
3573
|
+
var wrap = document.createElement('span');
|
|
3574
|
+
wrap.className = 'session-cwd';
|
|
3575
|
+
var current = cwd || '';
|
|
3576
|
+
var label = document.createElement('span');
|
|
3577
|
+
label.className = 'session-cwd-label' + (current ? '' : ' session-cwd-empty');
|
|
3578
|
+
label.textContent = current || 'Set working directory';
|
|
3579
|
+
label.title = "Click to change this session's working directory";
|
|
3580
|
+
label.tabIndex = 0;
|
|
3581
|
+
label.style.cursor = 'pointer';
|
|
3582
|
+
label.style.borderBottom = '1px dotted currentColor';
|
|
3583
|
+
wrap.appendChild(label);
|
|
3584
|
+
|
|
3585
|
+
function restore() {
|
|
3586
|
+
wrap.replaceChildren();
|
|
3587
|
+
label.textContent = current || 'Set working directory';
|
|
3588
|
+
label.className = 'session-cwd-label' + (current ? '' : ' session-cwd-empty');
|
|
3589
|
+
wrap.appendChild(label);
|
|
3590
|
+
}
|
|
3591
|
+
function commit(rawVal) {
|
|
3592
|
+
var val = (rawVal || '').trim();
|
|
3593
|
+
if (val && val !== current) {
|
|
3594
|
+
current = val;
|
|
3595
|
+
send({ type: 'update-session-cwd', id: sessionId, cwd: val });
|
|
3596
|
+
}
|
|
3597
|
+
restore();
|
|
3598
|
+
}
|
|
3599
|
+
function beginEdit() {
|
|
3600
|
+
var input = document.createElement('input');
|
|
3601
|
+
input.type = 'text';
|
|
3602
|
+
input.className = 'session-cwd-input';
|
|
3603
|
+
input.value = current;
|
|
3604
|
+
input.placeholder = '/absolute/path/to/project';
|
|
3605
|
+
input.style.minWidth = '220px';
|
|
3606
|
+
// Fit the dark session-meta row rather than a default white box.
|
|
3607
|
+
input.style.background = 'rgba(255,255,255,0.06)';
|
|
3608
|
+
input.style.color = 'inherit';
|
|
3609
|
+
input.style.border = '1px solid rgba(255,255,255,0.25)';
|
|
3610
|
+
input.style.borderRadius = '4px';
|
|
3611
|
+
input.style.padding = '1px 5px';
|
|
3612
|
+
input.style.font = 'inherit';
|
|
3613
|
+
wrap.replaceChildren();
|
|
3614
|
+
wrap.appendChild(input);
|
|
3615
|
+
input.focus();
|
|
3616
|
+
input.select();
|
|
3617
|
+
var done = false;
|
|
3618
|
+
var finish = function(save) {
|
|
3619
|
+
if (done) return;
|
|
3620
|
+
done = true;
|
|
3621
|
+
if (save) commit(input.value); else restore();
|
|
3622
|
+
};
|
|
3623
|
+
input.addEventListener('keydown', function(e) {
|
|
3624
|
+
if (e.key === 'Enter') { e.preventDefault(); finish(true); }
|
|
3625
|
+
else if (e.key === 'Escape') { e.preventDefault(); finish(false); }
|
|
3626
|
+
});
|
|
3627
|
+
input.addEventListener('blur', function() { finish(true); });
|
|
3628
|
+
}
|
|
3629
|
+
label.addEventListener('click', beginEdit);
|
|
3630
|
+
label.addEventListener('keydown', function(e) {
|
|
3631
|
+
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); beginEdit(); }
|
|
3632
|
+
});
|
|
3633
|
+
return wrap;
|
|
3634
|
+
}
|
|
3635
|
+
|
|
2348
3636
|
function renderPermissionCard(sessionId, ev) {
|
|
2349
3637
|
var card = document.createElement('div');
|
|
2350
3638
|
card.className = 'walle-perm-card';
|
|
@@ -2380,15 +3668,7 @@ window.WalleSession = (function() {
|
|
|
2380
3668
|
permId: ev.permId,
|
|
2381
3669
|
decision: decision,
|
|
2382
3670
|
});
|
|
2383
|
-
|
|
2384
|
-
card.classList.add('resolved');
|
|
2385
|
-
if (decision === 'deny') {
|
|
2386
|
-
card.classList.add('perm-denied');
|
|
2387
|
-
card.setAttribute('data-resolved', 'Denied');
|
|
2388
|
-
} else {
|
|
2389
|
-
card.classList.add('perm-approved');
|
|
2390
|
-
card.setAttribute('data-resolved', decision === 'allow_always' ? 'Allowed (always)' : 'Allowed (once)');
|
|
2391
|
-
}
|
|
3671
|
+
markPermissionCardResolved(card, decision === 'deny' ? 'deny' : decision, '');
|
|
2392
3672
|
};
|
|
2393
3673
|
})(buttons[i]));
|
|
2394
3674
|
}
|
|
@@ -2396,6 +3676,38 @@ window.WalleSession = (function() {
|
|
|
2396
3676
|
return card;
|
|
2397
3677
|
}
|
|
2398
3678
|
|
|
3679
|
+
// Apply the resolved visual state to a permission card. `decision` is the
|
|
3680
|
+
// card's button decision (allow_once/allow_always/deny) or a backend decision
|
|
3681
|
+
// (allow/deny); `label` overrides the badge text when provided.
|
|
3682
|
+
function markPermissionCardResolved(card, decision, label) {
|
|
3683
|
+
if (!card || card.classList.contains('resolved')) return;
|
|
3684
|
+
card.classList.add('resolved');
|
|
3685
|
+
var denied = decision === 'deny' || decision === 'reject';
|
|
3686
|
+
if (denied) {
|
|
3687
|
+
card.classList.add('perm-denied');
|
|
3688
|
+
card.setAttribute('data-resolved', label || 'Denied');
|
|
3689
|
+
} else {
|
|
3690
|
+
card.classList.add('perm-approved');
|
|
3691
|
+
var allowedLabel = label || (decision === 'allow_always' || decision === 'always'
|
|
3692
|
+
? 'Allowed (always)'
|
|
3693
|
+
: 'Allowed (once)');
|
|
3694
|
+
card.setAttribute('data-resolved', allowedLabel);
|
|
3695
|
+
}
|
|
3696
|
+
var btns = card.querySelectorAll('.walle-perm-actions button');
|
|
3697
|
+
for (var b = 0; b < btns.length; b++) btns[b].disabled = true;
|
|
3698
|
+
}
|
|
3699
|
+
|
|
3700
|
+
// Render a permission card from persisted history. Pending cards stay
|
|
3701
|
+
// actionable (reply re-routes to the same handler); resolved cards render in
|
|
3702
|
+
// their final state with the decision badge and disabled buttons.
|
|
3703
|
+
function renderPermissionCardForHistory(sessionId, permission) {
|
|
3704
|
+
var card = renderPermissionCard(sessionId, permission);
|
|
3705
|
+
if (permission && permission.status === 'resolved') {
|
|
3706
|
+
markPermissionCardResolved(card, permission.decision || 'allow', '');
|
|
3707
|
+
}
|
|
3708
|
+
return card;
|
|
3709
|
+
}
|
|
3710
|
+
|
|
2399
3711
|
// ---------- sendMessage ----------
|
|
2400
3712
|
function resolveContextSessionId(id) {
|
|
2401
3713
|
var preferred = state.lastActiveWorkSessionId || state._savedActiveSession || '';
|
|
@@ -2428,10 +3740,12 @@ window.WalleSession = (function() {
|
|
|
2428
3740
|
ws.messages.push(msg);
|
|
2429
3741
|
ws.messageCount++;
|
|
2430
3742
|
recordLiveRoleDelivery(ws, 'user');
|
|
3743
|
+
if (hasBranchSnapshot(ws)) saveWalleBranchSnapshot(id);
|
|
2431
3744
|
|
|
2432
3745
|
var messagesArea = document.getElementById('walle-messages-' + id);
|
|
2433
3746
|
if (messagesArea) {
|
|
2434
|
-
|
|
3747
|
+
if (restartVisibleMessageRenderIfBusy(id, ws)) return msg;
|
|
3748
|
+
renderMessage(messagesArea, msg, ws.messages.length - 1);
|
|
2435
3749
|
syncPromptMessageDomIndices(id);
|
|
2436
3750
|
updatePromptNav(id);
|
|
2437
3751
|
scrollToBottom(messagesArea);
|
|
@@ -2439,6 +3753,145 @@ window.WalleSession = (function() {
|
|
|
2439
3753
|
return msg;
|
|
2440
3754
|
}
|
|
2441
3755
|
|
|
3756
|
+
function rerenderWalleSessionPreservingComposer(id, focusSelector) {
|
|
3757
|
+
var composerSnapshot = captureComposerState(id);
|
|
3758
|
+
renderSession(id);
|
|
3759
|
+
restoreComposerState(id, composerSnapshot);
|
|
3760
|
+
if (focusSelector) {
|
|
3761
|
+
setTimeout(function() {
|
|
3762
|
+
var root = document.getElementById('walle-session-' + id);
|
|
3763
|
+
var target = root && root.querySelector(focusSelector);
|
|
3764
|
+
if (target && typeof target.focus === 'function') {
|
|
3765
|
+
target.focus();
|
|
3766
|
+
if (target.setSelectionRange) {
|
|
3767
|
+
var len = target.value ? target.value.length : 0;
|
|
3768
|
+
target.setSelectionRange(len, len);
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
}, 0);
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
|
|
3775
|
+
function editMessage(id, messageIndex) {
|
|
3776
|
+
var ws = getState(id);
|
|
3777
|
+
if (!ws) return;
|
|
3778
|
+
if (ws.isGenerating) {
|
|
3779
|
+
if (typeof toast === 'function') toast('Stop Wall-E before editing history.', { type: 'warning', duration: 2500 });
|
|
3780
|
+
return;
|
|
3781
|
+
}
|
|
3782
|
+
var msg = ws.messages[messageIndex];
|
|
3783
|
+
if (!msg || msg.role !== 'user') return;
|
|
3784
|
+
ws.editingMessageIndex = messageIndex;
|
|
3785
|
+
rerenderWalleSessionPreservingComposer(id, '.walle-edit-input');
|
|
3786
|
+
}
|
|
3787
|
+
|
|
3788
|
+
function cancelEdit(id) {
|
|
3789
|
+
var ws = getState(id);
|
|
3790
|
+
if (!ws) return;
|
|
3791
|
+
ws.editingMessageIndex = -1;
|
|
3792
|
+
rerenderWalleSessionPreservingComposer(id);
|
|
3793
|
+
}
|
|
3794
|
+
|
|
3795
|
+
function submitEdit(id, messageIndex) {
|
|
3796
|
+
var ws = getState(id);
|
|
3797
|
+
if (!ws) return;
|
|
3798
|
+
if (ws.isGenerating) {
|
|
3799
|
+
if (typeof toast === 'function') toast('Stop Wall-E before editing history.', { type: 'warning', duration: 2500 });
|
|
3800
|
+
return;
|
|
3801
|
+
}
|
|
3802
|
+
var root = document.getElementById('walle-session-' + id);
|
|
3803
|
+
var textarea = root && root.querySelector('.walle-edit-input');
|
|
3804
|
+
var text = String(textarea && textarea.value || '').trim();
|
|
3805
|
+
var original = ws.messages[messageIndex];
|
|
3806
|
+
if (!original || original.role !== 'user') return;
|
|
3807
|
+
var attachments = Array.isArray(original.attachments) ? cloneJsonSafe(original.attachments, []) : [];
|
|
3808
|
+
if (!text && !attachments.length) {
|
|
3809
|
+
if (typeof toast === 'function') toast('Edited prompt is empty.', { type: 'warning', duration: 2000 });
|
|
3810
|
+
return;
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3813
|
+
var oldTail = ws.messages.slice(messageIndex).map(cloneBranchMessage);
|
|
3814
|
+
var key = String(messageIndex);
|
|
3815
|
+
var branches = Array.isArray(ws.branches[key]) ? ws.branches[key] : null;
|
|
3816
|
+
if (!branches) {
|
|
3817
|
+
branches = [oldTail];
|
|
3818
|
+
ws.branches[key] = branches;
|
|
3819
|
+
ws.branchActive[key] = 1;
|
|
3820
|
+
} else {
|
|
3821
|
+
var current = Number(ws.branchActive[key] || 0);
|
|
3822
|
+
if (!Number.isFinite(current) || current < 0 || current >= branches.length) current = 0;
|
|
3823
|
+
branches[current] = oldTail;
|
|
3824
|
+
ws.branchActive[key] = branches.length;
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3827
|
+
var editedMessage = cloneBranchMessage(original);
|
|
3828
|
+
editedMessage.content = text;
|
|
3829
|
+
editedMessage.timestamp = Date.now();
|
|
3830
|
+
editedMessage.attachments = attachments;
|
|
3831
|
+
branches.push([editedMessage]);
|
|
3832
|
+
clearBranchesAfter(ws, messageIndex, false);
|
|
3833
|
+
|
|
3834
|
+
ws.messages = ws.messages.slice(0, messageIndex).map(cloneBranchMessage).concat([editedMessage]);
|
|
3835
|
+
ws.messageCount = ws.messages.length;
|
|
3836
|
+
ws.editingMessageIndex = -1;
|
|
3837
|
+
markBranchHistoryAuthoritative(ws);
|
|
3838
|
+
recordLiveRoleDelivery(ws, 'user');
|
|
3839
|
+
rerenderWalleSessionPreservingComposer(id);
|
|
3840
|
+
saveWalleBranchSnapshot(id);
|
|
3841
|
+
|
|
3842
|
+
sendOutboundMessage(id, text, attachments, {
|
|
3843
|
+
appendUser: false,
|
|
3844
|
+
recordHistory: true,
|
|
3845
|
+
outboundText: appendAttachmentReferences(text, attachments),
|
|
3846
|
+
timestamp: editedMessage.timestamp
|
|
3847
|
+
});
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
function deleteFromPrompt(id, messageIndex) {
|
|
3851
|
+
var ws = getState(id);
|
|
3852
|
+
if (!ws) return;
|
|
3853
|
+
if (ws.isGenerating) {
|
|
3854
|
+
if (typeof toast === 'function') toast('Stop Wall-E before deleting history.', { type: 'warning', duration: 2500 });
|
|
3855
|
+
return;
|
|
3856
|
+
}
|
|
3857
|
+
var msg = ws.messages[messageIndex];
|
|
3858
|
+
if (!msg || msg.role !== 'user') return;
|
|
3859
|
+
var ok = window.confirm('Delete this prompt and all messages after it? This truncates the active conversation. Existing earlier prompt versions are kept.');
|
|
3860
|
+
if (!ok) return;
|
|
3861
|
+
ws.messages = ws.messages.slice(0, messageIndex).map(cloneBranchMessage);
|
|
3862
|
+
ws.messageCount = ws.messages.length;
|
|
3863
|
+
ws.editingMessageIndex = -1;
|
|
3864
|
+
clearBranchesAfter(ws, messageIndex, true);
|
|
3865
|
+
refreshActiveBranchSlots(ws);
|
|
3866
|
+
markBranchHistoryAuthoritative(ws);
|
|
3867
|
+
rerenderWalleSessionPreservingComposer(id);
|
|
3868
|
+
saveWalleBranchSnapshot(id);
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3871
|
+
function switchBranch(id, messageIndex, direction) {
|
|
3872
|
+
var ws = getState(id);
|
|
3873
|
+
if (!ws || ws.isGenerating) {
|
|
3874
|
+
if (ws && ws.isGenerating && typeof toast === 'function') toast('Stop Wall-E before switching prompt versions.', { type: 'warning', duration: 2500 });
|
|
3875
|
+
return;
|
|
3876
|
+
}
|
|
3877
|
+
var key = String(messageIndex);
|
|
3878
|
+
var branches = ws.branches[key];
|
|
3879
|
+
if (!Array.isArray(branches) || branches.length <= 1) return;
|
|
3880
|
+
var current = Number(ws.branchActive[key] || 0);
|
|
3881
|
+
if (!Number.isFinite(current) || current < 0 || current >= branches.length) current = 0;
|
|
3882
|
+
var next = current + direction;
|
|
3883
|
+
if (next < 0 || next >= branches.length) return;
|
|
3884
|
+
branches[current] = ws.messages.slice(messageIndex).map(cloneBranchMessage);
|
|
3885
|
+
ws.branchActive[key] = next;
|
|
3886
|
+
ws.messages = ws.messages.slice(0, messageIndex).map(cloneBranchMessage).concat(normalizeBranchTail(branches[next]));
|
|
3887
|
+
ws.messageCount = ws.messages.length;
|
|
3888
|
+
ws.editingMessageIndex = -1;
|
|
3889
|
+
clearBranchesAfter(ws, messageIndex, false);
|
|
3890
|
+
markBranchHistoryAuthoritative(ws);
|
|
3891
|
+
rerenderWalleSessionPreservingComposer(id);
|
|
3892
|
+
saveWalleBranchSnapshot(id);
|
|
3893
|
+
}
|
|
3894
|
+
|
|
2442
3895
|
function recordLiveRoleDelivery(ws, role) {
|
|
2443
3896
|
if (!ws || !role) return;
|
|
2444
3897
|
if (!ws._lastLiveRoleAt) ws._lastLiveRoleAt = {};
|
|
@@ -2467,6 +3920,27 @@ window.WalleSession = (function() {
|
|
|
2467
3920
|
return false;
|
|
2468
3921
|
}
|
|
2469
3922
|
|
|
3923
|
+
function isOpenTurnActivityMessage(msg) {
|
|
3924
|
+
if (!msg || !msg.liveActivity) return false;
|
|
3925
|
+
var meta = msg.metadata || {};
|
|
3926
|
+
if (meta.source === 'walle-open-turn-activity') return true;
|
|
3927
|
+
var content = String(msg.content || msg.text || '').trim();
|
|
3928
|
+
return msg.role === 'system' && content.indexOf('Wall-E is working on this prompt.') === 0;
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
function captureLiveTurnState(id, ws) {
|
|
3932
|
+
if (!ws || !ws.isGenerating) return null;
|
|
3933
|
+
var messagesArea = document.getElementById('walle-messages-' + id);
|
|
3934
|
+
var hasLiveDom = !!(messagesArea && messagesArea.querySelector('.walle-live-activity, .walle-thinking'));
|
|
3935
|
+
var assistant = ws._currentAssistant || null;
|
|
3936
|
+
var hasAssistantActivity = !!(assistant && Array.isArray(assistant.toolCalls) && assistant.toolCalls.length);
|
|
3937
|
+
if (!hasOpenUserTurn(ws) && !hasLiveDom && !hasAssistantActivity) return null;
|
|
3938
|
+
return {
|
|
3939
|
+
currentAssistant: assistant,
|
|
3940
|
+
hadLiveDom: hasLiveDom || hasAssistantActivity
|
|
3941
|
+
};
|
|
3942
|
+
}
|
|
3943
|
+
|
|
2470
3944
|
function ensurePendingThinking(id) {
|
|
2471
3945
|
var ws = getState(id);
|
|
2472
3946
|
var messagesArea = document.getElementById('walle-messages-' + id);
|
|
@@ -2487,8 +3961,7 @@ window.WalleSession = (function() {
|
|
|
2487
3961
|
var latestSession = state.sessions.get(id);
|
|
2488
3962
|
if (!latestSession || !walleSessionIsActive(id)) return;
|
|
2489
3963
|
latestSession.needsAttach = false;
|
|
2490
|
-
|
|
2491
|
-
send({ type: 'attach', id: id, reason: reason || 'walle-transcript-append' });
|
|
3964
|
+
requestHistory(id, reason || 'walle-transcript-append', { force: true, noCache: true });
|
|
2492
3965
|
}, TRANSCRIPT_ATTACH_DEBOUNCE_MS);
|
|
2493
3966
|
}
|
|
2494
3967
|
|
|
@@ -2503,15 +3976,633 @@ window.WalleSession = (function() {
|
|
|
2503
3976
|
return Promise.resolve();
|
|
2504
3977
|
}
|
|
2505
3978
|
|
|
3979
|
+
var WORK_ACTIVE_STATUSES = { queued: true, running: true, stopping: true };
|
|
3980
|
+
|
|
3981
|
+
function normalizeWorkState(raw, id) {
|
|
3982
|
+
raw = raw && typeof raw === 'object' ? raw : {};
|
|
3983
|
+
var tasks = Array.isArray(raw.tasks) ? raw.tasks : [];
|
|
3984
|
+
var active = Array.isArray(raw.active) ? raw.active : tasks.filter(function(task) {
|
|
3985
|
+
return !!WORK_ACTIVE_STATUSES[String(task && task.status || '').toLowerCase()];
|
|
3986
|
+
});
|
|
3987
|
+
var recent = Array.isArray(raw.recent) ? raw.recent : tasks.filter(function(task) {
|
|
3988
|
+
return !WORK_ACTIVE_STATUSES[String(task && task.status || '').toLowerCase()];
|
|
3989
|
+
});
|
|
3990
|
+
var counts = raw.counts && typeof raw.counts === 'object' ? raw.counts : {};
|
|
3991
|
+
return {
|
|
3992
|
+
sessionId: raw.sessionId || raw.id || id,
|
|
3993
|
+
updatedAt: Number(raw.updatedAt || Date.now()),
|
|
3994
|
+
tasks: tasks,
|
|
3995
|
+
active: active,
|
|
3996
|
+
recent: recent,
|
|
3997
|
+
counts: {
|
|
3998
|
+
running: Number(counts.running || active.length || 0),
|
|
3999
|
+
shells: Number(counts.shells || 0),
|
|
4000
|
+
agents: Number(counts.agents || 0),
|
|
4001
|
+
tools: Number(counts.tools || 0),
|
|
4002
|
+
skills: Number(counts.skills || 0),
|
|
4003
|
+
queued: Number(counts.queued || 0)
|
|
4004
|
+
},
|
|
4005
|
+
isolation: raw.isolation || null
|
|
4006
|
+
};
|
|
4007
|
+
}
|
|
4008
|
+
|
|
4009
|
+
function workStateHasSignal(work) {
|
|
4010
|
+
if (!work) return false;
|
|
4011
|
+
var counts = work.counts || {};
|
|
4012
|
+
return (work.active && work.active.length)
|
|
4013
|
+
|| (work.recent && work.recent.length)
|
|
4014
|
+
|| Number(counts.queued || 0) > 0;
|
|
4015
|
+
}
|
|
4016
|
+
|
|
4017
|
+
function workKindLabel(kind) {
|
|
4018
|
+
var key = String(kind || '').toLowerCase();
|
|
4019
|
+
if (key === 'shell') return 'shell';
|
|
4020
|
+
if (key === 'agent') return 'agent';
|
|
4021
|
+
if (key === 'skill') return 'skill';
|
|
4022
|
+
if (key === 'turn') return 'turn';
|
|
4023
|
+
return 'tool';
|
|
4024
|
+
}
|
|
4025
|
+
|
|
4026
|
+
function workTaskAge(task) {
|
|
4027
|
+
var start = Number(task && (task.startedAt || task.started_at) || 0);
|
|
4028
|
+
if (!start) return '';
|
|
4029
|
+
var ms = Math.max(0, Date.now() - start);
|
|
4030
|
+
if (ms < 1000) return 'now';
|
|
4031
|
+
if (ms < 60000) return Math.floor(ms / 1000) + 's';
|
|
4032
|
+
if (ms < 3600000) return Math.floor(ms / 60000) + 'm';
|
|
4033
|
+
return Math.floor(ms / 3600000) + 'h';
|
|
4034
|
+
}
|
|
4035
|
+
|
|
4036
|
+
function workChip(label, value, tone) {
|
|
4037
|
+
var chip = document.createElement('span');
|
|
4038
|
+
chip.className = 'walle-work-chip' + (tone ? ' ' + tone : '');
|
|
4039
|
+
chip.textContent = value ? label + ' ' + value : label;
|
|
4040
|
+
return chip;
|
|
4041
|
+
}
|
|
4042
|
+
|
|
4043
|
+
function createWorkTaskRow(id, task) {
|
|
4044
|
+
task = task || {};
|
|
4045
|
+
var row = document.createElement('div');
|
|
4046
|
+
row.className = 'walle-work-row ' + String(task.status || 'running').toLowerCase();
|
|
4047
|
+
|
|
4048
|
+
var dot = document.createElement('span');
|
|
4049
|
+
dot.className = 'walle-work-dot';
|
|
4050
|
+
row.appendChild(dot);
|
|
4051
|
+
|
|
4052
|
+
var body = document.createElement('span');
|
|
4053
|
+
body.className = 'walle-work-row-body';
|
|
4054
|
+
var title = document.createElement('span');
|
|
4055
|
+
title.className = 'walle-work-row-title';
|
|
4056
|
+
title.textContent = task.label || task.summary || task.toolName || workKindLabel(task.kind);
|
|
4057
|
+
body.appendChild(title);
|
|
4058
|
+
var meta = document.createElement('span');
|
|
4059
|
+
meta.className = 'walle-work-row-meta';
|
|
4060
|
+
meta.textContent = [
|
|
4061
|
+
workKindLabel(task.kind),
|
|
4062
|
+
task.status || 'running',
|
|
4063
|
+
task.workerScope || task.worker_scope || '',
|
|
4064
|
+
workTaskAge(task)
|
|
4065
|
+
].filter(Boolean).join(' · ');
|
|
4066
|
+
body.appendChild(meta);
|
|
4067
|
+
if (task.inputPreview || task.input_preview || task.summary) {
|
|
4068
|
+
var preview = document.createElement('span');
|
|
4069
|
+
preview.className = 'walle-work-row-preview';
|
|
4070
|
+
preview.textContent = task.inputPreview || task.input_preview || task.summary || '';
|
|
4071
|
+
body.appendChild(preview);
|
|
4072
|
+
}
|
|
4073
|
+
row.appendChild(body);
|
|
4074
|
+
|
|
4075
|
+
var actions = document.createElement('span');
|
|
4076
|
+
actions.className = 'walle-work-row-actions';
|
|
4077
|
+
var view = document.createElement('button');
|
|
4078
|
+
view.type = 'button';
|
|
4079
|
+
view.textContent = 'View';
|
|
4080
|
+
view.onclick = function() {
|
|
4081
|
+
var area = document.getElementById('walle-messages-' + id);
|
|
4082
|
+
var live = area && area.querySelector('.walle-live-activity, .walle-tool-group, .walle-thinking');
|
|
4083
|
+
(live || area)?.scrollIntoView?.({ behavior: 'smooth', block: 'nearest' });
|
|
4084
|
+
if (live && live.classList) live.classList.add('walle-work-focus');
|
|
4085
|
+
setTimeout(function() { if (live && live.classList) live.classList.remove('walle-work-focus'); }, 1200);
|
|
4086
|
+
};
|
|
4087
|
+
actions.appendChild(view);
|
|
4088
|
+
if (task.kind === 'turn' && WORK_ACTIVE_STATUSES[String(task.status || '').toLowerCase()]) {
|
|
4089
|
+
var stop = document.createElement('button');
|
|
4090
|
+
stop.type = 'button';
|
|
4091
|
+
stop.className = 'danger';
|
|
4092
|
+
stop.textContent = 'Stop';
|
|
4093
|
+
stop.onclick = function() { send({ type: 'walle-cancel', id: id }); };
|
|
4094
|
+
actions.appendChild(stop);
|
|
4095
|
+
}
|
|
4096
|
+
row.appendChild(actions);
|
|
4097
|
+
return row;
|
|
4098
|
+
}
|
|
4099
|
+
|
|
4100
|
+
function renderWorkDrawer(id, root, work) {
|
|
4101
|
+
var drawer = document.createElement('div');
|
|
4102
|
+
drawer.className = 'walle-work-drawer';
|
|
4103
|
+
var headline = document.createElement('div');
|
|
4104
|
+
headline.className = 'walle-work-drawer-head';
|
|
4105
|
+
var title = document.createElement('strong');
|
|
4106
|
+
title.textContent = 'Runtime work';
|
|
4107
|
+
headline.appendChild(title);
|
|
4108
|
+
var note = document.createElement('span');
|
|
4109
|
+
note.textContent = 'metadata only · output stays in worker/runtime logs';
|
|
4110
|
+
headline.appendChild(note);
|
|
4111
|
+
drawer.appendChild(headline);
|
|
4112
|
+
|
|
4113
|
+
var active = (work.active || []).slice(0, 16);
|
|
4114
|
+
var recent = (work.recent || []).slice(0, 8);
|
|
4115
|
+
var groups = [
|
|
4116
|
+
{ label: 'Active', rows: active },
|
|
4117
|
+
{ label: 'Recent', rows: recent }
|
|
4118
|
+
];
|
|
4119
|
+
groups.forEach(function(group) {
|
|
4120
|
+
if (!group.rows.length) return;
|
|
4121
|
+
var groupEl = document.createElement('div');
|
|
4122
|
+
groupEl.className = 'walle-work-group';
|
|
4123
|
+
var groupTitle = document.createElement('div');
|
|
4124
|
+
groupTitle.className = 'walle-work-group-title';
|
|
4125
|
+
groupTitle.textContent = group.label;
|
|
4126
|
+
groupEl.appendChild(groupTitle);
|
|
4127
|
+
group.rows.forEach(function(task) { groupEl.appendChild(createWorkTaskRow(id, task)); });
|
|
4128
|
+
drawer.appendChild(groupEl);
|
|
4129
|
+
});
|
|
4130
|
+
if (!active.length && !recent.length) {
|
|
4131
|
+
var empty = document.createElement('div');
|
|
4132
|
+
empty.className = 'walle-work-empty';
|
|
4133
|
+
empty.textContent = 'No runtime work is active for this session.';
|
|
4134
|
+
drawer.appendChild(empty);
|
|
4135
|
+
}
|
|
4136
|
+
root.appendChild(drawer);
|
|
4137
|
+
}
|
|
4138
|
+
|
|
4139
|
+
function renderWorkBar(id) {
|
|
4140
|
+
var ws = getState(id);
|
|
4141
|
+
var slot = document.getElementById('walle-workbar-' + id);
|
|
4142
|
+
if (!ws || !slot) return;
|
|
4143
|
+
var work = normalizeWorkState(ws.workState, id);
|
|
4144
|
+
while (slot.firstChild) slot.removeChild(slot.firstChild);
|
|
4145
|
+
if (!workStateHasSignal(work) && !ws.workDrawerOpen) {
|
|
4146
|
+
slot.style.display = 'none';
|
|
4147
|
+
return;
|
|
4148
|
+
}
|
|
4149
|
+
slot.style.display = 'block';
|
|
4150
|
+
|
|
4151
|
+
var root = document.createElement('div');
|
|
4152
|
+
root.className = 'walle-workbar' + (ws.workDrawerOpen ? ' expanded' : '');
|
|
4153
|
+
|
|
4154
|
+
var rail = document.createElement('div');
|
|
4155
|
+
rail.className = 'walle-workbar-rail';
|
|
4156
|
+
var kicker = document.createElement('span');
|
|
4157
|
+
kicker.className = 'walle-workbar-kicker';
|
|
4158
|
+
kicker.textContent = 'WORK';
|
|
4159
|
+
rail.appendChild(kicker);
|
|
4160
|
+
|
|
4161
|
+
var counts = work.counts || {};
|
|
4162
|
+
if (counts.running) rail.appendChild(workChip('running', counts.running, 'active'));
|
|
4163
|
+
if (counts.shells) rail.appendChild(workChip('shell', counts.shells, 'shell'));
|
|
4164
|
+
if (counts.agents) rail.appendChild(workChip('agent', counts.agents, 'agent'));
|
|
4165
|
+
if (counts.tools) rail.appendChild(workChip('tool', counts.tools, 'tool'));
|
|
4166
|
+
if (counts.skills) rail.appendChild(workChip('skill', counts.skills, 'skill'));
|
|
4167
|
+
// (queued count intentionally omitted — queued prompts now render inline as
|
|
4168
|
+
// pending bubbles + a status line above the composer, not as a work-bar chip)
|
|
4169
|
+
if (!counts.running && work.recent && work.recent.length) rail.appendChild(workChip('recent', work.recent.length, 'done'));
|
|
4170
|
+
rail.appendChild(workChip('isolated', '', 'isolated'));
|
|
4171
|
+
root.appendChild(rail);
|
|
4172
|
+
|
|
4173
|
+
var summary = document.createElement('div');
|
|
4174
|
+
summary.className = 'walle-workbar-summary';
|
|
4175
|
+
var focusTask = (work.active && work.active[0]) || (work.recent && work.recent[0]) || null;
|
|
4176
|
+
summary.textContent = focusTask
|
|
4177
|
+
? (focusTask.summary || focusTask.label || 'Runtime work updated')
|
|
4178
|
+
: 'Runtime work is idle.';
|
|
4179
|
+
root.appendChild(summary);
|
|
4180
|
+
|
|
4181
|
+
var actions = document.createElement('div');
|
|
4182
|
+
actions.className = 'walle-workbar-actions';
|
|
4183
|
+
var manage = document.createElement('button');
|
|
4184
|
+
manage.type = 'button';
|
|
4185
|
+
manage.className = 'walle-workbar-manage';
|
|
4186
|
+
manage.textContent = ws.workDrawerOpen ? 'Hide' : 'Manage';
|
|
4187
|
+
manage.onclick = function() {
|
|
4188
|
+
var latest = getState(id);
|
|
4189
|
+
if (!latest) return;
|
|
4190
|
+
latest.workDrawerOpen = !latest.workDrawerOpen;
|
|
4191
|
+
renderWorkBar(id);
|
|
4192
|
+
};
|
|
4193
|
+
actions.appendChild(manage);
|
|
4194
|
+
if (work.active && work.active.some(function(task) { return task.kind === 'turn'; })) {
|
|
4195
|
+
var stop = document.createElement('button');
|
|
4196
|
+
stop.type = 'button';
|
|
4197
|
+
stop.className = 'walle-workbar-stop';
|
|
4198
|
+
stop.textContent = 'Stop';
|
|
4199
|
+
stop.onclick = function() { send({ type: 'walle-cancel', id: id }); };
|
|
4200
|
+
actions.appendChild(stop);
|
|
4201
|
+
}
|
|
4202
|
+
root.appendChild(actions);
|
|
4203
|
+
if (ws.workDrawerOpen) renderWorkDrawer(id, root, work);
|
|
4204
|
+
slot.appendChild(root);
|
|
4205
|
+
}
|
|
4206
|
+
|
|
4207
|
+
function confirmCancelActiveRun(id, event) {
|
|
4208
|
+
var ws = getState(id);
|
|
4209
|
+
if (!ws || !ws.isGenerating || _cancelConfirmationOpen) return false;
|
|
4210
|
+
if (event) {
|
|
4211
|
+
event.preventDefault();
|
|
4212
|
+
event.stopPropagation();
|
|
4213
|
+
}
|
|
4214
|
+
_cancelConfirmationOpen = true;
|
|
4215
|
+
var accepted = false;
|
|
4216
|
+
try {
|
|
4217
|
+
accepted = window.confirm('Stop the current Wall-E run?');
|
|
4218
|
+
} finally {
|
|
4219
|
+
_cancelConfirmationOpen = false;
|
|
4220
|
+
}
|
|
4221
|
+
if (!accepted) return true;
|
|
4222
|
+
send({ type: 'walle-cancel', id: id });
|
|
4223
|
+
return true;
|
|
4224
|
+
}
|
|
4225
|
+
|
|
4226
|
+
function hasEscapeDismissibleWalleUi() {
|
|
4227
|
+
var picker = document.getElementById('slash-picker');
|
|
4228
|
+
if (picker && picker.style && picker.style.display !== 'none') return true;
|
|
4229
|
+
if (_walleModelPicker && _walleModelPicker.el && document.body.contains(_walleModelPicker.el)) return true;
|
|
4230
|
+
return false;
|
|
4231
|
+
}
|
|
4232
|
+
|
|
4233
|
+
function handleGlobalEscape(id, event) {
|
|
4234
|
+
if (!id || !event || event.defaultPrevented) return false;
|
|
4235
|
+
if (event.key !== 'Escape' || event.metaKey || event.ctrlKey || event.altKey) return false;
|
|
4236
|
+
if (hasEscapeDismissibleWalleUi()) return false;
|
|
4237
|
+
return confirmCancelActiveRun(id, event);
|
|
4238
|
+
}
|
|
4239
|
+
|
|
4240
|
+
function handleWorkState(msg) {
|
|
4241
|
+
var id = msg && (msg.sessionId || msg.id);
|
|
4242
|
+
if (!id) return;
|
|
4243
|
+
var ws = getState(id);
|
|
4244
|
+
if (!ws) return;
|
|
4245
|
+
ws.workState = normalizeWorkState(msg.state || msg.workState || msg, id);
|
|
4246
|
+
renderWorkBar(id);
|
|
4247
|
+
}
|
|
4248
|
+
|
|
4249
|
+
function queueApiUrl(path) {
|
|
4250
|
+
return path;
|
|
4251
|
+
}
|
|
4252
|
+
|
|
4253
|
+
function queueItemIsActive(item) {
|
|
4254
|
+
var status = String(item && item.status || 'pending').toLowerCase();
|
|
4255
|
+
return status !== 'sent' && status !== 'skipped';
|
|
4256
|
+
}
|
|
4257
|
+
|
|
4258
|
+
function activeQueueItems(queue) {
|
|
4259
|
+
var items = queue && Array.isArray(queue.items) ? queue.items : [];
|
|
4260
|
+
return items.filter(queueItemIsActive);
|
|
4261
|
+
}
|
|
4262
|
+
|
|
4263
|
+
function selectedQueueItem(queue) {
|
|
4264
|
+
if (!queue) return null;
|
|
4265
|
+
var items = queue.items || [];
|
|
4266
|
+
var current = items[queue.currentIndex];
|
|
4267
|
+
if (queueItemIsActive(current)) return current;
|
|
4268
|
+
var active = activeQueueItems(queue);
|
|
4269
|
+
return active.length ? active[0] : null;
|
|
4270
|
+
}
|
|
4271
|
+
|
|
4272
|
+
function queueTitleForText(text) {
|
|
4273
|
+
var compact = String(text || '').replace(/\s+/g, ' ').trim();
|
|
4274
|
+
return compact ? compact.slice(0, 88) : 'Queued prompt';
|
|
4275
|
+
}
|
|
4276
|
+
|
|
4277
|
+
function queueStateRevisionValue(value) {
|
|
4278
|
+
var raw = Number(value && value.revision);
|
|
4279
|
+
return Number.isFinite(raw) ? raw : null;
|
|
4280
|
+
}
|
|
4281
|
+
|
|
4282
|
+
function shouldApplyQueueState(ws, msg) {
|
|
4283
|
+
if (!ws || !msg || !msg.sessionId) return true;
|
|
4284
|
+
var current = ws.queueState;
|
|
4285
|
+
if (!current || current.sessionId !== msg.sessionId) return true;
|
|
4286
|
+
var nextRevision = queueStateRevisionValue(msg);
|
|
4287
|
+
var currentRevision = queueStateRevisionValue(current);
|
|
4288
|
+
if (nextRevision === null || currentRevision === null) return true;
|
|
4289
|
+
return nextRevision >= currentRevision;
|
|
4290
|
+
}
|
|
4291
|
+
|
|
4292
|
+
function normalizeQueueImage(att, idx) {
|
|
4293
|
+
att = att || {};
|
|
4294
|
+
return {
|
|
4295
|
+
type: att.type || 'image',
|
|
4296
|
+
name: att.name || att.filename || ('image-' + (idx + 1) + '.png'),
|
|
4297
|
+
filename: att.filename || att.name || '',
|
|
4298
|
+
data: att.data || '',
|
|
4299
|
+
label: att.label || ('[Image #' + (idx + 1) + ']'),
|
|
4300
|
+
url: att.url || '',
|
|
4301
|
+
path: att.path || att.file_path || '',
|
|
4302
|
+
mediaType: att.mediaType || att.mimeType || '',
|
|
4303
|
+
imageWidth: att.imageWidth || null,
|
|
4304
|
+
imageHeight: att.imageHeight || null,
|
|
4305
|
+
originalWidth: att.originalWidth || null,
|
|
4306
|
+
originalHeight: att.originalHeight || null,
|
|
4307
|
+
resizedForProvider: !!att.resizedForProvider
|
|
4308
|
+
};
|
|
4309
|
+
}
|
|
4310
|
+
|
|
4311
|
+
// POST a queue action (next/mode/remove…) and refresh the inline UI.
|
|
4312
|
+
function queuePostAction(id, action, payload) {
|
|
4313
|
+
var ws = getState(id);
|
|
4314
|
+
return fetch(queueApiUrl('/api/queues/' + encodeURIComponent(id) + '/' + action), {
|
|
4315
|
+
method: 'POST',
|
|
4316
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + ((state && state.token) || '') },
|
|
4317
|
+
body: payload ? JSON.stringify(payload) : undefined,
|
|
4318
|
+
}).then(function(r) { return r.ok ? r.json() : null; })
|
|
4319
|
+
.then(function(data) { if (data && ws) { ws.queueState = data; renderQueuePreview(id); renderPendingQueueBubbles(id); } })
|
|
4320
|
+
.catch(function() {});
|
|
4321
|
+
}
|
|
4322
|
+
|
|
4323
|
+
function removeQueuedItem(id, itemId, activeCount) {
|
|
4324
|
+
// Removing the last active item == cancel the whole queue (clears state cleanly).
|
|
4325
|
+
if (activeCount <= 1) { cancelQueuedMessages(id); return; }
|
|
4326
|
+
queuePostAction(id, 'remove', { itemId: itemId });
|
|
4327
|
+
}
|
|
4328
|
+
|
|
4329
|
+
// Edit a queued prompt: pull its text + images back into the composer and drop
|
|
4330
|
+
// it from the queue, so the user re-sends after tweaking.
|
|
4331
|
+
function editQueuedItem(id, item, activeCount) {
|
|
4332
|
+
var ws = getState(id);
|
|
4333
|
+
if (!ws) return;
|
|
4334
|
+
var container = document.getElementById('walle-session-' + id);
|
|
4335
|
+
var textarea = container && container.querySelector('.walle-input');
|
|
4336
|
+
if (textarea) {
|
|
4337
|
+
textarea.value = String(item.text || '');
|
|
4338
|
+
if (typeof syncComposerChrome === 'function') syncComposerChrome(id, textarea);
|
|
4339
|
+
}
|
|
4340
|
+
ws.inputAttachments = (Array.isArray(item.images) ? item.images : []).map(function(im, i) { return normalizeQueueImage(im, i); });
|
|
4341
|
+
renderComposerAttachments(id);
|
|
4342
|
+
removeQueuedItem(id, item.id, activeCount);
|
|
4343
|
+
focusInput(id);
|
|
4344
|
+
}
|
|
4345
|
+
|
|
4346
|
+
// Render queued prompts INLINE in the conversation as faded "You · queued"
|
|
4347
|
+
// bubbles pinned to the bottom of the message list (CSS order keeps them last
|
|
4348
|
+
// even while Wall-E streams). This is where the user typed them, so they read
|
|
4349
|
+
// as the natural continuation of the conversation — not a separate banner.
|
|
4350
|
+
// Standard HTML5 drag-reorder helper: the pending bubble whose midpoint sits
|
|
4351
|
+
// just below the cursor (the dragged element goes before it).
|
|
4352
|
+
function getDragAfterElement(container, y) {
|
|
4353
|
+
var els = Array.prototype.slice.call(container.querySelectorAll('.walle-msg-pending:not(.dragging)'));
|
|
4354
|
+
var closest = { offset: -Infinity, element: null };
|
|
4355
|
+
els.forEach(function(child) {
|
|
4356
|
+
var box = child.getBoundingClientRect();
|
|
4357
|
+
var offset = y - box.top - box.height / 2;
|
|
4358
|
+
if (offset < 0 && offset > closest.offset) closest = { offset: offset, element: child };
|
|
4359
|
+
});
|
|
4360
|
+
return closest.element;
|
|
4361
|
+
}
|
|
4362
|
+
|
|
4363
|
+
function reorderQueuedItems(id) {
|
|
4364
|
+
var container = document.getElementById('walle-pending-' + id);
|
|
4365
|
+
if (!container) return;
|
|
4366
|
+
var order = Array.prototype.map.call(
|
|
4367
|
+
container.querySelectorAll('.walle-msg-pending'),
|
|
4368
|
+
function(el) { return el.dataset.queueItemId; }
|
|
4369
|
+
).filter(Boolean);
|
|
4370
|
+
if (order.length) queuePostAction(id, 'reorder', { order: order });
|
|
4371
|
+
}
|
|
4372
|
+
|
|
4373
|
+
function renderPendingQueueBubbles(id) {
|
|
4374
|
+
var ws = getState(id);
|
|
4375
|
+
var messagesArea = document.getElementById('walle-messages-' + id);
|
|
4376
|
+
if (!ws || !messagesArea) return;
|
|
4377
|
+
var container = document.getElementById('walle-pending-' + id);
|
|
4378
|
+
var active = activeQueueItems(ws.queueState);
|
|
4379
|
+
if (!active.length) { if (container) container.remove(); return; }
|
|
4380
|
+
if (!container) {
|
|
4381
|
+
container = document.createElement('div');
|
|
4382
|
+
container.className = 'walle-pending-queue';
|
|
4383
|
+
container.id = 'walle-pending-' + id;
|
|
4384
|
+
}
|
|
4385
|
+
while (container.firstChild) container.removeChild(container.firstChild);
|
|
4386
|
+
messagesArea.appendChild(container); // re-append → stays last (also order:9999 in CSS)
|
|
4387
|
+
|
|
4388
|
+
// Drag-to-reorder: live-move the dragged bubble, persist the new order on drop.
|
|
4389
|
+
container.ondragover = function(ev) {
|
|
4390
|
+
ev.preventDefault();
|
|
4391
|
+
var dragging = container.querySelector('.walle-msg-pending.dragging');
|
|
4392
|
+
if (!dragging) return;
|
|
4393
|
+
var after = getDragAfterElement(container, ev.clientY);
|
|
4394
|
+
if (after == null) container.appendChild(dragging);
|
|
4395
|
+
else if (after !== dragging) container.insertBefore(dragging, after);
|
|
4396
|
+
};
|
|
4397
|
+
container.ondrop = function(ev) { ev.preventDefault(); reorderQueuedItems(id); };
|
|
4398
|
+
|
|
4399
|
+
var reorderable = active.length > 1;
|
|
4400
|
+
active.forEach(function(item, idx) {
|
|
4401
|
+
var sending = String(item.status || '').toLowerCase() === 'sending';
|
|
4402
|
+
var el = document.createElement('div');
|
|
4403
|
+
el.className = 'walle-msg walle-msg-pending' + (sending ? ' sending' : '');
|
|
4404
|
+
el.dataset.queueItemId = item.id;
|
|
4405
|
+
if (reorderable && !sending) {
|
|
4406
|
+
el.draggable = true;
|
|
4407
|
+
el.addEventListener('dragstart', function(ev) {
|
|
4408
|
+
el.classList.add('dragging');
|
|
4409
|
+
try { ev.dataTransfer.effectAllowed = 'move'; ev.dataTransfer.setData('text/plain', item.id); } catch (_) {}
|
|
4410
|
+
});
|
|
4411
|
+
el.addEventListener('dragend', function() { el.classList.remove('dragging'); });
|
|
4412
|
+
}
|
|
4413
|
+
|
|
4414
|
+
var avatar = document.createElement('div');
|
|
4415
|
+
avatar.className = 'walle-msg-avatar user';
|
|
4416
|
+
avatar.textContent = 'U';
|
|
4417
|
+
el.appendChild(avatar);
|
|
4418
|
+
|
|
4419
|
+
var body = document.createElement('div');
|
|
4420
|
+
body.style.flex = '1'; body.style.minWidth = '0';
|
|
4421
|
+
|
|
4422
|
+
var hdr = document.createElement('div');
|
|
4423
|
+
hdr.className = 'walle-msg-header';
|
|
4424
|
+
var name = document.createElement('span');
|
|
4425
|
+
name.className = 'walle-msg-name';
|
|
4426
|
+
name.textContent = 'You';
|
|
4427
|
+
hdr.appendChild(name);
|
|
4428
|
+
var badge = document.createElement('span');
|
|
4429
|
+
badge.className = 'walle-pending-badge';
|
|
4430
|
+
badge.textContent = sending ? 'sending…'
|
|
4431
|
+
: (active.length > 1 ? ('queued · ' + (idx + 1) + '/' + active.length) : 'queued');
|
|
4432
|
+
hdr.appendChild(badge);
|
|
4433
|
+
if (!sending) {
|
|
4434
|
+
var actions = document.createElement('span');
|
|
4435
|
+
actions.className = 'walle-pending-actions';
|
|
4436
|
+
var editBtn = document.createElement('button');
|
|
4437
|
+
editBtn.type = 'button'; editBtn.className = 'walle-pending-action';
|
|
4438
|
+
editBtn.title = 'Edit — move back to the composer'; editBtn.textContent = '✎';
|
|
4439
|
+
editBtn.onclick = (function(it, cnt) { return function(e) { e.preventDefault(); editQueuedItem(id, it, cnt); }; })(item, active.length);
|
|
4440
|
+
actions.appendChild(editBtn);
|
|
4441
|
+
var cancelBtn = document.createElement('button');
|
|
4442
|
+
cancelBtn.type = 'button'; cancelBtn.className = 'walle-pending-action danger';
|
|
4443
|
+
cancelBtn.title = 'Remove from queue'; cancelBtn.textContent = '✕';
|
|
4444
|
+
cancelBtn.onclick = (function(it, cnt) { return function(e) { e.preventDefault(); removeQueuedItem(id, it.id, cnt); }; })(item, active.length);
|
|
4445
|
+
actions.appendChild(cancelBtn);
|
|
4446
|
+
hdr.appendChild(actions);
|
|
4447
|
+
}
|
|
4448
|
+
body.appendChild(hdr);
|
|
4449
|
+
|
|
4450
|
+
var content = document.createElement('div');
|
|
4451
|
+
content.className = 'walle-msg-content walle-pending-content';
|
|
4452
|
+
content.textContent = String(item.text || item.title || 'Queued prompt');
|
|
4453
|
+
body.appendChild(content);
|
|
4454
|
+
|
|
4455
|
+
if (Array.isArray(item.images) && item.images.length) {
|
|
4456
|
+
body.appendChild(renderAttachmentStrip(id, item.images, false));
|
|
4457
|
+
}
|
|
4458
|
+
|
|
4459
|
+
el.appendChild(body);
|
|
4460
|
+
container.appendChild(el);
|
|
4461
|
+
});
|
|
4462
|
+
}
|
|
4463
|
+
|
|
4464
|
+
// A thin one-line status above the composer (replaces the bulky yellow card).
|
|
4465
|
+
// The per-item controls live on the inline bubbles; this just shows count +
|
|
4466
|
+
// mode + cancel-all, and a "Send next" when in manual mode.
|
|
4467
|
+
function renderQueuePreview(id) {
|
|
4468
|
+
var ws = getState(id);
|
|
4469
|
+
var slot = document.getElementById('walle-queue-preview-' + id);
|
|
4470
|
+
if (!ws || !slot) return;
|
|
4471
|
+
while (slot.firstChild) slot.removeChild(slot.firstChild);
|
|
4472
|
+
|
|
4473
|
+
var queue = ws.queueState;
|
|
4474
|
+
var active = activeQueueItems(queue);
|
|
4475
|
+
if (!queue || queue.status === 'idle' || queue.status === 'done' || active.length === 0) {
|
|
4476
|
+
slot.style.display = 'none';
|
|
4477
|
+
return;
|
|
4478
|
+
}
|
|
4479
|
+
var mode = String(queue.mode || 'auto').toLowerCase();
|
|
4480
|
+
var n = active.length;
|
|
4481
|
+
|
|
4482
|
+
var bar = document.createElement('div');
|
|
4483
|
+
bar.className = 'walle-queue-status' + (queue.waitingMessage ? ' waiting' : '');
|
|
4484
|
+
|
|
4485
|
+
var label = document.createElement('span');
|
|
4486
|
+
label.className = 'walle-queue-status-label';
|
|
4487
|
+
label.textContent = queue.waitingMessage
|
|
4488
|
+
? queue.waitingMessage
|
|
4489
|
+
: (n + (n === 1 ? ' message queued — ' : ' messages queued — ')
|
|
4490
|
+
+ (mode === 'manual' ? 'manual send' : 'auto-sends when Wall-E is idle'));
|
|
4491
|
+
bar.appendChild(label);
|
|
4492
|
+
|
|
4493
|
+
var actions = document.createElement('span');
|
|
4494
|
+
actions.className = 'walle-queue-status-actions';
|
|
4495
|
+
if (mode === 'manual') {
|
|
4496
|
+
var nextBtn = document.createElement('button');
|
|
4497
|
+
nextBtn.type = 'button'; nextBtn.className = 'walle-queue-status-btn';
|
|
4498
|
+
nextBtn.textContent = 'Send next';
|
|
4499
|
+
nextBtn.onclick = function() { queuePostAction(id, 'next'); };
|
|
4500
|
+
actions.appendChild(nextBtn);
|
|
4501
|
+
}
|
|
4502
|
+
var modeBtn = document.createElement('button');
|
|
4503
|
+
modeBtn.type = 'button'; modeBtn.className = 'walle-queue-status-btn';
|
|
4504
|
+
modeBtn.textContent = mode === 'manual' ? 'Auto' : 'Manual';
|
|
4505
|
+
modeBtn.title = 'Switch to ' + (mode === 'manual' ? 'auto' : 'manual') + ' sending';
|
|
4506
|
+
modeBtn.onclick = function() { queuePostAction(id, 'mode', { mode: mode === 'manual' ? 'auto' : 'manual' }); };
|
|
4507
|
+
actions.appendChild(modeBtn);
|
|
4508
|
+
var cancelBtn = document.createElement('button');
|
|
4509
|
+
cancelBtn.type = 'button'; cancelBtn.className = 'walle-queue-status-btn danger';
|
|
4510
|
+
cancelBtn.textContent = 'Cancel all';
|
|
4511
|
+
cancelBtn.onclick = function() { cancelQueuedMessages(id); };
|
|
4512
|
+
actions.appendChild(cancelBtn);
|
|
4513
|
+
bar.appendChild(actions);
|
|
4514
|
+
|
|
4515
|
+
slot.style.display = 'block';
|
|
4516
|
+
slot.appendChild(bar);
|
|
4517
|
+
}
|
|
4518
|
+
|
|
4519
|
+
function handleQueueState(msg) {
|
|
4520
|
+
var id = msg && (msg.sessionId || msg.id);
|
|
4521
|
+
if (!id) return;
|
|
4522
|
+
var ws = getState(id);
|
|
4523
|
+
if (!ws) return;
|
|
4524
|
+
if (!shouldApplyQueueState(ws, msg)) return;
|
|
4525
|
+
ws.queueState = msg;
|
|
4526
|
+
renderQueuePreview(id);
|
|
4527
|
+
renderPendingQueueBubbles(id);
|
|
4528
|
+
}
|
|
4529
|
+
|
|
4530
|
+
async function cancelQueuedMessages(id) {
|
|
4531
|
+
var ws = getState(id);
|
|
4532
|
+
try {
|
|
4533
|
+
var res = await fetch(queueApiUrl('/api/queues/' + encodeURIComponent(id)), {
|
|
4534
|
+
method: 'DELETE',
|
|
4535
|
+
headers: { 'Authorization': 'Bearer ' + ((state && state.token) || '') }
|
|
4536
|
+
});
|
|
4537
|
+
if (!res.ok) throw new Error('Queue cancel failed');
|
|
4538
|
+
if (ws) {
|
|
4539
|
+
ws.queueState = { sessionId: id, status: 'idle', items: [] };
|
|
4540
|
+
renderQueuePreview(id);
|
|
4541
|
+
}
|
|
4542
|
+
if (typeof toast === 'function') toast('Queued prompts cancelled', { type: 'info', duration: 1800 });
|
|
4543
|
+
} catch (err) {
|
|
4544
|
+
if (typeof toast === 'function') toast(err && err.message ? err.message : 'Queue cancel failed', { type: 'error' });
|
|
4545
|
+
}
|
|
4546
|
+
}
|
|
4547
|
+
|
|
4548
|
+
async function queueOutboundMessage(id, text, attachments, options) {
|
|
4549
|
+
options = options || {};
|
|
4550
|
+
var ws = getState(id);
|
|
4551
|
+
if (!ws) return { ok: false, error: 'Wall-E session is not connected' };
|
|
4552
|
+
if (ws.isQueueingMessage) return { ok: false, error: 'Already queueing this prompt' };
|
|
4553
|
+
attachments = Array.isArray(attachments) ? attachments : [];
|
|
4554
|
+
text = String(text || '').trim();
|
|
4555
|
+
if (!text && !attachments.length) return { ok: false, error: 'Message is empty' };
|
|
4556
|
+
if (!text && attachments.length) text = attachments.length === 1
|
|
4557
|
+
? 'Please use the attached image.'
|
|
4558
|
+
: 'Please use the attached images.';
|
|
4559
|
+
|
|
4560
|
+
var queueText = options.outboundText || appendAttachmentReferences(text, attachments);
|
|
4561
|
+
ws.isQueueingMessage = true;
|
|
4562
|
+
try {
|
|
4563
|
+
var res = await fetch(queueApiUrl('/api/queues'), {
|
|
4564
|
+
method: 'POST',
|
|
4565
|
+
headers: {
|
|
4566
|
+
'Content-Type': 'application/json',
|
|
4567
|
+
'Authorization': 'Bearer ' + ((state && state.token) || '')
|
|
4568
|
+
},
|
|
4569
|
+
body: JSON.stringify({
|
|
4570
|
+
sessionId: id,
|
|
4571
|
+
mode: 'auto',
|
|
4572
|
+
append: true,
|
|
4573
|
+
strategy: 'append',
|
|
4574
|
+
autoStart: true,
|
|
4575
|
+
items: [{
|
|
4576
|
+
title: queueTitleForText(text || queueText),
|
|
4577
|
+
text: queueText,
|
|
4578
|
+
images: attachments.map(normalizeQueueImage)
|
|
4579
|
+
}]
|
|
4580
|
+
})
|
|
4581
|
+
});
|
|
4582
|
+
var data = await res.json().catch(function() { return {}; });
|
|
4583
|
+
if (!res.ok || !data || !data.sessionId) throw new Error((data && data.error) || 'Failed to queue prompt');
|
|
4584
|
+
ws.queueState = data;
|
|
4585
|
+
renderQueuePreview(id);
|
|
4586
|
+
if (options.recordHistory !== false) recordInputHistory(ws, text);
|
|
4587
|
+
return { ok: true, state: data };
|
|
4588
|
+
} catch (err) {
|
|
4589
|
+
return { ok: false, error: err && err.message ? err.message : String(err || 'Failed to queue prompt') };
|
|
4590
|
+
} finally {
|
|
4591
|
+
ws.isQueueingMessage = false;
|
|
4592
|
+
}
|
|
4593
|
+
}
|
|
4594
|
+
|
|
2506
4595
|
function sendOutboundMessageNow(id, text, attachments, options, prepared) {
|
|
2507
4596
|
var ws = getState(id);
|
|
2508
4597
|
if (!ws) return;
|
|
2509
4598
|
if (ws.isGenerating) return;
|
|
2510
4599
|
|
|
2511
4600
|
if (options.recordHistory !== false) recordInputHistory(ws, text);
|
|
2512
|
-
|
|
4601
|
+
if (options.appendUser !== false) {
|
|
4602
|
+
appendUserMessageToSession(id, text, attachments, options.timestamp);
|
|
4603
|
+
}
|
|
2513
4604
|
|
|
2514
|
-
var model = ws.selectedModel || '';
|
|
4605
|
+
var model = normalizeSessionModelId(ws.selectedModel || '');
|
|
2515
4606
|
var modelItem = findModelItem(model) || findModelItem(ws.selectedModelRegistryId || '');
|
|
2516
4607
|
var session = state.sessions.get(id);
|
|
2517
4608
|
var meta = (session && session.meta) || {};
|
|
@@ -2522,14 +4613,35 @@ window.WalleSession = (function() {
|
|
|
2522
4613
|
type: 'walle-message',
|
|
2523
4614
|
id: id,
|
|
2524
4615
|
text: prepared.outboundText,
|
|
2525
|
-
|
|
2526
|
-
|
|
4616
|
+
// Keep attachments that carry either inline base64 OR a disk reference.
|
|
4617
|
+
// A reloaded branch message has no `data` (branchSafeAttachments strips
|
|
4618
|
+
// it), but the image is still on disk — the server rehydrates the base64
|
|
4619
|
+
// from path/url/filename via _hydrateWalleAttachment.
|
|
4620
|
+
attachments: attachments.filter(function(a) {
|
|
4621
|
+
return a && (a.data || a.url || a.path || a.file_path || a.filename);
|
|
4622
|
+
}).map(function(a) {
|
|
4623
|
+
return {
|
|
4624
|
+
type: a.type || 'image',
|
|
4625
|
+
name: a.name || a.filename || 'image.png',
|
|
4626
|
+
filename: a.filename || a.name || '',
|
|
4627
|
+
data: a.data || '',
|
|
4628
|
+
label: a.label || undefined,
|
|
4629
|
+
url: a.url || '',
|
|
4630
|
+
path: a.path || a.file_path || '',
|
|
4631
|
+
mediaType: a.mediaType || a.mimeType || '',
|
|
4632
|
+
imageWidth: a.imageWidth || null,
|
|
4633
|
+
imageHeight: a.imageHeight || null,
|
|
4634
|
+
originalWidth: a.originalWidth || null,
|
|
4635
|
+
originalHeight: a.originalHeight || null,
|
|
4636
|
+
resizedForProvider: !!a.resizedForProvider
|
|
4637
|
+
};
|
|
2527
4638
|
}),
|
|
2528
4639
|
model: model,
|
|
2529
4640
|
provider: provider,
|
|
2530
4641
|
modelPinned: modelPinned,
|
|
2531
4642
|
allowProviderFallback: !modelPinned,
|
|
2532
|
-
contextSessionId: resolveContextSessionId(id)
|
|
4643
|
+
contextSessionId: resolveContextSessionId(id),
|
|
4644
|
+
contextMessages: contextMessagesForModel(ws, prepared.outboundText)
|
|
2533
4645
|
});
|
|
2534
4646
|
|
|
2535
4647
|
ws.isGenerating = true;
|
|
@@ -2566,7 +4678,19 @@ window.WalleSession = (function() {
|
|
|
2566
4678
|
return { ok: true };
|
|
2567
4679
|
}
|
|
2568
4680
|
|
|
2569
|
-
|
|
4681
|
+
// ⌘/Ctrl+Enter — "send now". Idle: send live immediately. Busy: interrupt the
|
|
4682
|
+
// current run, then queue the message so it fires the instant Wall-E stops.
|
|
4683
|
+
// (The server drops a walle-message received mid-run, so we route through the
|
|
4684
|
+
// queue's send-when-ready path instead of racing the cancel.)
|
|
4685
|
+
function sendNow(id) {
|
|
4686
|
+
var ws = getState(id);
|
|
4687
|
+
if (!ws || !ws.isGenerating) { sendMessage(id); return; }
|
|
4688
|
+
try { send({ type: 'walle-cancel', id: id }); } catch (_) {}
|
|
4689
|
+
if (typeof toast === 'function') toast('Interrupting Wall-E — sending your message next', { type: 'info' });
|
|
4690
|
+
sendMessage(id); // still generating → queues (auto); fires once the run aborts
|
|
4691
|
+
}
|
|
4692
|
+
|
|
4693
|
+
async function sendMessage(id) {
|
|
2570
4694
|
var container = document.getElementById('walle-session-' + id);
|
|
2571
4695
|
if (!container) return;
|
|
2572
4696
|
var textarea = container.querySelector('.walle-input');
|
|
@@ -2577,12 +4701,36 @@ window.WalleSession = (function() {
|
|
|
2577
4701
|
|
|
2578
4702
|
var ws = getState(id);
|
|
2579
4703
|
if (!ws) return;
|
|
4704
|
+
// Gate: don't send while a pasted image is still resizing/compressing —
|
|
4705
|
+
// otherwise the text fires without its attachment.
|
|
4706
|
+
if (hasPendingAttachments(ws)) {
|
|
4707
|
+
if (typeof toast === 'function') toast('Image still compressing — one moment…', { type: 'info' });
|
|
4708
|
+
return;
|
|
4709
|
+
}
|
|
2580
4710
|
var attachments = (ws.inputAttachments || []).slice();
|
|
4711
|
+
if (ws.isGenerating) {
|
|
4712
|
+
var queued = await queueOutboundMessage(id, text, attachments);
|
|
4713
|
+
if (!queued.ok) {
|
|
4714
|
+
if (queued.error !== 'Message is empty' && typeof toast === 'function') {
|
|
4715
|
+
toast(queued.error || 'Failed to queue prompt', { type: 'error' });
|
|
4716
|
+
}
|
|
4717
|
+
return;
|
|
4718
|
+
}
|
|
4719
|
+
textarea.value = '';
|
|
4720
|
+
clearComposerDraft(id, textarea);
|
|
4721
|
+
syncComposerChrome(id, textarea);
|
|
4722
|
+
ws.inputAttachments = [];
|
|
4723
|
+
renderComposerAttachments(id);
|
|
4724
|
+
focusInput(id);
|
|
4725
|
+
return;
|
|
4726
|
+
}
|
|
4727
|
+
|
|
2581
4728
|
var result = sendOutboundMessage(id, text, attachments);
|
|
2582
4729
|
if (!result.ok) return;
|
|
2583
4730
|
|
|
2584
4731
|
// Clear input
|
|
2585
4732
|
textarea.value = '';
|
|
4733
|
+
clearComposerDraft(id, textarea);
|
|
2586
4734
|
syncComposerChrome(id, textarea);
|
|
2587
4735
|
ws.inputAttachments = [];
|
|
2588
4736
|
renderComposerAttachments(id);
|
|
@@ -2599,6 +4747,12 @@ window.WalleSession = (function() {
|
|
|
2599
4747
|
function handleUser(msg) {
|
|
2600
4748
|
var id = msg.id;
|
|
2601
4749
|
var ws = getState(id);
|
|
4750
|
+
try {
|
|
4751
|
+
console.log('[queue-diag] client handleUser id=' + id + ' hasWs=' + !!ws
|
|
4752
|
+
+ ' contentLen=' + String(msg.content || msg.text || '').length
|
|
4753
|
+
+ ' areaExists=' + !!document.getElementById('walle-messages-' + id)
|
|
4754
|
+
+ ' activeTab=' + (typeof state !== 'undefined' && state.activeTab === id));
|
|
4755
|
+
} catch (_) {}
|
|
2602
4756
|
if (!ws) return;
|
|
2603
4757
|
appendUserMessageToSession(id, msg.content || msg.text || '', msg.attachments || [], msg.timestamp);
|
|
2604
4758
|
updateHeaderStats(id);
|
|
@@ -2712,9 +4866,18 @@ window.WalleSession = (function() {
|
|
|
2712
4866
|
|
|
2713
4867
|
case 'permission_request':
|
|
2714
4868
|
removeThinking(messagesArea);
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
4869
|
+
if (!ev.permId || !messagesArea.querySelector('#walle-perm-' + ev.permId)) {
|
|
4870
|
+
var permCard = renderPermissionCard(id, ev);
|
|
4871
|
+
messagesArea.appendChild(permCard);
|
|
4872
|
+
scrollToBottom(messagesArea);
|
|
4873
|
+
}
|
|
4874
|
+
break;
|
|
4875
|
+
|
|
4876
|
+
case 'permission_resolved':
|
|
4877
|
+
if (ev.permId) {
|
|
4878
|
+
var resolvedCard = messagesArea.querySelector('#walle-perm-' + ev.permId);
|
|
4879
|
+
if (resolvedCard) markPermissionCardResolved(resolvedCard, ev.decision || 'allow', '');
|
|
4880
|
+
}
|
|
2718
4881
|
break;
|
|
2719
4882
|
}
|
|
2720
4883
|
}
|
|
@@ -2751,12 +4914,20 @@ window.WalleSession = (function() {
|
|
|
2751
4914
|
ws.isGenerating = false;
|
|
2752
4915
|
recordLiveRoleDelivery(ws, 'assistant');
|
|
2753
4916
|
|
|
2754
|
-
|
|
4917
|
+
if (restartVisibleMessageRenderIfBusy(id, ws)) {
|
|
4918
|
+
updateSendButton(id, false);
|
|
4919
|
+
updateHeaderStats(id);
|
|
4920
|
+
return;
|
|
4921
|
+
}
|
|
4922
|
+
|
|
4923
|
+
renderMessage(messagesArea, assistantMsg, ws.messages.length - 1);
|
|
4924
|
+
if (hasBranchSnapshot(ws)) saveWalleBranchSnapshot(id);
|
|
2755
4925
|
syncPromptMessageDomIndices(id);
|
|
2756
4926
|
updatePromptNav(id);
|
|
2757
4927
|
scrollToBottom(messagesArea);
|
|
2758
4928
|
updateSendButton(id, false);
|
|
2759
4929
|
updateHeaderStats(id);
|
|
4930
|
+
renderWorkBar(id);
|
|
2760
4931
|
focusInput(id);
|
|
2761
4932
|
}
|
|
2762
4933
|
|
|
@@ -2765,40 +4936,79 @@ window.WalleSession = (function() {
|
|
|
2765
4936
|
var id = msg.id;
|
|
2766
4937
|
var ws = getState(id);
|
|
2767
4938
|
if (!ws) return;
|
|
4939
|
+
var requestId = Number(msg.historyRequestId || 0);
|
|
4940
|
+
var currentRequestId = Number(ws.historyRequestId || 0);
|
|
4941
|
+
if (requestId && currentRequestId && requestId < currentRequestId) return;
|
|
2768
4942
|
var composerSnapshot = captureComposerState(id);
|
|
4943
|
+
var liveTurn = captureLiveTurnState(id, ws);
|
|
4944
|
+
var messages = Array.isArray(msg.messages) ? msg.messages.slice() : [];
|
|
4945
|
+
if (liveTurn) {
|
|
4946
|
+
messages = messages.filter(function(m) { return !isOpenTurnActivityMessage(m); });
|
|
4947
|
+
}
|
|
4948
|
+
if (msg.error && (!Array.isArray(messages) || messages.length === 0)) {
|
|
4949
|
+
ws.historyStatus = 'error';
|
|
4950
|
+
ws.historyError = msg.error;
|
|
4951
|
+
var errSession = state.sessions.get(id);
|
|
4952
|
+
if (errSession) {
|
|
4953
|
+
errSession._walleHistoryLoading = false;
|
|
4954
|
+
errSession._walleHistoryLoaded = false;
|
|
4955
|
+
}
|
|
4956
|
+
renderSession(id);
|
|
4957
|
+
restoreComposerState(id, composerSnapshot);
|
|
4958
|
+
return;
|
|
4959
|
+
}
|
|
2769
4960
|
|
|
2770
4961
|
var session = state.sessions.get(id);
|
|
2771
4962
|
if (session) {
|
|
2772
4963
|
session.needsAttach = false;
|
|
2773
4964
|
session._walleHistoryRequested = true;
|
|
4965
|
+
session._walleHistoryLoading = false;
|
|
4966
|
+
session._walleHistoryLoaded = true;
|
|
2774
4967
|
}
|
|
4968
|
+
markHistoryLoaded(id);
|
|
2775
4969
|
|
|
2776
4970
|
ws.messages = [];
|
|
2777
4971
|
ws.messageCount = 0;
|
|
2778
4972
|
ws.totalCost = 0;
|
|
2779
4973
|
ws._currentAssistant = null;
|
|
2780
4974
|
|
|
2781
|
-
var messages = msg.messages || [];
|
|
2782
4975
|
for (var i = 0; i < messages.length; i++) {
|
|
2783
4976
|
var m = messages[i];
|
|
2784
|
-
|
|
4977
|
+
var normalized = {
|
|
2785
4978
|
role: m.role,
|
|
2786
|
-
content: m.content || '',
|
|
4979
|
+
content: m.content || m.text || '',
|
|
2787
4980
|
model: m.model_id || m.model || '',
|
|
2788
4981
|
latency_ms: m.latency_ms || 0,
|
|
2789
4982
|
timestamp: m.created_at || m.timestamp || 0,
|
|
2790
|
-
toolCalls: m.toolCalls || []
|
|
2791
|
-
|
|
4983
|
+
toolCalls: m.toolCalls || [],
|
|
4984
|
+
attachments: Array.isArray(m.attachments) ? m.attachments : []
|
|
4985
|
+
};
|
|
4986
|
+
if (m.liveActivity) normalized.liveActivity = true;
|
|
4987
|
+
if (m.agentLabel) normalized.agentLabel = m.agentLabel;
|
|
4988
|
+
if (m.metadata) normalized.metadata = cloneJsonSafe(m.metadata, m.metadata);
|
|
4989
|
+
if (m.runtimeDiagnostic) normalized.runtimeDiagnostic = cloneJsonSafe(m.runtimeDiagnostic, m.runtimeDiagnostic);
|
|
4990
|
+
ws.messages.push(normalized);
|
|
2792
4991
|
ws.messageCount++;
|
|
2793
4992
|
}
|
|
4993
|
+
if (liveTurn) {
|
|
4994
|
+
ws.isGenerating = true;
|
|
4995
|
+
ws._currentAssistant = liveTurn.currentAssistant;
|
|
4996
|
+
}
|
|
4997
|
+
var durableHistory = activeHistorySnapshot(ws);
|
|
4998
|
+
applyBranchHistoryIfAuthoritative(ws, durableHistory);
|
|
2794
4999
|
seedInputHistoryFromMessages(ws);
|
|
2795
5000
|
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
5001
|
+
ws.renderPending = true;
|
|
5002
|
+
|
|
5003
|
+
// Re-render only when visible. A large Wall-E replay must not monopolize
|
|
5004
|
+
// the browser while the user has already moved to an unrelated session.
|
|
5005
|
+
if (document.getElementById('walle-session-' + id) && isWalleSessionVisible(id)) {
|
|
2799
5006
|
renderSession(id);
|
|
2800
5007
|
restoreComposerState(id, composerSnapshot);
|
|
2801
5008
|
ensurePendingThinking(id);
|
|
5009
|
+
if (liveTurn && liveTurn.currentAssistant) {
|
|
5010
|
+
refreshLiveActivity(document.getElementById('walle-messages-' + id), id, ws);
|
|
5011
|
+
}
|
|
2802
5012
|
}
|
|
2803
5013
|
updatePromptNav(id);
|
|
2804
5014
|
}
|
|
@@ -2855,6 +5065,10 @@ window.WalleSession = (function() {
|
|
|
2855
5065
|
message: issue.userMessage || issue.message || source.message || source.error || 'Wall-E could not get a response from the configured AI provider.',
|
|
2856
5066
|
rawMessage: issue.rawMessage || '',
|
|
2857
5067
|
provider: issue.provider || '',
|
|
5068
|
+
providerId: issue.providerId || issue.provider_id || '',
|
|
5069
|
+
registryId: issue.registryId || issue.registry_id || '',
|
|
5070
|
+
routeLabel: issue.routeLabel || issue.route_label || '',
|
|
5071
|
+
connectionKind: issue.connectionKind || issue.connection_kind || '',
|
|
2858
5072
|
model: issue.model || '',
|
|
2859
5073
|
status: issue.status || '',
|
|
2860
5074
|
retryAfter: issue.retryAfter || '',
|
|
@@ -2878,6 +5092,10 @@ window.WalleSession = (function() {
|
|
|
2878
5092
|
|
|
2879
5093
|
var detailLines = [];
|
|
2880
5094
|
if (issue.status) detailLines.push('HTTP/status: ' + issue.status);
|
|
5095
|
+
if (issue.routeLabel) detailLines.push('Route: ' + issue.routeLabel);
|
|
5096
|
+
if (issue.providerId) detailLines.push('Provider ID: ' + issue.providerId);
|
|
5097
|
+
if (issue.registryId) detailLines.push('Registry ID: ' + issue.registryId);
|
|
5098
|
+
if (issue.connectionKind) detailLines.push('Connection: ' + issue.connectionKind);
|
|
2881
5099
|
if (issue.provider) detailLines.push('Provider: ' + issue.provider);
|
|
2882
5100
|
if (issue.model) detailLines.push('Model: ' + issue.model);
|
|
2883
5101
|
if (issue.retryAfter) detailLines.push('Retry after: ' + issue.retryAfter);
|
|
@@ -2928,6 +5146,7 @@ window.WalleSession = (function() {
|
|
|
2928
5146
|
messagesArea.appendChild(notice);
|
|
2929
5147
|
scrollToBottom(messagesArea);
|
|
2930
5148
|
updateSendButton(id, false);
|
|
5149
|
+
renderWorkBar(id);
|
|
2931
5150
|
focusInput(id);
|
|
2932
5151
|
}
|
|
2933
5152
|
|
|
@@ -2943,7 +5162,11 @@ window.WalleSession = (function() {
|
|
|
2943
5162
|
return Promise.resolve(_walleModelRegistryCache);
|
|
2944
5163
|
}
|
|
2945
5164
|
if (_walleModelRegistryPromise) return _walleModelRegistryPromise;
|
|
2946
|
-
_walleModelRegistryPromise = fetch('/api/models/coding-
|
|
5165
|
+
_walleModelRegistryPromise = fetch('/api/models/coding-catalog', { cache: 'no-store' })
|
|
5166
|
+
.then(function(r) {
|
|
5167
|
+
if (r.status === 404) return fetch('/api/models/coding-availability', { cache: 'no-store' });
|
|
5168
|
+
return r;
|
|
5169
|
+
})
|
|
2947
5170
|
.then(function(r) {
|
|
2948
5171
|
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
2949
5172
|
return r.json();
|
|
@@ -2952,9 +5175,10 @@ window.WalleSession = (function() {
|
|
|
2952
5175
|
if (data && data.error) throw new Error(data.error);
|
|
2953
5176
|
var rows = Array.isArray(data) ? data : (Array.isArray(data?.models) ? data.models : []);
|
|
2954
5177
|
_walleModelAvailabilityMeta = {
|
|
2955
|
-
source: data?.source || '
|
|
5178
|
+
source: data?.source || 'catalog',
|
|
2956
5179
|
generatedAt: data?.generated_at || '',
|
|
2957
5180
|
providers: Array.isArray(data?.providers) ? data.providers : [],
|
|
5181
|
+
counts: data?.counts || null,
|
|
2958
5182
|
error: null
|
|
2959
5183
|
};
|
|
2960
5184
|
_walleModelRegistryCache = normalizeWalleModels(rows);
|
|
@@ -2964,9 +5188,10 @@ window.WalleSession = (function() {
|
|
|
2964
5188
|
.catch(function(err) {
|
|
2965
5189
|
invalidateWalleModelCache();
|
|
2966
5190
|
_walleModelAvailabilityMeta = {
|
|
2967
|
-
source: '
|
|
5191
|
+
source: 'catalog',
|
|
2968
5192
|
generatedAt: '',
|
|
2969
5193
|
providers: [],
|
|
5194
|
+
counts: null,
|
|
2970
5195
|
error: err && err.message ? err.message : String(err || 'Failed to load models')
|
|
2971
5196
|
};
|
|
2972
5197
|
return [];
|
|
@@ -2990,7 +5215,11 @@ window.WalleSession = (function() {
|
|
|
2990
5215
|
}
|
|
2991
5216
|
|
|
2992
5217
|
function stripModelAlias(label) {
|
|
2993
|
-
return String(label || '').replace(
|
|
5218
|
+
return String(label || '').replace(/@(default|latest|\d{6,8})\b/gi, '').replace(/\s+/g, ' ').trim();
|
|
5219
|
+
}
|
|
5220
|
+
|
|
5221
|
+
function normalizeSessionModelId(modelId) {
|
|
5222
|
+
return String(modelId || '').trim().replace(/@(default|latest|20\d{6})$/i, '');
|
|
2994
5223
|
}
|
|
2995
5224
|
|
|
2996
5225
|
function isLegacyModel(m) {
|
|
@@ -3002,13 +5231,19 @@ window.WalleSession = (function() {
|
|
|
3002
5231
|
for (var i = 0; i < models.length; i++) {
|
|
3003
5232
|
var m = models[i];
|
|
3004
5233
|
if (!m || m.enabled === 0 || m.enabled === false) continue;
|
|
3005
|
-
if (m.
|
|
5234
|
+
if (m.selectable === false) continue;
|
|
5235
|
+
if (m.selectable === undefined && (m.available === false || m.coding_capable === false)) continue;
|
|
3006
5236
|
var provider = m.provider_type || m.provider || '';
|
|
3007
5237
|
var label = String(m.display_name || m.model_id || m.id || '').trim();
|
|
3008
5238
|
if (!label) continue;
|
|
5239
|
+
var modelId = normalizeSessionModelId(m.model_id || m.modelId || m.id);
|
|
5240
|
+
var catalogId = m.catalog_id || m.catalogId || ((provider || 'provider') + '|' + modelId);
|
|
3009
5241
|
enabled.push({
|
|
3010
|
-
id:
|
|
3011
|
-
|
|
5242
|
+
id: catalogId,
|
|
5243
|
+
catalogId: catalogId,
|
|
5244
|
+
registryId: m.registry_id || m.registryId || '',
|
|
5245
|
+
preferredRegistryId: m.preferred_registry_id || m.preferredRegistryId || '',
|
|
5246
|
+
modelId: modelId,
|
|
3012
5247
|
label: label,
|
|
3013
5248
|
baseLabel: stripModelAlias(label),
|
|
3014
5249
|
provider: provider,
|
|
@@ -3016,6 +5251,14 @@ window.WalleSession = (function() {
|
|
|
3016
5251
|
providerLabel: m.provider_name || providerLabel(provider),
|
|
3017
5252
|
capabilities: normalizeModelCapabilities(m.capabilities),
|
|
3018
5253
|
source: m.source || 'live',
|
|
5254
|
+
availabilityState: m.availability_state || m.availabilityState || (m.live_available ? 'available' : 'configured'),
|
|
5255
|
+
liveAvailable: m.live_available === true || m.source === 'live' || m.source === 'live+registry',
|
|
5256
|
+
routeMode: m.route_mode || m.routeMode || 'policy',
|
|
5257
|
+
routeLabel: m.route_label || m.routeLabel || '',
|
|
5258
|
+
routePolicy: m.route_policy || m.routePolicy || '',
|
|
5259
|
+
routeProviderId: m.route_provider_id || m.routeProviderId || '',
|
|
5260
|
+
routeProviderName: m.route_provider_name || m.routeProviderName || '',
|
|
5261
|
+
routeConnectionKind: m.route_connection_kind || m.routeConnectionKind || '',
|
|
3019
5262
|
codingCapable: m.coding_capable !== false,
|
|
3020
5263
|
codingReason: m.coding_reason || '',
|
|
3021
5264
|
speedTier: m.speed_tier || 3,
|
|
@@ -3055,7 +5298,7 @@ window.WalleSession = (function() {
|
|
|
3055
5298
|
var byKey = {};
|
|
3056
5299
|
for (var i = 0; i < items.length; i++) {
|
|
3057
5300
|
var item = items[i];
|
|
3058
|
-
var key = (item.provider || '') + '|' + item.
|
|
5301
|
+
var key = item.catalogId || ((item.provider || '') + '|' + item.modelId);
|
|
3059
5302
|
if (!byKey[key]) byKey[key] = item;
|
|
3060
5303
|
}
|
|
3061
5304
|
return Object.keys(byKey).map(function(k) { return byKey[k]; });
|
|
@@ -3149,7 +5392,7 @@ window.WalleSession = (function() {
|
|
|
3149
5392
|
var id = String(modelId);
|
|
3150
5393
|
for (var i = 0; i < _walleModelRegistryCache.length; i++) {
|
|
3151
5394
|
var item = _walleModelRegistryCache[i];
|
|
3152
|
-
if (item.id === id || item.modelId === id) return item;
|
|
5395
|
+
if (item.id === id || item.catalogId === id || item.modelId === id || item.registryId === id || item.preferredRegistryId === id) return item;
|
|
3153
5396
|
}
|
|
3154
5397
|
return null;
|
|
3155
5398
|
}
|
|
@@ -3157,6 +5400,7 @@ window.WalleSession = (function() {
|
|
|
3157
5400
|
function setSelectedModel(id, item) {
|
|
3158
5401
|
var ws = getState(id);
|
|
3159
5402
|
if (!ws) return;
|
|
5403
|
+
var composerSnapshot = captureComposerState(id);
|
|
3160
5404
|
ws.selectedModel = item ? item.modelId : '';
|
|
3161
5405
|
ws.selectedModelRegistryId = item ? item.id : '';
|
|
3162
5406
|
ws.selectedModelLabel = item ? item.baseLabel : '';
|
|
@@ -3165,19 +5409,23 @@ window.WalleSession = (function() {
|
|
|
3165
5409
|
ws._modelManual = true;
|
|
3166
5410
|
syncWalleModelButtons(id);
|
|
3167
5411
|
if (item && typeof send === 'function') {
|
|
5412
|
+
var exactRegistryId = item.routeMode === 'exact' ? (item.registryId || item.preferredRegistryId || '') : '';
|
|
3168
5413
|
send({
|
|
3169
5414
|
type: 'model-change',
|
|
3170
5415
|
id: id,
|
|
3171
5416
|
agent_type: 'walle',
|
|
3172
5417
|
model_id: item.modelId,
|
|
3173
5418
|
model_provider: item.provider,
|
|
3174
|
-
model_registry_id:
|
|
3175
|
-
model_provider_id:
|
|
5419
|
+
model_registry_id: exactRegistryId,
|
|
5420
|
+
model_provider_id: '',
|
|
5421
|
+
model_route_mode: item.routeMode || 'policy',
|
|
3176
5422
|
scope: 'session',
|
|
3177
5423
|
pinned: true,
|
|
3178
5424
|
});
|
|
3179
5425
|
}
|
|
3180
5426
|
closeModelPicker();
|
|
5427
|
+
restoreComposerState(id, composerSnapshot);
|
|
5428
|
+
focusInput(id);
|
|
3181
5429
|
}
|
|
3182
5430
|
|
|
3183
5431
|
function closeModelPicker() {
|
|
@@ -3273,7 +5521,19 @@ window.WalleSession = (function() {
|
|
|
3273
5521
|
oldTag.textContent = 'Versioned';
|
|
3274
5522
|
tags.appendChild(oldTag);
|
|
3275
5523
|
}
|
|
3276
|
-
if (item && item.
|
|
5524
|
+
if (item && item.routeConnectionKind) {
|
|
5525
|
+
var routeTag = document.createElement('span');
|
|
5526
|
+
routeTag.className = 'walle-model-tag muted';
|
|
5527
|
+
routeTag.textContent = item.routeConnectionKind === 'portkey' ? 'Portkey' : item.routeConnectionKind.replace(/_/g, ' ');
|
|
5528
|
+
tags.appendChild(routeTag);
|
|
5529
|
+
}
|
|
5530
|
+
if (item && item.availabilityState === 'route_issue') {
|
|
5531
|
+
var issueTag = document.createElement('span');
|
|
5532
|
+
issueTag.className = 'walle-model-tag muted';
|
|
5533
|
+
issueTag.textContent = 'Route issue';
|
|
5534
|
+
tags.appendChild(issueTag);
|
|
5535
|
+
}
|
|
5536
|
+
if (item && item.liveAvailable) {
|
|
3277
5537
|
var liveTag = document.createElement('span');
|
|
3278
5538
|
liveTag.className = 'walle-model-tag accent';
|
|
3279
5539
|
liveTag.textContent = 'Live';
|
|
@@ -3287,30 +5547,37 @@ window.WalleSession = (function() {
|
|
|
3287
5547
|
var q = search.value.trim().toLowerCase();
|
|
3288
5548
|
var filtered = models.filter(function(item) {
|
|
3289
5549
|
if (!q) return true;
|
|
3290
|
-
return (item.label + ' ' + item.modelId + ' ' + item.providerLabel + ' ' + (item.capabilities || []).join(' ')).toLowerCase().indexOf(q) !== -1;
|
|
5550
|
+
return (item.label + ' ' + item.modelId + ' ' + item.providerLabel + ' ' + item.routeLabel + ' ' + item.routeProviderName + ' ' + (item.capabilities || []).join(' ')).toLowerCase().indexOf(q) !== -1;
|
|
3291
5551
|
});
|
|
3292
5552
|
return dedupeVisibleModels(filtered);
|
|
3293
5553
|
}
|
|
3294
5554
|
|
|
3295
5555
|
function providerStatusSummary() {
|
|
3296
5556
|
var meta = _walleModelAvailabilityMeta || {};
|
|
5557
|
+
var counts = meta.counts || {};
|
|
3297
5558
|
var providers = Array.isArray(meta.providers) ? meta.providers : [];
|
|
3298
5559
|
var available = providers.filter(function(provider) { return provider.status === 'available'; }).length;
|
|
3299
5560
|
var unavailable = providers.filter(function(provider) {
|
|
3300
5561
|
return provider.status && provider.status !== 'available' && provider.status !== 'empty';
|
|
3301
5562
|
}).length;
|
|
3302
|
-
if (meta.error) return '
|
|
3303
|
-
if (
|
|
3304
|
-
|
|
3305
|
-
|
|
5563
|
+
if (meta.error) return 'Model catalog unavailable';
|
|
5564
|
+
if (counts && counts.models !== undefined) {
|
|
5565
|
+
var parts = [models.length + ' models'];
|
|
5566
|
+
if (counts.configured_routes) parts.push(counts.configured_routes + ' configured routes');
|
|
5567
|
+
if (counts.live) parts.push(counts.live + ' live');
|
|
5568
|
+
return parts.join(' · ');
|
|
5569
|
+
}
|
|
5570
|
+
if (available > 0) return models.length + ' models · ' + available + ' live provider' + (available === 1 ? '' : 's');
|
|
5571
|
+
if (unavailable > 0) return models.length + ' models · route health in AI Providers';
|
|
5572
|
+
return models.length + ' models';
|
|
3306
5573
|
}
|
|
3307
5574
|
|
|
3308
5575
|
function emptyModelMessage(hasQuery) {
|
|
3309
|
-
if (hasQuery) return 'No matching
|
|
5576
|
+
if (hasQuery) return 'No matching models';
|
|
3310
5577
|
if (_walleModelAvailabilityMeta && _walleModelAvailabilityMeta.error) {
|
|
3311
|
-
return 'Could not load
|
|
5578
|
+
return 'Could not load model catalog';
|
|
3312
5579
|
}
|
|
3313
|
-
return 'No
|
|
5580
|
+
return 'No configured models available';
|
|
3314
5581
|
}
|
|
3315
5582
|
|
|
3316
5583
|
function renderLoading() {
|
|
@@ -3319,7 +5586,7 @@ window.WalleSession = (function() {
|
|
|
3319
5586
|
loading.className = 'walle-model-empty';
|
|
3320
5587
|
loading.textContent = 'Loading models...';
|
|
3321
5588
|
body.appendChild(loading);
|
|
3322
|
-
sourceNote.textContent = '
|
|
5589
|
+
sourceNote.textContent = 'Loading model catalog...';
|
|
3323
5590
|
}
|
|
3324
5591
|
|
|
3325
5592
|
function renderList() {
|
|
@@ -3421,7 +5688,11 @@ window.WalleSession = (function() {
|
|
|
3421
5688
|
closeModelPicker();
|
|
3422
5689
|
},
|
|
3423
5690
|
onKeyDown: function(e) {
|
|
3424
|
-
if (e.key === 'Escape')
|
|
5691
|
+
if (e.key === 'Escape') {
|
|
5692
|
+
e.preventDefault();
|
|
5693
|
+
e.stopPropagation();
|
|
5694
|
+
closeModelPicker();
|
|
5695
|
+
}
|
|
3425
5696
|
},
|
|
3426
5697
|
onResize: function() {
|
|
3427
5698
|
positionModelPicker(anchor, popover);
|
|
@@ -3632,12 +5903,18 @@ window.WalleSession = (function() {
|
|
|
3632
5903
|
focusInput(id);
|
|
3633
5904
|
} else {
|
|
3634
5905
|
// Show send button
|
|
5906
|
+
var pendingAttach = hasPendingAttachments(getState(id));
|
|
3635
5907
|
if (btn) {
|
|
3636
5908
|
btn.textContent = '\u2191';
|
|
3637
5909
|
btn.style.background = '';
|
|
3638
|
-
btn.
|
|
3639
|
-
btn.setAttribute('aria-label', 'Send message');
|
|
5910
|
+
btn.setAttribute('aria-label', pendingAttach ? 'Waiting for image' : 'Send message');
|
|
3640
5911
|
btn.onclick = function() { sendMessage(id); };
|
|
5912
|
+
// Gate send until a pasted image finishes compressing, so the message
|
|
5913
|
+
// can't fire without its attachment.
|
|
5914
|
+
btn.disabled = pendingAttach;
|
|
5915
|
+
btn.title = pendingAttach ? 'Waiting for image to finish compressing\u2026' : 'Send message';
|
|
5916
|
+
btn.style.opacity = pendingAttach ? '0.5' : '';
|
|
5917
|
+
btn.style.cursor = pendingAttach ? 'progress' : '';
|
|
3641
5918
|
}
|
|
3642
5919
|
if (input) input.disabled = false;
|
|
3643
5920
|
if (attach) attach.disabled = false;
|
|
@@ -3663,7 +5940,11 @@ window.WalleSession = (function() {
|
|
|
3663
5940
|
var cwd = s?.meta?.cwd || '';
|
|
3664
5941
|
var parts = [];
|
|
3665
5942
|
if (cwd) parts.push(cwd);
|
|
3666
|
-
|
|
5943
|
+
if (historyStatusForSession(id, ws) === 'loading' && (ws.messageCount || 0) === 0) {
|
|
5944
|
+
parts.push('Loading messages...');
|
|
5945
|
+
} else {
|
|
5946
|
+
parts.push(ws.messageCount + ' messages');
|
|
5947
|
+
}
|
|
3667
5948
|
meta.textContent = parts.join(' ');
|
|
3668
5949
|
}
|
|
3669
5950
|
|
|
@@ -3700,7 +5981,7 @@ window.WalleSession = (function() {
|
|
|
3700
5981
|
&& ws.selectedModel === msg.model_id
|
|
3701
5982
|
&& (!msg.model_provider || ws.selectedModelProviderType === msg.model_provider);
|
|
3702
5983
|
if (ws._modelManual && !sameManualModel) return;
|
|
3703
|
-
ws.selectedModel = msg.model_id;
|
|
5984
|
+
ws.selectedModel = normalizeSessionModelId(msg.model_id);
|
|
3704
5985
|
ws.selectedModelRegistryId = msg.model_registry_id || msg.modelRegistryId || '';
|
|
3705
5986
|
ws.selectedModelLabel = '';
|
|
3706
5987
|
ws.selectedModelProviderType = msg.model_provider || ws.selectedModelProviderType || '';
|
|
@@ -3712,9 +5993,16 @@ window.WalleSession = (function() {
|
|
|
3712
5993
|
// ---------- public API ----------
|
|
3713
5994
|
return {
|
|
3714
5995
|
getState: getState,
|
|
5996
|
+
requestHistory: requestHistory,
|
|
5997
|
+
markHistoryLoading: markHistoryLoading,
|
|
5998
|
+
markHistoryLoaded: markHistoryLoaded,
|
|
5999
|
+
historyStatusForSession: historyStatusForSession,
|
|
6000
|
+
messageRenderNeedsRestart: messageRenderNeedsRestart,
|
|
3715
6001
|
renderSession: renderSession,
|
|
3716
6002
|
sendMessage: sendMessage,
|
|
3717
6003
|
sendQueuedMessage: sendQueuedMessage,
|
|
6004
|
+
handleQueueState: handleQueueState,
|
|
6005
|
+
handleWorkState: handleWorkState,
|
|
3718
6006
|
handleUser: handleUser,
|
|
3719
6007
|
handleProgress: handleProgress,
|
|
3720
6008
|
handleResponse: handleResponse,
|
|
@@ -3722,6 +6010,15 @@ window.WalleSession = (function() {
|
|
|
3722
6010
|
handleTranscriptAppend: handleTranscriptAppend,
|
|
3723
6011
|
handleError: handleError,
|
|
3724
6012
|
handleModel: handleModel,
|
|
6013
|
+
handleGlobalEscape: handleGlobalEscape,
|
|
6014
|
+
confirmCancelActiveRun: confirmCancelActiveRun,
|
|
6015
|
+
editMessage: editMessage,
|
|
6016
|
+
cancelEdit: cancelEdit,
|
|
6017
|
+
submitEdit: submitEdit,
|
|
6018
|
+
deleteFromPrompt: deleteFromPrompt,
|
|
6019
|
+
switchBranch: switchBranch,
|
|
6020
|
+
saveWalleBranchSnapshot: saveWalleBranchSnapshot,
|
|
6021
|
+
branchSafeAttachments: branchSafeAttachments,
|
|
3725
6022
|
promptNavGo: promptNavGo,
|
|
3726
6023
|
promptNavToggleList: promptNavToggleList,
|
|
3727
6024
|
updatePromptNav: updatePromptNav,
|