create-walle 0.10.0 → 0.10.2
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 +88 -41
- package/lib/owner-write-safety.js +135 -0
- package/lib/public-runtime.js +13 -5
- package/lib/release-schema.json +3 -2
- package/lib/runtime-closure-policy.json +7 -5
- package/package.json +10 -4
- package/template/bin/deps-doctor.js +1 -1
- package/template/bin/node-bin.sh +11 -0
- package/template/coding-task-manager/api-prompts.js +10 -22
- package/template/coding-task-manager/bin/capture-codex-rollout-records.js +417 -0
- package/template/coding-task-manager/bin/census-codex-lead-history.js +325 -0
- package/template/coding-task-manager/bin/claude-hook.js +7 -3
- package/template/coding-task-manager/bin/claude-table-parity.js +307 -0
- package/template/coding-task-manager/bin/conversation-storage-auto-migration-lib.js +16 -1
- package/template/coding-task-manager/bin/conversation-storage-generation-evidence.js +9 -2
- package/template/coding-task-manager/bin/conversation-storage-service-entry-lib.js +16 -1
- package/template/coding-task-manager/bin/conversation-storage-walle-owner.js +247 -0
- package/template/coding-task-manager/bin/ctm-machine-pair.js +160 -0
- package/template/coding-task-manager/bin/lead-wait-census.js +1348 -0
- package/template/coding-task-manager/bin/repair-foreign-clone.js +564 -0
- package/template/coding-task-manager/bin/repair-resume-echo-titles.js +248 -0
- package/template/coding-task-manager/bin/seam-replay-codex.js +301 -0
- package/template/coding-task-manager/bin/write-lock-bench.js +111 -0
- package/template/coding-task-manager/db.js +705 -286
- package/template/coding-task-manager/git-utils.js +44 -0
- package/template/coding-task-manager/lib/adopted-host-redial.js +4 -0
- package/template/coding-task-manager/lib/agent-presets.js +15 -0
- package/template/coding-task-manager/lib/agent-title-apply.js +88 -0
- package/template/coding-task-manager/lib/agent-title-backfill-read.js +92 -0
- package/template/coding-task-manager/lib/api/host-stats.js +98 -0
- package/template/coding-task-manager/lib/api/ingress.js +35 -12
- package/template/coding-task-manager/lib/api/machine-pairing.js +285 -0
- package/template/coding-task-manager/lib/api/machine-pool.js +307 -0
- package/template/coding-task-manager/lib/api/machines.js +461 -0
- package/template/coding-task-manager/lib/api/misc-ops.js +20 -5
- package/template/coding-task-manager/lib/api/providers.js +53 -6
- package/template/coding-task-manager/lib/api/resume-from-store.js +25 -0
- package/template/coding-task-manager/lib/api/sessions.js +160 -8
- package/template/coding-task-manager/lib/api/transcript-history.js +17 -4
- package/template/coding-task-manager/lib/api/worktrees.js +38 -37
- package/template/coding-task-manager/lib/approval-drift.js +16 -1
- package/template/coding-task-manager/lib/approval-logical-frame.js +5 -1
- package/template/coding-task-manager/lib/approval-scope.js +68 -4
- package/template/coding-task-manager/lib/auth-context.js +32 -2
- package/template/coding-task-manager/lib/auth-rate-limit.js +41 -1
- package/template/coding-task-manager/lib/auth-rules.js +186 -45
- package/template/coding-task-manager/lib/broadcast-serialize.js +2 -1
- package/template/coding-task-manager/lib/browser-session-broker.js +9 -1
- package/template/coding-task-manager/lib/browser-use-adapter.js +33 -2
- package/template/coding-task-manager/lib/claude-markdown-ansi.js +27 -85
- package/template/coding-task-manager/lib/claude-session-title.js +6 -30
- package/template/coding-task-manager/lib/claude-table-layout.js +322 -0
- package/template/coding-task-manager/lib/client-diag-handler.js +25 -0
- package/template/coding-task-manager/lib/codex-config-guard.js +33 -3
- package/template/coding-task-manager/lib/codex-harness-qualification.js +12 -0
- package/template/coding-task-manager/lib/codex-history-terminal-renderer.js +33 -33
- package/template/coding-task-manager/lib/codex-launch-health.js +29 -2
- package/template/coding-task-manager/lib/codex-link-preparation.js +9 -8
- package/template/coding-task-manager/lib/codex-local-links.js +267 -0
- package/template/coding-task-manager/lib/codex-markdown-lists.js +160 -0
- package/template/coding-task-manager/lib/codex-native-history-prefix.js +4 -0
- package/template/coding-task-manager/lib/codex-paths.js +2 -1
- package/template/coding-task-manager/lib/codex-source-handoff.js +186 -0
- package/template/coding-task-manager/lib/coding-agent-models.js +21 -30
- package/template/coding-task-manager/lib/coding-session-model-adapters.js +27 -2
- package/template/coding-task-manager/lib/coding-session-model-projection.js +5 -0
- package/template/coding-task-manager/lib/coding-session-model-runtime.js +1 -2
- package/template/coding-task-manager/lib/coding-session-model-state.js +10 -0
- package/template/coding-task-manager/lib/coding-session-model-sync-state.js +0 -1
- package/template/coding-task-manager/lib/conversation-import/cursor-guard-owner.js +10 -0
- package/template/coding-task-manager/lib/create/resolve-launch-params.js +8 -1
- package/template/coding-task-manager/lib/ctm-boot-lifecycle.js +17 -1
- package/template/coding-task-manager/lib/db-domain-tables.js +7 -0
- package/template/coding-task-manager/lib/db-domains.js +21 -2
- package/template/coding-task-manager/lib/db-owner-op-registry.js +7 -0
- package/template/coding-task-manager/lib/db-owner-worker-client.js +22 -0
- package/template/coding-task-manager/lib/db-schema-source-files.js +1 -0
- package/template/coding-task-manager/lib/feature-manifest.js +1 -3
- package/template/coding-task-manager/lib/fs-watcher.js +3 -2
- package/template/coding-task-manager/lib/harness-history-transition.js +41 -14
- package/template/coding-task-manager/lib/headless-inflight-ops.js +203 -0
- package/template/coding-task-manager/lib/headless-missing-reply.js +42 -0
- package/template/coding-task-manager/lib/headless-term-service.js +60 -23
- package/template/coding-task-manager/lib/history-transition-read-op.js +179 -23
- package/template/coding-task-manager/lib/host-control-dial.js +167 -0
- package/template/coding-task-manager/lib/host-handoff-orchestrator.js +16 -2
- package/template/coding-task-manager/lib/host-handoff.js +51 -13
- package/template/coding-task-manager/lib/host-health-ledger.js +164 -12
- package/template/coding-task-manager/lib/host-self-truth-watchdog.js +4 -0
- package/template/coding-task-manager/lib/host-stats.js +328 -0
- package/template/coding-task-manager/lib/host-threadpool-evacuation.js +160 -0
- package/template/coding-task-manager/lib/host-threadpool-monitor.js +14 -1
- package/template/coding-task-manager/lib/hosted-input-dispatch.js +203 -0
- package/template/coding-task-manager/lib/hosted-sync-worker.js +113 -5
- package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +4 -0
- package/template/coding-task-manager/lib/instance-manifest-runtime.js +3 -0
- package/template/coding-task-manager/lib/instance-manifest.js +13 -1
- package/template/coding-task-manager/lib/instance-sync-status.js +15 -0
- package/template/coding-task-manager/lib/kernel/diag-log.js +12 -0
- package/template/coding-task-manager/lib/launch-model-override.js +160 -1
- package/template/coding-task-manager/lib/launch-plan-worker.js +59 -2
- package/template/coding-task-manager/lib/lead-model-observer.js +17 -2
- package/template/coding-task-manager/lib/lead-model-switch-runtime.js +57 -4
- package/template/coding-task-manager/lib/lead-model-switch.js +11 -2
- package/template/coding-task-manager/lib/lead-model-transition-runtime.js +42 -4
- package/template/coding-task-manager/lib/lead-model-transition-store.js +61 -3
- package/template/coding-task-manager/lib/legacy-draft-cancellation-recovery.js +111 -0
- package/template/coding-task-manager/lib/login-tripwire.js +18 -0
- package/template/coding-task-manager/lib/machine-pairing.js +238 -0
- package/template/coding-task-manager/lib/machine-pool.js +409 -0
- package/template/coding-task-manager/lib/machine-registry.js +749 -0
- package/template/coding-task-manager/lib/machine-self.js +66 -0
- package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +56 -4
- package/template/coding-task-manager/lib/media-journal-control-reader.js +473 -0
- package/template/coding-task-manager/lib/media-journal.js +84 -27
- package/template/coding-task-manager/lib/media-store-options.js +43 -15
- package/template/coding-task-manager/lib/migrations/v56-host-samples-and-machine-pool.js +76 -0
- package/template/coding-task-manager/lib/mirror-feed-sanitize.js +12 -3
- package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +16 -3
- package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
- package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
- package/template/coding-task-manager/lib/model-proxy/collaboration-codec.js +94 -0
- package/template/coding-task-manager/lib/model-proxy/handoff.js +494 -0
- package/template/coding-task-manager/lib/model-proxy/lead-worker-picks.js +25 -0
- package/template/coding-task-manager/lib/model-proxy/native-lead-listener.js +61 -7
- package/template/coding-task-manager/lib/model-proxy/proxy-cli.js +200 -0
- package/template/coding-task-manager/lib/model-proxy/proxy-instance-record.js +257 -0
- package/template/coding-task-manager/lib/model-proxy/responses-face.js +24 -2
- package/template/coding-task-manager/lib/model-proxy/stream-accounting.js +205 -0
- package/template/coding-task-manager/lib/model-proxy/tokens.js +78 -0
- package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
- package/template/coding-task-manager/lib/native-history-control.js +62 -14
- package/template/coding-task-manager/lib/native-lead-settings-client.js +2 -1
- package/template/coding-task-manager/lib/native-lead-tls.js +18 -4
- package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +628 -64
- package/template/coding-task-manager/lib/owner-action-manifest-data.json +14 -0
- package/template/coding-task-manager/lib/painted-row-recorder.js +9 -1
- package/template/coding-task-manager/lib/prompt-admission-trace.js +69 -0
- package/template/coding-task-manager/lib/prompt-submission-contract.js +21 -1
- package/template/coding-task-manager/lib/provider-login-broker.js +192 -9
- package/template/coding-task-manager/lib/provider-login-capture.js +153 -0
- package/template/coding-task-manager/lib/queue-draft-status-overlay.js +36 -0
- package/template/coding-task-manager/lib/raw-codex-history-window.js +5 -3
- package/template/coding-task-manager/lib/raw-history-block.js +3 -1
- package/template/coding-task-manager/lib/read-pool-client.js +36 -24
- package/template/coding-task-manager/lib/read-pool-op-registry.js +10 -1
- package/template/coding-task-manager/lib/read-pool-telemetry.js +14 -5
- package/template/coding-task-manager/lib/recent-catalog-query.js +26 -0
- package/template/coding-task-manager/lib/recent-session-catalog-response.js +9 -0
- package/template/coding-task-manager/lib/remote-command-context.js +12 -0
- package/template/coding-task-manager/lib/remote-command-runtime.js +18 -5
- package/template/coding-task-manager/lib/remote-machine-catalog.js +1062 -0
- package/template/coding-task-manager/lib/remote-machine-create.js +693 -0
- package/template/coding-task-manager/lib/remote-machine-link.js +939 -0
- package/template/coding-task-manager/lib/remote-machine-proxy.js +520 -0
- package/template/coding-task-manager/lib/remote-session-forward.js +306 -0
- package/template/coding-task-manager/lib/remote-submission-status.js +6 -0
- package/template/coding-task-manager/lib/restart-outcome-truth.js +20 -3
- package/template/coding-task-manager/lib/restart-preflight.js +9 -2
- package/template/coding-task-manager/lib/restore-backoff-gate.js +10 -2
- package/template/coding-task-manager/lib/restore-prepare-retry.js +12 -5
- package/template/coding-task-manager/lib/restore-resume-batch.js +22 -1
- package/template/coding-task-manager/lib/resume-blocked-wire.js +62 -1
- package/template/coding-task-manager/lib/resume-cwd.js +13 -0
- package/template/coding-task-manager/lib/resume-from-store.js +12 -1
- package/template/coding-task-manager/lib/resume-origin-facts.js +38 -1
- package/template/coding-task-manager/lib/resume-preflight.js +21 -16
- package/template/coding-task-manager/lib/review-repo-admission.js +39 -6
- package/template/coding-task-manager/lib/review-repo-registry.js +17 -0
- package/template/coding-task-manager/lib/root-delivery-runtime.js +72 -15
- package/template/coding-task-manager/lib/root-delivery-selection.js +37 -1
- package/template/coding-task-manager/lib/root-prompt-admission.js +43 -11
- package/template/coding-task-manager/lib/root-prompt-flow.js +175 -38
- package/template/coding-task-manager/lib/scrollback-archive-store.js +7 -4
- package/template/coding-task-manager/lib/server-memory-sampler-runtime.js +10 -0
- package/template/coding-task-manager/lib/server-memory-sampler.js +41 -2
- package/template/coding-task-manager/lib/session-analysis-memory.js +2 -0
- package/template/coding-task-manager/lib/session-create/codex-native-provider-gate.js +218 -0
- package/template/coding-task-manager/lib/session-create/codex-resume-exit-classify.js +52 -0
- package/template/coding-task-manager/lib/session-create/delegate-worker.js +95 -6
- package/template/coding-task-manager/lib/session-create/identity.js +20 -1
- package/template/coding-task-manager/lib/session-create/prepared.js +69 -1
- package/template/coding-task-manager/lib/session-create/provider-launch-context.js +77 -0
- package/template/coding-task-manager/lib/session-create/provider-process.js +38 -39
- package/template/coding-task-manager/lib/session-create/response.js +30 -12
- package/template/coding-task-manager/lib/session-create/transcript.js +13 -4
- package/template/coding-task-manager/lib/session-create/worker-continuation.js +43 -1
- package/template/coding-task-manager/lib/session-create/worker-notices.js +94 -5
- package/template/coding-task-manager/lib/session-create/worker-observability.js +99 -7
- package/template/coding-task-manager/lib/session-create/worker-stop.js +11 -3
- package/template/coding-task-manager/lib/session-create/worker-wait-watcher.js +315 -0
- package/template/coding-task-manager/lib/session-geometry-authority-handler.js +10 -1
- package/template/coding-task-manager/lib/session-host-manager.js +152 -206
- package/template/coding-task-manager/lib/session-jobs.js +17 -9
- package/template/coding-task-manager/lib/session-lifecycle.js +20 -50
- package/template/coding-task-manager/lib/session-model-action-preconditions.js +68 -3
- package/template/coding-task-manager/lib/session-resource-sampler.js +67 -9
- package/template/coding-task-manager/lib/session-standup.js +67 -4
- package/template/coding-task-manager/lib/session-status-ledger.js +42 -3
- package/template/coding-task-manager/lib/session-status-runtime.js +45 -1
- package/template/coding-task-manager/lib/session-status.js +2 -2
- package/template/coding-task-manager/lib/session-title-mirror.js +25 -57
- package/template/coding-task-manager/lib/session-transcript-trash.js +8 -1
- package/template/coding-task-manager/lib/startup-pointer-write.js +57 -0
- package/template/coding-task-manager/lib/state-sync/painted-frame-field.js +11 -0
- package/template/coding-task-manager/lib/storage/coding-session-model-store.js +21 -22
- package/template/coding-task-manager/lib/surviving-owner-bootstrap.js +7 -2
- package/template/coding-task-manager/lib/surviving-owner-recovery.js +2 -1
- package/template/coding-task-manager/lib/terminal-draft-http-status.js +25 -0
- package/template/coding-task-manager/lib/transcript-archiver.js +229 -229
- package/template/coding-task-manager/lib/transcript-frame-cache.js +12 -2
- package/template/coding-task-manager/lib/transcript-guard-job.js +55 -4
- package/template/coding-task-manager/lib/transcript-link.js +77 -0
- package/template/coding-task-manager/lib/transcript-manifest-store.js +8 -4
- package/template/coding-task-manager/lib/transcript-source.js +8 -3
- package/template/coding-task-manager/lib/transcript-store.js +40 -0
- package/template/coding-task-manager/lib/tunnel-lane-governor.js +6 -3
- package/template/coding-task-manager/lib/typing-interaction-trace.js +136 -2
- package/template/coding-task-manager/lib/typing-worker-stages.js +208 -1
- package/template/coding-task-manager/lib/usage-ledger/claude-parse.js +53 -2
- package/template/coding-task-manager/lib/usage-ledger/ingest.js +13 -4
- package/template/coding-task-manager/lib/usage-ledger/read.js +14 -0
- package/template/coding-task-manager/lib/usage-ledger/schema.js +49 -1
- package/template/coding-task-manager/lib/usage-ledger/store.js +337 -6
- package/template/coding-task-manager/lib/usage-ledger/task-read.js +123 -5
- package/template/coding-task-manager/lib/visible-revision-watchdog.js +8 -12
- package/template/coding-task-manager/lib/wake-skip-reasons.js +46 -0
- package/template/coding-task-manager/lib/walle-supervisor.js +98 -14
- package/template/coding-task-manager/lib/worker-failure-classify.js +13 -1
- package/template/coding-task-manager/lib/ws/input.js +1 -0
- package/template/coding-task-manager/lib/ws/state-sync.js +19 -2
- package/template/coding-task-manager/lib/ws/terminal-attach.js +32 -1
- package/template/coding-task-manager/lib/ws-router.js +68 -1
- package/template/coding-task-manager/lib/ws-send-admission.js +22 -4
- package/template/coding-task-manager/oauth-proxy.js +99 -55
- package/template/coding-task-manager/package-lock.json +24 -0
- package/template/coding-task-manager/package.json +3 -0
- package/template/coding-task-manager/protocol/message-table.json +0 -10
- package/template/coding-task-manager/protocol/messages.js +0 -10
- package/template/coding-task-manager/protocol/ws-schema.json +0 -20
- package/template/coding-task-manager/protocol/ws-schema.md +0 -2
- package/template/coding-task-manager/providers/codex-mcp-extract.js +121 -19
- package/template/coding-task-manager/providers/codex-mcp.js +14 -5
- package/template/coding-task-manager/public/css/app.css +279 -2
- package/template/coding-task-manager/public/css/reviews.css +4 -0
- package/template/coding-task-manager/public/index.html +297 -240
- package/template/coding-task-manager/public/js/app/machines-page.js +1252 -0
- package/template/coding-task-manager/public/js/app/models-tab.js +546 -36
- package/template/coding-task-manager/public/js/app/worktrees-panel.js +7 -5
- package/template/coding-task-manager/public/js/background-work.js +13 -7
- package/template/coding-task-manager/public/js/client-health-core.js +81 -0
- package/template/coding-task-manager/public/js/client-health-main.js +61 -0
- package/template/coding-task-manager/public/js/coding-session-model-presentation.js +44 -4
- package/template/coding-task-manager/public/js/core/session-commands.js +676 -0
- package/template/coding-task-manager/public/js/core/session-selectors.js +5 -1
- package/template/coding-task-manager/public/js/history-app-seam.js +26 -2
- package/template/coding-task-manager/public/js/history-painted-rows.js +17 -2
- package/template/coding-task-manager/public/js/history-reader-park.js +13 -1
- package/template/coding-task-manager/public/js/history-scroller.js +187 -9
- package/template/coding-task-manager/public/js/history-transcript-pages.js +5 -5
- package/template/coding-task-manager/public/js/machine-pool-shared.js +436 -0
- package/template/coding-task-manager/public/js/private-state-scope.js +31 -3
- package/template/coding-task-manager/public/js/prompt-submission-client.js +173 -4
- package/template/coding-task-manager/public/js/prompt-submission-tray.js +281 -0
- package/template/coding-task-manager/public/js/protocol.js +0 -10
- package/template/coding-task-manager/public/js/queue-panel-submission-reconcile.js +174 -1
- package/template/coding-task-manager/public/js/reviews.js +7 -3
- package/template/coding-task-manager/public/js/root-draft-lifetime.js +139 -2
- package/template/coding-task-manager/public/js/root-queue-builder-lifetime.js +16 -0
- package/template/coding-task-manager/public/js/row-join.js +18 -3
- package/template/coding-task-manager/public/js/session-model-picker.js +1 -1
- package/template/coding-task-manager/public/js/session-search-utils.js +46 -19
- package/template/coding-task-manager/public/js/session-usage-view.js +1 -0
- package/template/coding-task-manager/public/js/state-sync-client.js +4 -0
- package/template/coding-task-manager/public/js/terminal/runtime-02.js +16 -7
- package/template/coding-task-manager/public/js/terminal/runtime-03.js +41 -0
- package/template/coding-task-manager/public/js/terminal/runtime-04.js +41 -4
- package/template/coding-task-manager/public/js/terminal/runtime-12.js +52 -2
- package/template/coding-task-manager/public/js/terminal/runtime-16.js +12 -12
- package/template/coding-task-manager/public/js/terminal/runtime-18.js +14 -0
- package/template/coding-task-manager/public/js/terminal/runtime-19.js +8 -4
- package/template/coding-task-manager/public/js/terminal/runtime-20.js +210 -73
- package/template/coding-task-manager/public/js/terminal/runtime-21.js +19 -6
- package/template/coding-task-manager/public/js/terminal/runtime-22.js +86 -0
- package/template/coding-task-manager/public/js/terminal-history-viewport.js +1 -3
- package/template/coding-task-manager/public/js/terminal-wheel.js +39 -2
- package/template/coding-task-manager/public/js/walle/chat-conversation.js +11 -3
- package/template/coding-task-manager/public/js/walle-session.js +30 -7
- package/template/coding-task-manager/public/m/app.css +226 -8
- package/template/coding-task-manager/public/m/app.js +502 -9
- package/template/coding-task-manager/public/m/index.html +38 -1
- package/template/coding-task-manager/public/m/machines.js +1313 -0
- package/template/coding-task-manager/public/m/pool-failover.js +391 -0
- package/template/coding-task-manager/public/m/private-queue-actions.js +26 -7
- package/template/coding-task-manager/public/m/sw.js +5 -0
- package/template/coding-task-manager/queue-engine.js +13 -0
- package/template/coding-task-manager/server/approvals.js +5 -6
- package/template/coding-task-manager/server/boot.js +136 -17
- package/template/coding-task-manager/server.js +964 -425
- package/template/coding-task-manager/session-integrity.js +7 -9
- package/template/coding-task-manager/workers/db-owner-worker.js +53 -7
- package/template/coding-task-manager/workers/read-pool-worker.js +17 -6
- package/template/coding-task-manager/workers/session-host-pool-process.js +18 -4
- package/template/coding-task-manager/workers/session-host-process.js +14 -3
- package/template/coding-task-manager/workers/transcript-archive-worker.js +1 -1
- package/template/coding-task-manager/workers/worktree-worker.js +34 -0
- package/template/package-lock.json +2 -2
- package/template/package.json +1 -1
- package/template/scripts/conversation-storage-v2-migration.js +78 -11
- package/template/shared/conversation-storage-v2-contract.js +42 -6
- package/template/shared/fixtures/worker-status-payloads.json +392 -0
- package/template/shared/git-remote-key.js +83 -0
- package/template/shared/host-identity.js +98 -2
- package/template/shared/owner-write-safety.js +135 -0
- package/template/shared/public-runtime.js +13 -5
- package/template/shared/release-schema.json +3 -2
- package/template/shared/report-citations.js +14 -0
- package/template/shared/sqlite-write-lock.js +361 -223
- package/template/shared/storage/contract.js +35 -3
- package/template/wall-e/agent.js +21 -0
- package/template/wall-e/api-walle.js +5 -0
- package/template/wall-e/bin/brain-purge-legacy-ctm.js +3 -2
- package/template/wall-e/bin/walle-mcp-stdio.js +12 -6
- package/template/wall-e/brain/helper-rules-schema.js +129 -0
- package/template/wall-e/brain/migrations.js +51 -22
- package/template/wall-e/brain/model-deployment-facts.js +31 -1
- package/template/wall-e/brain/owner-call-whitelist.js +16 -0
- package/template/wall-e/brain/schema-ensures.js +22 -0
- package/template/wall-e/brain.js +1 -1
- package/template/wall-e/coding/host-capabilities.js +2 -2
- package/template/wall-e/coding/journal-dispatcher.js +51 -0
- package/template/wall-e/coding/session-journal.js +28 -40
- package/template/wall-e/coding-orchestrator.js +23 -26
- package/template/wall-e/fly.toml +1 -1
- package/template/wall-e/http/auth.js +18 -1
- package/template/wall-e/http/blog-views-api.js +318 -0
- package/template/wall-e/http/helper-rules-api.js +321 -0
- package/template/wall-e/http/model-proxy-providers.js +3 -1
- package/template/wall-e/http/telemetry-api.js +12 -0
- package/template/wall-e/lib/blog-view-policy.js +88 -0
- package/template/wall-e/lib/blog-view-store.js +169 -0
- package/template/wall-e/lib/client-config-lock.js +2 -1
- package/template/wall-e/lib/coding-journal-writer-child-client.js +67 -18
- package/template/wall-e/lib/error-kinds.js +10 -1
- package/template/wall-e/lib/feature-manifest.js +1 -1
- package/template/wall-e/lib/helper-rules/cell-state.js +123 -0
- package/template/wall-e/lib/helper-rules/evaluate.js +357 -0
- package/template/wall-e/lib/helper-rules/explore-rubric.md +12 -0
- package/template/wall-e/lib/helper-rules/grade.js +277 -0
- package/template/wall-e/lib/helper-rules/label.js +247 -0
- package/template/wall-e/lib/helper-rules/lead-call-hook.js +522 -0
- package/template/wall-e/lib/helper-rules/store.js +667 -0
- package/template/wall-e/lib/mcp-integration.js +5 -6
- package/template/wall-e/lib/runtime-worker-pool.js +1 -0
- package/template/wall-e/lib/scheduler-worker-jobs.js +6 -0
- package/template/wall-e/lib/telemetry-db-schema.js +37 -0
- package/template/wall-e/lib/telemetry-process-client.js +76 -1
- package/template/wall-e/llm/default-fallback.js +4 -0
- package/template/wall-e/llm/model-catalog.js +13 -4
- package/template/wall-e/llm/model-roles.js +6 -0
- package/template/wall-e/llm/portkey-http.js +150 -0
- package/template/wall-e/llm/portkey-sync.js +15 -145
- package/template/wall-e/llm/worker-mcp-routing.js +61 -0
- package/template/wall-e/llm/worker-route.js +43 -1
- package/template/wall-e/loops/brain-custom-writes.js +45 -0
- package/template/wall-e/mcp-protocol-contract.js +41 -1
- package/template/wall-e/mcp-server.js +13 -2
- package/template/wall-e/mcp-worker-schemas.js +20 -2
- package/template/wall-e/memory/ctm-context-client.js +25 -5
- package/template/wall-e/shared/release-schema.json +3 -2
- package/template/wall-e/shared/sqlite-write-lock.js +361 -223
- package/template/wall-e/shared/storage/contract.js +35 -3
- package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
- package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +6 -0
- package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +7 -2
- package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +7 -2
- package/template/wall-e/tools/local-tools.js +12 -5
- package/template/wall-e/workers/brain-owner-worker.js +9 -0
- package/template/wall-e/workers/coding-journal-writer-child.js +8 -1
- package/template/wall-e/workers/telemetry-worker.js +8 -0
- package/template/coding-task-manager/lib/giant-freeze-park.js +0 -31
|
@@ -227,6 +227,7 @@
|
|
|
227
227
|
<button class="nav-pill" data-nav="insights" onclick="navTo('insights');closeNavMore()" role="menuitem">Insights</button>
|
|
228
228
|
<button class="nav-pill" data-nav="permissions" onclick="navTo('permissions');closeNavMore()" role="menuitem">Permissions</button>
|
|
229
229
|
<button class="nav-pill" data-nav="worktrees" onclick="navTo('worktrees');closeNavMore()" role="menuitem">Worktrees</button>
|
|
230
|
+
<button class="nav-pill" data-nav="machines" onclick="navTo('machines');closeNavMore()" role="menuitem">Machines</button>
|
|
230
231
|
</div>
|
|
231
232
|
</div>
|
|
232
233
|
<button class="nav-pill" data-nav="setup" onclick="navTo('setup')" title="Setup & Settings" style="margin-left:auto;font-size:16px;padding:4px 8px;">⚙</button>
|
|
@@ -593,6 +594,8 @@
|
|
|
593
594
|
<div id="worktrees-filters" style="padding:10px 20px 0;border-bottom:1px solid var(--border);display:none;flex-wrap:wrap;gap:6px;background:var(--bg);"></div>
|
|
594
595
|
<div id="worktrees-body" style="flex:1;overflow-y:auto;padding:16px 20px;"></div>
|
|
595
596
|
</div>
|
|
597
|
+
<!-- Machines (WP-5 / D-14): rendered by js/app/machines-page.js; replaces the Settings tab. -->
|
|
598
|
+
<div id="machines-panel" style="display:none;flex-direction:column;height:100%;overflow:hidden;"></div>
|
|
596
599
|
<div id="setup-panel">
|
|
597
600
|
<div class="setup-header">
|
|
598
601
|
<div style="display:flex;justify-content:space-between;align-items:baseline;">
|
|
@@ -1765,6 +1768,7 @@ url = "http://localhost:<span id="setup-mcp-port-display-toml">3457</span>/mcp"<
|
|
|
1765
1768
|
</div>
|
|
1766
1769
|
</div>
|
|
1767
1770
|
</div>
|
|
1771
|
+
|
|
1768
1772
|
<!-- TAB: Backups -->
|
|
1769
1773
|
<div class="setup-section" id="setup-section-backups" style="display:none;">
|
|
1770
1774
|
<div class="card" id="setup-backups">
|
|
@@ -2637,6 +2641,14 @@ url = "http://localhost:<span id="setup-mcp-port-display-toml">3457</span>/mcp"<
|
|
|
2637
2641
|
<!-- Populated dynamically by showNewSessionModal() -->
|
|
2638
2642
|
</div>
|
|
2639
2643
|
<input type="hidden" id="ns-agent" value="claude:" />
|
|
2644
|
+
<!-- WP-5 (S-11/AC-5a): machine cards, the agent grid's twin. Hidden with the hidden
|
|
2645
|
+
input emptied when the hub has no machines, so the modal renders exactly as before. -->
|
|
2646
|
+
<label id="ns-machine-label" style="display:none">Choose Machine</label>
|
|
2647
|
+
<div id="ns-machine-grid" class="ns-agent-grid ns-machine-grid" style="display:none">
|
|
2648
|
+
<!-- Populated dynamically by loadNewSessionMachines() -->
|
|
2649
|
+
</div>
|
|
2650
|
+
<input type="hidden" id="ns-machine" value="" />
|
|
2651
|
+
<div id="ns-machine-hint" class="ns-machine-hint" style="display:none"></div>
|
|
2640
2652
|
<label id="ns-cwd-label">Project Folder</label>
|
|
2641
2653
|
<div class="ns-path-picker" id="ns-path-picker">
|
|
2642
2654
|
<div class="ns-path-input-row">
|
|
@@ -2887,6 +2899,7 @@ url = "http://localhost:<span id="setup-mcp-port-display-toml">3457</span>/mcp"<
|
|
|
2887
2899
|
<script src="js/terminal-queue-plan.js?v=__CTM_ASSET_EPOCH__"></script>
|
|
2888
2900
|
<script src="js/prompt-submission-client.js?v=__CTM_ASSET_EPOCH__"></script>
|
|
2889
2901
|
<script src="js/queue-panel-submission-reconcile.js?v=__CTM_ASSET_EPOCH__"></script>
|
|
2902
|
+
<script src="js/prompt-submission-tray.js?v=__CTM_ASSET_EPOCH__"></script>
|
|
2890
2903
|
<script src="js/screenshot-router.js?v=__CTM_ASSET_EPOCH__"></script>
|
|
2891
2904
|
<script src="js/upload-banner-state.js?v=__CTM_ASSET_EPOCH__"></script>
|
|
2892
2905
|
<script src="js/standup-stale-followup.js?v=__CTM_ASSET_EPOCH__"></script>
|
|
@@ -2904,6 +2917,11 @@ url = "http://localhost:<span id="setup-mcp-port-display-toml">3457</span>/mcp"<
|
|
|
2904
2917
|
<script src="js/app/models-provider-tls.js?v=__CTM_ASSET_EPOCH__"></script>
|
|
2905
2918
|
<script src="js/app/backups-panel.js?v=__CTM_ASSET_EPOCH__"></script>
|
|
2906
2919
|
<script src="js/app/worktrees-panel.js?v=__CTM_ASSET_EPOCH__"></script>
|
|
2920
|
+
<!-- WP-6: the ONE DOM-free machine-pool presenter copy (order, search text, groups, load line,
|
|
2921
|
+
sparkline, pairing container). Loaded before the picker (js/core/session-commands.js) and the
|
|
2922
|
+
Machines page (js/app/machines-page.js), both of which call into it and no longer define it. -->
|
|
2923
|
+
<script src="js/machine-pool-shared.js?v=__CTM_ASSET_EPOCH__"></script>
|
|
2924
|
+
<script src="js/app/machines-page.js?v=__CTM_ASSET_EPOCH__"></script>
|
|
2907
2925
|
<script src="js/app/approvals-ui.js?v=__CTM_ASSET_EPOCH__"></script>
|
|
2908
2926
|
<script src="js/app/restart-banner.js?v=__CTM_ASSET_EPOCH__"></script>
|
|
2909
2927
|
<script src="js/background-work.js?v=__CTM_ASSET_EPOCH__"></script>
|
|
@@ -3600,6 +3618,7 @@ const state = window._ctmState = {
|
|
|
3600
3618
|
ws: null,
|
|
3601
3619
|
token: getCookie('ctm_token'),
|
|
3602
3620
|
sessions: new Map(), // id -> { term, fitAddon, container }
|
|
3621
|
+
pendingCloseIds: new Map(), // id -> addedAtMs; cleared only by evidence (unlisted, intentional exit, kill error), no timer
|
|
3603
3622
|
sessionScrollbackRows: {},
|
|
3604
3623
|
activeTab: null, // session id or 'rules'
|
|
3605
3624
|
// Lifecycle WP-6 (delegated-worker-lifecycle-identity-observability-2026-09-27.html §5 D-3):
|
|
@@ -4431,8 +4450,6 @@ function _dispatchWebSocketMessage(msg) {
|
|
|
4431
4450
|
case 'session-restore-failed': onSessionRestoreFailed(msg); break;
|
|
4432
4451
|
case 'session-parking': onSessionLifecycleEvent(msg); break;
|
|
4433
4452
|
case 'session-parked': onSessionLifecycleEvent(msg); break;
|
|
4434
|
-
case 'session-frozen': onSessionLifecycleEvent(msg); break;
|
|
4435
|
-
case 'session-thawed': onSessionLifecycleEvent(msg); break;
|
|
4436
4453
|
case 'session-waking': onSessionLifecycleEvent(msg); break;
|
|
4437
4454
|
case 'session-wake-ready': onSessionLifecycleEvent(msg); break;
|
|
4438
4455
|
case 'session-wake-blocked': onSessionLifecycleEvent(msg); break;
|
|
@@ -4443,7 +4460,25 @@ function _dispatchWebSocketMessage(msg) {
|
|
|
4443
4460
|
case 'resume-prompt-result': onResumePromptResult(msg); break;
|
|
4444
4461
|
case 'resume-blocked': onSessionResumeBlocked(msg); break;
|
|
4445
4462
|
case 'resume-retry-noop': onResumeRetryNoop(msg); break;
|
|
4446
|
-
case 'error':
|
|
4463
|
+
case 'error': {
|
|
4464
|
+
// WP-5 (§6.3/AC-5a): a machine-scoped failure (machine_unreachable, or the box's own code)
|
|
4465
|
+
// is SHOWN — the user picks another machine or fixes the box. Never retried here, and every
|
|
4466
|
+
// other error frame keeps its existing console-only path.
|
|
4467
|
+
var _machineErr = /^machine_/.test(String(msg.code || '')) || !!msg.machine;
|
|
4468
|
+
if (_machineErr && typeof toast === 'function') {
|
|
4469
|
+
// One toast per machine id: a storm of the same box's failures never stacks.
|
|
4470
|
+
toast(msg.message || msg.error || String(msg.code || 'Machine error'),
|
|
4471
|
+
{ type: 'error', title: 'Machine error', key: 'machine-error:' + String(msg.machine || msg.code || '') });
|
|
4472
|
+
}
|
|
4473
|
+
// D-7: the server refused THIS kill (e.g. owner_recovery_pending). Say so and drop the
|
|
4474
|
+
// pending entry, so the next list brings the tab back truthfully instead of hiding it.
|
|
4475
|
+
if (msg.id && state.pendingCloseIds.has(msg.id)) {
|
|
4476
|
+
state.pendingCloseIds.delete(msg.id);
|
|
4477
|
+
toast(msg.message || 'Could not close the session.', { type: 'error', key: 'kill-failed:' + msg.id });
|
|
4478
|
+
}
|
|
4479
|
+
console.error('Server error:', msg.message);
|
|
4480
|
+
break;
|
|
4481
|
+
}
|
|
4447
4482
|
case 'analysis-progress': onAnalysisProgress(msg); break;
|
|
4448
4483
|
case 'analysis-done': onAnalysisDone(msg); break;
|
|
4449
4484
|
case 'queue-state': onQueueState(msg); break;
|
|
@@ -5602,90 +5637,63 @@ function _promptTrayClearanceBottomPx(viewportHeight, inputStackTop) {
|
|
|
5602
5637
|
// ".walle-input-stack" today (no MutationObserver wired to it), so a TTL is
|
|
5603
5638
|
// the fallback staleness bound; forceRecompute lets a known-authoritative
|
|
5604
5639
|
// caller (resize) always get a fresh read.
|
|
5605
|
-
const _promptTrayAnchorCache = { atMs: 0, inputStackTop: 0 };
|
|
5640
|
+
const _promptTrayAnchorCache = { atMs: 0, inputStackTop: 0, queuePanelOpen: null };
|
|
5606
5641
|
const _promptTrayAnchorCacheTtlMs = 2000;
|
|
5607
5642
|
|
|
5608
5643
|
function _positionPromptSubmissionTray(tray, opts) {
|
|
5609
5644
|
const now = Date.now();
|
|
5645
|
+
const queuePanelOpen = window._ctmState?.queuePanelOpen === true;
|
|
5610
5646
|
const stale = !_promptTrayAnchorCache.atMs || (now - _promptTrayAnchorCache.atMs) > _promptTrayAnchorCacheTtlMs;
|
|
5611
|
-
if ((opts && opts.forceRecompute) || stale) {
|
|
5647
|
+
if ((opts && opts.forceRecompute) || stale || _promptTrayAnchorCache.queuePanelOpen !== queuePanelOpen) {
|
|
5612
5648
|
let inputStackTop = 0;
|
|
5613
|
-
for (const stack of document.querySelectorAll('.walle-input-stack')) {
|
|
5649
|
+
for (const stack of document.querySelectorAll('.walle-input-stack, #qp-bottom')) {
|
|
5614
5650
|
if (!stack.offsetParent) continue;
|
|
5615
5651
|
const rect = stack.getBoundingClientRect();
|
|
5616
5652
|
if (rect.height > 0 && (!inputStackTop || rect.top < inputStackTop)) inputStackTop = rect.top;
|
|
5617
5653
|
}
|
|
5618
|
-
_promptTrayAnchorCache
|
|
5619
|
-
_promptTrayAnchorCache.atMs = now;
|
|
5654
|
+
Object.assign(_promptTrayAnchorCache, { inputStackTop, atMs: now, queuePanelOpen });
|
|
5620
5655
|
}
|
|
5621
5656
|
tray.style.bottom = _promptTrayClearanceBottomPx(window.innerHeight, _promptTrayAnchorCache.inputStackTop) + 'px';
|
|
5622
5657
|
}
|
|
5623
5658
|
|
|
5659
|
+
// WP-1 (C2, design §6 + docs/queue-delivery-recovery-evidence-2026-10-23.html §6 "WP-1 boundary
|
|
5660
|
+
// update"): the tray renderer itself now lives in public/js/prompt-submission-tray.js — index.html
|
|
5661
|
+
// sits at its file-size ceiling, so only the renderer moved. The three dependencies that can only
|
|
5662
|
+
// exist here (the tray element, its composer clearance, and the owner-fenced recovery route) are
|
|
5663
|
+
// passed in explicitly; the module owns no globals and no queue state of its own.
|
|
5624
5664
|
function _renderPromptSubmissionStatus(record) {
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
}
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
setTimeout(() => window.CTMPromptSubmission.dismiss(record.clientMessageId), 0);
|
|
5658
|
-
return;
|
|
5659
|
-
} else {
|
|
5660
|
-
delete row.dataset.quietRevealAt;
|
|
5661
|
-
row.style.display = 'flex';
|
|
5662
|
-
}
|
|
5663
|
-
row.textContent = '';
|
|
5664
|
-
const label = document.createElement('span');
|
|
5665
|
-
label.style.flex = '1';
|
|
5666
|
-
label.textContent = record.status === 'pending' ? 'Sending prompt...' : record.status === 'sent' ? 'Prompt sent' : (record.message || 'Prompt needs attention');
|
|
5667
|
-
row.appendChild(label);
|
|
5668
|
-
if (record.status === 'needs_attention' || record.status === 'blocked' || record.status === 'failed') {
|
|
5669
|
-
const retry = document.createElement('button');
|
|
5670
|
-
retry.className = 'btn small'; retry.textContent = 'Retry';
|
|
5671
|
-
retry.onclick = () => window.CTMPromptSubmission.retry(record.clientMessageId);
|
|
5672
|
-
row.appendChild(retry);
|
|
5673
|
-
}
|
|
5674
|
-
if (record.status !== 'pending' && record.status !== 'queued') {
|
|
5675
|
-
const dismiss = document.createElement('button');
|
|
5676
|
-
dismiss.className = 'btn small'; dismiss.textContent = 'Dismiss';
|
|
5677
|
-
dismiss.onclick = () => window.CTMPromptSubmission.dismiss(record.clientMessageId);
|
|
5678
|
-
row.appendChild(dismiss);
|
|
5679
|
-
if (record.status === 'sent') setTimeout(() => window.CTMPromptSubmission.dismiss(record.clientMessageId), 2200);
|
|
5680
|
-
} else {
|
|
5681
|
-
// Quiet rows get a HIDE-ONLY dismiss — never CTMPromptOutbox.dismiss here, that would drop
|
|
5682
|
-
// the durable retry record while the prompt is still in flight. The row (and its tracking)
|
|
5683
|
-
// stays; only the pixels go, and a failure escalation clears hiddenByUser above.
|
|
5684
|
-
const hide = document.createElement('button');
|
|
5685
|
-
hide.className = 'btn small'; hide.textContent = 'Dismiss';
|
|
5686
|
-
hide.onclick = () => { row.dataset.hiddenByUser = '1'; row.style.display = 'none'; };
|
|
5687
|
-
row.appendChild(hide);
|
|
5688
|
-
}
|
|
5665
|
+
if (!window.PromptSubmissionTray) return null;
|
|
5666
|
+
return window.PromptSubmissionTray.renderStatus(record, {
|
|
5667
|
+
document,
|
|
5668
|
+
ensureTray: _ensurePromptSubmissionTray,
|
|
5669
|
+
positionTray: _positionPromptSubmissionTray,
|
|
5670
|
+
getManager: () => window.CTMPromptSubmission,
|
|
5671
|
+
onRecovery: _recoverPromptSubmission,
|
|
5672
|
+
setTimer: (callback, delayMs) => setTimeout(callback, delayMs),
|
|
5673
|
+
now: () => Date.now(),
|
|
5674
|
+
});
|
|
5675
|
+
}
|
|
5676
|
+
|
|
5677
|
+
// C2/C3 (R6, AC-1d/AC-1e, AC-1f): the tray's recovery gesture is routed by the queue module, which
|
|
5678
|
+
// owns the exact-item matching and the one-gesture-at-a-time rule; the page supplies its own state
|
|
5679
|
+
// and the owner-fenced resendQpItem, and never mints an identity here.
|
|
5680
|
+
function _recoverPromptSubmission(action, record, options = {}) {
|
|
5681
|
+
return window.QueuePanelSubmissionReconcile.routeTrayRecovery(action, record, options, {
|
|
5682
|
+
getSessionId: () => qpCurrentSession, getItems: () => qpItems, resendItem: resendQpItem,
|
|
5683
|
+
getManager: () => window.CTMPromptSubmission,
|
|
5684
|
+
});
|
|
5685
|
+
}
|
|
5686
|
+
|
|
5687
|
+
// WP-1 (C4, R7/AC-1e): the review surface for a queue item whose last outcome is uncertain. The
|
|
5688
|
+
// record and its lifetime guard live in the reconcile module and the row itself in the tray module;
|
|
5689
|
+
// the page supplies its own state plus the owner-fenced resend route behind the confirmation, and
|
|
5690
|
+
// never mints an identity here.
|
|
5691
|
+
function _reviewQpItems(items, canNotify) {
|
|
5692
|
+
return window.QueuePanelSubmissionReconcile.openItemReview(items, {
|
|
5693
|
+
getItems: () => qpItems, getSessionId: () => qpCurrentSession, getManager: () => window.CTMPromptSubmission,
|
|
5694
|
+
notify: message => canNotify && toast('Delivery unconfirmed — review this item before sending again. ' + message, { type: 'warning', key: 'qp-review:' + qpCurrentSession }),
|
|
5695
|
+
renderReview: (record, isCurrent) => window.PromptSubmissionTray.renderItemReview(record, { document, ensureTray: _ensurePromptSubmissionTray, positionTray: _positionPromptSubmissionTray, getManager: () => window.CTMPromptSubmission, isCurrent, onRecovery: _recoverPromptSubmission }),
|
|
5696
|
+
});
|
|
5689
5697
|
}
|
|
5690
5698
|
|
|
5691
5699
|
// S-16/S-17/D-12/D-16: the tray is no longer a status ghost for every outbox row of every
|
|
@@ -10744,7 +10752,7 @@ function createTerminal(id, opts) {
|
|
|
10744
10752
|
// transcript from archivedRows/seamLocated alone, and the server's own history-window
|
|
10745
10753
|
// route is what actually knows whether a transcript exists. S-15: option-injection, not a
|
|
10746
10754
|
// global lookup inside the module -- lets a unit test construct a controller with no `send`.
|
|
10747
|
-
sendDiag: function (details, event) { send({ type: 'client-diag', id, event: event || 'history-entry-failed', details }); }, // WP-7/D-11: the event name is the caller's, so this one function also carries the `history-entry-refused` rollup
|
|
10755
|
+
sendDiag: function (details, event) { const s = state.sessions.get(id); send({ type: 'client-diag', id, event: event || 'history-entry-failed', details: Object.assign({}, details, { provider: s ? _clientAgentTypeForSession(s) : 'unknown' }) }); }, // WP-7/D-11: the event name is the caller's, so this one function also carries the `history-entry-refused` rollup; WP-4/D-9 (seam-proof R3): the beacon's provider rides in here -- the module forbids global lookups (S-15), so the session kind is merged at the attach site that owns the id
|
|
10748
10756
|
onSegmentChange: function () { if (unifiedScrollbar) unifiedScrollbar.update(); if (window.HistoryPromptHeader) window.HistoryPromptHeader.update(id); }, // WP-2: reader scroll/render/enter/exit -> scrollbar update; prompt header
|
|
10749
10757
|
});
|
|
10750
10758
|
// The wheel handoff into history is installed INSIDE the existing screenEl wheel
|
|
@@ -12074,34 +12082,22 @@ function createTerminal(id, opts) {
|
|
|
12074
12082
|
return intent;
|
|
12075
12083
|
}
|
|
12076
12084
|
|
|
12077
|
-
async function _terminalDeleteServerDraft(id, attachment, options = {}) {
|
|
12078
|
-
if (attachment && !attachment.attachmentId && options.cancelRequest && attachment.clientId) {
|
|
12079
|
-
// Upload cancelled before the 201 arrived: cancel the server-side create by its request id.
|
|
12080
|
-
const cancel = await _terminalFetchWithRetry('/api/sessions/' + encodeURIComponent(id) + '/image-drafts?request_id=' +
|
|
12081
|
-
encodeURIComponent(attachment.clientId) + '&purge=1&token=' + encodeURIComponent(state.token || ''), { method: 'DELETE' }, { attempts: 3 });
|
|
12082
|
-
if (!cancel.ok && cancel.status !== 404) throw new Error('HTTP ' + cancel.status);
|
|
12083
|
-
return true;
|
|
12084
|
-
}
|
|
12085
|
-
if (!attachment || !attachment.attachmentId) return true;
|
|
12086
|
-
const headers = attachment.etag ? { 'If-Match': attachment.etag } : {};
|
|
12087
|
-
const response = await _terminalFetchWithRetry('/api/sessions/' + encodeURIComponent(id) + '/image-drafts/' +
|
|
12088
|
-
encodeURIComponent(attachment.attachmentId) + '?token=' + encodeURIComponent(state.token || '') +
|
|
12089
|
-
(options.purge === true ? '&purge=1' : ''), {
|
|
12090
|
-
method: 'DELETE', headers,
|
|
12091
|
-
}, { attempts: 3 });
|
|
12092
|
-
if (!response.ok && response.status !== 404) throw new Error('HTTP ' + response.status);
|
|
12093
|
-
return true;
|
|
12094
|
-
}
|
|
12095
|
-
|
|
12096
12085
|
async function _terminalRemoveDraftAttachment(id, attachment, metricEvent, options = {}) {
|
|
12097
12086
|
const Draft = window.TerminalAttachmentDraft;
|
|
12098
12087
|
const draft = _terminalAttachmentDraftState(id);
|
|
12099
12088
|
if (!Draft || !draft || !attachment || !draft.attachments.includes(attachment)) return false;
|
|
12089
|
+
const owned = _terminalDraftUploadOwnership(id);
|
|
12090
|
+
const current = () => Boolean(owned.lifetime && owned.lifetime === _terminalRootDraftLifetime
|
|
12091
|
+
&& !owned.reason && owned.context && owned.context.draft === draft
|
|
12092
|
+
&& owned.lifetime.attachmentCurrent(owned.context, id, draft, attachment));
|
|
12093
|
+
if (!current()) return false;
|
|
12100
12094
|
clearTimeout(attachment._unconfirmedReconcileTimer);
|
|
12101
12095
|
attachment._unconfirmedReconcileTimer = null;
|
|
12102
12096
|
try {
|
|
12103
|
-
await _terminalDeleteServerDraft(id, attachment, options
|
|
12097
|
+
if (!await _terminalDeleteServerDraft(id, attachment, options,
|
|
12098
|
+
{ context: owned.context, isCurrent: current }) || !current()) return false;
|
|
12104
12099
|
} catch (error) {
|
|
12100
|
+
if (!current()) return false;
|
|
12105
12101
|
console.warn('[terminal-image] draft cleanup failed:', error && error.message ? error.message : error);
|
|
12106
12102
|
_terminalAttachmentMetric(id, 'provider_delete_cleanup_failed', { attachmentCount: draft.attachments.length });
|
|
12107
12103
|
_terminalRenderAttachmentDraft(id);
|
|
@@ -12121,10 +12117,13 @@ function createTerminal(id, opts) {
|
|
|
12121
12117
|
if (action === 'disabled' || !action) return false;
|
|
12122
12118
|
if (action === 'cancel') return _terminalCancelAttachmentUpload(id, attachment);
|
|
12123
12119
|
const metric = action === 'detach' ? 'attached_detached_by_user' : 'chip_dismissed_by_user';
|
|
12120
|
+
const owned = _terminalDraftUploadOwnership(id);
|
|
12124
12121
|
const removed = _terminalRemoveDraftAttachment(id, attachment, metric);
|
|
12125
12122
|
if (action === 'detach') {
|
|
12126
12123
|
void Promise.resolve(removed).then(didRemove => {
|
|
12127
12124
|
if (!didRemove || typeof toast !== 'function') return;
|
|
12125
|
+
if (!owned.context || owned.lifetime !== _terminalRootDraftLifetime || owned.context.draft !== draft
|
|
12126
|
+
|| owned.lifetime.current(owned.context) !== true) return;
|
|
12128
12127
|
toast((attachment.label || 'Image token') + ' text remains in the composer - delete it there too.', {
|
|
12129
12128
|
type: 'info', duration: 4200,
|
|
12130
12129
|
});
|
|
@@ -12143,8 +12142,9 @@ function createTerminal(id, opts) {
|
|
|
12143
12142
|
return target ? String(target.dataset.attachmentId || '') : '';
|
|
12144
12143
|
}
|
|
12145
12144
|
|
|
12146
|
-
function _terminalFocusAfterAttachmentDismiss(id, adjacentClientId, dismissed) {
|
|
12145
|
+
function _terminalFocusAfterAttachmentDismiss(id, adjacentClientId, dismissed, context) {
|
|
12147
12146
|
if (!dismissed || !adjacentClientId) return;
|
|
12147
|
+
if (!context || context.id !== id || _terminalRootDraftLifetime?.capture(id, context.session, context.draft) !== context) return;
|
|
12148
12148
|
const region = document.getElementById('tray-chip-region');
|
|
12149
12149
|
if (!region || region.dataset.sessionId !== id) return;
|
|
12150
12150
|
const target = Array.from(region.querySelectorAll('.terminal-attachment-chip'))
|
|
@@ -12379,8 +12379,7 @@ function createTerminal(id, opts) {
|
|
|
12379
12379
|
function _terminalCancelAttachmentUpload(id, attachment) {
|
|
12380
12380
|
if (!attachment || (attachment.status !== 'uploading' && attachment.status !== 'materializing')) return false;
|
|
12381
12381
|
try { attachment._uploadAbortController?.abort(); } catch {}
|
|
12382
|
-
|
|
12383
|
-
return true;
|
|
12382
|
+
return _terminalRemoveDraftAttachment(id, attachment, 'upload_cancelled', { purge: true, cancelRequest: true });
|
|
12384
12383
|
}
|
|
12385
12384
|
|
|
12386
12385
|
// image-attach-latency-redesign F2: with the server reconciler on, retrying a failed draft
|
|
@@ -12562,6 +12561,8 @@ function createTerminal(id, opts) {
|
|
|
12562
12561
|
return;
|
|
12563
12562
|
}
|
|
12564
12563
|
region.replaceChildren();
|
|
12564
|
+
const focusOwned = _terminalDraftUploadOwnership(id);
|
|
12565
|
+
const focusContext = !focusOwned.reason && focusOwned.context?.draft === draft ? focusOwned.context : null;
|
|
12565
12566
|
for (const attachment of draft.attachments) {
|
|
12566
12567
|
const chip = document.createElement('div');
|
|
12567
12568
|
chip.className = 'terminal-attachment-chip tray-attachment-chip';
|
|
@@ -12636,7 +12637,7 @@ function createTerminal(id, opts) {
|
|
|
12636
12637
|
const adjacentClientId = _terminalAdjacentAttachmentClientId(id, attachment);
|
|
12637
12638
|
const dismissed = _terminalDismissAttachment(id, attachment);
|
|
12638
12639
|
void Promise.resolve(dismissed).then(result => {
|
|
12639
|
-
_terminalFocusAfterAttachmentDismiss(id, adjacentClientId, result);
|
|
12640
|
+
_terminalFocusAfterAttachmentDismiss(id, adjacentClientId, result, focusContext);
|
|
12640
12641
|
});
|
|
12641
12642
|
}
|
|
12642
12643
|
};
|
|
@@ -13325,16 +13326,22 @@ function createTerminal(id, opts) {
|
|
|
13325
13326
|
const failed = draft.attachments.filter(attachment => attachment && attachment.status === 'failed');
|
|
13326
13327
|
if (!failed.length) return [];
|
|
13327
13328
|
|
|
13329
|
+
const owned = _terminalDraftUploadOwnership(id);
|
|
13330
|
+
const current = () => Boolean(owned.lifetime && owned.lifetime === _terminalRootDraftLifetime
|
|
13331
|
+
&& !owned.reason && owned.context?.draft === draft && owned.lifetime.current(owned.context));
|
|
13328
13332
|
const removed = [];
|
|
13329
13333
|
for (const attachment of failed) {
|
|
13334
|
+
if (!current()) break;
|
|
13330
13335
|
if (!Draft.removeAttachment(draft, attachment.clientId)) continue;
|
|
13331
13336
|
removed.push(attachment);
|
|
13332
13337
|
_terminalReleaseAttachmentUploadSource(attachment);
|
|
13333
13338
|
await _terminalDeleteServerDraft(id, attachment).catch(error => {
|
|
13339
|
+
if (!current()) return;
|
|
13334
13340
|
console.warn('[terminal-image] failed draft cleanup after send failed:', error && error.message ? error.message : error);
|
|
13335
13341
|
_terminalAttachmentMetric(id, 'failed_draft_cleanup_failed', { attachmentCount: removed.length });
|
|
13336
13342
|
});
|
|
13337
13343
|
}
|
|
13344
|
+
if (!current()) return removed;
|
|
13338
13345
|
_terminalRenderAttachmentDraft(id);
|
|
13339
13346
|
_terminalAttachmentMetric(id, 'failed_drafts_discarded_on_send', { attachmentCount: removed.length });
|
|
13340
13347
|
if (options.userAuthorized === true) {
|
|
@@ -13867,12 +13874,13 @@ function createTerminal(id, opts) {
|
|
|
13867
13874
|
const providerOrderBase = draft.attachments.reduce((max, item) => (
|
|
13868
13875
|
Math.max(max, Number(item._providerOrdinal || item.providerOrder || 0))
|
|
13869
13876
|
), 0);
|
|
13877
|
+
const stagedOwnerHostId = _terminalAttachmentOwnerHostId(state.sessions.get(id)); // R-1: the id the server stamps
|
|
13870
13878
|
const staged = files.map((file, index) => {
|
|
13871
13879
|
const providerOrder = providerOrderBase + index + 1;
|
|
13872
13880
|
const attachment = Draft.addUploading(draft, {
|
|
13873
13881
|
clientId: _terminalAttachmentClientId(),
|
|
13874
13882
|
filename: file.name || ('clipboard-image-' + (index + 1) + '.png'),
|
|
13875
|
-
ownerHostId:
|
|
13883
|
+
ownerHostId: stagedOwnerHostId,
|
|
13876
13884
|
});
|
|
13877
13885
|
attachment._promptOffset = promptOffset;
|
|
13878
13886
|
attachment._promptOrder = index;
|
|
@@ -13932,7 +13940,7 @@ function createTerminal(id, opts) {
|
|
|
13932
13940
|
// is adopted here instead of being rendered as an intermediate 'ready' first. Only a
|
|
13933
13941
|
// row with no reservation (or no retained publication) takes the legacy transition.
|
|
13934
13942
|
const outcome = _terminalFinishAttachmentUpload(id, attachment, uploaded); // WP-3 §6: adopt or legacy ready
|
|
13935
|
-
if (outcome.outcome === 'rejected') return;
|
|
13943
|
+
if (outcome.outcome === 'rejected') return void _terminalRejectedUploadOutcome(id, attachment, outcome, { metric: _terminalAttachmentMetric, postDiagnostic: _terminalPostAttachmentDiagnostic }); // R-4: fails visibly, never silently
|
|
13936
13944
|
// image-attach-latency-redesign WP-1: with the server reconciler on, do not race a
|
|
13937
13945
|
// second PTY paste against it here -- the reconciler already got a nudge from this
|
|
13938
13946
|
// draft's server-side 'create' op and owns delivering it. readyCount here only
|
|
@@ -14389,12 +14397,13 @@ function createTerminal(id, opts) {
|
|
|
14389
14397
|
// the CONTINUOUS reader on THIS tick -- no mouse report is built/sent (AC-8d).
|
|
14390
14398
|
// WP-2 (D-4): px = |deltaY| in px per deltaMode -- the live terminal lands that far below the pane top.
|
|
14391
14399
|
if (decision.action === 'reader-enter') {
|
|
14392
|
-
const px = _wheelDeltaPx(e, term); wheelRecorder.noteEvent({ deltaPx: px, deltaMode: e.deltaMode, steps: 0 });
|
|
14400
|
+
if (writer._wheelSender && writer._wheelSender.cancel) writer._wheelSender.cancel(); const px = _wheelDeltaPx(e, term); wheelRecorder.noteEvent({ deltaPx: px, deltaMode: e.deltaMode, steps: 0 });
|
|
14393
14401
|
_enterContinuousForSession(id, px);
|
|
14394
14402
|
e.preventDefault();
|
|
14395
14403
|
e.stopPropagation();
|
|
14396
14404
|
return;
|
|
14397
14405
|
}
|
|
14406
|
+
if (decision.action === 'swallow') { e.preventDefault(); e.stopPropagation(); return; } // D-4: a Codex app view's wheel-down is CTM's, never the PTY's
|
|
14398
14407
|
if (decision.shouldForward) {
|
|
14399
14408
|
let cw = 0, ch = 0;
|
|
14400
14409
|
try { const d = term._core._renderService.dimensions.css.cell; cw = d.width; ch = d.height; } catch {}
|
|
@@ -14430,7 +14439,7 @@ function createTerminal(id, opts) {
|
|
|
14430
14439
|
e.stopPropagation();
|
|
14431
14440
|
return;
|
|
14432
14441
|
}
|
|
14433
|
-
} catch (error) {
|
|
14442
|
+
} catch (error) { e.preventDefault(); e.stopPropagation();
|
|
14434
14443
|
send({
|
|
14435
14444
|
type: 'client-diag', id, event: 'terminal-wheel-forward-error',
|
|
14436
14445
|
details: { message: String(error && error.message || error).slice(0, 160) },
|
|
@@ -14443,7 +14452,7 @@ function createTerminal(id, opts) {
|
|
|
14443
14452
|
if (wheelDecisionForDiag) {
|
|
14444
14453
|
if (wheelDecisionForDiag.action === 'reader') wheelRecorder.noteEvent({ deltaPx: _wheelDeltaPx(e, term), deltaMode: e.deltaMode, steps: 0, route: 'reader' }); // D-15: reader-open events
|
|
14445
14454
|
wheelRecorder.record(wheelDecisionForDiag.route, {
|
|
14446
|
-
bufferType: buf.type, baseY: buf.baseY,
|
|
14455
|
+
bufferType: buf.type, baseY: buf.baseY, deltaY: e.deltaY, action: wheelDecisionForDiag.action,
|
|
14447
14456
|
appMouse: wheelDecisionForDiag.mouseActive, isAlt: wheelDecisionForDiag.isAlt,
|
|
14448
14457
|
mouseSource: wheelDecisionForDiag.mouseState && wheelDecisionForDiag.mouseState.source, frameTracking: wheelDecisionForDiag.frameTracking,
|
|
14449
14458
|
});
|
|
@@ -17026,14 +17035,15 @@ function _showWarmSessionShell(id) {
|
|
|
17026
17035
|
for (const panelId of [
|
|
17027
17036
|
'rules-panel', 'review-panel', 'codereview-panel', 'insights-panel',
|
|
17028
17037
|
'permissions-panel', 'prompts-panel', 'walle-panel', 'models-panel',
|
|
17029
|
-
'backups-panel', 'worktrees-panel', 'setup-panel',
|
|
17038
|
+
'backups-panel', 'worktrees-panel', 'machines-panel', 'setup-panel',
|
|
17030
17039
|
]) {
|
|
17031
17040
|
const panel = document.getElementById(panelId);
|
|
17032
17041
|
panel?.classList?.remove('active');
|
|
17033
|
-
if (panel && ['models-panel', 'backups-panel', 'worktrees-panel', 'setup-panel'].includes(panelId)) {
|
|
17042
|
+
if (panel && ['models-panel', 'backups-panel', 'worktrees-panel', 'machines-panel', 'setup-panel'].includes(panelId)) {
|
|
17034
17043
|
panel.style.display = 'none';
|
|
17035
17044
|
}
|
|
17036
17045
|
}
|
|
17046
|
+
if (typeof machinesPanelHidden === 'function') machinesPanelHidden();
|
|
17037
17047
|
const sidebar = document.getElementById('sidebar');
|
|
17038
17048
|
const sidebarResize = document.getElementById('sidebar-resize');
|
|
17039
17049
|
if (!state.sidebarManuallyHidden) {
|
|
@@ -17145,7 +17155,7 @@ function _prepareSurfaceResume(id) {
|
|
|
17145
17155
|
const previousId = state.activeTab;
|
|
17146
17156
|
const switchingTabs = previousId !== id;
|
|
17147
17157
|
const session = state.sessions.get(id);
|
|
17148
|
-
const isPanel = ['rules', 'insights', 'permissions', 'prompts', 'codereview', 'walle', 'models', 'worktrees', 'setup'].includes(id);
|
|
17158
|
+
const isPanel = ['rules', 'insights', 'permissions', 'prompts', 'codereview', 'walle', 'models', 'worktrees', 'machines', 'setup'].includes(id);
|
|
17149
17159
|
const wakeDirtyEvent = session?._wakeStreamDirty;
|
|
17150
17160
|
if (wakeDirtyEvent) {
|
|
17151
17161
|
session._wakeStreamDirty = false;
|
|
@@ -17357,6 +17367,7 @@ function _commitSurfaceVisibility(id, isPanel, wasSubscribedBeforeActivation) {
|
|
|
17357
17367
|
if (bkp) { bkp.classList.remove('active'); bkp.style.display = 'none'; }
|
|
17358
17368
|
var wtp = document.getElementById('worktrees-panel');
|
|
17359
17369
|
if (wtp) { wtp.classList.remove('active'); wtp.style.display = 'none'; }
|
|
17370
|
+
var mcp = document.getElementById('machines-panel'); if (mcp) { mcp.classList.remove('active'); mcp.style.display = 'none'; } if (typeof machinesPanelHidden === 'function') machinesPanelHidden();
|
|
17360
17371
|
var stp = document.getElementById('setup-panel');
|
|
17361
17372
|
if (stp) { stp.classList.remove('active'); stp.style.display = 'none'; }
|
|
17362
17373
|
state.activeTab = id;
|
|
@@ -17469,6 +17480,10 @@ if (id === 'rules') {
|
|
|
17469
17480
|
wtp.style.display = 'flex';
|
|
17470
17481
|
wtp.classList.add('active');
|
|
17471
17482
|
loadWorktreesPanel();
|
|
17483
|
+
} else if (id === 'machines') {
|
|
17484
|
+
var mcp = document.getElementById('machines-panel');
|
|
17485
|
+
mcp.style.display = 'flex'; mcp.classList.add('active');
|
|
17486
|
+
loadMachinesPanel();
|
|
17472
17487
|
} else if (id === 'setup') {
|
|
17473
17488
|
var sp = document.getElementById('setup-panel');
|
|
17474
17489
|
sp.style.display = 'flex';
|
|
@@ -17727,7 +17742,7 @@ function activateTab(id) {
|
|
|
17727
17742
|
|
|
17728
17743
|
function syncNavPills(activeId) {
|
|
17729
17744
|
const navPills = document.querySelectorAll('#topbar-nav .nav-pill');
|
|
17730
|
-
const specialPanels = ['rules', 'insights', 'permissions', 'prompts', 'codereview', 'walle', 'models', 'backups', 'worktrees', 'setup'];
|
|
17745
|
+
const specialPanels = ['rules', 'insights', 'permissions', 'prompts', 'codereview', 'walle', 'models', 'backups', 'worktrees', 'machines', 'setup'];
|
|
17731
17746
|
const navId = specialPanels.includes(activeId) ? activeId : 'sessions';
|
|
17732
17747
|
navPills.forEach(pill => {
|
|
17733
17748
|
pill.classList.toggle('active', pill.dataset.nav === navId);
|
|
@@ -18423,10 +18438,10 @@ function showStandupDashboard(opts) {
|
|
|
18423
18438
|
const el = document.getElementById(id);
|
|
18424
18439
|
if (el) el.classList.remove('active');
|
|
18425
18440
|
});
|
|
18426
|
-
['models-panel', 'backups-panel', 'worktrees-panel', 'setup-panel'].forEach(id => {
|
|
18441
|
+
['models-panel', 'backups-panel', 'worktrees-panel', 'machines-panel', 'setup-panel'].forEach(id => {
|
|
18427
18442
|
const el = document.getElementById(id);
|
|
18428
18443
|
if (el) { el.classList.remove('active'); el.style.display = 'none'; }
|
|
18429
|
-
});
|
|
18444
|
+
}); if (typeof machinesPanelHidden === 'function') machinesPanelHidden();
|
|
18430
18445
|
|
|
18431
18446
|
state.activeTab = SESSIONS_OVERVIEW_TAB_ID;
|
|
18432
18447
|
_projectSessionListSelection(SESSIONS_OVERVIEW_TAB_ID);
|
|
@@ -18830,6 +18845,9 @@ function renderStandupCard(card, opts) {
|
|
|
18830
18845
|
opts = opts || {};
|
|
18831
18846
|
const tokens = opts.tokens || [];
|
|
18832
18847
|
const providerBadge = renderStandupProviderBadge(card);
|
|
18848
|
+
// WP-5 (§6.6/S-14): the standup card carries the same chip. '' for a local session (and for a
|
|
18849
|
+
// catalog that predates the hub — the presenter tolerates the missing fields).
|
|
18850
|
+
const machineChip = machineChipHtml(card);
|
|
18833
18851
|
const subtitle = [card.agent, card.model, card.branch].filter(Boolean).join(' / ');
|
|
18834
18852
|
const goal = standupCleanGoalText(card.goal || card.intent || '');
|
|
18835
18853
|
const progress = card.progressSummary || card.progress || '';
|
|
@@ -18849,6 +18867,7 @@ function renderStandupCard(card, opts) {
|
|
|
18849
18867
|
<div class="standup-card-title">${titleHtml}</div>
|
|
18850
18868
|
<div class="standup-card-meta" title="${standupEsc(subtitle || card.cwd || '')}">
|
|
18851
18869
|
${providerBadge}
|
|
18870
|
+
${machineChip}
|
|
18852
18871
|
<div class="standup-card-subtitle">${standupEsc(subtitle || card.cwd || '')}</div>
|
|
18853
18872
|
</div>
|
|
18854
18873
|
</div>
|
|
@@ -19045,6 +19064,8 @@ function navTo(target, opts) {
|
|
|
19045
19064
|
showSetupPanel();
|
|
19046
19065
|
} else if (target === 'worktrees') {
|
|
19047
19066
|
showWorktreesPanel();
|
|
19067
|
+
} else if (target === 'machines') {
|
|
19068
|
+
showMachinesPanel(opts || {});
|
|
19048
19069
|
}
|
|
19049
19070
|
}
|
|
19050
19071
|
|
|
@@ -22780,19 +22801,19 @@ function onSnapshot(msg) {
|
|
|
22780
22801
|
const dimsMismatch = gridMismatchDecision.mismatch;
|
|
22781
22802
|
if (dimsMismatch) {
|
|
22782
22803
|
const manualReflowSnapshot = restoreSource === 'reflow';
|
|
22783
|
-
|
|
22784
|
-
|
|
22785
|
-
|
|
22786
|
-
|
|
22787
|
-
|
|
22788
|
-
|
|
22789
|
-
|
|
22790
|
-
|
|
22791
|
-
|
|
22792
|
-
|
|
22793
|
-
|
|
22794
|
-
|
|
22795
|
-
|
|
22804
|
+
// Serialized xterm snapshots are grid-relative. Final-exit and manual reflow snapshots are
|
|
22805
|
+
// user-authoritative recovery frames, so replay them at the captured PTY grid instead of
|
|
22806
|
+
// throwing them away after a local fit drift. Activation/cache snapshots can still be
|
|
22807
|
+
// discarded and re-requested at local dimensions.
|
|
22808
|
+
// WP-1 (D-1): adopting that grid is all-or-nothing. A grid the cols-pin chokepoint refused
|
|
22809
|
+
// is NOT replayed at the landed hybrid -- it takes the mismatch branch below (resync the
|
|
22810
|
+
// server to our dims, request a fresh frame), which is the recovery this path already had.
|
|
22811
|
+
const adoptAuthoritative = ptyCols > 0 && ptyRows > 0
|
|
22812
|
+
&& gridMismatchDecision.action === 'apply-authoritative'
|
|
22813
|
+
&& _adoptTerminalGrid(s, ptyCols, ptyRows, 'authoritative-snapshot-resize');
|
|
22814
|
+
if (adoptAuthoritative) {
|
|
22815
|
+
if (finalExitSnapshot) s._lastFinalExitSnapshotResizeAt = Date.now();
|
|
22816
|
+
if (manualReflowSnapshot) s._lastManualReflowSnapshotResizeAt = Date.now();
|
|
22796
22817
|
} else {
|
|
22797
22818
|
// Non-interim, non-authoritative snapshot at a foreign grid: the client
|
|
22798
22819
|
// resized after requesting. Do NOT replay foreign-width data. Resync the
|
|
@@ -23282,6 +23303,13 @@ function _showLoadingOverlay(s, opts) {
|
|
|
23282
23303
|
}
|
|
23283
23304
|
const DISMISS_AUTHORITIES = new Set(['activation-ready', 'degraded', 'user', 'reconcile']); // R5 §6.4: replaces {authoritative:true}; only 'reconcile' is vetoable (I-9: veto is a no-op, NEVER _showLoadingOverlay)
|
|
23284
23305
|
function _loadingOverlayDismissAuthority(opts) { return (opts && DISMISS_AUTHORITIES.has(opts.authority)) ? opts.authority : (opts && opts.authoritative === true ? 'activation-ready' : 'reconcile'); }
|
|
23306
|
+
// WP-4a (§5 OQ-4, §6): record which exit ran and the store lifecycle it saw, on the session, so the
|
|
23307
|
+
// next stranded cover can be attributed (H-A store missing/active while mounted, H-B resume-blocked
|
|
23308
|
+
// held, H-C no dismissal ran) without a per-call beacon. Instrumentation only: no exit changes.
|
|
23309
|
+
function _recordOverlayDismiss(s, authority, exit) {
|
|
23310
|
+
if (!s) return;
|
|
23311
|
+
s._lastOverlayDismiss = { authority, exit, lifecycle: _presentationStore.getSessionState(_resumeClientSessionId(s))?.lifecycle ?? null, at: Date.now() };
|
|
23312
|
+
}
|
|
23285
23313
|
function _dismissLoadingOverlay(s, opts) {
|
|
23286
23314
|
opts = opts || {}; const authority = _loadingOverlayDismissAuthority(opts);
|
|
23287
23315
|
// WI-1: keep the corner chip in sync on every dismiss checkpoint (this function is called from
|
|
@@ -23296,16 +23324,19 @@ function _dismissLoadingOverlay(s, opts) {
|
|
|
23296
23324
|
// "session-restore-started" means the PTY exists, not that the visible page is ready.
|
|
23297
23325
|
if (s && authority === 'reconcile') {
|
|
23298
23326
|
const rs = _resumeStateForSession(s);
|
|
23299
|
-
if (rs && rs.dismissBlocked) return { vetoed: true }; // I-9: never _showLoadingOverlay here.
|
|
23327
|
+
if (rs && rs.dismissBlocked) { _recordOverlayDismiss(s, authority, 'vetoed-dismiss-blocked'); return { vetoed: true }; } // I-9: never _showLoadingOverlay here.
|
|
23300
23328
|
}
|
|
23301
23329
|
// A resume-blocked card is a terminal recovery state, not a loading cover. Readiness and
|
|
23302
23330
|
// lifecycle handlers share this DOM shell, but they do not own the card and must not erase its
|
|
23303
23331
|
// diagnosis/actions. Retry and the server's explicit blocked=false projection clear it.
|
|
23304
|
-
|
|
23305
|
-
|
|
23332
|
+
const lifecycle = _presentationStore.getSessionState(_resumeClientSessionId(s))?.lifecycle ?? null;
|
|
23333
|
+
if (lifecycle === 'resume-blocked' && opts.clearResumeBlocked !== true) { _recordOverlayDismiss(s, authority, 'resume-blocked-held'); return; }
|
|
23306
23334
|
if (s._loadingOverlayTimer) { clearTimeout(s._loadingOverlayTimer); s._loadingOverlayTimer = null; }
|
|
23307
23335
|
s._resumeLoadStartedAt = 0;
|
|
23308
|
-
if (!s.container) return;
|
|
23336
|
+
if (!s.container) { _recordOverlayDismiss(s, authority, 'no-container'); return; }
|
|
23337
|
+
// The store keeps ONE lifecycle per session, and _clearSessionLifecycle only writes the family it
|
|
23338
|
+
// owns; naming that mismatch here is what separates a silent no-op clear from a real one.
|
|
23339
|
+
_recordOverlayDismiss(s, authority, LIFECYCLE_COVER_FAMILY[lifecycle] === 'restore' ? 'cleared' : 'not-restore-family');
|
|
23309
23340
|
_clearSessionLifecycle(s, 'restore');
|
|
23310
23341
|
s._loadingOverlayShownAt = 0;
|
|
23311
23342
|
}
|
|
@@ -23313,6 +23344,9 @@ function _dismissLoadingOverlay(s, opts) {
|
|
|
23313
23344
|
|
|
23314
23345
|
|
|
23315
23346
|
function onExit(msg) {
|
|
23347
|
+
// D-7: an intentional exit is the server confirming the close. It must clear the pending entry
|
|
23348
|
+
// BEFORE the early return below — killSession already removed the tab, so there is no `s`.
|
|
23349
|
+
if (msg.intentional === true) state.pendingCloseIds.delete(msg.id);
|
|
23316
23350
|
const s = state.sessions.get(msg.id);
|
|
23317
23351
|
if (!s) return;
|
|
23318
23352
|
// S-19 (terminal-sync-adopted-restart-race-2026-09-07.html §9 S-19): an exited session
|
|
@@ -23451,7 +23485,7 @@ function _clearSessionRestoreUiState(s) {
|
|
|
23451
23485
|
window._ctmClearSessionRestoreState = _clearSessionRestoreUiState;
|
|
23452
23486
|
|
|
23453
23487
|
// Two banner families own the one element: 'resume-blocked' (the rail line standing in for a
|
|
23454
|
-
// withheld card) and 'park' (parked/waking/parking/deferred/live
|
|
23488
|
+
// withheld card) and 'park' (parked/waking/parking/deferred/live). An empty-text call only
|
|
23455
23489
|
// removes a banner its own family owns, or any banner when it passes '*'.
|
|
23456
23490
|
function _lifecycleBannerFamily(state) {
|
|
23457
23491
|
return state === 'resume-blocked' ? 'resume-blocked' : 'park';
|
|
@@ -23604,14 +23638,6 @@ function onSessionLifecycleEvent(msg) {
|
|
|
23604
23638
|
}
|
|
23605
23639
|
_setSessionLifecycleBanner(s, '', 'parked');
|
|
23606
23640
|
_projectSessionParked(s, { idleMin: idle, reason: msg.reason });
|
|
23607
|
-
} else if (msg.type === 'session-frozen') {
|
|
23608
|
-
_applySessionLifecycleState(s, 'frozen');
|
|
23609
|
-
_clearSessionLifecycle(s, 'park');
|
|
23610
|
-
_setSessionLifecycleBanner(s, 'Paused while idle · focus or type to continue', 'frozen');
|
|
23611
|
-
} else if (msg.type === 'session-thawed') {
|
|
23612
|
-
_applySessionLifecycleState(s, 'live');
|
|
23613
|
-
_clearSessionLifecycle(s, 'park');
|
|
23614
|
-
_setSessionLifecycleBanner(s, '', 'live');
|
|
23615
23641
|
} else if (msg.type === 'session-waking') {
|
|
23616
23642
|
_applySessionLifecycleState(s, 'waking');
|
|
23617
23643
|
if (s.meta) {
|
|
@@ -24017,72 +24043,6 @@ function onSessionRestoreFailed(msg) {
|
|
|
24017
24043
|
);
|
|
24018
24044
|
}
|
|
24019
24045
|
|
|
24020
|
-
// WI-5: cause-specific, non-alarming copy for the enumerated deterministic resume_blocked reason
|
|
24021
|
-
// codes the server can send (see server.js markSessionResumeBlocked / _notifyResumePreflightFailure).
|
|
24022
|
-
// Branch ONLY on `msg.code` here — never on message text — so this stays correct even if the
|
|
24023
|
-
// human-readable message wording changes server-side. An unrecognized code (contract drift, or a
|
|
24024
|
-
// code we haven't special-cased yet) falls back to the same honest red failure banner
|
|
24025
|
-
// onSessionRestoreFailed already renders, rather than inventing false calm.
|
|
24026
|
-
const RESUME_BLOCKED_COPY = {
|
|
24027
|
-
missing_claude_transcript: {
|
|
24028
|
-
tone: 'info',
|
|
24029
|
-
title: "This session's transcript is gone from this machine.",
|
|
24030
|
-
body: 'The file was removed before CTM stored any of its prompts (Claude Code deletes transcripts older than '
|
|
24031
|
-
+ 'its retention period, 30 days by default). There is nothing to resume; start a fresh session in the same folder.',
|
|
24032
|
-
},
|
|
24033
|
-
missing_codex_rollout: {
|
|
24034
|
-
tone: 'info',
|
|
24035
|
-
title: "This session's transcript is gone from this machine.",
|
|
24036
|
-
body: 'The file was removed before CTM stored any of its prompts. There is nothing to resume; '
|
|
24037
|
-
+ 'start a fresh session in the same folder.',
|
|
24038
|
-
},
|
|
24039
|
-
agent_cli_not_found: {
|
|
24040
|
-
tone: 'warn',
|
|
24041
|
-
title: 'The agent CLI isn’t available on this host.',
|
|
24042
|
-
body: 'CTM could not find the CLI needed to resume this session on this machine. '
|
|
24043
|
-
+ 'Install it (or fix PATH), then reload — or start fresh here.',
|
|
24044
|
-
},
|
|
24045
|
-
resume_exit_loop: {
|
|
24046
|
-
tone: 'warn',
|
|
24047
|
-
title: 'This session could not finish resuming.',
|
|
24048
|
-
body: '',
|
|
24049
|
-
},
|
|
24050
|
-
resume_no_frame: {
|
|
24051
|
-
tone: 'warn',
|
|
24052
|
-
title: 'The session never painted a usable screen.',
|
|
24053
|
-
body: '',
|
|
24054
|
-
},
|
|
24055
|
-
resume_no_output: {
|
|
24056
|
-
tone: 'warn',
|
|
24057
|
-
title: 'The agent did not respond while resuming.',
|
|
24058
|
-
body: 'It may be waiting on authentication, blocked by configuration, or unavailable on this host.',
|
|
24059
|
-
},
|
|
24060
|
-
// Mixed-version compatibility: current servers normalize this internal
|
|
24061
|
-
// lifecycle reason to resume_no_output before it reaches the browser.
|
|
24062
|
-
no_provider_output: {
|
|
24063
|
-
tone: 'warn',
|
|
24064
|
-
title: 'The agent did not respond while resuming.',
|
|
24065
|
-
body: 'It may be waiting on authentication, blocked by configuration, or unavailable on this host.',
|
|
24066
|
-
},
|
|
24067
|
-
// Compatibility for a server from before the resume lifecycle became provider-neutral.
|
|
24068
|
-
codex_no_output: {
|
|
24069
|
-
tone: 'warn',
|
|
24070
|
-
title: 'Codex did not respond while resuming.',
|
|
24071
|
-
body: 'It may be waiting on authentication, blocked by configuration, or unavailable on this host.',
|
|
24072
|
-
},
|
|
24073
|
-
restore_retries_exhausted: {
|
|
24074
|
-
tone: 'warn',
|
|
24075
|
-
title: 'CTM could not prepare this session after several tries.',
|
|
24076
|
-
body: 'The launch planner kept timing out. Retry now, or start fresh here.',
|
|
24077
|
-
},
|
|
24078
|
-
};
|
|
24079
|
-
|
|
24080
|
-
|
|
24081
|
-
|
|
24082
|
-
|
|
24083
|
-
|
|
24084
|
-
|
|
24085
|
-
|
|
24086
24046
|
// Render (or reuse) the same DOM overlay shell used for the codex resume-loading spinner, but as
|
|
24087
24047
|
// a STATIC informational/warning card — no spinner, no progress bar, no red "failed" styling
|
|
24088
24048
|
// (WI-5: the cross-host case is expected, not an error). Returns false for an unrecognized code
|
|
@@ -24436,6 +24396,9 @@ async function onSessionsList(msg, options = {}) {
|
|
|
24436
24396
|
|
|
24437
24397
|
// Add tabs for sessions we don't have terminals for (reconnect scenario)
|
|
24438
24398
|
for (const sess of msg.sessions) {
|
|
24399
|
+
// D-7: a close we already sent wins over any list that still carries the id — otherwise this
|
|
24400
|
+
// loop re-creates the stub and re-pushes tabOrder, and the tab the user just closed returns.
|
|
24401
|
+
if (state.pendingCloseIds.has(sess.id)) continue;
|
|
24439
24402
|
const incomingLiveStatus = sess && sess.liveStatus;
|
|
24440
24403
|
const hasIncomingLiveStatus = incomingLiveStatus !== undefined && incomingLiveStatus !== null && incomingLiveStatus !== '';
|
|
24441
24404
|
const existingBeforeShape = state.sessions.get(sess.id);
|
|
@@ -24611,6 +24574,9 @@ async function onSessionsList(msg, options = {}) {
|
|
|
24611
24574
|
lastSeenAt: sess.resumeBlockedLastSeenAt || '',
|
|
24612
24575
|
trashPath: sess.resumeBlockedTrashPath || '',
|
|
24613
24576
|
originalPath: sess.resumeBlockedOriginalPath || '',
|
|
24577
|
+
// WP-3 (D-13): why the copy is in CTM's trash and when — the card's "why" sentence.
|
|
24578
|
+
trashReason: sess.resumeBlockedTrashReason || '',
|
|
24579
|
+
trashedAt: sess.resumeBlockedTrashedAt || '',
|
|
24614
24580
|
// WP-6: the resume ladder's answer for the missing-transcript card.
|
|
24615
24581
|
resumeRung: sess.resumeBlockedRung || '',
|
|
24616
24582
|
coverage: sess.resumeBlockedCoverage || null,
|
|
@@ -24660,15 +24626,23 @@ async function onSessionsList(msg, options = {}) {
|
|
|
24660
24626
|
deterministic: false,
|
|
24661
24627
|
});
|
|
24662
24628
|
}
|
|
24663
|
-
} else if (existing.
|
|
24629
|
+
} else if (existing._resumeCheckPending !== true) {
|
|
24664
24630
|
// A pending Check again is not a clear: its answer (resume-blocked / resume-retry-noop / restore-started) arrives on its own.
|
|
24665
|
-
existing._resumeBlocked
|
|
24666
|
-
|
|
24667
|
-
|
|
24668
|
-
|
|
24669
|
-
|
|
24670
|
-
|
|
24671
|
-
|
|
24631
|
+
if (existing._resumeBlocked) {
|
|
24632
|
+
existing._resumeBlocked = false;
|
|
24633
|
+
existing._resumeBlockedRung = '';
|
|
24634
|
+
existing._resumeBlockedCode = '';
|
|
24635
|
+
existing._resumeBlockedMessage = '';
|
|
24636
|
+
existing._resumeBlockedDetail = '';
|
|
24637
|
+
existing._resumeBlockedAttempts = 0;
|
|
24638
|
+
}
|
|
24639
|
+
// WP-4c (session-exit-close-truth-2026-10-24.html §5 OQ-4 = H-B, §8 WP-4c): the server's
|
|
24640
|
+
// resumeBlocked:false is the authority for a SERVER-owned block, and it must clear a
|
|
24641
|
+
// resume-blocked store lifecycle even when THIS client object never saw the flag -- a
|
|
24642
|
+
// `created` replacement rebuilds the object (`onCreated` -> createTerminal) while
|
|
24643
|
+
// _presentationStore (keyed by session id) keeps the card, so a flag-only test strands the
|
|
24644
|
+
// card's shell (and the loading cover that shell hosts) forever. Leave any other lifecycle:
|
|
24645
|
+
// a loading cover (a Retry in flight) is not this tick's to clear.
|
|
24672
24646
|
if (existing.container && _presentationStore.getSessionState(_resumeClientSessionId(existing))?.lifecycle === 'resume-blocked') _clearSessionLifecycle(existing, 'restore');
|
|
24673
24647
|
}
|
|
24674
24648
|
}
|
|
@@ -24729,6 +24703,11 @@ async function onSessionsList(msg, options = {}) {
|
|
|
24729
24703
|
_maybeRearmTerminalBootstrapFromInventory(existing, sess, { restoring });
|
|
24730
24704
|
}
|
|
24731
24705
|
}
|
|
24706
|
+
// D-7: a FULL list that omits the id is the server's own evidence that the close landed — only
|
|
24707
|
+
// then is the pending entry dropped (no timer). A restoring list is partial by design, so it is no evidence.
|
|
24708
|
+
if (!restoring) for (const pendingId of Array.from(state.pendingCloseIds.keys())) {
|
|
24709
|
+
if (!serverIds.has(pendingId)) state.pendingCloseIds.delete(pendingId);
|
|
24710
|
+
}
|
|
24732
24711
|
|
|
24733
24712
|
if (!options.skipStreamStatusRefresh && !restoring && typeof refreshStreamStatuses === 'function') {
|
|
24734
24713
|
await refreshStreamStatuses({ force: true, render: false, timeoutMs: 3000,
|
|
@@ -24761,6 +24740,10 @@ async function onSessionsList(msg, options = {}) {
|
|
|
24761
24740
|
}
|
|
24762
24741
|
|
|
24763
24742
|
if (!ownsRoot()) return false;
|
|
24743
|
+
// WP-5 (S-15): repaint machine chips that are already mounted from THIS frame's facts (the
|
|
24744
|
+
// render paths build chips from the row text, but a tab chip reparented into the toolbar is not
|
|
24745
|
+
// rebuilt by a skipped/deferred render). A no-op for every local session.
|
|
24746
|
+
if (typeof refreshSessionMachineChips === 'function') refreshSessionMachineChips(msg.sessions);
|
|
24764
24747
|
if (!options.deferRender) _renderSessionsListProjection();
|
|
24765
24748
|
|
|
24766
24749
|
// Auto-activate explicit hash targets as soon as they appear. Saved-session
|
|
@@ -25328,9 +25311,12 @@ function populateModelSwitcher(sessionId, opts) {
|
|
|
25328
25311
|
// The chip label is the lead model + tags + token badge only; effort and the delegate model ride the
|
|
25329
25312
|
// tooltip / aria-label that syncChip paints. This writes only .model-chip-lead.
|
|
25330
25313
|
var leadHost = _chipLeadHost(chip);
|
|
25331
|
-
leadHost.
|
|
25314
|
+
var labelHost = leadHost.querySelector ? leadHost.querySelector('.model-chip-label') : null;
|
|
25315
|
+
if (!labelHost) { labelHost = document.createElement('span'); labelHost.className = 'model-chip-label'; }
|
|
25316
|
+
labelHost.textContent = chipView.text;
|
|
25317
|
+
while (leadHost.firstChild) leadHost.removeChild(leadHost.firstChild);
|
|
25318
|
+
leadHost.appendChild(labelHost);
|
|
25332
25319
|
chip.classList.toggle('model-stale', chipView.stale);
|
|
25333
|
-
leadHost.appendChild(document.createTextNode(chipView.text));
|
|
25334
25320
|
chipView.tags.forEach(function(tag) {
|
|
25335
25321
|
var tagEl = document.createElement('span');
|
|
25336
25322
|
tagEl.className = 'model-chip-tag';
|
|
@@ -26557,7 +26543,6 @@ function sessionLifecycleView(session) {
|
|
|
26557
26543
|
deferred: lifecycleState === 'deferred',
|
|
26558
26544
|
asleep: lifecycleState === 'parked' || lifecycleState === 'deferred',
|
|
26559
26545
|
exited,
|
|
26560
|
-
frozen: lifecycleState === 'frozen',
|
|
26561
26546
|
pinned: !!(meta.lifecyclePinned ?? meta.lifecycle_pinned),
|
|
26562
26547
|
idleMin: rawIdleMin == null ? null : Math.max(0, Math.floor(Number(rawIdleMin) || 0)),
|
|
26563
26548
|
rssMB: exited ? 0 : Math.max(0, Number(meta.rssMB ?? meta.rss_mb) || 0),
|
|
@@ -26612,9 +26597,6 @@ function sessionLifecycleChipHtml(session) {
|
|
|
26612
26597
|
} else if (view.exited) {
|
|
26613
26598
|
cssState = 'exited';
|
|
26614
26599
|
label = 'exited';
|
|
26615
|
-
} else if (view.frozen) {
|
|
26616
|
-
cssState = 'frozen';
|
|
26617
|
-
label = `frozen · ${age}`;
|
|
26618
26600
|
} else if (view.state === 'waking') {
|
|
26619
26601
|
label = `waking · ${age}`;
|
|
26620
26602
|
} else if (view.state === 'parking') {
|
|
@@ -26631,7 +26613,7 @@ function sessionLifecycleChipHtml(session) {
|
|
|
26631
26613
|
? `Parked${view.parkReason ? ': ' + view.parkReason : ''}; wakes on input`
|
|
26632
26614
|
: (view.deferred ? 'Boot-deferred; starts on input'
|
|
26633
26615
|
: (view.exited ? 'Process exited'
|
|
26634
|
-
: (
|
|
26616
|
+
: ('Lifecycle: ' + view.state)))];
|
|
26635
26617
|
if (recency.title) titleParts.push(recency.title);
|
|
26636
26618
|
if (!view.exited && view.idleMin != null) titleParts.push('Idle: ' + view.idleMin + ' min');
|
|
26637
26619
|
if (view.rssMB > 0) titleParts.push('Process tree RSS: ' + Math.round(view.rssMB) + ' MB');
|
|
@@ -27112,7 +27094,7 @@ function renderSessionList(force) {
|
|
|
27112
27094
|
}).map(p =>
|
|
27113
27095
|
`<span class="prompt-badge" onclick="event.stopPropagation();openPromptInEditor(${p.prompt_id})" title="${escHtml(p.title || 'Prompt')}">${escHtml((p.title || 'Prompt').slice(0, 20))}</span>`
|
|
27114
27096
|
).join('');
|
|
27115
|
-
const branchBadge = activeSessionBranchBadge(s);
|
|
27097
|
+
const branchBadge = activeSessionBranchBadge(s) + machineChipHtml(s); // + WP-5 machine chip ('' local)
|
|
27116
27098
|
const forkBadge = activeSessionForkBadge(s);
|
|
27117
27099
|
const worktreeBadge = worktreeAttentionBadge(s);
|
|
27118
27100
|
const ownerChip = sessionOwnerChipHtml(s);
|
|
@@ -28267,9 +28249,12 @@ function _renderTabsLegacy(force) {
|
|
|
28267
28249
|
? SessionWorkspaceTracking.tabBranchNode(SessionWorkspaceTracking.trackingForSession(s), tabBranch, '\u25ED', branchBadgeHtml)
|
|
28268
28250
|
: null;
|
|
28269
28251
|
const lifecycleChip = _sessionLifecycleChipNode(s);
|
|
28252
|
+
// WP-5 (\u00a76.6/S-14): the same presenter as the stream-chrome tab (session-commands.js), so both
|
|
28253
|
+
// tab render modes show an identical chip. null for a local session.
|
|
28254
|
+
const tabMachineChip = machineSessionChipNode(s, id);
|
|
28270
28255
|
const tabClose = document.createElement('span'); tabClose.className = 'close-tab'; tabClose.textContent = '\u00d7';
|
|
28271
28256
|
tabClose.onclick = function(e) { e.stopPropagation(); killSession(id); };
|
|
28272
|
-
tab.appendChild(tabIcon); tab.appendChild(tabLabel); if (tabBranchEl) tab.appendChild(tabBranchEl); if (lifecycleChip) tab.appendChild(lifecycleChip); tab.appendChild(tabClose);
|
|
28257
|
+
tab.appendChild(tabIcon); tab.appendChild(tabLabel); if (tabBranchEl) tab.appendChild(tabBranchEl); if (tabMachineChip) tab.appendChild(tabMachineChip); if (lifecycleChip) tab.appendChild(lifecycleChip); tab.appendChild(tabClose);
|
|
28273
28258
|
tab.onpointerdown = function(e) {
|
|
28274
28259
|
if (e.target?.closest?.('.close-tab')) return;
|
|
28275
28260
|
if (e.button === undefined || e.button === 0) {
|
|
@@ -28376,6 +28361,9 @@ function _streamChromeTabUnitForId(id) {
|
|
|
28376
28361
|
branchInfo.displayNamespace || '',
|
|
28377
28362
|
workspaceTracking ? JSON.stringify(workspaceTracking) : '',
|
|
28378
28363
|
lifecycleHtml,
|
|
28364
|
+
// WP-5 (S-15): the machine chip's label/title depend on the machine's health, so a
|
|
28365
|
+
// reachable -> unreachable flip must rebuild the tab instead of leaving a stale chip.
|
|
28366
|
+
JSON.stringify(sessionMachineFacts(session)),
|
|
28379
28367
|
]),
|
|
28380
28368
|
visualHash: JSON.stringify([
|
|
28381
28369
|
state.activeTab === id,
|
|
@@ -28883,9 +28871,12 @@ function _nsPathRefreshHostStatus() {
|
|
|
28883
28871
|
attempt: bounded ? _nsPathPickerState.hostPathAttempts : 0,
|
|
28884
28872
|
count: candidates.length,
|
|
28885
28873
|
});
|
|
28874
|
+
// WP-5 (§6.5): the probe is answered BY the selected machine via the X-CTM-Machine header (a
|
|
28875
|
+
// local selection sends none), and the seq guard above is what keeps a box answer from ever
|
|
28876
|
+
// landing in a local path field.
|
|
28886
28877
|
fetch('/api/path-status', {
|
|
28887
28878
|
method: 'POST',
|
|
28888
|
-
headers: { 'Content-Type': 'application/json' },
|
|
28879
|
+
headers: machineScopedHeaders({ 'Content-Type': 'application/json' }),
|
|
28889
28880
|
body: JSON.stringify({ paths: candidates }),
|
|
28890
28881
|
})
|
|
28891
28882
|
.then(function(r) {
|
|
@@ -28972,7 +28963,10 @@ function _nsPathRefreshHostStatus() {
|
|
|
28972
28963
|
function _nsPathLoadProjects() {
|
|
28973
28964
|
if (_nsPathPickerState.projectsLoaded || _nsPathPickerState.projectsLoading) return;
|
|
28974
28965
|
_nsPathPickerState.projectsLoading = true;
|
|
28975
|
-
|
|
28966
|
+
// WP-5 (§6.5): the selected machine answers this list ('' ⇒ this Mac, URL unchanged). These rows
|
|
28967
|
+
// are the only place the same-repo prefill looks — plain gitRemoteKey equality inside
|
|
28968
|
+
// nsMachineAfterProjectsLoaded(), against the keys of the machine we are pointed at.
|
|
28969
|
+
fetch('/api/projects?token=' + encodeURIComponent(state.token || '') + machineQueryParam())
|
|
28976
28970
|
.then(function(r) { return r.ok ? r.json() : []; })
|
|
28977
28971
|
.then(function(projects) {
|
|
28978
28972
|
_nsPathPickerState.projects = Array.isArray(projects)
|
|
@@ -28981,6 +28975,7 @@ function _nsPathLoadProjects() {
|
|
|
28981
28975
|
_nsPathPickerState.projectsLoaded = true;
|
|
28982
28976
|
_nsPathPickerState.projectsLoading = false;
|
|
28983
28977
|
_nsPathRefreshHostStatus();
|
|
28978
|
+
if (typeof nsMachineAfterProjectsLoaded === 'function') nsMachineAfterProjectsLoaded(_nsPathPickerState);
|
|
28984
28979
|
if (_nsPathPickerState.open) _nsPathRenderSuggestions();
|
|
28985
28980
|
_nsPathUpdateValidation();
|
|
28986
28981
|
if (typeof _storagePathRepaintOpen === 'function') _storagePathRepaintOpen();
|
|
@@ -29193,7 +29188,9 @@ function _nsFetchFsSuggestions(value) {
|
|
|
29193
29188
|
clearTimeout(_nsPathPickerState.fsTimer);
|
|
29194
29189
|
_nsPathPickerState.fsTimer = setTimeout(function() {
|
|
29195
29190
|
var seq = ++_nsPathPickerState.fsSeq;
|
|
29196
|
-
|
|
29191
|
+
// WP-5 (§6.5): suggestions come FROM the selected machine; the fsSeq guard above already drops
|
|
29192
|
+
// a stale answer, including one that arrives after a machine switch.
|
|
29193
|
+
fetch('/api/path-suggest?q=' + encodeURIComponent(q) + machineQueryParam(), { cache: 'no-store' })
|
|
29197
29194
|
.then(function(r) { return r.ok ? r.json() : null; })
|
|
29198
29195
|
.then(function(d) {
|
|
29199
29196
|
if (!d || seq !== _nsPathPickerState.fsSeq) return; // a newer keystroke won
|
|
@@ -29937,6 +29934,9 @@ async function _maybeRecommendWorktreeForNewSession(force) {
|
|
|
29937
29934
|
|
|
29938
29935
|
function showNewSessionModal() {
|
|
29939
29936
|
document.getElementById('new-session-modal').classList.remove('hidden');
|
|
29937
|
+
// WP-5: the machine list is fetched per open; with zero machines the grid stays unrendered
|
|
29938
|
+
// and this modal is pixel-identical to its pre-hub self (AC-5a).
|
|
29939
|
+
if (typeof loadNewSessionMachines === 'function') loadNewSessionMachines();
|
|
29940
29940
|
_nsPathReset(getLastSessionCwd());
|
|
29941
29941
|
document.getElementById('ns-label').value = '';
|
|
29942
29942
|
_nsWorktreeTouched = false;
|
|
@@ -30122,6 +30122,8 @@ function killSession(id, opts) {
|
|
|
30122
30122
|
const s = state.sessions.get(id);
|
|
30123
30123
|
const label = s?.meta?.label || id.slice(0, 8);
|
|
30124
30124
|
if (!opts.skipConfirm && !confirm(`Close session "${label}"?`)) return;
|
|
30125
|
+
// D-7: our close wins over any list that was already in flight when it was sent.
|
|
30126
|
+
state.pendingCloseIds.set(id, Date.now());
|
|
30125
30127
|
send({ type: 'kill', id });
|
|
30126
30128
|
// Clean up locally
|
|
30127
30129
|
if (s) {
|
|
@@ -30186,6 +30188,8 @@ function _killTabSilent(id) {
|
|
|
30186
30188
|
if (window.CTMNativeSubagentReview?.hasTab(id)) { closeSpecialTab(id); return; }
|
|
30187
30189
|
const s = state.sessions.get(id);
|
|
30188
30190
|
if (!s) return;
|
|
30191
|
+
// D-7: see killSession — the pending entry bridges until the list proves the close landed.
|
|
30192
|
+
state.pendingCloseIds.set(id, Date.now());
|
|
30189
30193
|
send({ type: 'kill', id });
|
|
30190
30194
|
_clearTerminalRecoveryProtocolResources(s);
|
|
30191
30195
|
if (s._outputIdleTimer) clearTimeout(s._outputIdleTimer);
|
|
@@ -31074,6 +31078,8 @@ document.addEventListener('keydown', (e) => {
|
|
|
31074
31078
|
}
|
|
31075
31079
|
// Cmd+Shift+V / Ctrl+Shift+V: cycle the available surfaces in product order.
|
|
31076
31080
|
if ((e.metaKey || e.ctrlKey) && e.shiftKey && (e.key === 'V' || e.key === 'v')) {
|
|
31081
|
+
// One keystroke, one action: the terminal already consumed this chord as a path paste.
|
|
31082
|
+
if (e.defaultPrevented) return;
|
|
31077
31083
|
const id = state.activeTab;
|
|
31078
31084
|
if (id && state.sessions && state.sessions.has(id) && typeof setSessionView === 'function') {
|
|
31079
31085
|
e.preventDefault();
|
|
@@ -32074,12 +32080,13 @@ async function loadRecentSessions(options = {}) {
|
|
|
32074
32080
|
if (!state.tabOrder.includes('review')) state.tabOrder.push('review');
|
|
32075
32081
|
// If not post-restart (e.g. normal page load with saved review), restore active tab now
|
|
32076
32082
|
if (!state._postRestart) {
|
|
32077
|
-
|
|
32083
|
+
// AC-5e: resolve the alias before classifying, so boot never flashes the old #setup tab.
|
|
32084
|
+
const _hashNav = (typeof _machinesHashAliasTarget === 'function' ? _machinesHashAliasTarget(location.hash) : '') || location.hash.slice(1).split('&')[0];
|
|
32078
32085
|
// Map hash aliases (e.g. #review → codereview) to the internal nav target so a
|
|
32079
32086
|
// panel-hash load isn't mistaken for "no panel" and overridden by the saved
|
|
32080
32087
|
// session below — the bug that made #review steal focus to the last session.
|
|
32081
32088
|
const _hashTarget = NAV_TARGET_ALIASES[_hashNav] || _hashNav;
|
|
32082
|
-
const _isHashPanel = ['command', 'rules', 'insights', 'permissions', 'prompts', 'review', 'codereview', 'walle', 'models', 'backups', 'worktrees', 'setup'].includes(_hashNav);
|
|
32089
|
+
const _isHashPanel = ['command', 'rules', 'insights', 'permissions', 'prompts', 'review', 'codereview', 'walle', 'models', 'backups', 'worktrees', 'machines', 'setup'].includes(_hashNav);
|
|
32083
32090
|
if (_isHashPanel) {
|
|
32084
32091
|
const _alreadyOnHashPanel = state.activeTab === _hashTarget || (_hashNav === 'command' && state.activeTab === 'overview');
|
|
32085
32092
|
if (!_alreadyOnHashPanel) navTo(_hashTarget, { skipHash: true });
|
|
@@ -32659,6 +32666,9 @@ function sessionItemHtml(s, opts = {}) {
|
|
|
32659
32666
|
const deviceTag = (!thisDevice && s.hostname) ? `<span style="color:var(--accent);font-size:9px;margin-left:4px">${escHtml(s.hostname)}</span>` : '';
|
|
32660
32667
|
const msgCount = s.userMsgCount > 0 ? `<span>${s.userMsgCount} msgs</span>` : '';
|
|
32661
32668
|
const providerBadge = recentSessionAgentBadgeHtml(s);
|
|
32669
|
+
// WP-5 (§6.6/S-14): '' for a local session, so local rows keep their exact markup. NOTE: this
|
|
32670
|
+
// row template is duplicated in renderServerSearchResults() below — both must stay in step.
|
|
32671
|
+
const machineChip = machineChipHtml(s);
|
|
32662
32672
|
const activeInfo = recentSessionActiveInfo(s, opts.activeIndex || null);
|
|
32663
32673
|
const activeBadge = recentSessionActiveBadgeHtml(activeInfo);
|
|
32664
32674
|
const activeCls = activeInfo ? ' active-session' : '';
|
|
@@ -32673,6 +32683,7 @@ function sessionItemHtml(s, opts = {}) {
|
|
|
32673
32683
|
<span class="project">${escHtml(project)}</span>
|
|
32674
32684
|
${activeBadge}
|
|
32675
32685
|
${providerBadge}
|
|
32686
|
+
${machineChip}
|
|
32676
32687
|
${branch}
|
|
32677
32688
|
${msgCount}
|
|
32678
32689
|
<span>${ago}</span>
|
|
@@ -32856,7 +32867,7 @@ function continueInShell(sessionId, cwd) {
|
|
|
32856
32867
|
send({
|
|
32857
32868
|
type: 'create',
|
|
32858
32869
|
cwd: cwd || undefined,
|
|
32859
|
-
label: `Shell: ${cwd}`,
|
|
32870
|
+
label: `Shell: ${cwd}`, titleIntent: 'default', // D-13: CTM-composed name, never tier 1
|
|
32860
32871
|
});
|
|
32861
32872
|
}
|
|
32862
32873
|
|
|
@@ -33364,7 +33375,7 @@ function openFolder(folderPath) {
|
|
|
33364
33375
|
cmd: 'open',
|
|
33365
33376
|
args: [folderPath],
|
|
33366
33377
|
cwd: folderPath,
|
|
33367
|
-
label: `Folder: ${folderPath.replace(/^\/Users\/[^/]+\//, '~/')}`,
|
|
33378
|
+
label: `Folder: ${folderPath.replace(/^\/Users\/[^/]+\//, '~/')}`, titleIntent: 'default', // D-13: CTM-composed name
|
|
33368
33379
|
});
|
|
33369
33380
|
}
|
|
33370
33381
|
|
|
@@ -34946,6 +34957,8 @@ function renderServerSearchResults(results, total, query, degraded) {
|
|
|
34946
34957
|
const branch = s.gitBranch ? branchMetaHtml(s.gitBranch) : '';
|
|
34947
34958
|
const msgCount = s.userMsgCount > 0 ? `<span>${s.userMsgCount} msgs</span>` : '';
|
|
34948
34959
|
const providerBadge = recentSessionAgentBadgeHtml(s);
|
|
34960
|
+
// WP-5 (§6.6/S-14): the SECOND copy of the recent-item row template (see sessionItemHtml).
|
|
34961
|
+
const machineChip = machineChipHtml(s);
|
|
34949
34962
|
const activeInfo = recentSessionActiveInfo(s, _activeIndex);
|
|
34950
34963
|
const activeBadge = recentSessionActiveBadgeHtml(activeInfo);
|
|
34951
34964
|
const activeCls = activeInfo ? ' active-session' : '';
|
|
@@ -34974,7 +34987,7 @@ function renderServerSearchResults(results, total, query, degraded) {
|
|
|
34974
34987
|
+ '<div class="recent-msg"><span class="pin-icon" style="' + (isPinned ? 'cursor:grab;' : '') + '">★</span><span class="recent-msg-text">' + highlighted + '</span></div>'
|
|
34975
34988
|
+ matchCtx
|
|
34976
34989
|
+ msgSnippet
|
|
34977
|
-
+ '<div class="recent-meta"><span class="project">' + escHtml(project) + '</span>' + activeBadge + providerBadge + branch + msgCount + '<span>' + ago + '</span></div>'
|
|
34990
|
+
+ '<div class="recent-meta"><span class="project">' + escHtml(project) + '</span>' + activeBadge + providerBadge + machineChip + branch + msgCount + '<span>' + ago + '</span></div>'
|
|
34978
34991
|
+ '</div>';
|
|
34979
34992
|
}).join('');
|
|
34980
34993
|
|
|
@@ -35455,11 +35468,11 @@ function onDataChanged(msg) {
|
|
|
35455
35468
|
refreshQpPromptsIfLoaded();
|
|
35456
35469
|
}
|
|
35457
35470
|
if (r === 'queue-draft') {
|
|
35458
|
-
|
|
35459
|
-
|
|
35460
|
-
|
|
35461
|
-
|
|
35462
|
-
|
|
35471
|
+
// clock-free r4 R-5: invalidate the session the echo names, not the panel's
|
|
35472
|
+
// session dropdown — an echo that lands while the select is empty (getQpSessionId() ===
|
|
35473
|
+
// null) used to be dropped, so the stale draft was never re-checked.
|
|
35474
|
+
const target = String(msg.id || '') || getQpSessionId() || qpCurrentSession;
|
|
35475
|
+
if (target) qpScheduleDraftRevalidate(target, 'data-changed');
|
|
35463
35476
|
}
|
|
35464
35477
|
if (r === 'auto-approvals' || r === 'approval-rules' || r === 'approval-decisions') {
|
|
35465
35478
|
if (state.activeTab === 'permissions') loadAutoApprovals();
|
|
@@ -36020,10 +36033,14 @@ window.addEventListener('message', (e) => {
|
|
|
36020
36033
|
// 'review' is the canonical code-review route; 'codereview' is kept as a legacy alias.
|
|
36021
36034
|
// Both classify as nav panels (so initial-load session-restore suppression treats them
|
|
36022
36035
|
// like any other panel), but they dispatch to the same internal target ('codereview').
|
|
36023
|
-
const NAV_TARGETS = ['sessions', 'command', 'prompts', 'rules', 'insights', 'permissions', 'review', 'codereview', 'walle', 'models', 'backups', 'worktrees', 'setup'];
|
|
36036
|
+
const NAV_TARGETS = ['sessions', 'command', 'prompts', 'rules', 'insights', 'permissions', 'review', 'codereview', 'walle', 'models', 'backups', 'worktrees', 'machines', 'setup'];
|
|
36024
36037
|
// Hash nav targets that are aliases for a different internal navTo() id.
|
|
36025
36038
|
const NAV_TARGET_ALIASES = { review: 'codereview' };
|
|
36026
36039
|
|
|
36040
|
+
// AC-5e: the old Settings → Machines deep link resolves to the first-class `machines` page (WP-5/D-14).
|
|
36041
|
+
const _MACHINES_HASH_ALIAS = 'setup&tab=machines';
|
|
36042
|
+
function _machinesHashAliasTarget(hash) { return String(hash || '').replace(/^#/, '') === _MACHINES_HASH_ALIAS ? 'machines' : ''; }
|
|
36043
|
+
|
|
36027
36044
|
function _parseHashRoute() {
|
|
36028
36045
|
const hash = location.hash.slice(1);
|
|
36029
36046
|
const parts = hash ? hash.split('&') : [];
|
|
@@ -36079,8 +36096,12 @@ function handleHashRoute() {
|
|
|
36079
36096
|
const hash = route.hash;
|
|
36080
36097
|
const routeLease = _rootPrivateRuntime?.capture?.();
|
|
36081
36098
|
if (!_isRootPrivateLeaseCurrent(routeLease)) {
|
|
36082
|
-
|
|
36083
|
-
|
|
36099
|
+
// Machines is the pool-admin page that replaced a Settings tab, so its deep link (AC-5e alias
|
|
36100
|
+
// included) resolves here alongside setup/models/backups.
|
|
36101
|
+
// typeof-guarded: this router is extracted and run on its own by the hash-route harnesses.
|
|
36102
|
+
const _publicAlias = route.isNav && typeof _machinesHashAliasTarget === 'function' ? _machinesHashAliasTarget(route.hash) : '';
|
|
36103
|
+
const publicNav = route.isNav && route.firstPart && ['setup', 'models', 'backups', 'machines'].includes(route.firstPart);
|
|
36104
|
+
if (publicNav) navTo(_publicAlias || NAV_TARGET_ALIASES[route.firstPart] || route.firstPart, { skipHash: true });
|
|
36084
36105
|
return;
|
|
36085
36106
|
}
|
|
36086
36107
|
const deferPromptRoute = (delay, action) => {
|
|
@@ -36142,6 +36163,9 @@ function handleHashRoute() {
|
|
|
36142
36163
|
return;
|
|
36143
36164
|
}
|
|
36144
36165
|
|
|
36166
|
+
// #setup&tab=machines (AC-5e) — the Settings tab is gone, so the alias rewrites onto #machines.
|
|
36167
|
+
if (isNav && typeof _machinesHashAliasTarget === 'function' && _machinesHashAliasTarget(hash) === 'machines') { navTo('machines'); return; }
|
|
36168
|
+
|
|
36145
36169
|
// #setup&tab=access — open setup and let SETUP.init pick the inner tab.
|
|
36146
36170
|
if (isNav && firstPart === 'setup') {
|
|
36147
36171
|
navTo('setup', { skipHash: true });
|
|
@@ -36973,6 +36997,11 @@ function qpReconcileSubmissionMessage(message, options = {}) {
|
|
|
36973
36997
|
status: message.status,
|
|
36974
36998
|
retryable: message.retryable === true,
|
|
36975
36999
|
message: message.message,
|
|
37000
|
+
// WP-1 (C3, R3/R7): the broadcast frame carries the same typed recovery verdict as the
|
|
37001
|
+
// direct reply — pass it through so lastAttempt is never quietly downgraded to a guess.
|
|
37002
|
+
recoveryAction: message.recoveryAction,
|
|
37003
|
+
reason: message.reason,
|
|
37004
|
+
code: message.code,
|
|
36976
37005
|
observedAt: message.observedAt || message.updated_at || message.updatedAt || Date.now(),
|
|
36977
37006
|
});
|
|
36978
37007
|
if (options.owner && !qpDraftLifetime.current(options.owner)) return false;
|
|
@@ -37520,6 +37549,9 @@ async function loadQpDraftForSession(sessionId) {
|
|
|
37520
37549
|
}
|
|
37521
37550
|
const loadState = qpDraftStateFor(sessionId, owner);
|
|
37522
37551
|
loadState.phase = 'loading';
|
|
37552
|
+
// clock-free r4 R-5b: this load may only clear the invalidations it covered — an
|
|
37553
|
+
// echo that lands in the awaits below cannot have been seen by this fetch.
|
|
37554
|
+
const invalidationAtStart = loadState.invalidationSeq || 0;
|
|
37523
37555
|
try {
|
|
37524
37556
|
const data = (await qpFetchDraftWithRetry(sessionId, owner)) || {};
|
|
37525
37557
|
if (!isQpDraftLoadCurrent(sessionId, seq, owner)) return;
|
|
@@ -37537,7 +37569,9 @@ async function loadQpDraftForSession(sessionId) {
|
|
|
37537
37569
|
loadState.failCount = 0;
|
|
37538
37570
|
loadState.lastError = '';
|
|
37539
37571
|
loadState.nextRetryAt = 0;
|
|
37540
|
-
|
|
37572
|
+
// clock-free r4 R-5b: keep stale set when an echo invalidated this session after
|
|
37573
|
+
// the fetch above started; only the invalidations this load covered are cleared.
|
|
37574
|
+
loadState.stale = (loadState.invalidationSeq || 0) !== invalidationAtStart;
|
|
37541
37575
|
qpDraftFailureEpisodeActive = false;
|
|
37542
37576
|
clearDegradedIndicator('queue-draft');
|
|
37543
37577
|
clearToastByKey('queue-draft-load');
|
|
@@ -37560,10 +37594,11 @@ async function loadQpDraftForSession(sessionId) {
|
|
|
37560
37594
|
await _handleQpDraftLoadFailure(sessionId, seq, error, owner);
|
|
37561
37595
|
} finally {
|
|
37562
37596
|
// Safety net (WP-2a): guarantee the panel never strands on "Loading draft..."
|
|
37563
|
-
// even if something above throws unexpectedly.
|
|
37564
|
-
//
|
|
37565
|
-
//
|
|
37566
|
-
|
|
37597
|
+
// even if something above throws unexpectedly. The clear never awaits: the
|
|
37598
|
+
// currency check below is synchronous, so a private-scope verify that is slow
|
|
37599
|
+
// or fails can no longer hold the flag. A stale/superseded load must never
|
|
37600
|
+
// clobber a newer load's state, which isQpDraftLoadCurrent already encodes.
|
|
37601
|
+
if (isQpDraftLoadCurrent(sessionId, seq, owner) && qpDraftLoading) {
|
|
37567
37602
|
qpDraftLoading = false;
|
|
37568
37603
|
renderQpItems();
|
|
37569
37604
|
updateQpModeBtn();
|
|
@@ -37587,6 +37622,9 @@ async function qpScheduleDraftRevalidate(sessionId, reason = 'invalidation', ori
|
|
|
37587
37622
|
const loadState = qpDraftStateFor(sessionId, timerOwner);
|
|
37588
37623
|
if (!qpDraftLifetime.current(timerOwner)) return false;
|
|
37589
37624
|
loadState.stale = true;
|
|
37625
|
+
// clock-free r4 R-5b: the counter lets an in-flight load tell which invalidations
|
|
37626
|
+
// it predates, so completing it cannot clear an echo it never saw.
|
|
37627
|
+
loadState.invalidationSeq = (loadState.invalidationSeq || 0) + 1;
|
|
37590
37628
|
if (sessionId !== qpCurrentSession || !qpQueuePanelVisible()) return false;
|
|
37591
37629
|
if (qpEditingIdx >= 0 || qpDraftSavePending(sessionId) || qpDraftLoading) {
|
|
37592
37630
|
qpDraftLifetime.defer(timerOwner, qpDeferredDraftReloads);
|
|
@@ -37637,7 +37675,6 @@ async function syncQpDraftWithServerQueue(sessionId, seq = qpDraftLoadSeq, owner
|
|
|
37637
37675
|
try {
|
|
37638
37676
|
const res = await qpDraftLifetime.request(owner, fetch, `/api/queues/${encodeURIComponent(sessionId)}`, {
|
|
37639
37677
|
headers: { 'Authorization': 'Bearer ' + owner?.token },
|
|
37640
|
-
signal: AbortSignal.timeout(4000),
|
|
37641
37678
|
});
|
|
37642
37679
|
if (!res || !isQpDraftLoadCurrent(sessionId, seq, owner)) return false;
|
|
37643
37680
|
if (res.status === 204) {
|
|
@@ -39061,6 +39098,9 @@ async function sendQueueFromPanel(actionOwner, viewOwner) {
|
|
|
39061
39098
|
if (ownsContext) { qpDraftLifetime.finish(actionOwner); qpDraftLifetime.finish(viewOwner); }
|
|
39062
39099
|
return;
|
|
39063
39100
|
}
|
|
39101
|
+
// WP-1 (C4, AC-1e): review-blocking happens BEFORE any dispatch, single item or auto batch, so
|
|
39102
|
+
// an uncertain outcome can never be sent by switching mode.
|
|
39103
|
+
if (_reviewQpItems(pendingItems, canPublish())) { finishOwnedAction(); return; }
|
|
39064
39104
|
if (pendingItems.length === 1) {
|
|
39065
39105
|
try {
|
|
39066
39106
|
if (await qpMaybeRouteWorkflow(sessionId, pendingItems[0])) {
|
|
@@ -39168,6 +39208,13 @@ async function sendQueueFromPanel(actionOwner, viewOwner) {
|
|
|
39168
39208
|
try {
|
|
39169
39209
|
const session = actionOwner.session;
|
|
39170
39210
|
if (!session) throw new Error('Session not connected');
|
|
39211
|
+
// R7 (AC-1e): Send Next invokes the recovery policy published with this item's last failure
|
|
39212
|
+
// instead of blindly repeating an identity the server already refused. A `review` verdict was
|
|
39213
|
+
// already handled by the review gate above -- it is never dispatched from here.
|
|
39214
|
+
const itemPolicy = window.QueuePanelSubmissionReconcile.itemRecoveryPolicy(item);
|
|
39215
|
+
// The certified no-effect recovery is the one place an item may drop the dead identity it
|
|
39216
|
+
// was admitted with -- and only because the user just asked for a fresh attempt.
|
|
39217
|
+
if (itemPolicy === 'resend') { delete item._submissionClientMessageId; delete item.lastAttempt; item._forceResend = true; }
|
|
39171
39218
|
const displayText = qpBuildDisplayText(item);
|
|
39172
39219
|
const images = (item && item.images) || [];
|
|
39173
39220
|
const wantsImagePaste = images.length > 0 && window.TerminalImageTransport.providerAcceptsImagePathPaste(
|
|
@@ -39203,9 +39250,15 @@ async function sendQueueFromPanel(actionOwner, viewOwner) {
|
|
|
39203
39250
|
clientMessageId: submissionId,
|
|
39204
39251
|
status: result?.status || 'failed',
|
|
39205
39252
|
retryable: result?.retryable === true,
|
|
39253
|
+
recoveryAction: result?.recoveryAction,
|
|
39254
|
+
reason: result?.reason,
|
|
39255
|
+
code: result?.code,
|
|
39206
39256
|
message: result?.message || '',
|
|
39207
39257
|
observedAt: Date.now(),
|
|
39208
39258
|
}, { itemId, owner: actionOwner });
|
|
39259
|
+
// The explicit fresh attempt has been dispatched: this transient click intent must not
|
|
39260
|
+
// outlive it, or the next Send Next would mint yet another identity from the same gesture.
|
|
39261
|
+
delete item._forceResend;
|
|
39209
39262
|
if (!result || result.status !== 'sent') {
|
|
39210
39263
|
manualSendSent = false;
|
|
39211
39264
|
const liveItem = canPublish() ? qpItems.find(candidate => candidate?.id === itemId
|
|
@@ -39217,12 +39270,16 @@ async function sendQueueFromPanel(actionOwner, viewOwner) {
|
|
|
39217
39270
|
outcome: result?.status || 'unresolved',
|
|
39218
39271
|
message: result?.message || '',
|
|
39219
39272
|
retryable: result?.retryable === true,
|
|
39273
|
+
// Same typed verdict the direct reply carried, so the next Send Next follows
|
|
39274
|
+
// the server's policy for this failure rather than guessing one.
|
|
39275
|
+
recoveryAction: result?.recoveryAction,
|
|
39276
|
+
...(result?.reason || result?.code ? { reason: result?.reason || result?.code } : {}),
|
|
39220
39277
|
};
|
|
39221
39278
|
}
|
|
39222
39279
|
}
|
|
39223
39280
|
const msg = result?.message || 'CTM could not verify that the prompt was accepted';
|
|
39224
39281
|
if (canPublish()) toast(
|
|
39225
|
-
result?.retryable ? 'Not sent yet — ' + msg + ' It stays queued.' : '
|
|
39282
|
+
result?.retryable ? 'Not sent yet — ' + msg + ' It stays queued.' : 'Prompt needs attention — ' + msg,
|
|
39226
39283
|
{ type: result?.retryable ? 'warning' : 'error', key: 'qp-send-failed:' + sessionId },
|
|
39227
39284
|
);
|
|
39228
39285
|
saveQpDraft(actionOwner);
|