create-walle 0.10.1 → 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 +4 -0
- 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 +6 -4
- package/package.json +1 -1
- 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/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/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 +5 -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-launch-health.js +29 -2
- package/template/coding-task-manager/lib/codex-link-preparation.js +9 -8
- package/template/coding-task-manager/lib/codex-paths.js +2 -1
- 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-schema-source-files.js +1 -0
- package/template/coding-task-manager/lib/feature-manifest.js +1 -1
- package/template/coding-task-manager/lib/fs-watcher.js +3 -2
- package/template/coding-task-manager/lib/harness-history-transition.js +3 -3
- 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 +55 -17
- 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/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-switch-runtime.js +57 -4
- package/template/coding-task-manager/lib/lead-model-switch.js +3 -2
- 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 +1 -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 +52 -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-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/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/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 +2 -2
- 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-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/read-pool-client.js +5 -2
- package/template/coding-task-manager/lib/read-pool-op-registry.js +10 -1
- 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 +13 -1
- 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 +33 -2
- package/template/coding-task-manager/lib/root-prompt-flow.js +167 -33
- package/template/coding-task-manager/lib/scrollback-archive-store.js +7 -4
- 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 +34 -37
- 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 +18 -0
- package/template/coding-task-manager/lib/session-model-action-preconditions.js +9 -0
- package/template/coding-task-manager/lib/session-resource-sampler.js +19 -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-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 +92 -0
- package/template/coding-task-manager/lib/typing-worker-stages.js +60 -0
- 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/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/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 -1
- package/template/coding-task-manager/public/css/reviews.css +4 -0
- package/template/coding-task-manager/public/index.html +294 -223
- 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/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 -8
- package/template/coding-task-manager/public/js/prompt-submission-tray.js +281 -0
- 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 +4 -2
- 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/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 -1
- 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 +902 -239
- 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/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/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 +6 -4
- 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
|
@@ -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):
|
|
@@ -4441,7 +4460,25 @@ function _dispatchWebSocketMessage(msg) {
|
|
|
4441
4460
|
case 'resume-prompt-result': onResumePromptResult(msg); break;
|
|
4442
4461
|
case 'resume-blocked': onSessionResumeBlocked(msg); break;
|
|
4443
4462
|
case 'resume-retry-noop': onResumeRetryNoop(msg); break;
|
|
4444
|
-
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
|
+
}
|
|
4445
4482
|
case 'analysis-progress': onAnalysisProgress(msg); break;
|
|
4446
4483
|
case 'analysis-done': onAnalysisDone(msg); break;
|
|
4447
4484
|
case 'queue-state': onQueueState(msg); break;
|
|
@@ -5600,90 +5637,63 @@ function _promptTrayClearanceBottomPx(viewportHeight, inputStackTop) {
|
|
|
5600
5637
|
// ".walle-input-stack" today (no MutationObserver wired to it), so a TTL is
|
|
5601
5638
|
// the fallback staleness bound; forceRecompute lets a known-authoritative
|
|
5602
5639
|
// caller (resize) always get a fresh read.
|
|
5603
|
-
const _promptTrayAnchorCache = { atMs: 0, inputStackTop: 0 };
|
|
5640
|
+
const _promptTrayAnchorCache = { atMs: 0, inputStackTop: 0, queuePanelOpen: null };
|
|
5604
5641
|
const _promptTrayAnchorCacheTtlMs = 2000;
|
|
5605
5642
|
|
|
5606
5643
|
function _positionPromptSubmissionTray(tray, opts) {
|
|
5607
5644
|
const now = Date.now();
|
|
5645
|
+
const queuePanelOpen = window._ctmState?.queuePanelOpen === true;
|
|
5608
5646
|
const stale = !_promptTrayAnchorCache.atMs || (now - _promptTrayAnchorCache.atMs) > _promptTrayAnchorCacheTtlMs;
|
|
5609
|
-
if ((opts && opts.forceRecompute) || stale) {
|
|
5647
|
+
if ((opts && opts.forceRecompute) || stale || _promptTrayAnchorCache.queuePanelOpen !== queuePanelOpen) {
|
|
5610
5648
|
let inputStackTop = 0;
|
|
5611
|
-
for (const stack of document.querySelectorAll('.walle-input-stack')) {
|
|
5649
|
+
for (const stack of document.querySelectorAll('.walle-input-stack, #qp-bottom')) {
|
|
5612
5650
|
if (!stack.offsetParent) continue;
|
|
5613
5651
|
const rect = stack.getBoundingClientRect();
|
|
5614
5652
|
if (rect.height > 0 && (!inputStackTop || rect.top < inputStackTop)) inputStackTop = rect.top;
|
|
5615
5653
|
}
|
|
5616
|
-
_promptTrayAnchorCache
|
|
5617
|
-
_promptTrayAnchorCache.atMs = now;
|
|
5654
|
+
Object.assign(_promptTrayAnchorCache, { inputStackTop, atMs: now, queuePanelOpen });
|
|
5618
5655
|
}
|
|
5619
5656
|
tray.style.bottom = _promptTrayClearanceBottomPx(window.innerHeight, _promptTrayAnchorCache.inputStackTop) + 'px';
|
|
5620
5657
|
}
|
|
5621
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.
|
|
5622
5664
|
function _renderPromptSubmissionStatus(record) {
|
|
5623
|
-
|
|
5624
|
-
|
|
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
|
-
setTimeout(() => window.CTMPromptSubmission.dismiss(record.clientMessageId), 0);
|
|
5656
|
-
return;
|
|
5657
|
-
} else {
|
|
5658
|
-
delete row.dataset.quietRevealAt;
|
|
5659
|
-
row.style.display = 'flex';
|
|
5660
|
-
}
|
|
5661
|
-
row.textContent = '';
|
|
5662
|
-
const label = document.createElement('span');
|
|
5663
|
-
label.style.flex = '1';
|
|
5664
|
-
label.textContent = record.status === 'pending' ? 'Sending prompt...' : record.status === 'sent' ? 'Prompt sent' : (record.message || 'Prompt needs attention');
|
|
5665
|
-
row.appendChild(label);
|
|
5666
|
-
if (record.status === 'needs_attention' || record.status === 'blocked' || record.status === 'failed') {
|
|
5667
|
-
const retry = document.createElement('button');
|
|
5668
|
-
retry.className = 'btn small'; retry.textContent = 'Retry';
|
|
5669
|
-
retry.onclick = () => window.CTMPromptSubmission.retry(record.clientMessageId);
|
|
5670
|
-
row.appendChild(retry);
|
|
5671
|
-
}
|
|
5672
|
-
if (record.status !== 'pending' && record.status !== 'queued') {
|
|
5673
|
-
const dismiss = document.createElement('button');
|
|
5674
|
-
dismiss.className = 'btn small'; dismiss.textContent = 'Dismiss';
|
|
5675
|
-
dismiss.onclick = () => window.CTMPromptSubmission.dismiss(record.clientMessageId);
|
|
5676
|
-
row.appendChild(dismiss);
|
|
5677
|
-
if (record.status === 'sent') setTimeout(() => window.CTMPromptSubmission.dismiss(record.clientMessageId), 2200);
|
|
5678
|
-
} else {
|
|
5679
|
-
// Quiet rows get a HIDE-ONLY dismiss — never CTMPromptOutbox.dismiss here, that would drop
|
|
5680
|
-
// the durable retry record while the prompt is still in flight. The row (and its tracking)
|
|
5681
|
-
// stays; only the pixels go, and a failure escalation clears hiddenByUser above.
|
|
5682
|
-
const hide = document.createElement('button');
|
|
5683
|
-
hide.className = 'btn small'; hide.textContent = 'Dismiss';
|
|
5684
|
-
hide.onclick = () => { row.dataset.hiddenByUser = '1'; row.style.display = 'none'; };
|
|
5685
|
-
row.appendChild(hide);
|
|
5686
|
-
}
|
|
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
|
+
});
|
|
5687
5697
|
}
|
|
5688
5698
|
|
|
5689
5699
|
// S-16/S-17/D-12/D-16: the tray is no longer a status ghost for every outbox row of every
|
|
@@ -12072,34 +12082,22 @@ function createTerminal(id, opts) {
|
|
|
12072
12082
|
return intent;
|
|
12073
12083
|
}
|
|
12074
12084
|
|
|
12075
|
-
async function _terminalDeleteServerDraft(id, attachment, options = {}) {
|
|
12076
|
-
if (attachment && !attachment.attachmentId && options.cancelRequest && attachment.clientId) {
|
|
12077
|
-
// Upload cancelled before the 201 arrived: cancel the server-side create by its request id.
|
|
12078
|
-
const cancel = await _terminalFetchWithRetry('/api/sessions/' + encodeURIComponent(id) + '/image-drafts?request_id=' +
|
|
12079
|
-
encodeURIComponent(attachment.clientId) + '&purge=1&token=' + encodeURIComponent(state.token || ''), { method: 'DELETE' }, { attempts: 3 });
|
|
12080
|
-
if (!cancel.ok && cancel.status !== 404) throw new Error('HTTP ' + cancel.status);
|
|
12081
|
-
return true;
|
|
12082
|
-
}
|
|
12083
|
-
if (!attachment || !attachment.attachmentId) return true;
|
|
12084
|
-
const headers = attachment.etag ? { 'If-Match': attachment.etag } : {};
|
|
12085
|
-
const response = await _terminalFetchWithRetry('/api/sessions/' + encodeURIComponent(id) + '/image-drafts/' +
|
|
12086
|
-
encodeURIComponent(attachment.attachmentId) + '?token=' + encodeURIComponent(state.token || '') +
|
|
12087
|
-
(options.purge === true ? '&purge=1' : ''), {
|
|
12088
|
-
method: 'DELETE', headers,
|
|
12089
|
-
}, { attempts: 3 });
|
|
12090
|
-
if (!response.ok && response.status !== 404) throw new Error('HTTP ' + response.status);
|
|
12091
|
-
return true;
|
|
12092
|
-
}
|
|
12093
|
-
|
|
12094
12085
|
async function _terminalRemoveDraftAttachment(id, attachment, metricEvent, options = {}) {
|
|
12095
12086
|
const Draft = window.TerminalAttachmentDraft;
|
|
12096
12087
|
const draft = _terminalAttachmentDraftState(id);
|
|
12097
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;
|
|
12098
12094
|
clearTimeout(attachment._unconfirmedReconcileTimer);
|
|
12099
12095
|
attachment._unconfirmedReconcileTimer = null;
|
|
12100
12096
|
try {
|
|
12101
|
-
await _terminalDeleteServerDraft(id, attachment, options
|
|
12097
|
+
if (!await _terminalDeleteServerDraft(id, attachment, options,
|
|
12098
|
+
{ context: owned.context, isCurrent: current }) || !current()) return false;
|
|
12102
12099
|
} catch (error) {
|
|
12100
|
+
if (!current()) return false;
|
|
12103
12101
|
console.warn('[terminal-image] draft cleanup failed:', error && error.message ? error.message : error);
|
|
12104
12102
|
_terminalAttachmentMetric(id, 'provider_delete_cleanup_failed', { attachmentCount: draft.attachments.length });
|
|
12105
12103
|
_terminalRenderAttachmentDraft(id);
|
|
@@ -12119,10 +12117,13 @@ function createTerminal(id, opts) {
|
|
|
12119
12117
|
if (action === 'disabled' || !action) return false;
|
|
12120
12118
|
if (action === 'cancel') return _terminalCancelAttachmentUpload(id, attachment);
|
|
12121
12119
|
const metric = action === 'detach' ? 'attached_detached_by_user' : 'chip_dismissed_by_user';
|
|
12120
|
+
const owned = _terminalDraftUploadOwnership(id);
|
|
12122
12121
|
const removed = _terminalRemoveDraftAttachment(id, attachment, metric);
|
|
12123
12122
|
if (action === 'detach') {
|
|
12124
12123
|
void Promise.resolve(removed).then(didRemove => {
|
|
12125
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;
|
|
12126
12127
|
toast((attachment.label || 'Image token') + ' text remains in the composer - delete it there too.', {
|
|
12127
12128
|
type: 'info', duration: 4200,
|
|
12128
12129
|
});
|
|
@@ -12141,8 +12142,9 @@ function createTerminal(id, opts) {
|
|
|
12141
12142
|
return target ? String(target.dataset.attachmentId || '') : '';
|
|
12142
12143
|
}
|
|
12143
12144
|
|
|
12144
|
-
function _terminalFocusAfterAttachmentDismiss(id, adjacentClientId, dismissed) {
|
|
12145
|
+
function _terminalFocusAfterAttachmentDismiss(id, adjacentClientId, dismissed, context) {
|
|
12145
12146
|
if (!dismissed || !adjacentClientId) return;
|
|
12147
|
+
if (!context || context.id !== id || _terminalRootDraftLifetime?.capture(id, context.session, context.draft) !== context) return;
|
|
12146
12148
|
const region = document.getElementById('tray-chip-region');
|
|
12147
12149
|
if (!region || region.dataset.sessionId !== id) return;
|
|
12148
12150
|
const target = Array.from(region.querySelectorAll('.terminal-attachment-chip'))
|
|
@@ -12377,8 +12379,7 @@ function createTerminal(id, opts) {
|
|
|
12377
12379
|
function _terminalCancelAttachmentUpload(id, attachment) {
|
|
12378
12380
|
if (!attachment || (attachment.status !== 'uploading' && attachment.status !== 'materializing')) return false;
|
|
12379
12381
|
try { attachment._uploadAbortController?.abort(); } catch {}
|
|
12380
|
-
|
|
12381
|
-
return true;
|
|
12382
|
+
return _terminalRemoveDraftAttachment(id, attachment, 'upload_cancelled', { purge: true, cancelRequest: true });
|
|
12382
12383
|
}
|
|
12383
12384
|
|
|
12384
12385
|
// image-attach-latency-redesign F2: with the server reconciler on, retrying a failed draft
|
|
@@ -12560,6 +12561,8 @@ function createTerminal(id, opts) {
|
|
|
12560
12561
|
return;
|
|
12561
12562
|
}
|
|
12562
12563
|
region.replaceChildren();
|
|
12564
|
+
const focusOwned = _terminalDraftUploadOwnership(id);
|
|
12565
|
+
const focusContext = !focusOwned.reason && focusOwned.context?.draft === draft ? focusOwned.context : null;
|
|
12563
12566
|
for (const attachment of draft.attachments) {
|
|
12564
12567
|
const chip = document.createElement('div');
|
|
12565
12568
|
chip.className = 'terminal-attachment-chip tray-attachment-chip';
|
|
@@ -12634,7 +12637,7 @@ function createTerminal(id, opts) {
|
|
|
12634
12637
|
const adjacentClientId = _terminalAdjacentAttachmentClientId(id, attachment);
|
|
12635
12638
|
const dismissed = _terminalDismissAttachment(id, attachment);
|
|
12636
12639
|
void Promise.resolve(dismissed).then(result => {
|
|
12637
|
-
_terminalFocusAfterAttachmentDismiss(id, adjacentClientId, result);
|
|
12640
|
+
_terminalFocusAfterAttachmentDismiss(id, adjacentClientId, result, focusContext);
|
|
12638
12641
|
});
|
|
12639
12642
|
}
|
|
12640
12643
|
};
|
|
@@ -13323,16 +13326,22 @@ function createTerminal(id, opts) {
|
|
|
13323
13326
|
const failed = draft.attachments.filter(attachment => attachment && attachment.status === 'failed');
|
|
13324
13327
|
if (!failed.length) return [];
|
|
13325
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));
|
|
13326
13332
|
const removed = [];
|
|
13327
13333
|
for (const attachment of failed) {
|
|
13334
|
+
if (!current()) break;
|
|
13328
13335
|
if (!Draft.removeAttachment(draft, attachment.clientId)) continue;
|
|
13329
13336
|
removed.push(attachment);
|
|
13330
13337
|
_terminalReleaseAttachmentUploadSource(attachment);
|
|
13331
13338
|
await _terminalDeleteServerDraft(id, attachment).catch(error => {
|
|
13339
|
+
if (!current()) return;
|
|
13332
13340
|
console.warn('[terminal-image] failed draft cleanup after send failed:', error && error.message ? error.message : error);
|
|
13333
13341
|
_terminalAttachmentMetric(id, 'failed_draft_cleanup_failed', { attachmentCount: removed.length });
|
|
13334
13342
|
});
|
|
13335
13343
|
}
|
|
13344
|
+
if (!current()) return removed;
|
|
13336
13345
|
_terminalRenderAttachmentDraft(id);
|
|
13337
13346
|
_terminalAttachmentMetric(id, 'failed_drafts_discarded_on_send', { attachmentCount: removed.length });
|
|
13338
13347
|
if (options.userAuthorized === true) {
|
|
@@ -13865,12 +13874,13 @@ function createTerminal(id, opts) {
|
|
|
13865
13874
|
const providerOrderBase = draft.attachments.reduce((max, item) => (
|
|
13866
13875
|
Math.max(max, Number(item._providerOrdinal || item.providerOrder || 0))
|
|
13867
13876
|
), 0);
|
|
13877
|
+
const stagedOwnerHostId = _terminalAttachmentOwnerHostId(state.sessions.get(id)); // R-1: the id the server stamps
|
|
13868
13878
|
const staged = files.map((file, index) => {
|
|
13869
13879
|
const providerOrder = providerOrderBase + index + 1;
|
|
13870
13880
|
const attachment = Draft.addUploading(draft, {
|
|
13871
13881
|
clientId: _terminalAttachmentClientId(),
|
|
13872
13882
|
filename: file.name || ('clipboard-image-' + (index + 1) + '.png'),
|
|
13873
|
-
ownerHostId:
|
|
13883
|
+
ownerHostId: stagedOwnerHostId,
|
|
13874
13884
|
});
|
|
13875
13885
|
attachment._promptOffset = promptOffset;
|
|
13876
13886
|
attachment._promptOrder = index;
|
|
@@ -13930,7 +13940,7 @@ function createTerminal(id, opts) {
|
|
|
13930
13940
|
// is adopted here instead of being rendered as an intermediate 'ready' first. Only a
|
|
13931
13941
|
// row with no reservation (or no retained publication) takes the legacy transition.
|
|
13932
13942
|
const outcome = _terminalFinishAttachmentUpload(id, attachment, uploaded); // WP-3 §6: adopt or legacy ready
|
|
13933
|
-
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
|
|
13934
13944
|
// image-attach-latency-redesign WP-1: with the server reconciler on, do not race a
|
|
13935
13945
|
// second PTY paste against it here -- the reconciler already got a nudge from this
|
|
13936
13946
|
// draft's server-side 'create' op and owns delivering it. readyCount here only
|
|
@@ -14387,12 +14397,13 @@ function createTerminal(id, opts) {
|
|
|
14387
14397
|
// the CONTINUOUS reader on THIS tick -- no mouse report is built/sent (AC-8d).
|
|
14388
14398
|
// WP-2 (D-4): px = |deltaY| in px per deltaMode -- the live terminal lands that far below the pane top.
|
|
14389
14399
|
if (decision.action === 'reader-enter') {
|
|
14390
|
-
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 });
|
|
14391
14401
|
_enterContinuousForSession(id, px);
|
|
14392
14402
|
e.preventDefault();
|
|
14393
14403
|
e.stopPropagation();
|
|
14394
14404
|
return;
|
|
14395
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
|
|
14396
14407
|
if (decision.shouldForward) {
|
|
14397
14408
|
let cw = 0, ch = 0;
|
|
14398
14409
|
try { const d = term._core._renderService.dimensions.css.cell; cw = d.width; ch = d.height; } catch {}
|
|
@@ -14428,7 +14439,7 @@ function createTerminal(id, opts) {
|
|
|
14428
14439
|
e.stopPropagation();
|
|
14429
14440
|
return;
|
|
14430
14441
|
}
|
|
14431
|
-
} catch (error) {
|
|
14442
|
+
} catch (error) { e.preventDefault(); e.stopPropagation();
|
|
14432
14443
|
send({
|
|
14433
14444
|
type: 'client-diag', id, event: 'terminal-wheel-forward-error',
|
|
14434
14445
|
details: { message: String(error && error.message || error).slice(0, 160) },
|
|
@@ -14441,7 +14452,7 @@ function createTerminal(id, opts) {
|
|
|
14441
14452
|
if (wheelDecisionForDiag) {
|
|
14442
14453
|
if (wheelDecisionForDiag.action === 'reader') wheelRecorder.noteEvent({ deltaPx: _wheelDeltaPx(e, term), deltaMode: e.deltaMode, steps: 0, route: 'reader' }); // D-15: reader-open events
|
|
14443
14454
|
wheelRecorder.record(wheelDecisionForDiag.route, {
|
|
14444
|
-
bufferType: buf.type, baseY: buf.baseY,
|
|
14455
|
+
bufferType: buf.type, baseY: buf.baseY, deltaY: e.deltaY, action: wheelDecisionForDiag.action,
|
|
14445
14456
|
appMouse: wheelDecisionForDiag.mouseActive, isAlt: wheelDecisionForDiag.isAlt,
|
|
14446
14457
|
mouseSource: wheelDecisionForDiag.mouseState && wheelDecisionForDiag.mouseState.source, frameTracking: wheelDecisionForDiag.frameTracking,
|
|
14447
14458
|
});
|
|
@@ -17024,14 +17035,15 @@ function _showWarmSessionShell(id) {
|
|
|
17024
17035
|
for (const panelId of [
|
|
17025
17036
|
'rules-panel', 'review-panel', 'codereview-panel', 'insights-panel',
|
|
17026
17037
|
'permissions-panel', 'prompts-panel', 'walle-panel', 'models-panel',
|
|
17027
|
-
'backups-panel', 'worktrees-panel', 'setup-panel',
|
|
17038
|
+
'backups-panel', 'worktrees-panel', 'machines-panel', 'setup-panel',
|
|
17028
17039
|
]) {
|
|
17029
17040
|
const panel = document.getElementById(panelId);
|
|
17030
17041
|
panel?.classList?.remove('active');
|
|
17031
|
-
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)) {
|
|
17032
17043
|
panel.style.display = 'none';
|
|
17033
17044
|
}
|
|
17034
17045
|
}
|
|
17046
|
+
if (typeof machinesPanelHidden === 'function') machinesPanelHidden();
|
|
17035
17047
|
const sidebar = document.getElementById('sidebar');
|
|
17036
17048
|
const sidebarResize = document.getElementById('sidebar-resize');
|
|
17037
17049
|
if (!state.sidebarManuallyHidden) {
|
|
@@ -17143,7 +17155,7 @@ function _prepareSurfaceResume(id) {
|
|
|
17143
17155
|
const previousId = state.activeTab;
|
|
17144
17156
|
const switchingTabs = previousId !== id;
|
|
17145
17157
|
const session = state.sessions.get(id);
|
|
17146
|
-
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);
|
|
17147
17159
|
const wakeDirtyEvent = session?._wakeStreamDirty;
|
|
17148
17160
|
if (wakeDirtyEvent) {
|
|
17149
17161
|
session._wakeStreamDirty = false;
|
|
@@ -17355,6 +17367,7 @@ function _commitSurfaceVisibility(id, isPanel, wasSubscribedBeforeActivation) {
|
|
|
17355
17367
|
if (bkp) { bkp.classList.remove('active'); bkp.style.display = 'none'; }
|
|
17356
17368
|
var wtp = document.getElementById('worktrees-panel');
|
|
17357
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();
|
|
17358
17371
|
var stp = document.getElementById('setup-panel');
|
|
17359
17372
|
if (stp) { stp.classList.remove('active'); stp.style.display = 'none'; }
|
|
17360
17373
|
state.activeTab = id;
|
|
@@ -17467,6 +17480,10 @@ if (id === 'rules') {
|
|
|
17467
17480
|
wtp.style.display = 'flex';
|
|
17468
17481
|
wtp.classList.add('active');
|
|
17469
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();
|
|
17470
17487
|
} else if (id === 'setup') {
|
|
17471
17488
|
var sp = document.getElementById('setup-panel');
|
|
17472
17489
|
sp.style.display = 'flex';
|
|
@@ -17725,7 +17742,7 @@ function activateTab(id) {
|
|
|
17725
17742
|
|
|
17726
17743
|
function syncNavPills(activeId) {
|
|
17727
17744
|
const navPills = document.querySelectorAll('#topbar-nav .nav-pill');
|
|
17728
|
-
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'];
|
|
17729
17746
|
const navId = specialPanels.includes(activeId) ? activeId : 'sessions';
|
|
17730
17747
|
navPills.forEach(pill => {
|
|
17731
17748
|
pill.classList.toggle('active', pill.dataset.nav === navId);
|
|
@@ -18421,10 +18438,10 @@ function showStandupDashboard(opts) {
|
|
|
18421
18438
|
const el = document.getElementById(id);
|
|
18422
18439
|
if (el) el.classList.remove('active');
|
|
18423
18440
|
});
|
|
18424
|
-
['models-panel', 'backups-panel', 'worktrees-panel', 'setup-panel'].forEach(id => {
|
|
18441
|
+
['models-panel', 'backups-panel', 'worktrees-panel', 'machines-panel', 'setup-panel'].forEach(id => {
|
|
18425
18442
|
const el = document.getElementById(id);
|
|
18426
18443
|
if (el) { el.classList.remove('active'); el.style.display = 'none'; }
|
|
18427
|
-
});
|
|
18444
|
+
}); if (typeof machinesPanelHidden === 'function') machinesPanelHidden();
|
|
18428
18445
|
|
|
18429
18446
|
state.activeTab = SESSIONS_OVERVIEW_TAB_ID;
|
|
18430
18447
|
_projectSessionListSelection(SESSIONS_OVERVIEW_TAB_ID);
|
|
@@ -18828,6 +18845,9 @@ function renderStandupCard(card, opts) {
|
|
|
18828
18845
|
opts = opts || {};
|
|
18829
18846
|
const tokens = opts.tokens || [];
|
|
18830
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);
|
|
18831
18851
|
const subtitle = [card.agent, card.model, card.branch].filter(Boolean).join(' / ');
|
|
18832
18852
|
const goal = standupCleanGoalText(card.goal || card.intent || '');
|
|
18833
18853
|
const progress = card.progressSummary || card.progress || '';
|
|
@@ -18847,6 +18867,7 @@ function renderStandupCard(card, opts) {
|
|
|
18847
18867
|
<div class="standup-card-title">${titleHtml}</div>
|
|
18848
18868
|
<div class="standup-card-meta" title="${standupEsc(subtitle || card.cwd || '')}">
|
|
18849
18869
|
${providerBadge}
|
|
18870
|
+
${machineChip}
|
|
18850
18871
|
<div class="standup-card-subtitle">${standupEsc(subtitle || card.cwd || '')}</div>
|
|
18851
18872
|
</div>
|
|
18852
18873
|
</div>
|
|
@@ -19043,6 +19064,8 @@ function navTo(target, opts) {
|
|
|
19043
19064
|
showSetupPanel();
|
|
19044
19065
|
} else if (target === 'worktrees') {
|
|
19045
19066
|
showWorktreesPanel();
|
|
19067
|
+
} else if (target === 'machines') {
|
|
19068
|
+
showMachinesPanel(opts || {});
|
|
19046
19069
|
}
|
|
19047
19070
|
}
|
|
19048
19071
|
|
|
@@ -22778,19 +22801,19 @@ function onSnapshot(msg) {
|
|
|
22778
22801
|
const dimsMismatch = gridMismatchDecision.mismatch;
|
|
22779
22802
|
if (dimsMismatch) {
|
|
22780
22803
|
const manualReflowSnapshot = restoreSource === 'reflow';
|
|
22781
|
-
|
|
22782
|
-
|
|
22783
|
-
|
|
22784
|
-
|
|
22785
|
-
|
|
22786
|
-
|
|
22787
|
-
|
|
22788
|
-
|
|
22789
|
-
|
|
22790
|
-
|
|
22791
|
-
|
|
22792
|
-
|
|
22793
|
-
|
|
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();
|
|
22794
22817
|
} else {
|
|
22795
22818
|
// Non-interim, non-authoritative snapshot at a foreign grid: the client
|
|
22796
22819
|
// resized after requesting. Do NOT replay foreign-width data. Resync the
|
|
@@ -23280,6 +23303,13 @@ function _showLoadingOverlay(s, opts) {
|
|
|
23280
23303
|
}
|
|
23281
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)
|
|
23282
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
|
+
}
|
|
23283
23313
|
function _dismissLoadingOverlay(s, opts) {
|
|
23284
23314
|
opts = opts || {}; const authority = _loadingOverlayDismissAuthority(opts);
|
|
23285
23315
|
// WI-1: keep the corner chip in sync on every dismiss checkpoint (this function is called from
|
|
@@ -23294,16 +23324,19 @@ function _dismissLoadingOverlay(s, opts) {
|
|
|
23294
23324
|
// "session-restore-started" means the PTY exists, not that the visible page is ready.
|
|
23295
23325
|
if (s && authority === 'reconcile') {
|
|
23296
23326
|
const rs = _resumeStateForSession(s);
|
|
23297
|
-
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.
|
|
23298
23328
|
}
|
|
23299
23329
|
// A resume-blocked card is a terminal recovery state, not a loading cover. Readiness and
|
|
23300
23330
|
// lifecycle handlers share this DOM shell, but they do not own the card and must not erase its
|
|
23301
23331
|
// diagnosis/actions. Retry and the server's explicit blocked=false projection clear it.
|
|
23302
|
-
|
|
23303
|
-
|
|
23332
|
+
const lifecycle = _presentationStore.getSessionState(_resumeClientSessionId(s))?.lifecycle ?? null;
|
|
23333
|
+
if (lifecycle === 'resume-blocked' && opts.clearResumeBlocked !== true) { _recordOverlayDismiss(s, authority, 'resume-blocked-held'); return; }
|
|
23304
23334
|
if (s._loadingOverlayTimer) { clearTimeout(s._loadingOverlayTimer); s._loadingOverlayTimer = null; }
|
|
23305
23335
|
s._resumeLoadStartedAt = 0;
|
|
23306
|
-
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');
|
|
23307
23340
|
_clearSessionLifecycle(s, 'restore');
|
|
23308
23341
|
s._loadingOverlayShownAt = 0;
|
|
23309
23342
|
}
|
|
@@ -23311,6 +23344,9 @@ function _dismissLoadingOverlay(s, opts) {
|
|
|
23311
23344
|
|
|
23312
23345
|
|
|
23313
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);
|
|
23314
23350
|
const s = state.sessions.get(msg.id);
|
|
23315
23351
|
if (!s) return;
|
|
23316
23352
|
// S-19 (terminal-sync-adopted-restart-race-2026-09-07.html §9 S-19): an exited session
|
|
@@ -24007,72 +24043,6 @@ function onSessionRestoreFailed(msg) {
|
|
|
24007
24043
|
);
|
|
24008
24044
|
}
|
|
24009
24045
|
|
|
24010
|
-
// WI-5: cause-specific, non-alarming copy for the enumerated deterministic resume_blocked reason
|
|
24011
|
-
// codes the server can send (see server.js markSessionResumeBlocked / _notifyResumePreflightFailure).
|
|
24012
|
-
// Branch ONLY on `msg.code` here — never on message text — so this stays correct even if the
|
|
24013
|
-
// human-readable message wording changes server-side. An unrecognized code (contract drift, or a
|
|
24014
|
-
// code we haven't special-cased yet) falls back to the same honest red failure banner
|
|
24015
|
-
// onSessionRestoreFailed already renders, rather than inventing false calm.
|
|
24016
|
-
const RESUME_BLOCKED_COPY = {
|
|
24017
|
-
missing_claude_transcript: {
|
|
24018
|
-
tone: 'info',
|
|
24019
|
-
title: "This session's transcript is gone from this machine.",
|
|
24020
|
-
body: 'The file was removed before CTM stored any of its prompts (Claude Code deletes transcripts older than '
|
|
24021
|
-
+ 'its retention period, 30 days by default). There is nothing to resume; start a fresh session in the same folder.',
|
|
24022
|
-
},
|
|
24023
|
-
missing_codex_rollout: {
|
|
24024
|
-
tone: 'info',
|
|
24025
|
-
title: "This session's transcript is gone from this machine.",
|
|
24026
|
-
body: 'The file was removed before CTM stored any of its prompts. There is nothing to resume; '
|
|
24027
|
-
+ 'start a fresh session in the same folder.',
|
|
24028
|
-
},
|
|
24029
|
-
agent_cli_not_found: {
|
|
24030
|
-
tone: 'warn',
|
|
24031
|
-
title: 'The agent CLI isn’t available on this host.',
|
|
24032
|
-
body: 'CTM could not find the CLI needed to resume this session on this machine. '
|
|
24033
|
-
+ 'Install it (or fix PATH), then reload — or start fresh here.',
|
|
24034
|
-
},
|
|
24035
|
-
resume_exit_loop: {
|
|
24036
|
-
tone: 'warn',
|
|
24037
|
-
title: 'This session could not finish resuming.',
|
|
24038
|
-
body: '',
|
|
24039
|
-
},
|
|
24040
|
-
resume_no_frame: {
|
|
24041
|
-
tone: 'warn',
|
|
24042
|
-
title: 'The session never painted a usable screen.',
|
|
24043
|
-
body: '',
|
|
24044
|
-
},
|
|
24045
|
-
resume_no_output: {
|
|
24046
|
-
tone: 'warn',
|
|
24047
|
-
title: 'The agent did not respond while resuming.',
|
|
24048
|
-
body: 'It may be waiting on authentication, blocked by configuration, or unavailable on this host.',
|
|
24049
|
-
},
|
|
24050
|
-
// Mixed-version compatibility: current servers normalize this internal
|
|
24051
|
-
// lifecycle reason to resume_no_output before it reaches the browser.
|
|
24052
|
-
no_provider_output: {
|
|
24053
|
-
tone: 'warn',
|
|
24054
|
-
title: 'The agent did not respond while resuming.',
|
|
24055
|
-
body: 'It may be waiting on authentication, blocked by configuration, or unavailable on this host.',
|
|
24056
|
-
},
|
|
24057
|
-
// Compatibility for a server from before the resume lifecycle became provider-neutral.
|
|
24058
|
-
codex_no_output: {
|
|
24059
|
-
tone: 'warn',
|
|
24060
|
-
title: 'Codex did not respond while resuming.',
|
|
24061
|
-
body: 'It may be waiting on authentication, blocked by configuration, or unavailable on this host.',
|
|
24062
|
-
},
|
|
24063
|
-
restore_retries_exhausted: {
|
|
24064
|
-
tone: 'warn',
|
|
24065
|
-
title: 'CTM could not prepare this session after several tries.',
|
|
24066
|
-
body: 'The launch planner kept timing out. Retry now, or start fresh here.',
|
|
24067
|
-
},
|
|
24068
|
-
};
|
|
24069
|
-
|
|
24070
|
-
|
|
24071
|
-
|
|
24072
|
-
|
|
24073
|
-
|
|
24074
|
-
|
|
24075
|
-
|
|
24076
24046
|
// Render (or reuse) the same DOM overlay shell used for the codex resume-loading spinner, but as
|
|
24077
24047
|
// a STATIC informational/warning card — no spinner, no progress bar, no red "failed" styling
|
|
24078
24048
|
// (WI-5: the cross-host case is expected, not an error). Returns false for an unrecognized code
|
|
@@ -24426,6 +24396,9 @@ async function onSessionsList(msg, options = {}) {
|
|
|
24426
24396
|
|
|
24427
24397
|
// Add tabs for sessions we don't have terminals for (reconnect scenario)
|
|
24428
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;
|
|
24429
24402
|
const incomingLiveStatus = sess && sess.liveStatus;
|
|
24430
24403
|
const hasIncomingLiveStatus = incomingLiveStatus !== undefined && incomingLiveStatus !== null && incomingLiveStatus !== '';
|
|
24431
24404
|
const existingBeforeShape = state.sessions.get(sess.id);
|
|
@@ -24601,6 +24574,9 @@ async function onSessionsList(msg, options = {}) {
|
|
|
24601
24574
|
lastSeenAt: sess.resumeBlockedLastSeenAt || '',
|
|
24602
24575
|
trashPath: sess.resumeBlockedTrashPath || '',
|
|
24603
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 || '',
|
|
24604
24580
|
// WP-6: the resume ladder's answer for the missing-transcript card.
|
|
24605
24581
|
resumeRung: sess.resumeBlockedRung || '',
|
|
24606
24582
|
coverage: sess.resumeBlockedCoverage || null,
|
|
@@ -24650,15 +24626,23 @@ async function onSessionsList(msg, options = {}) {
|
|
|
24650
24626
|
deterministic: false,
|
|
24651
24627
|
});
|
|
24652
24628
|
}
|
|
24653
|
-
} else if (existing.
|
|
24629
|
+
} else if (existing._resumeCheckPending !== true) {
|
|
24654
24630
|
// A pending Check again is not a clear: its answer (resume-blocked / resume-retry-noop / restore-started) arrives on its own.
|
|
24655
|
-
existing._resumeBlocked
|
|
24656
|
-
|
|
24657
|
-
|
|
24658
|
-
|
|
24659
|
-
|
|
24660
|
-
|
|
24661
|
-
|
|
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.
|
|
24662
24646
|
if (existing.container && _presentationStore.getSessionState(_resumeClientSessionId(existing))?.lifecycle === 'resume-blocked') _clearSessionLifecycle(existing, 'restore');
|
|
24663
24647
|
}
|
|
24664
24648
|
}
|
|
@@ -24719,6 +24703,11 @@ async function onSessionsList(msg, options = {}) {
|
|
|
24719
24703
|
_maybeRearmTerminalBootstrapFromInventory(existing, sess, { restoring });
|
|
24720
24704
|
}
|
|
24721
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
|
+
}
|
|
24722
24711
|
|
|
24723
24712
|
if (!options.skipStreamStatusRefresh && !restoring && typeof refreshStreamStatuses === 'function') {
|
|
24724
24713
|
await refreshStreamStatuses({ force: true, render: false, timeoutMs: 3000,
|
|
@@ -24751,6 +24740,10 @@ async function onSessionsList(msg, options = {}) {
|
|
|
24751
24740
|
}
|
|
24752
24741
|
|
|
24753
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);
|
|
24754
24747
|
if (!options.deferRender) _renderSessionsListProjection();
|
|
24755
24748
|
|
|
24756
24749
|
// Auto-activate explicit hash targets as soon as they appear. Saved-session
|
|
@@ -25318,9 +25311,12 @@ function populateModelSwitcher(sessionId, opts) {
|
|
|
25318
25311
|
// The chip label is the lead model + tags + token badge only; effort and the delegate model ride the
|
|
25319
25312
|
// tooltip / aria-label that syncChip paints. This writes only .model-chip-lead.
|
|
25320
25313
|
var leadHost = _chipLeadHost(chip);
|
|
25321
|
-
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);
|
|
25322
25319
|
chip.classList.toggle('model-stale', chipView.stale);
|
|
25323
|
-
leadHost.appendChild(document.createTextNode(chipView.text));
|
|
25324
25320
|
chipView.tags.forEach(function(tag) {
|
|
25325
25321
|
var tagEl = document.createElement('span');
|
|
25326
25322
|
tagEl.className = 'model-chip-tag';
|
|
@@ -27098,7 +27094,7 @@ function renderSessionList(force) {
|
|
|
27098
27094
|
}).map(p =>
|
|
27099
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>`
|
|
27100
27096
|
).join('');
|
|
27101
|
-
const branchBadge = activeSessionBranchBadge(s);
|
|
27097
|
+
const branchBadge = activeSessionBranchBadge(s) + machineChipHtml(s); // + WP-5 machine chip ('' local)
|
|
27102
27098
|
const forkBadge = activeSessionForkBadge(s);
|
|
27103
27099
|
const worktreeBadge = worktreeAttentionBadge(s);
|
|
27104
27100
|
const ownerChip = sessionOwnerChipHtml(s);
|
|
@@ -28253,9 +28249,12 @@ function _renderTabsLegacy(force) {
|
|
|
28253
28249
|
? SessionWorkspaceTracking.tabBranchNode(SessionWorkspaceTracking.trackingForSession(s), tabBranch, '\u25ED', branchBadgeHtml)
|
|
28254
28250
|
: null;
|
|
28255
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);
|
|
28256
28255
|
const tabClose = document.createElement('span'); tabClose.className = 'close-tab'; tabClose.textContent = '\u00d7';
|
|
28257
28256
|
tabClose.onclick = function(e) { e.stopPropagation(); killSession(id); };
|
|
28258
|
-
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);
|
|
28259
28258
|
tab.onpointerdown = function(e) {
|
|
28260
28259
|
if (e.target?.closest?.('.close-tab')) return;
|
|
28261
28260
|
if (e.button === undefined || e.button === 0) {
|
|
@@ -28362,6 +28361,9 @@ function _streamChromeTabUnitForId(id) {
|
|
|
28362
28361
|
branchInfo.displayNamespace || '',
|
|
28363
28362
|
workspaceTracking ? JSON.stringify(workspaceTracking) : '',
|
|
28364
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)),
|
|
28365
28367
|
]),
|
|
28366
28368
|
visualHash: JSON.stringify([
|
|
28367
28369
|
state.activeTab === id,
|
|
@@ -28869,9 +28871,12 @@ function _nsPathRefreshHostStatus() {
|
|
|
28869
28871
|
attempt: bounded ? _nsPathPickerState.hostPathAttempts : 0,
|
|
28870
28872
|
count: candidates.length,
|
|
28871
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.
|
|
28872
28877
|
fetch('/api/path-status', {
|
|
28873
28878
|
method: 'POST',
|
|
28874
|
-
headers: { 'Content-Type': 'application/json' },
|
|
28879
|
+
headers: machineScopedHeaders({ 'Content-Type': 'application/json' }),
|
|
28875
28880
|
body: JSON.stringify({ paths: candidates }),
|
|
28876
28881
|
})
|
|
28877
28882
|
.then(function(r) {
|
|
@@ -28958,7 +28963,10 @@ function _nsPathRefreshHostStatus() {
|
|
|
28958
28963
|
function _nsPathLoadProjects() {
|
|
28959
28964
|
if (_nsPathPickerState.projectsLoaded || _nsPathPickerState.projectsLoading) return;
|
|
28960
28965
|
_nsPathPickerState.projectsLoading = true;
|
|
28961
|
-
|
|
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())
|
|
28962
28970
|
.then(function(r) { return r.ok ? r.json() : []; })
|
|
28963
28971
|
.then(function(projects) {
|
|
28964
28972
|
_nsPathPickerState.projects = Array.isArray(projects)
|
|
@@ -28967,6 +28975,7 @@ function _nsPathLoadProjects() {
|
|
|
28967
28975
|
_nsPathPickerState.projectsLoaded = true;
|
|
28968
28976
|
_nsPathPickerState.projectsLoading = false;
|
|
28969
28977
|
_nsPathRefreshHostStatus();
|
|
28978
|
+
if (typeof nsMachineAfterProjectsLoaded === 'function') nsMachineAfterProjectsLoaded(_nsPathPickerState);
|
|
28970
28979
|
if (_nsPathPickerState.open) _nsPathRenderSuggestions();
|
|
28971
28980
|
_nsPathUpdateValidation();
|
|
28972
28981
|
if (typeof _storagePathRepaintOpen === 'function') _storagePathRepaintOpen();
|
|
@@ -29179,7 +29188,9 @@ function _nsFetchFsSuggestions(value) {
|
|
|
29179
29188
|
clearTimeout(_nsPathPickerState.fsTimer);
|
|
29180
29189
|
_nsPathPickerState.fsTimer = setTimeout(function() {
|
|
29181
29190
|
var seq = ++_nsPathPickerState.fsSeq;
|
|
29182
|
-
|
|
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' })
|
|
29183
29194
|
.then(function(r) { return r.ok ? r.json() : null; })
|
|
29184
29195
|
.then(function(d) {
|
|
29185
29196
|
if (!d || seq !== _nsPathPickerState.fsSeq) return; // a newer keystroke won
|
|
@@ -29923,6 +29934,9 @@ async function _maybeRecommendWorktreeForNewSession(force) {
|
|
|
29923
29934
|
|
|
29924
29935
|
function showNewSessionModal() {
|
|
29925
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();
|
|
29926
29940
|
_nsPathReset(getLastSessionCwd());
|
|
29927
29941
|
document.getElementById('ns-label').value = '';
|
|
29928
29942
|
_nsWorktreeTouched = false;
|
|
@@ -30108,6 +30122,8 @@ function killSession(id, opts) {
|
|
|
30108
30122
|
const s = state.sessions.get(id);
|
|
30109
30123
|
const label = s?.meta?.label || id.slice(0, 8);
|
|
30110
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());
|
|
30111
30127
|
send({ type: 'kill', id });
|
|
30112
30128
|
// Clean up locally
|
|
30113
30129
|
if (s) {
|
|
@@ -30172,6 +30188,8 @@ function _killTabSilent(id) {
|
|
|
30172
30188
|
if (window.CTMNativeSubagentReview?.hasTab(id)) { closeSpecialTab(id); return; }
|
|
30173
30189
|
const s = state.sessions.get(id);
|
|
30174
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());
|
|
30175
30193
|
send({ type: 'kill', id });
|
|
30176
30194
|
_clearTerminalRecoveryProtocolResources(s);
|
|
30177
30195
|
if (s._outputIdleTimer) clearTimeout(s._outputIdleTimer);
|
|
@@ -31060,6 +31078,8 @@ document.addEventListener('keydown', (e) => {
|
|
|
31060
31078
|
}
|
|
31061
31079
|
// Cmd+Shift+V / Ctrl+Shift+V: cycle the available surfaces in product order.
|
|
31062
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;
|
|
31063
31083
|
const id = state.activeTab;
|
|
31064
31084
|
if (id && state.sessions && state.sessions.has(id) && typeof setSessionView === 'function') {
|
|
31065
31085
|
e.preventDefault();
|
|
@@ -32060,12 +32080,13 @@ async function loadRecentSessions(options = {}) {
|
|
|
32060
32080
|
if (!state.tabOrder.includes('review')) state.tabOrder.push('review');
|
|
32061
32081
|
// If not post-restart (e.g. normal page load with saved review), restore active tab now
|
|
32062
32082
|
if (!state._postRestart) {
|
|
32063
|
-
|
|
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];
|
|
32064
32085
|
// Map hash aliases (e.g. #review → codereview) to the internal nav target so a
|
|
32065
32086
|
// panel-hash load isn't mistaken for "no panel" and overridden by the saved
|
|
32066
32087
|
// session below — the bug that made #review steal focus to the last session.
|
|
32067
32088
|
const _hashTarget = NAV_TARGET_ALIASES[_hashNav] || _hashNav;
|
|
32068
|
-
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);
|
|
32069
32090
|
if (_isHashPanel) {
|
|
32070
32091
|
const _alreadyOnHashPanel = state.activeTab === _hashTarget || (_hashNav === 'command' && state.activeTab === 'overview');
|
|
32071
32092
|
if (!_alreadyOnHashPanel) navTo(_hashTarget, { skipHash: true });
|
|
@@ -32645,6 +32666,9 @@ function sessionItemHtml(s, opts = {}) {
|
|
|
32645
32666
|
const deviceTag = (!thisDevice && s.hostname) ? `<span style="color:var(--accent);font-size:9px;margin-left:4px">${escHtml(s.hostname)}</span>` : '';
|
|
32646
32667
|
const msgCount = s.userMsgCount > 0 ? `<span>${s.userMsgCount} msgs</span>` : '';
|
|
32647
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);
|
|
32648
32672
|
const activeInfo = recentSessionActiveInfo(s, opts.activeIndex || null);
|
|
32649
32673
|
const activeBadge = recentSessionActiveBadgeHtml(activeInfo);
|
|
32650
32674
|
const activeCls = activeInfo ? ' active-session' : '';
|
|
@@ -32659,6 +32683,7 @@ function sessionItemHtml(s, opts = {}) {
|
|
|
32659
32683
|
<span class="project">${escHtml(project)}</span>
|
|
32660
32684
|
${activeBadge}
|
|
32661
32685
|
${providerBadge}
|
|
32686
|
+
${machineChip}
|
|
32662
32687
|
${branch}
|
|
32663
32688
|
${msgCount}
|
|
32664
32689
|
<span>${ago}</span>
|
|
@@ -32842,7 +32867,7 @@ function continueInShell(sessionId, cwd) {
|
|
|
32842
32867
|
send({
|
|
32843
32868
|
type: 'create',
|
|
32844
32869
|
cwd: cwd || undefined,
|
|
32845
|
-
label: `Shell: ${cwd}`,
|
|
32870
|
+
label: `Shell: ${cwd}`, titleIntent: 'default', // D-13: CTM-composed name, never tier 1
|
|
32846
32871
|
});
|
|
32847
32872
|
}
|
|
32848
32873
|
|
|
@@ -33350,7 +33375,7 @@ function openFolder(folderPath) {
|
|
|
33350
33375
|
cmd: 'open',
|
|
33351
33376
|
args: [folderPath],
|
|
33352
33377
|
cwd: folderPath,
|
|
33353
|
-
label: `Folder: ${folderPath.replace(/^\/Users\/[^/]+\//, '~/')}`,
|
|
33378
|
+
label: `Folder: ${folderPath.replace(/^\/Users\/[^/]+\//, '~/')}`, titleIntent: 'default', // D-13: CTM-composed name
|
|
33354
33379
|
});
|
|
33355
33380
|
}
|
|
33356
33381
|
|
|
@@ -34932,6 +34957,8 @@ function renderServerSearchResults(results, total, query, degraded) {
|
|
|
34932
34957
|
const branch = s.gitBranch ? branchMetaHtml(s.gitBranch) : '';
|
|
34933
34958
|
const msgCount = s.userMsgCount > 0 ? `<span>${s.userMsgCount} msgs</span>` : '';
|
|
34934
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);
|
|
34935
34962
|
const activeInfo = recentSessionActiveInfo(s, _activeIndex);
|
|
34936
34963
|
const activeBadge = recentSessionActiveBadgeHtml(activeInfo);
|
|
34937
34964
|
const activeCls = activeInfo ? ' active-session' : '';
|
|
@@ -34960,7 +34987,7 @@ function renderServerSearchResults(results, total, query, degraded) {
|
|
|
34960
34987
|
+ '<div class="recent-msg"><span class="pin-icon" style="' + (isPinned ? 'cursor:grab;' : '') + '">★</span><span class="recent-msg-text">' + highlighted + '</span></div>'
|
|
34961
34988
|
+ matchCtx
|
|
34962
34989
|
+ msgSnippet
|
|
34963
|
-
+ '<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>'
|
|
34964
34991
|
+ '</div>';
|
|
34965
34992
|
}).join('');
|
|
34966
34993
|
|
|
@@ -35441,11 +35468,11 @@ function onDataChanged(msg) {
|
|
|
35441
35468
|
refreshQpPromptsIfLoaded();
|
|
35442
35469
|
}
|
|
35443
35470
|
if (r === 'queue-draft') {
|
|
35444
|
-
|
|
35445
|
-
|
|
35446
|
-
|
|
35447
|
-
|
|
35448
|
-
|
|
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');
|
|
35449
35476
|
}
|
|
35450
35477
|
if (r === 'auto-approvals' || r === 'approval-rules' || r === 'approval-decisions') {
|
|
35451
35478
|
if (state.activeTab === 'permissions') loadAutoApprovals();
|
|
@@ -36006,10 +36033,14 @@ window.addEventListener('message', (e) => {
|
|
|
36006
36033
|
// 'review' is the canonical code-review route; 'codereview' is kept as a legacy alias.
|
|
36007
36034
|
// Both classify as nav panels (so initial-load session-restore suppression treats them
|
|
36008
36035
|
// like any other panel), but they dispatch to the same internal target ('codereview').
|
|
36009
|
-
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'];
|
|
36010
36037
|
// Hash nav targets that are aliases for a different internal navTo() id.
|
|
36011
36038
|
const NAV_TARGET_ALIASES = { review: 'codereview' };
|
|
36012
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
|
+
|
|
36013
36044
|
function _parseHashRoute() {
|
|
36014
36045
|
const hash = location.hash.slice(1);
|
|
36015
36046
|
const parts = hash ? hash.split('&') : [];
|
|
@@ -36065,8 +36096,12 @@ function handleHashRoute() {
|
|
|
36065
36096
|
const hash = route.hash;
|
|
36066
36097
|
const routeLease = _rootPrivateRuntime?.capture?.();
|
|
36067
36098
|
if (!_isRootPrivateLeaseCurrent(routeLease)) {
|
|
36068
|
-
|
|
36069
|
-
|
|
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 });
|
|
36070
36105
|
return;
|
|
36071
36106
|
}
|
|
36072
36107
|
const deferPromptRoute = (delay, action) => {
|
|
@@ -36128,6 +36163,9 @@ function handleHashRoute() {
|
|
|
36128
36163
|
return;
|
|
36129
36164
|
}
|
|
36130
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
|
+
|
|
36131
36169
|
// #setup&tab=access — open setup and let SETUP.init pick the inner tab.
|
|
36132
36170
|
if (isNav && firstPart === 'setup') {
|
|
36133
36171
|
navTo('setup', { skipHash: true });
|
|
@@ -36959,6 +36997,11 @@ function qpReconcileSubmissionMessage(message, options = {}) {
|
|
|
36959
36997
|
status: message.status,
|
|
36960
36998
|
retryable: message.retryable === true,
|
|
36961
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,
|
|
36962
37005
|
observedAt: message.observedAt || message.updated_at || message.updatedAt || Date.now(),
|
|
36963
37006
|
});
|
|
36964
37007
|
if (options.owner && !qpDraftLifetime.current(options.owner)) return false;
|
|
@@ -37506,6 +37549,9 @@ async function loadQpDraftForSession(sessionId) {
|
|
|
37506
37549
|
}
|
|
37507
37550
|
const loadState = qpDraftStateFor(sessionId, owner);
|
|
37508
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;
|
|
37509
37555
|
try {
|
|
37510
37556
|
const data = (await qpFetchDraftWithRetry(sessionId, owner)) || {};
|
|
37511
37557
|
if (!isQpDraftLoadCurrent(sessionId, seq, owner)) return;
|
|
@@ -37523,7 +37569,9 @@ async function loadQpDraftForSession(sessionId) {
|
|
|
37523
37569
|
loadState.failCount = 0;
|
|
37524
37570
|
loadState.lastError = '';
|
|
37525
37571
|
loadState.nextRetryAt = 0;
|
|
37526
|
-
|
|
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;
|
|
37527
37575
|
qpDraftFailureEpisodeActive = false;
|
|
37528
37576
|
clearDegradedIndicator('queue-draft');
|
|
37529
37577
|
clearToastByKey('queue-draft-load');
|
|
@@ -37546,10 +37594,11 @@ async function loadQpDraftForSession(sessionId) {
|
|
|
37546
37594
|
await _handleQpDraftLoadFailure(sessionId, seq, error, owner);
|
|
37547
37595
|
} finally {
|
|
37548
37596
|
// Safety net (WP-2a): guarantee the panel never strands on "Loading draft..."
|
|
37549
|
-
// even if something above throws unexpectedly.
|
|
37550
|
-
//
|
|
37551
|
-
//
|
|
37552
|
-
|
|
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) {
|
|
37553
37602
|
qpDraftLoading = false;
|
|
37554
37603
|
renderQpItems();
|
|
37555
37604
|
updateQpModeBtn();
|
|
@@ -37573,6 +37622,9 @@ async function qpScheduleDraftRevalidate(sessionId, reason = 'invalidation', ori
|
|
|
37573
37622
|
const loadState = qpDraftStateFor(sessionId, timerOwner);
|
|
37574
37623
|
if (!qpDraftLifetime.current(timerOwner)) return false;
|
|
37575
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;
|
|
37576
37628
|
if (sessionId !== qpCurrentSession || !qpQueuePanelVisible()) return false;
|
|
37577
37629
|
if (qpEditingIdx >= 0 || qpDraftSavePending(sessionId) || qpDraftLoading) {
|
|
37578
37630
|
qpDraftLifetime.defer(timerOwner, qpDeferredDraftReloads);
|
|
@@ -37623,7 +37675,6 @@ async function syncQpDraftWithServerQueue(sessionId, seq = qpDraftLoadSeq, owner
|
|
|
37623
37675
|
try {
|
|
37624
37676
|
const res = await qpDraftLifetime.request(owner, fetch, `/api/queues/${encodeURIComponent(sessionId)}`, {
|
|
37625
37677
|
headers: { 'Authorization': 'Bearer ' + owner?.token },
|
|
37626
|
-
signal: AbortSignal.timeout(4000),
|
|
37627
37678
|
});
|
|
37628
37679
|
if (!res || !isQpDraftLoadCurrent(sessionId, seq, owner)) return false;
|
|
37629
37680
|
if (res.status === 204) {
|
|
@@ -39047,6 +39098,9 @@ async function sendQueueFromPanel(actionOwner, viewOwner) {
|
|
|
39047
39098
|
if (ownsContext) { qpDraftLifetime.finish(actionOwner); qpDraftLifetime.finish(viewOwner); }
|
|
39048
39099
|
return;
|
|
39049
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; }
|
|
39050
39104
|
if (pendingItems.length === 1) {
|
|
39051
39105
|
try {
|
|
39052
39106
|
if (await qpMaybeRouteWorkflow(sessionId, pendingItems[0])) {
|
|
@@ -39154,6 +39208,13 @@ async function sendQueueFromPanel(actionOwner, viewOwner) {
|
|
|
39154
39208
|
try {
|
|
39155
39209
|
const session = actionOwner.session;
|
|
39156
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; }
|
|
39157
39218
|
const displayText = qpBuildDisplayText(item);
|
|
39158
39219
|
const images = (item && item.images) || [];
|
|
39159
39220
|
const wantsImagePaste = images.length > 0 && window.TerminalImageTransport.providerAcceptsImagePathPaste(
|
|
@@ -39189,9 +39250,15 @@ async function sendQueueFromPanel(actionOwner, viewOwner) {
|
|
|
39189
39250
|
clientMessageId: submissionId,
|
|
39190
39251
|
status: result?.status || 'failed',
|
|
39191
39252
|
retryable: result?.retryable === true,
|
|
39253
|
+
recoveryAction: result?.recoveryAction,
|
|
39254
|
+
reason: result?.reason,
|
|
39255
|
+
code: result?.code,
|
|
39192
39256
|
message: result?.message || '',
|
|
39193
39257
|
observedAt: Date.now(),
|
|
39194
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;
|
|
39195
39262
|
if (!result || result.status !== 'sent') {
|
|
39196
39263
|
manualSendSent = false;
|
|
39197
39264
|
const liveItem = canPublish() ? qpItems.find(candidate => candidate?.id === itemId
|
|
@@ -39203,12 +39270,16 @@ async function sendQueueFromPanel(actionOwner, viewOwner) {
|
|
|
39203
39270
|
outcome: result?.status || 'unresolved',
|
|
39204
39271
|
message: result?.message || '',
|
|
39205
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 } : {}),
|
|
39206
39277
|
};
|
|
39207
39278
|
}
|
|
39208
39279
|
}
|
|
39209
39280
|
const msg = result?.message || 'CTM could not verify that the prompt was accepted';
|
|
39210
39281
|
if (canPublish()) toast(
|
|
39211
|
-
result?.retryable ? 'Not sent yet — ' + msg + ' It stays queued.' : '
|
|
39282
|
+
result?.retryable ? 'Not sent yet — ' + msg + ' It stays queued.' : 'Prompt needs attention — ' + msg,
|
|
39212
39283
|
{ type: result?.retryable ? 'warning' : 'error', key: 'qp-send-failed:' + sessionId },
|
|
39213
39284
|
);
|
|
39214
39285
|
saveQpDraft(actionOwner);
|