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
|
@@ -7,6 +7,26 @@ const db = require('./db');
|
|
|
7
7
|
// Map<sessionId, QueueState>
|
|
8
8
|
const queues = new Map();
|
|
9
9
|
|
|
10
|
+
// Lifecycle tracing for the "queued message stuck / sent all at once /
|
|
11
|
+
// disappeared" investigation, tagged [queue-trace] so it's greppable in ctm.log.
|
|
12
|
+
// Each queue object gets a stable _uid so we can SEE when the live object is
|
|
13
|
+
// swapped out from under an in-flight completion (the completeAsyncSend
|
|
14
|
+
// `live !== q` drop). Anomalies (_qlog) are always on; verbose per-op trace
|
|
15
|
+
// (_qtrace) is gated behind CTM_DEBUG_QUEUE.
|
|
16
|
+
let _queueUidSeq = 0;
|
|
17
|
+
// Anomaly logs (drops, dedupe, queue-object replacement, recovery) are always on
|
|
18
|
+
// — they are rare and each one flags a real problem worth seeing in prod.
|
|
19
|
+
function _qlog(msg) { try { console.log(`[queue-trace] ${msg}`); } catch {} }
|
|
20
|
+
// Verbose per-operation trace (every wake/dispatch/complete) is gated behind an
|
|
21
|
+
// env flag so it doesn't spam prod; enable with CTM_DEBUG_QUEUE=1 when diagnosing.
|
|
22
|
+
const _QUEUE_TRACE = !!process.env.CTM_DEBUG_QUEUE;
|
|
23
|
+
function _qtrace(msg) { if (_QUEUE_TRACE) { try { console.log(`[queue-trace] ${msg}`); } catch {} } }
|
|
24
|
+
function _itemSummary(q) {
|
|
25
|
+
try {
|
|
26
|
+
return (q.items || []).map((it, i) => `${i}${i === q.currentIndex ? '*' : ''}:${it.status}`).join(',');
|
|
27
|
+
} catch { return '?'; }
|
|
28
|
+
}
|
|
29
|
+
|
|
10
30
|
// Hook called when a new queue is created (set by server.js)
|
|
11
31
|
let _onQueueCreated = null;
|
|
12
32
|
function setOnQueueCreated(fn) { _onQueueCreated = fn; }
|
|
@@ -33,6 +53,107 @@ function appendMissingImageLabels(text, images) {
|
|
|
33
53
|
return out;
|
|
34
54
|
}
|
|
35
55
|
|
|
56
|
+
const ITEM_STATUSES = new Set(['pending', 'sending', 'sent', 'skipped', 'failed']);
|
|
57
|
+
|
|
58
|
+
function normalizeQueueItem(item = {}, options = {}) {
|
|
59
|
+
const text = item.text || item.body || item.prompt || '';
|
|
60
|
+
const status = options.preserveStatus && ITEM_STATUSES.has(String(item.status || '').toLowerCase())
|
|
61
|
+
? String(item.status || '').toLowerCase()
|
|
62
|
+
: 'pending';
|
|
63
|
+
return {
|
|
64
|
+
id: item.id || crypto.randomUUID(),
|
|
65
|
+
type: item.type || 'inline',
|
|
66
|
+
promptId: item.promptId || item.prompt_id || null,
|
|
67
|
+
title: item.title || String(text).slice(0, 60) || 'Untitled',
|
|
68
|
+
text,
|
|
69
|
+
images: Array.isArray(item.images) ? item.images : [],
|
|
70
|
+
status,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function normalizeReadiness(value) {
|
|
75
|
+
if (value === false) {
|
|
76
|
+
return {
|
|
77
|
+
ok: false,
|
|
78
|
+
code: 'target_not_ready',
|
|
79
|
+
reason: 'target_not_ready',
|
|
80
|
+
message: 'The target session is not ready for another prompt.',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (value && typeof value === 'object' && value.ok === false) {
|
|
84
|
+
return {
|
|
85
|
+
ok: false,
|
|
86
|
+
code: value.code || value.reason || 'target_not_ready',
|
|
87
|
+
reason: value.reason || value.code || 'target_not_ready',
|
|
88
|
+
message: value.message || value.reason || 'The target session is not ready for another prompt.',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return { ok: true };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function setWaiting(q, readiness) {
|
|
95
|
+
const code = readiness.code || readiness.reason || 'target_not_ready';
|
|
96
|
+
const reason = readiness.reason || code;
|
|
97
|
+
const message = readiness.message || reason;
|
|
98
|
+
const changed = q.waitingCode !== code || q.waitingReason !== reason || q.waitingMessage !== message;
|
|
99
|
+
q.waitingCode = code;
|
|
100
|
+
q.waitingReason = reason;
|
|
101
|
+
q.waitingMessage = message;
|
|
102
|
+
return changed;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function clearWaiting(q) {
|
|
106
|
+
const changed = !!(q.waitingCode || q.waitingReason || q.waitingMessage);
|
|
107
|
+
q.waitingCode = '';
|
|
108
|
+
q.waitingReason = '';
|
|
109
|
+
q.waitingMessage = '';
|
|
110
|
+
return changed;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function canDispatch(q, item) {
|
|
114
|
+
if (!q._canSendFn) return { ok: true };
|
|
115
|
+
try {
|
|
116
|
+
return normalizeReadiness(q._canSendFn({
|
|
117
|
+
sessionId: q.sessionId,
|
|
118
|
+
item: { ...item },
|
|
119
|
+
queue: getState(q.sessionId),
|
|
120
|
+
}));
|
|
121
|
+
} catch (err) {
|
|
122
|
+
return {
|
|
123
|
+
ok: false,
|
|
124
|
+
code: 'readiness_check_failed',
|
|
125
|
+
reason: 'readiness_check_failed',
|
|
126
|
+
message: err && err.message ? err.message : 'Could not verify session readiness.',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function canRecoverSending(q, item, reason) {
|
|
132
|
+
if (!q._canRecoverSendingFn) {
|
|
133
|
+
return {
|
|
134
|
+
ok: false,
|
|
135
|
+
code: 'sending_in_flight',
|
|
136
|
+
reason: 'sending_in_flight',
|
|
137
|
+
message: 'The current queued item is still marked as in flight.',
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
return normalizeReadiness(q._canRecoverSendingFn({
|
|
142
|
+
sessionId: q.sessionId,
|
|
143
|
+
item: { ...item },
|
|
144
|
+
queue: getState(q.sessionId),
|
|
145
|
+
reason,
|
|
146
|
+
}));
|
|
147
|
+
} catch (err) {
|
|
148
|
+
return {
|
|
149
|
+
ok: false,
|
|
150
|
+
code: 'recovery_check_failed',
|
|
151
|
+
reason: 'recovery_check_failed',
|
|
152
|
+
message: err && err.message ? err.message : 'Could not verify whether the queued turn is still running.',
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
36
157
|
// --- Persistence (SQLite) ---
|
|
37
158
|
|
|
38
159
|
function saveToDb(q) {
|
|
@@ -61,21 +182,53 @@ function loadFromDb() {
|
|
|
61
182
|
try {
|
|
62
183
|
const rows = db.loadAllQueues();
|
|
63
184
|
for (const saved of rows) {
|
|
64
|
-
// Pause any running queues on restore (PTY may have moved on)
|
|
185
|
+
// Pause any running queues on restore (PTY may have moved on). This is a
|
|
186
|
+
// SYSTEM pause, not a user pause — flag it so wake() can auto-resume the
|
|
187
|
+
// queue once the session signals readiness again, instead of stranding
|
|
188
|
+
// still-pending items forever (the "I queued it but it never sent after a
|
|
189
|
+
// restart" bug). A user-initiated pause does NOT set this flag.
|
|
190
|
+
const wasRunning = saved.status === 'running';
|
|
191
|
+
// An item left in 'sending' was mid-dispatch when the previous process
|
|
192
|
+
// exited. The turn that owned it — and would have resolved it to 'sent'
|
|
193
|
+
// (completeAsyncSend) or rolled it back to 'pending' (failAsyncSend) — died
|
|
194
|
+
// with that process, so it can NEVER leave 'sending' on its own. Both
|
|
195
|
+
// sendNext() and wake() bail on a 'sending' item at currentIndex, so an
|
|
196
|
+
// orphaned 'sending' permanently wedges the queue: the item shows "sending"
|
|
197
|
+
// forever and no further queued message can dispatch ("I can't send my
|
|
198
|
+
// queued message"). Requeue it so it re-dispatches once the session is
|
|
199
|
+
// ready again. This is distinct from the restart-pause auto-resume below:
|
|
200
|
+
// that unblocks a still-PENDING item; this unblocks a SENDING one.
|
|
201
|
+
let earliestRequeued = -1;
|
|
202
|
+
const items = saved.items.map((item, idx) => {
|
|
203
|
+
const norm = normalizeQueueItem(item, { preserveStatus: true });
|
|
204
|
+
if (norm.status === 'sending') {
|
|
205
|
+
norm.status = 'pending';
|
|
206
|
+
if (earliestRequeued === -1) earliestRequeued = idx;
|
|
207
|
+
}
|
|
208
|
+
return norm;
|
|
209
|
+
});
|
|
210
|
+
// sendNext()/wake() only scan for the next pending AFTER currentIndex, so
|
|
211
|
+
// move the cursor just before the recovered item; otherwise the re-dispatch
|
|
212
|
+
// scan skips right over it and the message is silently dropped.
|
|
213
|
+
let currentIndex = saved.currentIndex;
|
|
214
|
+
if (earliestRequeued !== -1 && earliestRequeued <= currentIndex) {
|
|
215
|
+
currentIndex = earliestRequeued - 1;
|
|
216
|
+
}
|
|
217
|
+
if (earliestRequeued !== -1) {
|
|
218
|
+
console.log(`[queue] recovered orphaned 'sending' item on restore session=${saved.sessionId} (mid-turn restart) → requeued for re-dispatch`);
|
|
219
|
+
}
|
|
220
|
+
const prior = queues.get(saved.sessionId);
|
|
221
|
+
if (prior) {
|
|
222
|
+
_qlog(`loadFromDb REPLACING live queue session=${saved.sessionId} olduid=${prior._uid} oldStatus=${prior.status} oldItems=[${_itemSummary(prior)}] — any in-flight completion bound to olduid will be DROPPED (live!==q)`);
|
|
223
|
+
}
|
|
65
224
|
const queue = {
|
|
66
225
|
sessionId: saved.sessionId,
|
|
226
|
+
_uid: ++_queueUidSeq,
|
|
67
227
|
mode: saved.mode,
|
|
68
|
-
status:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
type: item.type || 'inline',
|
|
73
|
-
promptId: item.promptId || null,
|
|
74
|
-
title: item.title || 'Untitled',
|
|
75
|
-
text: item.text || '',
|
|
76
|
-
images: Array.isArray(item.images) ? item.images : [],
|
|
77
|
-
status: item.status || 'pending',
|
|
78
|
-
})),
|
|
228
|
+
status: wasRunning ? 'paused' : saved.status,
|
|
229
|
+
_restorePaused: wasRunning,
|
|
230
|
+
currentIndex,
|
|
231
|
+
items,
|
|
79
232
|
idleTimeoutMs: saved.idleTimeoutMs || 10000,
|
|
80
233
|
promptPatterns: DEFAULT_PROMPT_PATTERNS,
|
|
81
234
|
_idleTimer: null,
|
|
@@ -84,8 +237,16 @@ function loadFromDb() {
|
|
|
84
237
|
_lastOutputTime: 0,
|
|
85
238
|
_onStateChange: null,
|
|
86
239
|
_sendFn: null,
|
|
240
|
+
_canSendFn: null,
|
|
241
|
+
_canRecoverSendingFn: null,
|
|
242
|
+
revision: 0,
|
|
243
|
+
updatedAt: Date.now(),
|
|
244
|
+
waitingCode: '',
|
|
245
|
+
waitingReason: '',
|
|
246
|
+
waitingMessage: '',
|
|
87
247
|
};
|
|
88
248
|
queues.set(saved.sessionId, queue);
|
|
249
|
+
_qlog(`loadFromDb created uid=${queue._uid} session=${saved.sessionId} status=${queue.status} items=[${_itemSummary(queue)}]`);
|
|
89
250
|
}
|
|
90
251
|
if (queues.size > 0) {
|
|
91
252
|
console.log(` Restored ${queues.size} queue(s) from database`);
|
|
@@ -102,20 +263,17 @@ function init() {
|
|
|
102
263
|
}
|
|
103
264
|
|
|
104
265
|
function createQueue(sessionId, { mode = 'manual', items = [], idleTimeoutMs = 10000, promptPatterns } = {}) {
|
|
266
|
+
const prior = queues.get(sessionId);
|
|
267
|
+
if (prior) {
|
|
268
|
+
_qlog(`createQueue REPLACING live queue session=${sessionId} olduid=${prior._uid} oldStatus=${prior.status} oldItems=[${_itemSummary(prior)}] — in-flight completion on olduid will be DROPPED`);
|
|
269
|
+
}
|
|
105
270
|
const queue = {
|
|
106
271
|
sessionId,
|
|
272
|
+
_uid: ++_queueUidSeq,
|
|
107
273
|
mode,
|
|
108
274
|
status: 'idle',
|
|
109
275
|
currentIndex: -1,
|
|
110
|
-
items: items.map(item => (
|
|
111
|
-
id: crypto.randomUUID(),
|
|
112
|
-
type: item.type || 'inline',
|
|
113
|
-
promptId: item.promptId || null,
|
|
114
|
-
title: item.title || item.text?.slice(0, 60) || 'Untitled',
|
|
115
|
-
text: item.text || '',
|
|
116
|
-
images: Array.isArray(item.images) ? item.images : [],
|
|
117
|
-
status: 'pending',
|
|
118
|
-
})),
|
|
276
|
+
items: items.map(item => normalizeQueueItem(item)),
|
|
119
277
|
idleTimeoutMs: idleTimeoutMs || 10000,
|
|
120
278
|
promptPatterns: promptPatterns || DEFAULT_PROMPT_PATTERNS,
|
|
121
279
|
_idleTimer: null,
|
|
@@ -124,6 +282,13 @@ function createQueue(sessionId, { mode = 'manual', items = [], idleTimeoutMs = 1
|
|
|
124
282
|
_lastOutputTime: 0,
|
|
125
283
|
_onStateChange: null,
|
|
126
284
|
_sendFn: null,
|
|
285
|
+
_canSendFn: null,
|
|
286
|
+
_canRecoverSendingFn: null,
|
|
287
|
+
revision: 0,
|
|
288
|
+
updatedAt: Date.now(),
|
|
289
|
+
waitingCode: '',
|
|
290
|
+
waitingReason: '',
|
|
291
|
+
waitingMessage: '',
|
|
127
292
|
};
|
|
128
293
|
queues.set(sessionId, queue);
|
|
129
294
|
saveToDb(queue);
|
|
@@ -153,10 +318,57 @@ function getState(sessionId) {
|
|
|
153
318
|
status: i.status,
|
|
154
319
|
})),
|
|
155
320
|
idleTimeoutMs: q.idleTimeoutMs,
|
|
321
|
+
revision: q.revision || 0,
|
|
322
|
+
updatedAt: q.updatedAt || 0,
|
|
156
323
|
error: q.error || null,
|
|
324
|
+
waitingCode: q.waitingCode || '',
|
|
325
|
+
waitingReason: q.waitingReason || '',
|
|
326
|
+
waitingMessage: q.waitingMessage || '',
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function stateFromSavedQueue(saved) {
|
|
331
|
+
if (!saved || !saved.sessionId) return null;
|
|
332
|
+
const currentIndex = Number(saved.currentIndex);
|
|
333
|
+
return {
|
|
334
|
+
sessionId: saved.sessionId,
|
|
335
|
+
mode: saved.mode || 'manual',
|
|
336
|
+
status: saved.status || 'idle',
|
|
337
|
+
currentIndex: Number.isFinite(currentIndex) ? currentIndex : -1,
|
|
338
|
+
items: (Array.isArray(saved.items) ? saved.items : []).map(item => {
|
|
339
|
+
const normalized = normalizeQueueItem(item, { preserveStatus: true });
|
|
340
|
+
return {
|
|
341
|
+
id: normalized.id,
|
|
342
|
+
type: normalized.type,
|
|
343
|
+
promptId: normalized.promptId,
|
|
344
|
+
title: normalized.title,
|
|
345
|
+
text: normalized.text,
|
|
346
|
+
images: Array.isArray(normalized.images) ? normalized.images : [],
|
|
347
|
+
status: normalized.status,
|
|
348
|
+
};
|
|
349
|
+
}),
|
|
350
|
+
idleTimeoutMs: saved.idleTimeoutMs || 10000,
|
|
351
|
+
revision: 0,
|
|
352
|
+
updatedAt: saved.updatedAt || 0,
|
|
353
|
+
error: null,
|
|
354
|
+
waitingCode: '',
|
|
355
|
+
waitingReason: '',
|
|
356
|
+
waitingMessage: '',
|
|
357
|
+
persisted: true,
|
|
157
358
|
};
|
|
158
359
|
}
|
|
159
360
|
|
|
361
|
+
function getPersistedState(sessionId) {
|
|
362
|
+
const live = getState(sessionId);
|
|
363
|
+
if (live) return live;
|
|
364
|
+
try {
|
|
365
|
+
return stateFromSavedQueue(db.loadQueue(sessionId));
|
|
366
|
+
} catch (err) {
|
|
367
|
+
console.error('[queue] failed to load persisted queue state:', err && err.message ? err.message : err);
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
160
372
|
function getAllStates() {
|
|
161
373
|
const result = {};
|
|
162
374
|
for (const [sessionId] of queues) {
|
|
@@ -174,7 +386,108 @@ function deleteQueue(sessionId) {
|
|
|
174
386
|
}
|
|
175
387
|
}
|
|
176
388
|
|
|
389
|
+
function appendItems(sessionId, { mode, items = [], idleTimeoutMs, autoStart = false } = {}) {
|
|
390
|
+
const normalized = Array.isArray(items) ? items.map(item => normalizeQueueItem(item)) : [];
|
|
391
|
+
if (normalized.length === 0) return getState(sessionId);
|
|
392
|
+
|
|
393
|
+
let q = queues.get(sessionId);
|
|
394
|
+
if (!q || q.status === 'done') {
|
|
395
|
+
const state = createQueue(sessionId, {
|
|
396
|
+
mode: mode || 'manual',
|
|
397
|
+
items: normalized,
|
|
398
|
+
idleTimeoutMs,
|
|
399
|
+
});
|
|
400
|
+
return autoStart ? start(sessionId) || state : state;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Reconcile an incoming post against the existing queue, by id. The Wall-E
|
|
404
|
+
// "Send Queue" path re-posts items, so the same id can arrive again for three
|
|
405
|
+
// distinct reasons — each needs different handling:
|
|
406
|
+
//
|
|
407
|
+
// 1. New id → append (genuinely new message).
|
|
408
|
+
// 2. Existing is 'pending' → refresh text/title/images in place (honor an
|
|
409
|
+
// EDIT re-send) but do NOT push a duplicate.
|
|
410
|
+
// 3. Existing is 'sending' → leave it; re-dispatching would double-send the
|
|
411
|
+
// or 'queued' (in flight) same turn. (This is the duplicate-suppression
|
|
412
|
+
// that stops "sent all at once" on a double-click.)
|
|
413
|
+
// 4. Existing is DONE → a genuine RE-SEND ("Re-send this item" / a
|
|
414
|
+
// (sent/skipped/failed) re-post after the queue restored 'paused' on a
|
|
415
|
+
// CTM restart). Reset it to 'pending', refresh its
|
|
416
|
+
// content, and re-dispatch. The OLD behavior only
|
|
417
|
+
// handled case 2 and silently swallowed this one,
|
|
418
|
+
// so the re-sent message never reached Wall-E.
|
|
419
|
+
const DONE_STATUSES = new Set(['sent', 'skipped', 'failed']);
|
|
420
|
+
const fresh = [];
|
|
421
|
+
let deduped = 0;
|
|
422
|
+
let resent = 0;
|
|
423
|
+
let earliestResent = -1;
|
|
424
|
+
for (const it of normalized) {
|
|
425
|
+
const idx = q.items.findIndex(x => x && x.id === it.id);
|
|
426
|
+
if (idx === -1) { fresh.push(it); continue; }
|
|
427
|
+
const existing = q.items[idx];
|
|
428
|
+
if (DONE_STATUSES.has(existing.status)) {
|
|
429
|
+
existing.status = 'pending';
|
|
430
|
+
existing.statusUpdatedAt = Date.now();
|
|
431
|
+
existing.text = it.text;
|
|
432
|
+
existing.title = it.title;
|
|
433
|
+
existing.images = it.images;
|
|
434
|
+
existing.promptId = it.promptId;
|
|
435
|
+
resent++;
|
|
436
|
+
if (earliestResent === -1 || idx < earliestResent) earliestResent = idx;
|
|
437
|
+
} else {
|
|
438
|
+
deduped++;
|
|
439
|
+
if (existing.status === 'pending') {
|
|
440
|
+
existing.text = it.text;
|
|
441
|
+
existing.title = it.title;
|
|
442
|
+
existing.images = it.images;
|
|
443
|
+
existing.promptId = it.promptId;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (deduped > 0) {
|
|
448
|
+
_qlog(`appendItems deduped ${deduped} already-queued item(s) session=${sessionId} uid=${q._uid} — re-send did NOT duplicate-dispatch`);
|
|
449
|
+
}
|
|
450
|
+
if (resent > 0) {
|
|
451
|
+
_qlog(`appendItems re-sent ${resent} finished item(s) session=${sessionId} uid=${q._uid} — reset to 'pending' for re-dispatch`);
|
|
452
|
+
// sendNext()/wake() only scan for the next pending AFTER currentIndex, so a
|
|
453
|
+
// re-sent item sitting BEHIND the cursor would be skipped. Roll the cursor to
|
|
454
|
+
// just before the earliest re-sent item (sent items in between are skipped by
|
|
455
|
+
// the pending-only scan, so they are NOT re-dispatched). Mirrors the
|
|
456
|
+
// orphaned-'sending' recovery in loadFromDb().
|
|
457
|
+
if (earliestResent <= q.currentIndex) q.currentIndex = earliestResent - 1;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (fresh.length > 0) q.items.push(...fresh);
|
|
461
|
+
if (mode) q.mode = mode;
|
|
462
|
+
if (idleTimeoutMs) q.idleTimeoutMs = idleTimeoutMs;
|
|
463
|
+
|
|
464
|
+
const hasNewWork = fresh.length > 0 || resent > 0;
|
|
465
|
+
if (hasNewWork) {
|
|
466
|
+
q.error = null;
|
|
467
|
+
if (autoStart && (q.status === 'idle' || q.status === 'paused')) {
|
|
468
|
+
q.status = 'running';
|
|
469
|
+
q._restorePaused = false;
|
|
470
|
+
}
|
|
471
|
+
notifyChange(q);
|
|
472
|
+
if (autoStart && q.status === 'running') return wake(sessionId, 'append');
|
|
473
|
+
return getState(sessionId);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Nothing new to dispatch (every incoming id was an in-flight or pending
|
|
477
|
+
// duplicate). Still honor autoStart so a re-click can resume/kick a stalled
|
|
478
|
+
// queue, and persist any in-place edits we just applied — but never duplicate.
|
|
479
|
+
if (autoStart && (q.status === 'idle' || q.status === 'paused')) {
|
|
480
|
+
q.status = 'running';
|
|
481
|
+
notifyChange(q);
|
|
482
|
+
return wake(sessionId, 'append');
|
|
483
|
+
}
|
|
484
|
+
if (deduped > 0) notifyChange(q); // persist edited-in-place text
|
|
485
|
+
return getState(sessionId);
|
|
486
|
+
}
|
|
487
|
+
|
|
177
488
|
function notifyChange(q) {
|
|
489
|
+
q.revision = (Number(q.revision) || 0) + 1;
|
|
490
|
+
q.updatedAt = Date.now();
|
|
178
491
|
saveToDb(q);
|
|
179
492
|
if (q._onStateChange) {
|
|
180
493
|
q._onStateChange(getState(q.sessionId));
|
|
@@ -189,6 +502,7 @@ function start(sessionId) {
|
|
|
189
502
|
if (q.items.length === 0) return getState(sessionId);
|
|
190
503
|
|
|
191
504
|
q.status = 'running';
|
|
505
|
+
q._restorePaused = false;
|
|
192
506
|
q.currentIndex = -1;
|
|
193
507
|
sendNext(sessionId);
|
|
194
508
|
return getState(sessionId);
|
|
@@ -212,13 +526,24 @@ function sendNext(sessionId) {
|
|
|
212
526
|
if (nextIdx === -1) {
|
|
213
527
|
// All done
|
|
214
528
|
q.status = 'done';
|
|
529
|
+
clearWaiting(q);
|
|
215
530
|
clearIdleTimer(q);
|
|
216
531
|
notifyChange(q);
|
|
217
532
|
return getState(sessionId);
|
|
218
533
|
}
|
|
219
534
|
|
|
220
|
-
q.currentIndex = nextIdx;
|
|
221
535
|
const item = q.items[nextIdx];
|
|
536
|
+
const readiness = canDispatch(q, item);
|
|
537
|
+
if (!readiness.ok) {
|
|
538
|
+
console.log(`[queue-diag] sendNext DEFERRED session=${sessionId} reason=${readiness.code || readiness.reason || 'not-ready'} item="${String(item.text || '').slice(0, 50)}"`);
|
|
539
|
+
if (setWaiting(q, readiness)) notifyChange(q);
|
|
540
|
+
return getState(sessionId);
|
|
541
|
+
}
|
|
542
|
+
clearWaiting(q);
|
|
543
|
+
console.log(`[queue-diag] sendNext DISPATCHING session=${sessionId} mode=${q.mode} item="${String(item.text || '').slice(0, 50)}"`);
|
|
544
|
+
_qtrace(`sendNext DISPATCH uid=${q._uid} session=${sessionId} idx=${nextIdx} item=${item.id} mode=${q.mode} items_before=[${_itemSummary(q)}] — completion will bind to uid=${q._uid}`);
|
|
545
|
+
|
|
546
|
+
q.currentIndex = nextIdx;
|
|
222
547
|
item.status = 'sending';
|
|
223
548
|
q.error = null;
|
|
224
549
|
q._outputSinceLastSend = '';
|
|
@@ -262,10 +587,18 @@ function sendNext(sessionId) {
|
|
|
262
587
|
|
|
263
588
|
function completeAsyncSend(q, item) {
|
|
264
589
|
const live = queues.get(q.sessionId);
|
|
265
|
-
if (live !== q)
|
|
590
|
+
if (live !== q) {
|
|
591
|
+
_qlog(`completeAsyncSend DROPPED (queue swapped) session=${q.sessionId} bounduid=${q._uid} liveuid=${live ? live._uid : 'none'} item=${item.id} → item NEVER marked 'sent' (point #1)`);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
266
594
|
const current = q.items[q.currentIndex];
|
|
267
|
-
if (!current || current.id !== item.id || current.status !== 'sending')
|
|
595
|
+
if (!current || current.id !== item.id || current.status !== 'sending') {
|
|
596
|
+
_qlog(`completeAsyncSend DROPPED (cursor/status moved) session=${q.sessionId} uid=${q._uid} item=${item.id} currentIdx=${q.currentIndex} currentId=${current ? current.id : 'none'} currentStatus=${current ? current.status : 'none'} items=[${_itemSummary(q)}]`);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
268
599
|
current.status = 'sent';
|
|
600
|
+
_qtrace(`completeAsyncSend OK session=${q.sessionId} uid=${q._uid} item=${item.id} → sent items=[${_itemSummary(q)}]`);
|
|
601
|
+
clearWaiting(q);
|
|
269
602
|
notifyChange(q);
|
|
270
603
|
if (q.mode === 'auto' && q.status === 'running') {
|
|
271
604
|
setTimeout(() => {
|
|
@@ -274,18 +607,105 @@ function completeAsyncSend(q, item) {
|
|
|
274
607
|
}
|
|
275
608
|
}
|
|
276
609
|
|
|
610
|
+
// A "busy"/not-ready failure is transient: the dispatch lost the race against
|
|
611
|
+
// an in-flight turn (the server throws "Wall-E is still thinking") or the target
|
|
612
|
+
// briefly wasn't ready. These must NOT dead-pause the queue — that strands the
|
|
613
|
+
// item until a manual resume. Keep it running + waiting so the next wake (turn
|
|
614
|
+
// finished) retries. Genuine errors still pause so we don't hammer a broken
|
|
615
|
+
// provider in a loop.
|
|
616
|
+
const RETRYABLE_SEND_FAILURE = /still thinking|walle[_ ]busy|not ready|not connected|queued until|finishes the current turn/i;
|
|
617
|
+
|
|
277
618
|
function failAsyncSend(q, item, err) {
|
|
278
619
|
const live = queues.get(q.sessionId);
|
|
279
|
-
if (live !== q)
|
|
620
|
+
if (live !== q) {
|
|
621
|
+
_qlog(`failAsyncSend DROPPED (queue swapped) session=${q.sessionId} bounduid=${q._uid} liveuid=${live ? live._uid : 'none'} item=${item.id}`);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
280
624
|
const current = q.items[q.currentIndex];
|
|
281
|
-
if (!current || current.id !== item.id)
|
|
625
|
+
if (!current || current.id !== item.id) {
|
|
626
|
+
_qlog(`failAsyncSend DROPPED (cursor moved) session=${q.sessionId} uid=${q._uid} item=${item.id} currentIdx=${q.currentIndex} currentId=${current ? current.id : 'none'}`);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
282
629
|
current.status = 'pending';
|
|
283
|
-
|
|
284
|
-
q.
|
|
630
|
+
const message = err && err.message ? err.message : String(err || 'Queue send failed');
|
|
631
|
+
_qlog(`failAsyncSend session=${q.sessionId} uid=${q._uid} item=${item.id} err="${String(message).slice(0, 80)}" retryable=${RETRYABLE_SEND_FAILURE.test(message)}`);
|
|
285
632
|
clearIdleTimer(q);
|
|
633
|
+
if (q.status === 'running' && RETRYABLE_SEND_FAILURE.test(message)) {
|
|
634
|
+
// Transient — stay running and surface as "waiting" so wake() retries.
|
|
635
|
+
// sendNext advanced currentIndex onto this (now-failed) item before the
|
|
636
|
+
// send threw; roll it back one so the retry's "next pending after
|
|
637
|
+
// currentIndex" scan re-finds it instead of skipping it.
|
|
638
|
+
q.currentIndex = Math.max(-1, q.currentIndex - 1);
|
|
639
|
+
q.error = null;
|
|
640
|
+
setWaiting(q, { code: 'walle_busy', reason: 'walle_busy', message });
|
|
641
|
+
notifyChange(q);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
q.status = 'paused';
|
|
645
|
+
q.error = message;
|
|
646
|
+
clearWaiting(q);
|
|
286
647
|
notifyChange(q);
|
|
287
648
|
}
|
|
288
649
|
|
|
650
|
+
// Reasons that prove no turn is running for this session, so a still-'sending'
|
|
651
|
+
// item is definitionally orphaned and must be recovered (not bailed on). The
|
|
652
|
+
// server fires wake() with 'walle-turn-finished' from handleWalleMessage's
|
|
653
|
+
// finally — i.e. AFTER the turn that owned the in-flight item has ended.
|
|
654
|
+
const TURN_FINISHED_WAKE_REASONS = new Set(['walle-turn-finished']);
|
|
655
|
+
|
|
656
|
+
function wake(sessionId, reason) {
|
|
657
|
+
const q = queues.get(sessionId);
|
|
658
|
+
if (!q) return getState(sessionId);
|
|
659
|
+
_qtrace(`wake reason=${reason || '(none)'} uid=${q._uid} session=${sessionId} status=${q.status} items=[${_itemSummary(q)}]`);
|
|
660
|
+
// A queue auto-paused by the system (a CTM restart restore) should resume the
|
|
661
|
+
// moment the session is ready again — otherwise queued messages sit forever.
|
|
662
|
+
// Only auto-resume SYSTEM pauses with work left; a user pause stays paused.
|
|
663
|
+
if (q.status === 'paused' && q._restorePaused && hasPendingItems(q)) {
|
|
664
|
+
q._restorePaused = false;
|
|
665
|
+
q.status = 'running';
|
|
666
|
+
notifyChange(q);
|
|
667
|
+
}
|
|
668
|
+
if (q.status !== 'running') return getState(sessionId);
|
|
669
|
+
const current = q.items[q.currentIndex];
|
|
670
|
+
if (current && current.status === 'sending') {
|
|
671
|
+
// An item is held 'sending' for the entire turn and has exactly ONE
|
|
672
|
+
// completion path: the _sendFn promise's .then(completeAsyncSend) /
|
|
673
|
+
// .catch(failAsyncSend). If that single callback is ever missed or its guard
|
|
674
|
+
// drops (queue object swapped, currentIndex moved, status raced under it),
|
|
675
|
+
// the item is stranded 'sending' and every later wake()/sendNext() bails on
|
|
676
|
+
// it — wedging the queue in-process until a restart ("my queued message is
|
|
677
|
+
// stuck sending, I can't send"). The restart-restore recovery in loadFromDb
|
|
678
|
+
// only heals this across a restart; this heals it live.
|
|
679
|
+
//
|
|
680
|
+
// A turn-finished wake is the server's authoritative "no turn is running"
|
|
681
|
+
// signal, so a still-'sending' item is orphaned: resolve it (the message WAS
|
|
682
|
+
// delivered — the turn ran and ended) and let the queue advance. In the
|
|
683
|
+
// normal case completeAsyncSend (a microtask) has already flipped the item to
|
|
684
|
+
// 'sent' before this macrotask wake runs, so this branch is a no-op safety
|
|
685
|
+
// net; it only fires when the promise completion was genuinely lost. Any
|
|
686
|
+
// other wake reason keeps bailing, since a real turn may be in flight.
|
|
687
|
+
const recovery = TURN_FINISHED_WAKE_REASONS.has(reason)
|
|
688
|
+
? { ok: true, code: 'turn_finished' }
|
|
689
|
+
: canRecoverSending(q, current, reason);
|
|
690
|
+
if (recovery.ok) {
|
|
691
|
+
console.log(`[queue] recovered orphaned 'sending' item on ${reason || 'manual'} wake session=${sessionId} (${recovery.code || 'target idle'}) → resolved`);
|
|
692
|
+
current.status = 'sent';
|
|
693
|
+
clearWaiting(q);
|
|
694
|
+
notifyChange(q);
|
|
695
|
+
return sendNext(sessionId);
|
|
696
|
+
}
|
|
697
|
+
if (reason === 'manual-next' || reason === 'manual-recover') {
|
|
698
|
+
if (setWaiting(q, recovery)) notifyChange(q);
|
|
699
|
+
}
|
|
700
|
+
return getState(sessionId);
|
|
701
|
+
}
|
|
702
|
+
return sendNext(sessionId);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function hasPendingItems(q) {
|
|
706
|
+
return Array.isArray(q.items) && q.items.some(it => it && it.status === 'pending');
|
|
707
|
+
}
|
|
708
|
+
|
|
289
709
|
function skip(sessionId) {
|
|
290
710
|
const q = queues.get(sessionId);
|
|
291
711
|
if (!q || q.status !== 'running') return null;
|
|
@@ -302,11 +722,50 @@ function skip(sessionId) {
|
|
|
302
722
|
return getState(sessionId);
|
|
303
723
|
}
|
|
304
724
|
|
|
725
|
+
// Remove a single queued item by id (used by the per-bubble ✕/edit actions).
|
|
726
|
+
// Only pending/failed items can be pulled; an item mid-send is left alone.
|
|
727
|
+
function removeItem(sessionId, itemId) {
|
|
728
|
+
const q = queues.get(sessionId);
|
|
729
|
+
if (!q) return null;
|
|
730
|
+
const idx = q.items.findIndex(it => it && it.id === itemId);
|
|
731
|
+
if (idx === -1) return getState(sessionId);
|
|
732
|
+
if (q.items[idx].status === 'sending') return getState(sessionId);
|
|
733
|
+
q.items.splice(idx, 1);
|
|
734
|
+
if (idx <= q.currentIndex) q.currentIndex -= 1;
|
|
735
|
+
const anyActive = q.items.some(it => it && (it.status === 'pending' || it.status === 'sending'));
|
|
736
|
+
if (!anyActive && (q.status === 'running' || q.status === 'paused')) q.status = 'done';
|
|
737
|
+
notifyChange(q);
|
|
738
|
+
return getState(sessionId);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Reorder the PENDING items to match `orderedIds` (drag-to-reorder). Sent/
|
|
742
|
+
// sending/skipped items keep their slots; any pending id not listed is appended.
|
|
743
|
+
function reorderItems(sessionId, orderedIds) {
|
|
744
|
+
const q = queues.get(sessionId);
|
|
745
|
+
if (!q) return null;
|
|
746
|
+
if (!Array.isArray(orderedIds)) return getState(sessionId);
|
|
747
|
+
const byId = new Map(q.items.map(it => [it.id, it]));
|
|
748
|
+
const pending = q.items.filter(it => it && it.status === 'pending');
|
|
749
|
+
const pendingIds = new Set(pending.map(it => it.id));
|
|
750
|
+
const seen = new Set();
|
|
751
|
+
const newPending = [];
|
|
752
|
+
for (const idv of orderedIds) {
|
|
753
|
+
if (pendingIds.has(idv) && !seen.has(idv)) { newPending.push(byId.get(idv)); seen.add(idv); }
|
|
754
|
+
}
|
|
755
|
+
for (const it of pending) if (!seen.has(it.id)) newPending.push(it);
|
|
756
|
+
let pi = 0;
|
|
757
|
+
q.items = q.items.map(it => (it && it.status === 'pending') ? newPending[pi++] : it);
|
|
758
|
+
notifyChange(q);
|
|
759
|
+
return getState(sessionId);
|
|
760
|
+
}
|
|
761
|
+
|
|
305
762
|
function pause(sessionId) {
|
|
306
763
|
const q = queues.get(sessionId);
|
|
307
764
|
if (!q || q.status !== 'running') return null;
|
|
308
765
|
|
|
309
766
|
q.status = 'paused';
|
|
767
|
+
q._restorePaused = false; // deliberate user pause — wake() must respect it
|
|
768
|
+
clearWaiting(q);
|
|
310
769
|
clearIdleTimer(q);
|
|
311
770
|
notifyChange(q);
|
|
312
771
|
return getState(sessionId);
|
|
@@ -317,6 +776,7 @@ function resume(sessionId) {
|
|
|
317
776
|
if (!q || q.status !== 'paused') return null;
|
|
318
777
|
|
|
319
778
|
q.status = 'running';
|
|
779
|
+
q._restorePaused = false;
|
|
320
780
|
|
|
321
781
|
// If current item is already sent, check if we should auto-advance
|
|
322
782
|
const currentItem = q.items[q.currentIndex];
|
|
@@ -327,6 +787,7 @@ function resume(sessionId) {
|
|
|
327
787
|
}
|
|
328
788
|
|
|
329
789
|
notifyChange(q);
|
|
790
|
+
wake(sessionId);
|
|
330
791
|
return getState(sessionId);
|
|
331
792
|
}
|
|
332
793
|
|
|
@@ -335,6 +796,7 @@ function stop(sessionId) {
|
|
|
335
796
|
if (!q) return null;
|
|
336
797
|
|
|
337
798
|
q.status = 'done';
|
|
799
|
+
clearWaiting(q);
|
|
338
800
|
clearIdleTimer(q);
|
|
339
801
|
notifyChange(q);
|
|
340
802
|
return getState(sessionId);
|
|
@@ -437,6 +899,16 @@ function setSendFn(sessionId, fn) {
|
|
|
437
899
|
if (q) q._sendFn = fn;
|
|
438
900
|
}
|
|
439
901
|
|
|
902
|
+
function setCanSendFn(sessionId, fn) {
|
|
903
|
+
const q = queues.get(sessionId);
|
|
904
|
+
if (q) q._canSendFn = typeof fn === 'function' ? fn : null;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function setCanRecoverSendingFn(sessionId, fn) {
|
|
908
|
+
const q = queues.get(sessionId);
|
|
909
|
+
if (q) q._canRecoverSendingFn = typeof fn === 'function' ? fn : null;
|
|
910
|
+
}
|
|
911
|
+
|
|
440
912
|
function onSessionExit(sessionId) {
|
|
441
913
|
const q = queues.get(sessionId);
|
|
442
914
|
// Preserve queues that have unsent items — they'll be restored on next startup.
|
|
@@ -458,20 +930,27 @@ function onSessionExit(sessionId) {
|
|
|
458
930
|
module.exports = {
|
|
459
931
|
init,
|
|
460
932
|
createQueue,
|
|
933
|
+
appendItems,
|
|
461
934
|
getQueue,
|
|
462
935
|
getState,
|
|
936
|
+
getPersistedState,
|
|
463
937
|
getAllStates,
|
|
464
938
|
deleteQueue,
|
|
465
939
|
start,
|
|
466
940
|
sendNext,
|
|
467
941
|
skip,
|
|
942
|
+
removeItem,
|
|
943
|
+
reorderItems,
|
|
468
944
|
pause,
|
|
469
945
|
resume,
|
|
470
946
|
stop,
|
|
947
|
+
wake,
|
|
471
948
|
setMode,
|
|
472
949
|
feedOutput,
|
|
473
950
|
setOnStateChange,
|
|
474
951
|
setSendFn,
|
|
952
|
+
setCanSendFn,
|
|
953
|
+
setCanRecoverSendingFn,
|
|
475
954
|
onSessionExit,
|
|
476
955
|
setOnQueueCreated,
|
|
477
956
|
};
|