create-walle 0.10.0 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -41
- package/lib/owner-write-safety.js +135 -0
- package/lib/public-runtime.js +13 -5
- package/lib/release-schema.json +3 -2
- package/lib/runtime-closure-policy.json +7 -5
- package/package.json +10 -4
- package/template/bin/deps-doctor.js +1 -1
- package/template/bin/node-bin.sh +11 -0
- package/template/coding-task-manager/api-prompts.js +10 -22
- package/template/coding-task-manager/bin/capture-codex-rollout-records.js +417 -0
- package/template/coding-task-manager/bin/census-codex-lead-history.js +325 -0
- package/template/coding-task-manager/bin/claude-hook.js +7 -3
- package/template/coding-task-manager/bin/claude-table-parity.js +307 -0
- package/template/coding-task-manager/bin/conversation-storage-auto-migration-lib.js +16 -1
- package/template/coding-task-manager/bin/conversation-storage-generation-evidence.js +9 -2
- package/template/coding-task-manager/bin/conversation-storage-service-entry-lib.js +16 -1
- package/template/coding-task-manager/bin/conversation-storage-walle-owner.js +247 -0
- package/template/coding-task-manager/bin/ctm-machine-pair.js +160 -0
- package/template/coding-task-manager/bin/lead-wait-census.js +1348 -0
- package/template/coding-task-manager/bin/repair-foreign-clone.js +564 -0
- package/template/coding-task-manager/bin/repair-resume-echo-titles.js +248 -0
- package/template/coding-task-manager/bin/seam-replay-codex.js +301 -0
- package/template/coding-task-manager/bin/write-lock-bench.js +111 -0
- package/template/coding-task-manager/db.js +705 -286
- package/template/coding-task-manager/git-utils.js +44 -0
- package/template/coding-task-manager/lib/adopted-host-redial.js +4 -0
- package/template/coding-task-manager/lib/agent-presets.js +15 -0
- package/template/coding-task-manager/lib/agent-title-apply.js +88 -0
- package/template/coding-task-manager/lib/agent-title-backfill-read.js +92 -0
- package/template/coding-task-manager/lib/api/host-stats.js +98 -0
- package/template/coding-task-manager/lib/api/ingress.js +35 -12
- package/template/coding-task-manager/lib/api/machine-pairing.js +285 -0
- package/template/coding-task-manager/lib/api/machine-pool.js +307 -0
- package/template/coding-task-manager/lib/api/machines.js +461 -0
- package/template/coding-task-manager/lib/api/misc-ops.js +20 -5
- package/template/coding-task-manager/lib/api/providers.js +53 -6
- package/template/coding-task-manager/lib/api/resume-from-store.js +25 -0
- package/template/coding-task-manager/lib/api/sessions.js +160 -8
- package/template/coding-task-manager/lib/api/transcript-history.js +17 -4
- package/template/coding-task-manager/lib/api/worktrees.js +38 -37
- package/template/coding-task-manager/lib/approval-drift.js +16 -1
- package/template/coding-task-manager/lib/approval-logical-frame.js +5 -1
- package/template/coding-task-manager/lib/approval-scope.js +68 -4
- package/template/coding-task-manager/lib/auth-context.js +32 -2
- package/template/coding-task-manager/lib/auth-rate-limit.js +41 -1
- package/template/coding-task-manager/lib/auth-rules.js +186 -45
- package/template/coding-task-manager/lib/broadcast-serialize.js +2 -1
- package/template/coding-task-manager/lib/browser-session-broker.js +9 -1
- package/template/coding-task-manager/lib/browser-use-adapter.js +33 -2
- package/template/coding-task-manager/lib/claude-markdown-ansi.js +27 -85
- package/template/coding-task-manager/lib/claude-session-title.js +6 -30
- package/template/coding-task-manager/lib/claude-table-layout.js +322 -0
- package/template/coding-task-manager/lib/client-diag-handler.js +25 -0
- package/template/coding-task-manager/lib/codex-config-guard.js +33 -3
- package/template/coding-task-manager/lib/codex-harness-qualification.js +12 -0
- package/template/coding-task-manager/lib/codex-history-terminal-renderer.js +33 -33
- package/template/coding-task-manager/lib/codex-launch-health.js +29 -2
- package/template/coding-task-manager/lib/codex-link-preparation.js +9 -8
- package/template/coding-task-manager/lib/codex-local-links.js +267 -0
- package/template/coding-task-manager/lib/codex-markdown-lists.js +160 -0
- package/template/coding-task-manager/lib/codex-native-history-prefix.js +4 -0
- package/template/coding-task-manager/lib/codex-paths.js +2 -1
- package/template/coding-task-manager/lib/codex-source-handoff.js +186 -0
- package/template/coding-task-manager/lib/coding-agent-models.js +21 -30
- package/template/coding-task-manager/lib/coding-session-model-adapters.js +27 -2
- package/template/coding-task-manager/lib/coding-session-model-projection.js +5 -0
- package/template/coding-task-manager/lib/coding-session-model-runtime.js +1 -2
- package/template/coding-task-manager/lib/coding-session-model-state.js +10 -0
- package/template/coding-task-manager/lib/coding-session-model-sync-state.js +0 -1
- package/template/coding-task-manager/lib/conversation-import/cursor-guard-owner.js +10 -0
- package/template/coding-task-manager/lib/create/resolve-launch-params.js +8 -1
- package/template/coding-task-manager/lib/ctm-boot-lifecycle.js +17 -1
- package/template/coding-task-manager/lib/db-domain-tables.js +7 -0
- package/template/coding-task-manager/lib/db-domains.js +21 -2
- package/template/coding-task-manager/lib/db-owner-op-registry.js +7 -0
- package/template/coding-task-manager/lib/db-owner-worker-client.js +22 -0
- package/template/coding-task-manager/lib/db-schema-source-files.js +1 -0
- package/template/coding-task-manager/lib/feature-manifest.js +1 -3
- package/template/coding-task-manager/lib/fs-watcher.js +3 -2
- package/template/coding-task-manager/lib/harness-history-transition.js +41 -14
- package/template/coding-task-manager/lib/headless-inflight-ops.js +203 -0
- package/template/coding-task-manager/lib/headless-missing-reply.js +42 -0
- package/template/coding-task-manager/lib/headless-term-service.js +60 -23
- package/template/coding-task-manager/lib/history-transition-read-op.js +179 -23
- package/template/coding-task-manager/lib/host-control-dial.js +167 -0
- package/template/coding-task-manager/lib/host-handoff-orchestrator.js +16 -2
- package/template/coding-task-manager/lib/host-handoff.js +51 -13
- package/template/coding-task-manager/lib/host-health-ledger.js +164 -12
- package/template/coding-task-manager/lib/host-self-truth-watchdog.js +4 -0
- package/template/coding-task-manager/lib/host-stats.js +328 -0
- package/template/coding-task-manager/lib/host-threadpool-evacuation.js +160 -0
- package/template/coding-task-manager/lib/host-threadpool-monitor.js +14 -1
- package/template/coding-task-manager/lib/hosted-input-dispatch.js +203 -0
- package/template/coding-task-manager/lib/hosted-sync-worker.js +113 -5
- package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +4 -0
- package/template/coding-task-manager/lib/instance-manifest-runtime.js +3 -0
- package/template/coding-task-manager/lib/instance-manifest.js +13 -1
- package/template/coding-task-manager/lib/instance-sync-status.js +15 -0
- package/template/coding-task-manager/lib/kernel/diag-log.js +12 -0
- package/template/coding-task-manager/lib/launch-model-override.js +160 -1
- package/template/coding-task-manager/lib/launch-plan-worker.js +59 -2
- package/template/coding-task-manager/lib/lead-model-observer.js +17 -2
- package/template/coding-task-manager/lib/lead-model-switch-runtime.js +57 -4
- package/template/coding-task-manager/lib/lead-model-switch.js +11 -2
- package/template/coding-task-manager/lib/lead-model-transition-runtime.js +42 -4
- package/template/coding-task-manager/lib/lead-model-transition-store.js +61 -3
- package/template/coding-task-manager/lib/legacy-draft-cancellation-recovery.js +111 -0
- package/template/coding-task-manager/lib/login-tripwire.js +18 -0
- package/template/coding-task-manager/lib/machine-pairing.js +238 -0
- package/template/coding-task-manager/lib/machine-pool.js +409 -0
- package/template/coding-task-manager/lib/machine-registry.js +749 -0
- package/template/coding-task-manager/lib/machine-self.js +66 -0
- package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +56 -4
- package/template/coding-task-manager/lib/media-journal-control-reader.js +473 -0
- package/template/coding-task-manager/lib/media-journal.js +84 -27
- package/template/coding-task-manager/lib/media-store-options.js +43 -15
- package/template/coding-task-manager/lib/migrations/v56-host-samples-and-machine-pool.js +76 -0
- package/template/coding-task-manager/lib/mirror-feed-sanitize.js +12 -3
- package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +16 -3
- package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
- package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
- package/template/coding-task-manager/lib/model-proxy/collaboration-codec.js +94 -0
- package/template/coding-task-manager/lib/model-proxy/handoff.js +494 -0
- package/template/coding-task-manager/lib/model-proxy/lead-worker-picks.js +25 -0
- package/template/coding-task-manager/lib/model-proxy/native-lead-listener.js +61 -7
- package/template/coding-task-manager/lib/model-proxy/proxy-cli.js +200 -0
- package/template/coding-task-manager/lib/model-proxy/proxy-instance-record.js +257 -0
- package/template/coding-task-manager/lib/model-proxy/responses-face.js +24 -2
- package/template/coding-task-manager/lib/model-proxy/stream-accounting.js +205 -0
- package/template/coding-task-manager/lib/model-proxy/tokens.js +78 -0
- package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
- package/template/coding-task-manager/lib/native-history-control.js +62 -14
- package/template/coding-task-manager/lib/native-lead-settings-client.js +2 -1
- package/template/coding-task-manager/lib/native-lead-tls.js +18 -4
- package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +628 -64
- package/template/coding-task-manager/lib/owner-action-manifest-data.json +14 -0
- package/template/coding-task-manager/lib/painted-row-recorder.js +9 -1
- package/template/coding-task-manager/lib/prompt-admission-trace.js +69 -0
- package/template/coding-task-manager/lib/prompt-submission-contract.js +21 -1
- package/template/coding-task-manager/lib/provider-login-broker.js +192 -9
- package/template/coding-task-manager/lib/provider-login-capture.js +153 -0
- package/template/coding-task-manager/lib/queue-draft-status-overlay.js +36 -0
- package/template/coding-task-manager/lib/raw-codex-history-window.js +5 -3
- package/template/coding-task-manager/lib/raw-history-block.js +3 -1
- package/template/coding-task-manager/lib/read-pool-client.js +36 -24
- package/template/coding-task-manager/lib/read-pool-op-registry.js +10 -1
- package/template/coding-task-manager/lib/read-pool-telemetry.js +14 -5
- package/template/coding-task-manager/lib/recent-catalog-query.js +26 -0
- package/template/coding-task-manager/lib/recent-session-catalog-response.js +9 -0
- package/template/coding-task-manager/lib/remote-command-context.js +12 -0
- package/template/coding-task-manager/lib/remote-command-runtime.js +18 -5
- package/template/coding-task-manager/lib/remote-machine-catalog.js +1062 -0
- package/template/coding-task-manager/lib/remote-machine-create.js +693 -0
- package/template/coding-task-manager/lib/remote-machine-link.js +939 -0
- package/template/coding-task-manager/lib/remote-machine-proxy.js +520 -0
- package/template/coding-task-manager/lib/remote-session-forward.js +306 -0
- package/template/coding-task-manager/lib/remote-submission-status.js +6 -0
- package/template/coding-task-manager/lib/restart-outcome-truth.js +20 -3
- package/template/coding-task-manager/lib/restart-preflight.js +9 -2
- package/template/coding-task-manager/lib/restore-backoff-gate.js +10 -2
- package/template/coding-task-manager/lib/restore-prepare-retry.js +12 -5
- package/template/coding-task-manager/lib/restore-resume-batch.js +22 -1
- package/template/coding-task-manager/lib/resume-blocked-wire.js +62 -1
- package/template/coding-task-manager/lib/resume-cwd.js +13 -0
- package/template/coding-task-manager/lib/resume-from-store.js +12 -1
- package/template/coding-task-manager/lib/resume-origin-facts.js +38 -1
- package/template/coding-task-manager/lib/resume-preflight.js +21 -16
- package/template/coding-task-manager/lib/review-repo-admission.js +39 -6
- package/template/coding-task-manager/lib/review-repo-registry.js +17 -0
- package/template/coding-task-manager/lib/root-delivery-runtime.js +72 -15
- package/template/coding-task-manager/lib/root-delivery-selection.js +37 -1
- package/template/coding-task-manager/lib/root-prompt-admission.js +43 -11
- package/template/coding-task-manager/lib/root-prompt-flow.js +175 -38
- package/template/coding-task-manager/lib/scrollback-archive-store.js +7 -4
- package/template/coding-task-manager/lib/server-memory-sampler-runtime.js +10 -0
- package/template/coding-task-manager/lib/server-memory-sampler.js +41 -2
- package/template/coding-task-manager/lib/session-analysis-memory.js +2 -0
- package/template/coding-task-manager/lib/session-create/codex-native-provider-gate.js +218 -0
- package/template/coding-task-manager/lib/session-create/codex-resume-exit-classify.js +52 -0
- package/template/coding-task-manager/lib/session-create/delegate-worker.js +95 -6
- package/template/coding-task-manager/lib/session-create/identity.js +20 -1
- package/template/coding-task-manager/lib/session-create/prepared.js +69 -1
- package/template/coding-task-manager/lib/session-create/provider-launch-context.js +77 -0
- package/template/coding-task-manager/lib/session-create/provider-process.js +38 -39
- package/template/coding-task-manager/lib/session-create/response.js +30 -12
- package/template/coding-task-manager/lib/session-create/transcript.js +13 -4
- package/template/coding-task-manager/lib/session-create/worker-continuation.js +43 -1
- package/template/coding-task-manager/lib/session-create/worker-notices.js +94 -5
- package/template/coding-task-manager/lib/session-create/worker-observability.js +99 -7
- package/template/coding-task-manager/lib/session-create/worker-stop.js +11 -3
- package/template/coding-task-manager/lib/session-create/worker-wait-watcher.js +315 -0
- package/template/coding-task-manager/lib/session-geometry-authority-handler.js +10 -1
- package/template/coding-task-manager/lib/session-host-manager.js +152 -206
- package/template/coding-task-manager/lib/session-jobs.js +17 -9
- package/template/coding-task-manager/lib/session-lifecycle.js +20 -50
- package/template/coding-task-manager/lib/session-model-action-preconditions.js +68 -3
- package/template/coding-task-manager/lib/session-resource-sampler.js +67 -9
- package/template/coding-task-manager/lib/session-standup.js +67 -4
- package/template/coding-task-manager/lib/session-status-ledger.js +42 -3
- package/template/coding-task-manager/lib/session-status-runtime.js +45 -1
- package/template/coding-task-manager/lib/session-status.js +2 -2
- package/template/coding-task-manager/lib/session-title-mirror.js +25 -57
- package/template/coding-task-manager/lib/session-transcript-trash.js +8 -1
- package/template/coding-task-manager/lib/startup-pointer-write.js +57 -0
- package/template/coding-task-manager/lib/state-sync/painted-frame-field.js +11 -0
- package/template/coding-task-manager/lib/storage/coding-session-model-store.js +21 -22
- package/template/coding-task-manager/lib/surviving-owner-bootstrap.js +7 -2
- package/template/coding-task-manager/lib/surviving-owner-recovery.js +2 -1
- package/template/coding-task-manager/lib/terminal-draft-http-status.js +25 -0
- package/template/coding-task-manager/lib/transcript-archiver.js +229 -229
- package/template/coding-task-manager/lib/transcript-frame-cache.js +12 -2
- package/template/coding-task-manager/lib/transcript-guard-job.js +55 -4
- package/template/coding-task-manager/lib/transcript-link.js +77 -0
- package/template/coding-task-manager/lib/transcript-manifest-store.js +8 -4
- package/template/coding-task-manager/lib/transcript-source.js +8 -3
- package/template/coding-task-manager/lib/transcript-store.js +40 -0
- package/template/coding-task-manager/lib/tunnel-lane-governor.js +6 -3
- package/template/coding-task-manager/lib/typing-interaction-trace.js +136 -2
- package/template/coding-task-manager/lib/typing-worker-stages.js +208 -1
- package/template/coding-task-manager/lib/usage-ledger/claude-parse.js +53 -2
- package/template/coding-task-manager/lib/usage-ledger/ingest.js +13 -4
- package/template/coding-task-manager/lib/usage-ledger/read.js +14 -0
- package/template/coding-task-manager/lib/usage-ledger/schema.js +49 -1
- package/template/coding-task-manager/lib/usage-ledger/store.js +337 -6
- package/template/coding-task-manager/lib/usage-ledger/task-read.js +123 -5
- package/template/coding-task-manager/lib/visible-revision-watchdog.js +8 -12
- package/template/coding-task-manager/lib/wake-skip-reasons.js +46 -0
- package/template/coding-task-manager/lib/walle-supervisor.js +98 -14
- package/template/coding-task-manager/lib/worker-failure-classify.js +13 -1
- package/template/coding-task-manager/lib/ws/input.js +1 -0
- package/template/coding-task-manager/lib/ws/state-sync.js +19 -2
- package/template/coding-task-manager/lib/ws/terminal-attach.js +32 -1
- package/template/coding-task-manager/lib/ws-router.js +68 -1
- package/template/coding-task-manager/lib/ws-send-admission.js +22 -4
- package/template/coding-task-manager/oauth-proxy.js +99 -55
- package/template/coding-task-manager/package-lock.json +24 -0
- package/template/coding-task-manager/package.json +3 -0
- package/template/coding-task-manager/protocol/message-table.json +0 -10
- package/template/coding-task-manager/protocol/messages.js +0 -10
- package/template/coding-task-manager/protocol/ws-schema.json +0 -20
- package/template/coding-task-manager/protocol/ws-schema.md +0 -2
- package/template/coding-task-manager/providers/codex-mcp-extract.js +121 -19
- package/template/coding-task-manager/providers/codex-mcp.js +14 -5
- package/template/coding-task-manager/public/css/app.css +279 -2
- package/template/coding-task-manager/public/css/reviews.css +4 -0
- package/template/coding-task-manager/public/index.html +297 -240
- package/template/coding-task-manager/public/js/app/machines-page.js +1252 -0
- package/template/coding-task-manager/public/js/app/models-tab.js +546 -36
- package/template/coding-task-manager/public/js/app/worktrees-panel.js +7 -5
- package/template/coding-task-manager/public/js/background-work.js +13 -7
- package/template/coding-task-manager/public/js/client-health-core.js +81 -0
- package/template/coding-task-manager/public/js/client-health-main.js +61 -0
- package/template/coding-task-manager/public/js/coding-session-model-presentation.js +44 -4
- package/template/coding-task-manager/public/js/core/session-commands.js +676 -0
- package/template/coding-task-manager/public/js/core/session-selectors.js +5 -1
- package/template/coding-task-manager/public/js/history-app-seam.js +26 -2
- package/template/coding-task-manager/public/js/history-painted-rows.js +17 -2
- package/template/coding-task-manager/public/js/history-reader-park.js +13 -1
- package/template/coding-task-manager/public/js/history-scroller.js +187 -9
- package/template/coding-task-manager/public/js/history-transcript-pages.js +5 -5
- package/template/coding-task-manager/public/js/machine-pool-shared.js +436 -0
- package/template/coding-task-manager/public/js/private-state-scope.js +31 -3
- package/template/coding-task-manager/public/js/prompt-submission-client.js +173 -4
- package/template/coding-task-manager/public/js/prompt-submission-tray.js +281 -0
- package/template/coding-task-manager/public/js/protocol.js +0 -10
- package/template/coding-task-manager/public/js/queue-panel-submission-reconcile.js +174 -1
- package/template/coding-task-manager/public/js/reviews.js +7 -3
- package/template/coding-task-manager/public/js/root-draft-lifetime.js +139 -2
- package/template/coding-task-manager/public/js/root-queue-builder-lifetime.js +16 -0
- package/template/coding-task-manager/public/js/row-join.js +18 -3
- package/template/coding-task-manager/public/js/session-model-picker.js +1 -1
- package/template/coding-task-manager/public/js/session-search-utils.js +46 -19
- package/template/coding-task-manager/public/js/session-usage-view.js +1 -0
- package/template/coding-task-manager/public/js/state-sync-client.js +4 -0
- package/template/coding-task-manager/public/js/terminal/runtime-02.js +16 -7
- package/template/coding-task-manager/public/js/terminal/runtime-03.js +41 -0
- package/template/coding-task-manager/public/js/terminal/runtime-04.js +41 -4
- package/template/coding-task-manager/public/js/terminal/runtime-12.js +52 -2
- package/template/coding-task-manager/public/js/terminal/runtime-16.js +12 -12
- package/template/coding-task-manager/public/js/terminal/runtime-18.js +14 -0
- package/template/coding-task-manager/public/js/terminal/runtime-19.js +8 -4
- package/template/coding-task-manager/public/js/terminal/runtime-20.js +210 -73
- package/template/coding-task-manager/public/js/terminal/runtime-21.js +19 -6
- package/template/coding-task-manager/public/js/terminal/runtime-22.js +86 -0
- package/template/coding-task-manager/public/js/terminal-history-viewport.js +1 -3
- package/template/coding-task-manager/public/js/terminal-wheel.js +39 -2
- package/template/coding-task-manager/public/js/walle/chat-conversation.js +11 -3
- package/template/coding-task-manager/public/js/walle-session.js +30 -7
- package/template/coding-task-manager/public/m/app.css +226 -8
- package/template/coding-task-manager/public/m/app.js +502 -9
- package/template/coding-task-manager/public/m/index.html +38 -1
- package/template/coding-task-manager/public/m/machines.js +1313 -0
- package/template/coding-task-manager/public/m/pool-failover.js +391 -0
- package/template/coding-task-manager/public/m/private-queue-actions.js +26 -7
- package/template/coding-task-manager/public/m/sw.js +5 -0
- package/template/coding-task-manager/queue-engine.js +13 -0
- package/template/coding-task-manager/server/approvals.js +5 -6
- package/template/coding-task-manager/server/boot.js +136 -17
- package/template/coding-task-manager/server.js +964 -425
- package/template/coding-task-manager/session-integrity.js +7 -9
- package/template/coding-task-manager/workers/db-owner-worker.js +53 -7
- package/template/coding-task-manager/workers/read-pool-worker.js +17 -6
- package/template/coding-task-manager/workers/session-host-pool-process.js +18 -4
- package/template/coding-task-manager/workers/session-host-process.js +14 -3
- package/template/coding-task-manager/workers/transcript-archive-worker.js +1 -1
- package/template/coding-task-manager/workers/worktree-worker.js +34 -0
- package/template/package-lock.json +2 -2
- package/template/package.json +1 -1
- package/template/scripts/conversation-storage-v2-migration.js +78 -11
- package/template/shared/conversation-storage-v2-contract.js +42 -6
- package/template/shared/fixtures/worker-status-payloads.json +392 -0
- package/template/shared/git-remote-key.js +83 -0
- package/template/shared/host-identity.js +98 -2
- package/template/shared/owner-write-safety.js +135 -0
- package/template/shared/public-runtime.js +13 -5
- package/template/shared/release-schema.json +3 -2
- package/template/shared/report-citations.js +14 -0
- package/template/shared/sqlite-write-lock.js +361 -223
- package/template/shared/storage/contract.js +35 -3
- package/template/wall-e/agent.js +21 -0
- package/template/wall-e/api-walle.js +5 -0
- package/template/wall-e/bin/brain-purge-legacy-ctm.js +3 -2
- package/template/wall-e/bin/walle-mcp-stdio.js +12 -6
- package/template/wall-e/brain/helper-rules-schema.js +129 -0
- package/template/wall-e/brain/migrations.js +51 -22
- package/template/wall-e/brain/model-deployment-facts.js +31 -1
- package/template/wall-e/brain/owner-call-whitelist.js +16 -0
- package/template/wall-e/brain/schema-ensures.js +22 -0
- package/template/wall-e/brain.js +1 -1
- package/template/wall-e/coding/host-capabilities.js +2 -2
- package/template/wall-e/coding/journal-dispatcher.js +51 -0
- package/template/wall-e/coding/session-journal.js +28 -40
- package/template/wall-e/coding-orchestrator.js +23 -26
- package/template/wall-e/fly.toml +1 -1
- package/template/wall-e/http/auth.js +18 -1
- package/template/wall-e/http/blog-views-api.js +318 -0
- package/template/wall-e/http/helper-rules-api.js +321 -0
- package/template/wall-e/http/model-proxy-providers.js +3 -1
- package/template/wall-e/http/telemetry-api.js +12 -0
- package/template/wall-e/lib/blog-view-policy.js +88 -0
- package/template/wall-e/lib/blog-view-store.js +169 -0
- package/template/wall-e/lib/client-config-lock.js +2 -1
- package/template/wall-e/lib/coding-journal-writer-child-client.js +67 -18
- package/template/wall-e/lib/error-kinds.js +10 -1
- package/template/wall-e/lib/feature-manifest.js +1 -1
- package/template/wall-e/lib/helper-rules/cell-state.js +123 -0
- package/template/wall-e/lib/helper-rules/evaluate.js +357 -0
- package/template/wall-e/lib/helper-rules/explore-rubric.md +12 -0
- package/template/wall-e/lib/helper-rules/grade.js +277 -0
- package/template/wall-e/lib/helper-rules/label.js +247 -0
- package/template/wall-e/lib/helper-rules/lead-call-hook.js +522 -0
- package/template/wall-e/lib/helper-rules/store.js +667 -0
- package/template/wall-e/lib/mcp-integration.js +5 -6
- package/template/wall-e/lib/runtime-worker-pool.js +1 -0
- package/template/wall-e/lib/scheduler-worker-jobs.js +6 -0
- package/template/wall-e/lib/telemetry-db-schema.js +37 -0
- package/template/wall-e/lib/telemetry-process-client.js +76 -1
- package/template/wall-e/llm/default-fallback.js +4 -0
- package/template/wall-e/llm/model-catalog.js +13 -4
- package/template/wall-e/llm/model-roles.js +6 -0
- package/template/wall-e/llm/portkey-http.js +150 -0
- package/template/wall-e/llm/portkey-sync.js +15 -145
- package/template/wall-e/llm/worker-mcp-routing.js +61 -0
- package/template/wall-e/llm/worker-route.js +43 -1
- package/template/wall-e/loops/brain-custom-writes.js +45 -0
- package/template/wall-e/mcp-protocol-contract.js +41 -1
- package/template/wall-e/mcp-server.js +13 -2
- package/template/wall-e/mcp-worker-schemas.js +20 -2
- package/template/wall-e/memory/ctm-context-client.js +25 -5
- package/template/wall-e/shared/release-schema.json +3 -2
- package/template/wall-e/shared/sqlite-write-lock.js +361 -223
- package/template/wall-e/shared/storage/contract.js +35 -3
- package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
- package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +6 -0
- package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +7 -2
- package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +7 -2
- package/template/wall-e/tools/local-tools.js +12 -5
- package/template/wall-e/workers/brain-owner-worker.js +9 -0
- package/template/wall-e/workers/coding-journal-writer-child.js +8 -1
- package/template/wall-e/workers/telemetry-worker.js +8 -0
- package/template/coding-task-manager/lib/giant-freeze-park.js +0 -31
|
@@ -142,6 +142,106 @@ function createSessionModelControl(sessionId) {
|
|
|
142
142
|
return control;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
// ── WP-5 machine chip (docs/remote-machine-hub-sessions-2026-10-22.html §6.6, §8 S-14/S-15) ──
|
|
146
|
+
// The branch badge's sibling: ONE presenter, rendered from the session row's own facts
|
|
147
|
+
// (`machine` / `machineName` / `machineHealth`, served by the hub) and looked up GLOBALLY by
|
|
148
|
+
// data-session-id so every refresh path keeps working after the toolbar reparent (§6.6,
|
|
149
|
+
// S-15 — the same rule as _findSessionModelChip). A local session (`machine === ''`) gets NO
|
|
150
|
+
// chip at all, so local rows render exactly as they did before this work (AC-5b).
|
|
151
|
+
const MACHINE_HEALTH_UNREACHABLE = 'unreachable';
|
|
152
|
+
// Card states that still render in the new-session grid: reachable is selectable, the other two
|
|
153
|
+
// render DISABLED with their reason. `unreachable` machines are ABSENT from the grid (AC-5a).
|
|
154
|
+
const MACHINE_CARD_STATES = ['reachable', 'unknown', 'unhealthy'];
|
|
155
|
+
|
|
156
|
+
// Machine facts off either a `sessions`-frame row or a client session object (`s.meta`).
|
|
157
|
+
function sessionMachineFacts(row) {
|
|
158
|
+
const meta = (row && row.meta) || row || {};
|
|
159
|
+
return {
|
|
160
|
+
machine: String(meta.machine || '').trim(),
|
|
161
|
+
name: String(meta.machineName || meta.machine_name || '').trim(),
|
|
162
|
+
health: String(meta.machineHealth || meta.machine_health || '').trim(),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Pure presentation: null for a local session, else the label/title/health the chip renders.
|
|
167
|
+
function machineChipPresentation(row) {
|
|
168
|
+
const facts = sessionMachineFacts(row);
|
|
169
|
+
if (!facts.machine) return null;
|
|
170
|
+
const name = facts.name || facts.machine;
|
|
171
|
+
const unreachable = facts.health === MACHINE_HEALTH_UNREACHABLE;
|
|
172
|
+
const title = unreachable
|
|
173
|
+
? `Runs on ${name} — unreachable (showing its last known state)`
|
|
174
|
+
: `Runs on ${name}`;
|
|
175
|
+
return { name, health: facts.health, unreachable, title, label: (unreachable ? '⚠ ' : '◈ ') + name };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// HTML twin, for the row templates that build markup as strings.
|
|
179
|
+
function machineChipHtml(row) {
|
|
180
|
+
const view = machineChipPresentation(row);
|
|
181
|
+
if (!view) return '';
|
|
182
|
+
const classes = 'machine-chip' + (view.unreachable ? ' unreachable' : '');
|
|
183
|
+
const id = _machineRowId(row);
|
|
184
|
+
return `<span class="${classes}" title="${escHtml(view.title)}" aria-label="${escHtml(view.title)}"`
|
|
185
|
+
+ (id ? ` data-session-id="${escHtml(id)}"` : '')
|
|
186
|
+
+ ` data-machine-name="${escHtml(view.name)}" data-machine-health="${escHtml(view.health)}">${escHtml(view.label)}</span>`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Node twin, for the tab chrome (built with createElement, like the branch badge).
|
|
190
|
+
function machineSessionChipNode(row, sessionId) {
|
|
191
|
+
const view = machineChipPresentation(row);
|
|
192
|
+
if (!view) return null;
|
|
193
|
+
const chip = document.createElement('span');
|
|
194
|
+
chip.className = 'machine-chip' + (view.unreachable ? ' unreachable' : '');
|
|
195
|
+
chip.textContent = view.label;
|
|
196
|
+
chip.title = view.title;
|
|
197
|
+
chip.setAttribute('aria-label', view.title);
|
|
198
|
+
chip.dataset.machineName = view.name;
|
|
199
|
+
chip.dataset.machineHealth = view.health;
|
|
200
|
+
const id = String(sessionId || _machineRowId(row) || '');
|
|
201
|
+
if (id) chip.dataset.sessionId = id;
|
|
202
|
+
return chip;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function _machineRowId(row) {
|
|
206
|
+
if (!row) return '';
|
|
207
|
+
const meta = row.meta || {};
|
|
208
|
+
return String(row._id || row.sessionId || row.session_id || row.id || meta.id || meta.sessionId || '');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// The global lookup by data-session-id (same rule as the model chip): an active session's toolbar
|
|
212
|
+
// is reparented into #tabbar-session-controls, so its container no longer holds the chip.
|
|
213
|
+
function _findSessionMachineChip(sessionId) {
|
|
214
|
+
if (!sessionId) return null;
|
|
215
|
+
const escaped = (window.CSS && CSS.escape) ? CSS.escape(String(sessionId)) : String(sessionId);
|
|
216
|
+
return document.querySelector('.machine-chip[data-session-id="' + escaped + '"]');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Repaint an already-mounted chip from fresh facts (health flips reachable -> unreachable without
|
|
220
|
+
// a tab rebuild, since the tab unit's structural hash does not carry machine health).
|
|
221
|
+
function refreshSessionMachineChip(row) {
|
|
222
|
+
const view = machineChipPresentation(row);
|
|
223
|
+
if (!view) return false;
|
|
224
|
+
const chip = _findSessionMachineChip(_machineRowId(row));
|
|
225
|
+
if (!chip) return false;
|
|
226
|
+
chip.textContent = view.label;
|
|
227
|
+
chip.title = view.title;
|
|
228
|
+
chip.setAttribute('aria-label', view.title);
|
|
229
|
+
chip.dataset.machineName = view.name;
|
|
230
|
+
chip.dataset.machineHealth = view.health;
|
|
231
|
+
chip.classList.toggle('unreachable', view.unreachable);
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function refreshSessionMachineChips(rows) {
|
|
236
|
+
if (!Array.isArray(rows)) return 0;
|
|
237
|
+
let changed = 0;
|
|
238
|
+
for (const row of rows) {
|
|
239
|
+
if (!sessionMachineFacts(row).machine) continue;
|
|
240
|
+
if (refreshSessionMachineChip(row)) changed += 1;
|
|
241
|
+
}
|
|
242
|
+
return changed;
|
|
243
|
+
}
|
|
244
|
+
|
|
145
245
|
function createSessionsOverviewTab() {
|
|
146
246
|
const tab = document.createElement('div');
|
|
147
247
|
tab.className = `tab pinned-tab sessions-overview-tab ${isSessionsOverviewActive() ? 'active' : ''}`;
|
|
@@ -267,6 +367,11 @@ function _createStreamChromeSessionTab(unit) {
|
|
|
267
367
|
: null;
|
|
268
368
|
if (trackingBadge) tab.appendChild(trackingBadge);
|
|
269
369
|
|
|
370
|
+
// WP-5 (S-15/AC-5b): the machine chip sits beside the branch badge, built by the same
|
|
371
|
+
// presenter the sidebar rows use. Local sessions produce null and the tab is unchanged.
|
|
372
|
+
const machineChip = machineSessionChipNode(unit.session, id);
|
|
373
|
+
if (machineChip) tab.appendChild(machineChip);
|
|
374
|
+
|
|
270
375
|
const lifecycleTemplate = document.createElement('template');
|
|
271
376
|
lifecycleTemplate.innerHTML = lifecycleHtml;
|
|
272
377
|
const lifecycleChip = lifecycleTemplate.content.firstElementChild;
|
|
@@ -479,6 +584,9 @@ function createSession() {
|
|
|
479
584
|
var cwd = document.getElementById('ns-cwd').value || undefined;
|
|
480
585
|
var label = document.getElementById('ns-label').value;
|
|
481
586
|
var useWorktree = document.getElementById('ns-worktree').checked;
|
|
587
|
+
// WP-5 (S-12/§6.3): the selected machine travels with the create frame as `machine`; '' is this
|
|
588
|
+
// machine and the field is omitted, so the local create frame is byte-identical to today.
|
|
589
|
+
var machine = machineSelection();
|
|
482
590
|
// WP-2 (design doc agent-context-budget-claude-codex-2026-09-26.html §5 C-1, §8 WP-2):
|
|
483
591
|
// send the selected MCP profile for every agent (the server only applies it to
|
|
484
592
|
// Claude/Codex, simpler than special-casing here) and remember it per project.
|
|
@@ -497,6 +605,7 @@ function createSession() {
|
|
|
497
605
|
|
|
498
606
|
function _doCreate(finalCwd) {
|
|
499
607
|
var msg = { type: 'create', cwd: finalCwd, label: label || undefined, mcp_profile: mcpProfile };
|
|
608
|
+
if (machine) msg.machine = machine;
|
|
500
609
|
if (wantCreateCwd && !useWorktree && finalCwd) msg.createCwd = true;
|
|
501
610
|
// Spawn the PTY at the terminal's real size so the agent's first paint
|
|
502
611
|
// matches the final height (no 120x30→fit reflow that strands Codex's cursor).
|
|
@@ -538,6 +647,10 @@ function createSession() {
|
|
|
538
647
|
}
|
|
539
648
|
var createBtn = document.querySelector('#new-session-modal .btn.primary');
|
|
540
649
|
if (createBtn) { createBtn.disabled = true; createBtn.textContent = 'Creating worktree...'; }
|
|
650
|
+
// WP-5 (S-12/§6.5): the worktree is created ON the selected machine, so `cwd` is the
|
|
651
|
+
// box-local path the picker returned and `machine` routes the request (the hub proxies
|
|
652
|
+
// POST /api/worktrees/create?machine=<id>). No local source field is ever named here —
|
|
653
|
+
// the box refuses `machine_source_mixed` if one is.
|
|
541
654
|
_wtCreateWithProgress({
|
|
542
655
|
name: wtName,
|
|
543
656
|
cwd: cwd,
|
|
@@ -548,6 +661,7 @@ function createSession() {
|
|
|
548
661
|
branch: wtName,
|
|
549
662
|
subtitle: 'CTM is creating the isolated checkout before launching the session.',
|
|
550
663
|
showAfterMs: 900,
|
|
664
|
+
machine: machine,
|
|
551
665
|
})
|
|
552
666
|
.then(function(d) {
|
|
553
667
|
if (d.error) {
|
|
@@ -922,3 +1036,565 @@ function closeSessionPalette() {
|
|
|
922
1036
|
_sessionPaletteIdx = 0;
|
|
923
1037
|
_sessionPaletteItems = [];
|
|
924
1038
|
}
|
|
1039
|
+
|
|
1040
|
+
// ─────────────────────────────────────────────────────────────────────────────────────────────
|
|
1041
|
+
// WP-5 machine surfaces (docs/remote-machine-hub-sessions-2026-10-22.html §6.5, §6.6, D-11)
|
|
1042
|
+
// S-11/S-13/AC-5a new-session machine cards (agent-grid pattern) + per-machine path truth
|
|
1043
|
+
// §6.5 box picker routing (every request carries ?machine=<id>) and the
|
|
1044
|
+
// same-repo prefill — plain gitRemoteKey equality, never client-side
|
|
1045
|
+
// normalization (the server normalizes both ends)
|
|
1046
|
+
// Settings register / list / remove machines (loopback-only writable, D-11)
|
|
1047
|
+
// No feature flag: with zero machines the card grids do not render and the modal is identical
|
|
1048
|
+
// to its pre-hub self (AC-5a).
|
|
1049
|
+
// ─────────────────────────────────────────────────────────────────────────────────────────────
|
|
1050
|
+
|
|
1051
|
+
// `''` is this machine — the default, and the only selection when the hub has no machines.
|
|
1052
|
+
// `renderedAt` is the clock the last card render judged D-12 staleness against, so a re-render
|
|
1053
|
+
// (a popover pick) cannot move the numbers under the user; `pickerMachine` is the D-1 row picked
|
|
1054
|
+
// from the popover, kept for THIS modal session only.
|
|
1055
|
+
const _nsMachineState = {
|
|
1056
|
+
machines: [], localRepoKeyPath: null, localKeysRequested: false, localCwd: '',
|
|
1057
|
+
renderedAt: 0, pickerMachine: '',
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
function machineSelection() {
|
|
1061
|
+
const el = document.getElementById('ns-machine');
|
|
1062
|
+
return el ? String(el.value || '') : '';
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
function machineSelectionName() {
|
|
1066
|
+
const id = machineSelection();
|
|
1067
|
+
if (!id) return 'This Mac';
|
|
1068
|
+
const machine = _nsMachineState.machines.find(m => m && m.instanceId === id);
|
|
1069
|
+
return (machine && (machine.displayName || machine.url)) || id;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// WP-4 (docs/dgx-spark-coding-readiness-2026-10-24.html): a box can be REACHABLE and still have a
|
|
1073
|
+
// coding CLI that is signed out, so a create would open at a login prompt. The hub refuses that
|
|
1074
|
+
// create on its own (lib/remote-machine-create.js → machine_agent_signed_out), so the card only
|
|
1075
|
+
// has to SAY it: the box stays selectable because shell sessions still work there. `agents` is the
|
|
1076
|
+
// hub's last-read verdict ({claude, codex, checkedAt}); a missing field, or any value that is not
|
|
1077
|
+
// exactly 'signed_out' (signed_in / unknown), adds nothing — the view is then byte-identical to
|
|
1078
|
+
// its pre-WP-4 self.
|
|
1079
|
+
// WP-6: the labels themselves are the ONE shared copy, MACHINE_AGENT_LABELS in
|
|
1080
|
+
// public/js/machine-pool-shared.js, which public/index.html loads before this file (the phone reads
|
|
1081
|
+
// the same pair through public/m/index.html). A second declaration here is exactly what S-18 forbids.
|
|
1082
|
+
function machineSignedOutAgents(machine) {
|
|
1083
|
+
const agents = machine && machine.agents;
|
|
1084
|
+
if (!agents || typeof agents !== 'object') return [];
|
|
1085
|
+
return ['claude', 'codex']
|
|
1086
|
+
.filter((key) => agents[key] === 'signed_out')
|
|
1087
|
+
.map((key) => MACHINE_AGENT_LABELS[key]);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// AC-5a: reachable is selectable; unknown/unhealthy render DISABLED carrying their reason;
|
|
1091
|
+
// unreachable (and any state the hub may add later) is ABSENT from the grid.
|
|
1092
|
+
function machineCardView(machine, now) {
|
|
1093
|
+
const health = String((machine && machine.health) || '').trim();
|
|
1094
|
+
if (MACHINE_CARD_STATES.indexOf(health) === -1) return null;
|
|
1095
|
+
const id = String((machine && machine.instanceId) || '');
|
|
1096
|
+
if (!id) return null;
|
|
1097
|
+
const name = (machine.displayName || machine.url || id);
|
|
1098
|
+
const note = health === 'reachable'
|
|
1099
|
+
? (machine.url || '')
|
|
1100
|
+
: health === 'unknown'
|
|
1101
|
+
? 'Health unknown — the box is not answering probes'
|
|
1102
|
+
: 'Unhealthy — the box is up but its hub is degraded';
|
|
1103
|
+
const view = { id, name, health, sub: health === 'reachable' ? 'reachable' : note, disabled: health !== 'reachable', note };
|
|
1104
|
+
// WP-4: only a reachable box can name an agent — a disabled card keeps its single reason.
|
|
1105
|
+
if (health === 'reachable') {
|
|
1106
|
+
const signInNeeded = machineSignedOutAgents(machine);
|
|
1107
|
+
if (signInNeeded.length) view.signInNeeded = signInNeeded;
|
|
1108
|
+
}
|
|
1109
|
+
// D-12: the box's live numbers are the card's third line. A box the hub has never sampled adds
|
|
1110
|
+
// NOTHING (the field is absent, never ''), so today's two-line card stays byte-identical and the
|
|
1111
|
+
// existing "no new field at all" pin keeps holding.
|
|
1112
|
+
const load = machineLoadLine(machine, now);
|
|
1113
|
+
if (load) view.load = load;
|
|
1114
|
+
return view;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
function _nsMachineCardNode(view) {
|
|
1118
|
+
const card = document.createElement('div');
|
|
1119
|
+
card.className = 'ns-agent-card ns-machine-card' + (view.disabled ? ' disabled' : '');
|
|
1120
|
+
card.dataset.value = view.id;
|
|
1121
|
+
card.dataset.health = view.health;
|
|
1122
|
+
card.style.setProperty('--ns-card-accent', view.disabled ? 'var(--warning, #e0af68)' : '#7aa2f7');
|
|
1123
|
+
// WP-4: the card stays clickable and keeps its accent — only the sub line and the tooltip name
|
|
1124
|
+
// the signed-out agents. '' here (nothing signed out) reproduces the pre-WP-4 strings exactly.
|
|
1125
|
+
const signIn = !view.disabled && Array.isArray(view.signInNeeded) && view.signInNeeded.length
|
|
1126
|
+
? view.signInNeeded.join(', ')
|
|
1127
|
+
: '';
|
|
1128
|
+
// WP-3 (docs/remote-agent-sign-in-from-hub-browser-2026-10-25.html §9 S-14): the tooltip points
|
|
1129
|
+
// at the Machines page, which signs in a box's coding agent through the hub's own proxy — the
|
|
1130
|
+
// box's local page ("open <url> → AI Providers → Coding Agents → Sign in") is not reachable from
|
|
1131
|
+
// a phone, and no longer the instruction on either surface.
|
|
1132
|
+
card.title = view.disabled
|
|
1133
|
+
? view.name + ': ' + view.note
|
|
1134
|
+
: signIn
|
|
1135
|
+
? 'Create this session on ' + view.name + ' — not signed in there: ' + signIn + ' (Sign in from the Machines page)'
|
|
1136
|
+
: 'Create this session on ' + view.name;
|
|
1137
|
+
const icon = document.createElement('div');
|
|
1138
|
+
icon.className = 'ns-card-icon';
|
|
1139
|
+
icon.style.background = view.disabled ? 'var(--warning, #e0af68)' : '#7aa2f7';
|
|
1140
|
+
icon.textContent = view.id ? '◈' : '⌘';
|
|
1141
|
+
const name = document.createElement('div');
|
|
1142
|
+
name.className = 'ns-card-name';
|
|
1143
|
+
name.textContent = view.name;
|
|
1144
|
+
const sub = document.createElement('div');
|
|
1145
|
+
sub.className = 'ns-card-sub';
|
|
1146
|
+
sub.textContent = view.disabled ? 'unavailable' : (signIn ? 'sign in: ' + signIn : 'ready');
|
|
1147
|
+
card.appendChild(icon);
|
|
1148
|
+
card.appendChild(name);
|
|
1149
|
+
card.appendChild(sub);
|
|
1150
|
+
// D-12: the load line is the card's THIRD line and only exists when the hub holds a sample.
|
|
1151
|
+
if (view.load) {
|
|
1152
|
+
const load = document.createElement('div');
|
|
1153
|
+
load.className = 'ns-card-load';
|
|
1154
|
+
load.textContent = view.load;
|
|
1155
|
+
card.appendChild(load);
|
|
1156
|
+
}
|
|
1157
|
+
if (!view.disabled) card.onclick = function() { _selectMachineCard(view.id); };
|
|
1158
|
+
return card;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// ── WP-4 picker (docs/machine-pool-picker-pairing-dashboard-2026-10-24.html §5 D-1/D-2/D-3/D-12) ──
|
|
1162
|
+
// D-1: while N = 1 + the visible (non-hidden) registry rows is at most MACHINE_PICKER_CARD_LIMIT the
|
|
1163
|
+
// grid stays the one-click card row it has always been; above the limit it keeps This Mac + the top
|
|
1164
|
+
// three and adds a fifth "All machines (N)…" card. That card opens the APG combobox that already
|
|
1165
|
+
// ships as window.CtmModelPicker — P4: one combobox, never a fifth copy.
|
|
1166
|
+
//
|
|
1167
|
+
// WP-6: MACHINE_PICKER_CARD_LIMIT, MACHINE_STATS_STALE_MS, MACHINE_RECENT_MS and the presenters
|
|
1168
|
+
// below (machinePickerOrder / machineSearchText / machineLoadLine / machineIsOffline /
|
|
1169
|
+
// machineIsFavorite / machinePickerGroupRows / machineHealthLabel / machineLastSeenText) now live in
|
|
1170
|
+
// the ONE shared copy, public/js/machine-pool-shared.js, which public/index.html loads before this
|
|
1171
|
+
// file and public/m/index.html loads for the phone. This file keeps only the desktop grid/combobox.
|
|
1172
|
+
// The sentinel this card's data-value carries. It is never '' — an empty value would make
|
|
1173
|
+
// _selectMachineCard paint the "All machines" card as the selected machine.
|
|
1174
|
+
const MACHINE_ALL_CARD_VALUE = '__all_machines__';
|
|
1175
|
+
// WP-5 (D-14): the machine pool is a first-class page — nav target `machines`, hash `#machines`
|
|
1176
|
+
// (js/app/machines-page.js). index.html's router rewrites the old `#setup&tab=machines` deep link
|
|
1177
|
+
// onto it, so bookmarks keep working.
|
|
1178
|
+
const MACHINE_MANAGE_HREF = '#machines';
|
|
1179
|
+
const MACHINE_MANAGE_LABEL = 'Manage machines';
|
|
1180
|
+
const MACHINE_MANAGE_ID = 'ns-machine-manage';
|
|
1181
|
+
|
|
1182
|
+
// The picker component, or null on a page that never loaded it (an older boot snapshot): the card
|
|
1183
|
+
// row still renders, the "All machines" card just does nothing.
|
|
1184
|
+
function _machinePickerComponent() {
|
|
1185
|
+
if (typeof window !== 'undefined' && window && window.CtmModelPicker) return window.CtmModelPicker;
|
|
1186
|
+
if (typeof CtmModelPicker !== 'undefined' && CtmModelPicker) return CtmModelPicker;
|
|
1187
|
+
return null;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// D-1: the machine a row picked this modal session, moved into the THIRD top card. Cleared by
|
|
1191
|
+
// loadNewSessionMachines, so it can never outlive the modal it was chosen in.
|
|
1192
|
+
function _machineTopCardOrder(order) {
|
|
1193
|
+
const forced = String(_nsMachineState.pickerMachine || '');
|
|
1194
|
+
if (!forced) return order;
|
|
1195
|
+
const chosen = order.find((m) => m.instanceId === forced);
|
|
1196
|
+
if (!chosen) return order;
|
|
1197
|
+
const rest = order.filter((m) => m.instanceId !== forced);
|
|
1198
|
+
const head = rest.slice(0, MACHINE_PICKER_CARD_LIMIT - 2);
|
|
1199
|
+
return head.concat([chosen], rest.slice(MACHINE_PICKER_CARD_LIMIT - 2));
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
// D-1: the top three cards, in order, skipping any row that produces no card at all (an unreachable
|
|
1203
|
+
// box stays out of the row — HUB AC-5a — but keeps its place in the popover's Offline group).
|
|
1204
|
+
function _nsMachineTopViews(order, now) {
|
|
1205
|
+
const views = [];
|
|
1206
|
+
for (const machine of _machineTopCardOrder(order)) {
|
|
1207
|
+
if (views.length >= MACHINE_PICKER_CARD_LIMIT - 1) break;
|
|
1208
|
+
const view = machineCardView(machine, now);
|
|
1209
|
+
if (view) views.push(view);
|
|
1210
|
+
}
|
|
1211
|
+
return views;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
// D-3 groups. Favorites and Recent are filtered views of All (a box may legitimately appear in two
|
|
1215
|
+
// groups); Offline carries the rows HUB AC-5a keeps out of the card row, disabled with the reason a
|
|
1216
|
+
// "where is my machine" search is looking for. WP-6: the group SET is the shared
|
|
1217
|
+
// machinePickerGroupRows (public/js/machine-pool-shared.js), so the phone's sheet lists the same
|
|
1218
|
+
// groups in the same order; this function only turns each row into a CtmModelPicker row.
|
|
1219
|
+
function _machinePickerGroups(now) {
|
|
1220
|
+
const groups = machinePickerGroupRows(_nsMachineState.machines, now)
|
|
1221
|
+
.map((group) => ({
|
|
1222
|
+
id: group.id,
|
|
1223
|
+
label: group.label,
|
|
1224
|
+
rows: group.rows.map((machine) => _machinePickerRow(machine, group.id, now)),
|
|
1225
|
+
}));
|
|
1226
|
+
return {
|
|
1227
|
+
groups,
|
|
1228
|
+
emptyMessage: 'No machines yet',
|
|
1229
|
+
noMatchMessage: 'No machine matches that search',
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
function _machinePickerRow(machine, groupId, now) {
|
|
1234
|
+
const load = machineLoadLine(machine, now);
|
|
1235
|
+
const row = {
|
|
1236
|
+
// Group-scoped key: the same box may be listed in two groups, and only the option with this key
|
|
1237
|
+
// is ever marked selected.
|
|
1238
|
+
key: groupId + ':' + machine.instanceId,
|
|
1239
|
+
label: machine.displayName || machine.hostname || machine.url || machine.instanceId,
|
|
1240
|
+
sublabel: load,
|
|
1241
|
+
search: machineSearchText(machine),
|
|
1242
|
+
current: String(machine.instanceId) === machineSelection(),
|
|
1243
|
+
data: { instanceId: machine.instanceId },
|
|
1244
|
+
};
|
|
1245
|
+
if (machineIsOffline(machine)) {
|
|
1246
|
+
row.disabled = { reason: machineLastSeenText(machine) };
|
|
1247
|
+
row.badges = [{ text: 'offline', tone: 'warn' }];
|
|
1248
|
+
}
|
|
1249
|
+
return row;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
function _machinePickerDetail(row, now) {
|
|
1253
|
+
const pane = document.createElement('div');
|
|
1254
|
+
const machine = row && row.data
|
|
1255
|
+
? _nsMachineState.machines.find((m) => m && m.instanceId === row.data.instanceId)
|
|
1256
|
+
: null;
|
|
1257
|
+
if (!machine) {
|
|
1258
|
+
pane.className = 'walle-model-config-provider';
|
|
1259
|
+
pane.textContent = 'Pick a machine to see where the session will run.';
|
|
1260
|
+
return pane;
|
|
1261
|
+
}
|
|
1262
|
+
const eyebrow = document.createElement('div');
|
|
1263
|
+
eyebrow.className = 'walle-model-config-eyebrow';
|
|
1264
|
+
eyebrow.textContent = 'Machine';
|
|
1265
|
+
const title = document.createElement('div');
|
|
1266
|
+
title.className = 'walle-model-config-title';
|
|
1267
|
+
title.textContent = machine.displayName || machine.hostname || machine.url || machine.instanceId;
|
|
1268
|
+
const meta = document.createElement('div');
|
|
1269
|
+
meta.className = 'walle-model-config-provider';
|
|
1270
|
+
meta.textContent = [
|
|
1271
|
+
machineHealthLabel(machine),
|
|
1272
|
+
machineLastSeenText(machine),
|
|
1273
|
+
machineLoadLine(machine, now),
|
|
1274
|
+
[machine.os, machine.arch].filter(Boolean).join('/'),
|
|
1275
|
+
machine.url,
|
|
1276
|
+
].filter(Boolean).join(' · ');
|
|
1277
|
+
pane.appendChild(eyebrow);
|
|
1278
|
+
pane.appendChild(title);
|
|
1279
|
+
pane.appendChild(meta);
|
|
1280
|
+
return pane;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// D-1: choosing a row makes it the selected card for this modal session and goes through
|
|
1284
|
+
// _selectMachineCard, so the per-machine path truth (S-13) is re-keyed exactly as a card click does.
|
|
1285
|
+
function _machinePickerChoose(row) {
|
|
1286
|
+
const id = row && row.data ? String(row.data.instanceId || '') : '';
|
|
1287
|
+
const machine = id ? _nsMachineState.machines.find((m) => m && m.instanceId === id) : null;
|
|
1288
|
+
if (!machine || machineIsOffline(machine)) return false;
|
|
1289
|
+
_nsMachineState.pickerMachine = id;
|
|
1290
|
+
renderNewSessionMachineCards(_nsMachineState.machines, _nsMachineState.renderedAt);
|
|
1291
|
+
_selectMachineCard(id);
|
|
1292
|
+
return true;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// The "All machines (N)…" card. It is a real button (role=button + tabindex + aria-haspopup), so
|
|
1296
|
+
// the APG keyboard contract holds: Enter/Space opens the popover and focus lands back here on close.
|
|
1297
|
+
function _nsMachineAllCardNode(count) {
|
|
1298
|
+
const card = document.createElement('div');
|
|
1299
|
+
card.className = 'ns-agent-card ns-machine-card ns-machine-all';
|
|
1300
|
+
card.dataset.value = MACHINE_ALL_CARD_VALUE;
|
|
1301
|
+
card.setAttribute('role', 'button');
|
|
1302
|
+
card.setAttribute('tabindex', '0');
|
|
1303
|
+
card.setAttribute('aria-haspopup', 'dialog');
|
|
1304
|
+
card.title = 'Show all ' + count + ' machines';
|
|
1305
|
+
const icon = document.createElement('div');
|
|
1306
|
+
icon.className = 'ns-card-icon';
|
|
1307
|
+
icon.style.background = '#7aa2f7';
|
|
1308
|
+
icon.textContent = '⋯';
|
|
1309
|
+
const name = document.createElement('div');
|
|
1310
|
+
name.className = 'ns-card-name';
|
|
1311
|
+
name.textContent = 'All machines (' + count + ')…';
|
|
1312
|
+
card.appendChild(icon);
|
|
1313
|
+
card.appendChild(name);
|
|
1314
|
+
card.onclick = function() { openAllMachinesPicker(card); };
|
|
1315
|
+
card.addEventListener('keydown', function(event) {
|
|
1316
|
+
const key = String((event && event.key) || '');
|
|
1317
|
+
if (key !== 'Enter' && key !== ' ' && key !== 'Spacebar') return;
|
|
1318
|
+
if (typeof event.preventDefault === 'function') event.preventDefault();
|
|
1319
|
+
openAllMachinesPicker(card);
|
|
1320
|
+
});
|
|
1321
|
+
return card;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
// The popover's anchor, re-resolved because choosing a row re-renders the grid (the node we opened
|
|
1325
|
+
// from is detached) and focus must land on the live card (APG, AC-4c).
|
|
1326
|
+
function _nsMachineAllCardAnchor() {
|
|
1327
|
+
const grid = document.getElementById('ns-machine-grid');
|
|
1328
|
+
if (!grid || typeof grid.querySelectorAll !== 'function') return null;
|
|
1329
|
+
const cards = grid.querySelectorAll('.ns-machine-all');
|
|
1330
|
+
return cards && cards.length ? cards[0] : null;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
function openAllMachinesPicker(anchor, now) {
|
|
1334
|
+
const picker = _machinePickerComponent();
|
|
1335
|
+
if (!picker || typeof picker.open !== 'function') return null;
|
|
1336
|
+
const at = Number(now == null ? Date.now() : now);
|
|
1337
|
+
return picker.open({
|
|
1338
|
+
anchor: anchor,
|
|
1339
|
+
title: 'Choose machine',
|
|
1340
|
+
tabs: [{
|
|
1341
|
+
id: 'machines',
|
|
1342
|
+
label: 'Machines',
|
|
1343
|
+
searchPlaceholder: 'Search machines',
|
|
1344
|
+
loadRows: function() { return Promise.resolve(_machinePickerGroups(at)); },
|
|
1345
|
+
detail: function(row) { return _machinePickerDetail(row, at); },
|
|
1346
|
+
actions: [{ id: 'choose', label: 'Choose this machine', primary: true, disabled: (row) => !row || !!row.disabled }],
|
|
1347
|
+
onApply: function(row) {
|
|
1348
|
+
// An offline row can never be committed: the hub would have nowhere to put the session.
|
|
1349
|
+
if (!_machinePickerChoose(row)) return Promise.resolve({ ok: false, message: 'That machine cannot be selected.' });
|
|
1350
|
+
return Promise.resolve({ ok: true });
|
|
1351
|
+
},
|
|
1352
|
+
}],
|
|
1353
|
+
onClose: function(reason) {
|
|
1354
|
+
// APG AC-4c: focus returns to the "All machines" card. CtmModelPicker focuses the anchor it was
|
|
1355
|
+
// given, but a pick has re-rendered the grid, so re-resolve the live card and fall back to it.
|
|
1356
|
+
if (reason === 'replaced') return;
|
|
1357
|
+
const back = _nsMachineAllCardAnchor() || anchor;
|
|
1358
|
+
if (back && typeof back.focus === 'function') back.focus();
|
|
1359
|
+
},
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// Item 7: the "Manage machines" link under the grid. There is no slot in index.html for it (that
|
|
1364
|
+
// file is WP-5's), so it is created once and mounted as the grid's next sibling — the modal is its
|
|
1365
|
+
// parent, and no markup change is needed to place it.
|
|
1366
|
+
function _nsMachineManageLink() {
|
|
1367
|
+
let link = document.getElementById(MACHINE_MANAGE_ID);
|
|
1368
|
+
if (!link) {
|
|
1369
|
+
link = document.createElement('a');
|
|
1370
|
+
link.id = MACHINE_MANAGE_ID;
|
|
1371
|
+
link.className = 'ns-machine-manage';
|
|
1372
|
+
link.href = MACHINE_MANAGE_HREF;
|
|
1373
|
+
link.textContent = MACHINE_MANAGE_LABEL;
|
|
1374
|
+
link.title = 'Open the Machines page';
|
|
1375
|
+
}
|
|
1376
|
+
return link;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
function _nsMachineMountManageLink(grid) {
|
|
1380
|
+
const link = _nsMachineManageLink();
|
|
1381
|
+
const parent = grid && grid.parentNode;
|
|
1382
|
+
// Nothing to mount into (a bare DOM stub, or a detached grid): the caller still gets the node so
|
|
1383
|
+
// it can flip its visibility.
|
|
1384
|
+
if (!link || !parent || typeof parent.insertBefore !== 'function') return link;
|
|
1385
|
+
if (link.parentNode !== parent) parent.insertBefore(link, grid.nextSibling || null);
|
|
1386
|
+
return link;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
function _nsMachineShowManageLink(link, on) {
|
|
1390
|
+
if (link && link.style) link.style.display = on ? '' : 'none';
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// AC-5a: with nothing renderable the block is removed from the DOM entirely (no grid, no
|
|
1394
|
+
// hidden input value), which is what keeps the zero-machine modal pixel-identical to today.
|
|
1395
|
+
// WP-4 D-1: above MACHINE_PICKER_CARD_LIMIT the grid keeps This Mac + the top three and adds the
|
|
1396
|
+
// "All machines (N)…" card, which is the only way to reach a box that never renders a card.
|
|
1397
|
+
function renderNewSessionMachineCards(machines, now) {
|
|
1398
|
+
const grid = document.getElementById('ns-machine-grid');
|
|
1399
|
+
const label = document.getElementById('ns-machine-label');
|
|
1400
|
+
const hint = document.getElementById('ns-machine-hint');
|
|
1401
|
+
const hidden = document.getElementById('ns-machine');
|
|
1402
|
+
if (!grid || !label || !hidden) return false;
|
|
1403
|
+
const at = Number(now == null ? Date.now() : now);
|
|
1404
|
+
_nsMachineState.machines = (Array.isArray(machines) ? machines : []).filter(m => m && m.instanceId);
|
|
1405
|
+
_nsMachineState.renderedAt = at;
|
|
1406
|
+
const visible = _nsMachineState.machines.filter(m => !m.hidden);
|
|
1407
|
+
// Registry order, filtered to the rows that render — today's grid, unchanged (AC-4a).
|
|
1408
|
+
const views = visible.map(m => machineCardView(m, at)).filter(Boolean);
|
|
1409
|
+
const manage = _nsMachineMountManageLink(grid);
|
|
1410
|
+
if (!views.length) {
|
|
1411
|
+
grid.textContent = '';
|
|
1412
|
+
grid.style.display = 'none';
|
|
1413
|
+
label.style.display = 'none';
|
|
1414
|
+
hidden.value = '';
|
|
1415
|
+
_nsMachineShowManageLink(manage, false);
|
|
1416
|
+
if (hint) { hint.textContent = ''; hint.style.display = 'none'; }
|
|
1417
|
+
return false;
|
|
1418
|
+
}
|
|
1419
|
+
// D-1: the switch to the "All machines" card counts This Mac plus each row that renders a card — an
|
|
1420
|
+
// unreachable box renders none (HUB AC-5a), so below the limit today's grid is unchanged. The card's
|
|
1421
|
+
// label counts every visible row, offline included, so it matches the popover's All group it opens.
|
|
1422
|
+
const total = 1 + views.length;
|
|
1423
|
+
label.style.display = '';
|
|
1424
|
+
grid.style.display = '';
|
|
1425
|
+
grid.textContent = '';
|
|
1426
|
+
grid.appendChild(_nsMachineCardNode({ id: '', name: 'This Mac', health: 'reachable', sub: 'ready', disabled: false }));
|
|
1427
|
+
if (total > MACHINE_PICKER_CARD_LIMIT) {
|
|
1428
|
+
_nsMachineTopViews(machinePickerOrder(_nsMachineState.machines), at).forEach(view => grid.appendChild(_nsMachineCardNode(view)));
|
|
1429
|
+
grid.appendChild(_nsMachineAllCardNode(1 + visible.length));
|
|
1430
|
+
} else {
|
|
1431
|
+
views.forEach(view => grid.appendChild(_nsMachineCardNode(view)));
|
|
1432
|
+
}
|
|
1433
|
+
_nsMachineShowManageLink(manage, true);
|
|
1434
|
+
hidden.value = '';
|
|
1435
|
+
_selectMachineCard('');
|
|
1436
|
+
_nsMachineSetHint('');
|
|
1437
|
+
return true;
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
function _nsMachineSetHint(text, warn) {
|
|
1441
|
+
const hint = document.getElementById('ns-machine-hint');
|
|
1442
|
+
if (!hint) return;
|
|
1443
|
+
hint.textContent = String(text || '');
|
|
1444
|
+
hint.style.display = text ? '' : 'none';
|
|
1445
|
+
hint.classList.toggle('warn', !!warn);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
function _selectMachineCard(value) {
|
|
1449
|
+
const hidden = document.getElementById('ns-machine');
|
|
1450
|
+
const next = String(value || '');
|
|
1451
|
+
const previous = hidden ? String(hidden.value || '') : '';
|
|
1452
|
+
if (hidden) hidden.value = next;
|
|
1453
|
+
const grid = document.getElementById('ns-machine-grid');
|
|
1454
|
+
if (grid) {
|
|
1455
|
+
grid.querySelectorAll('.ns-machine-card').forEach(function(card) {
|
|
1456
|
+
card.classList.toggle('selected', String(card.dataset.value || '') === next);
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
_nsMachineSetHint(next ? 'Sessions will spawn on ' + machineSelectionName() + ' — pick a folder on that box.' : '');
|
|
1460
|
+
// S-13: host-path truth belongs to exactly one machine. Re-run the picker reset on every
|
|
1461
|
+
// change — it rebinds the host probe (which now carries ?machine=) and forgets the previous
|
|
1462
|
+
// machine's project list — so a box answer can never land on a local path field.
|
|
1463
|
+
if (previous !== next && typeof _nsPathReset === 'function') {
|
|
1464
|
+
// The same-repo prefill matches THIS project (the local cwd the user had when they picked
|
|
1465
|
+
// the box), never just any repo both machines happen to share.
|
|
1466
|
+
const cwdInput = document.getElementById('ns-cwd');
|
|
1467
|
+
if (!previous) _nsMachineState.localCwd = cwdInput ? String(cwdInput.value || '').trim() : '';
|
|
1468
|
+
if (typeof _nsPathPickerState !== 'undefined') {
|
|
1469
|
+
_nsPathPickerState.machine = next;
|
|
1470
|
+
_nsPathPickerState.projectsLoaded = false;
|
|
1471
|
+
_nsPathPickerState.projects = [];
|
|
1472
|
+
}
|
|
1473
|
+
_nsPathReset(next ? '' : (typeof getLastSessionCwd === 'function' ? getLastSessionCwd() : ''));
|
|
1474
|
+
}
|
|
1475
|
+
return next;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
// The ?machine= fragment the picker routes carry (§6.5). '' produces nothing, so every local
|
|
1479
|
+
// request keeps its exact current URL.
|
|
1480
|
+
function machineQueryParam() {
|
|
1481
|
+
const id = machineSelection();
|
|
1482
|
+
return id ? '&machine=' + encodeURIComponent(id) : '';
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
// The header form of the same routing, for POSTs that already own their query string
|
|
1486
|
+
// (/api/path-status). A local selection adds nothing (§6.5: routing is by query/header ONLY,
|
|
1487
|
+
// so no request body ever carries the machine).
|
|
1488
|
+
function machineScopedHeaders(baseHeaders) {
|
|
1489
|
+
const headers = Object.assign({}, baseHeaders || {});
|
|
1490
|
+
const id = machineSelection();
|
|
1491
|
+
if (id) headers['X-CTM-Machine'] = id;
|
|
1492
|
+
return headers;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
// The key of the local project that contains `cwd` (the longest project path that is cwd or an
|
|
1496
|
+
// ancestor of it), '' when none does. Shared shape with the phone's localProjectKeyFor.
|
|
1497
|
+
function localProjectKeyFor(cwd, keyPath) {
|
|
1498
|
+
const target = String(cwd || '').replace(/\/+$/, '');
|
|
1499
|
+
let best = '';
|
|
1500
|
+
let bestLen = -1;
|
|
1501
|
+
Object.keys(keyPath || {}).forEach(function(key) {
|
|
1502
|
+
const root = String(keyPath[key] || '').replace(/\/+$/, '');
|
|
1503
|
+
if (!root || (target !== root && target.indexOf(root + '/') !== 0)) return;
|
|
1504
|
+
if (root.length > bestLen) { best = key; bestLen = root.length; }
|
|
1505
|
+
});
|
|
1506
|
+
return best;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
// Local gitRemoteKey -> path, so the same-repo prefill is a plain key equality against the
|
|
1510
|
+
// box's own /api/projects rows. Normalization stays server-side (§6.5).
|
|
1511
|
+
function loadLocalRepoKeys() {
|
|
1512
|
+
if (_nsMachineState.localKeysRequested) return;
|
|
1513
|
+
_nsMachineState.localKeysRequested = true;
|
|
1514
|
+
const token = (typeof state !== 'undefined' && state.token) || '';
|
|
1515
|
+
fetch('/api/projects?token=' + encodeURIComponent(token))
|
|
1516
|
+
.then(function(r) { return r.ok ? r.json() : null; })
|
|
1517
|
+
.then(function(rows) {
|
|
1518
|
+
const map = Object.create(null);
|
|
1519
|
+
(Array.isArray(rows) ? rows : []).forEach(function(row) {
|
|
1520
|
+
if (row && row.path && row.gitRemoteKey) map[String(row.gitRemoteKey)] = row.path;
|
|
1521
|
+
});
|
|
1522
|
+
_nsMachineState.localRepoKeyPath = map;
|
|
1523
|
+
})
|
|
1524
|
+
.catch(function() { /* expected: without the local key list the picker simply browses */ });
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
// Called by _nsPathLoadProjects once the SELECTED machine's projects answer arrives.
|
|
1528
|
+
function nsMachineAfterProjectsLoaded(pickerState) {
|
|
1529
|
+
const machine = machineSelection();
|
|
1530
|
+
if (!machine) return false;
|
|
1531
|
+
const rows = (pickerState && Array.isArray(pickerState.projects)) ? pickerState.projects : [];
|
|
1532
|
+
const localKey = localProjectKeyFor(_nsMachineState.localCwd, _nsMachineState.localRepoKeyPath);
|
|
1533
|
+
const match = localKey ? rows.find(function(row) {
|
|
1534
|
+
return row && row.path && String(row.gitRemoteKey || '') === localKey;
|
|
1535
|
+
}) : null;
|
|
1536
|
+
const input = document.getElementById('ns-cwd');
|
|
1537
|
+
if (!input) return false;
|
|
1538
|
+
if (!match) {
|
|
1539
|
+
_nsMachineSetHint('No checkout on ' + machineSelectionName() + " carries this project's git remote — browse to a folder on that box.", true);
|
|
1540
|
+
return false;
|
|
1541
|
+
}
|
|
1542
|
+
input.value = match.path;
|
|
1543
|
+
if (pickerState) {
|
|
1544
|
+
pickerState.prefillCwd = match.path;
|
|
1545
|
+
pickerState.autoActivePath = match.path;
|
|
1546
|
+
}
|
|
1547
|
+
_nsMachineSetHint('Same repository found on ' + machineSelectionName() + '.');
|
|
1548
|
+
if (typeof _nsPathRenderSuggestions === 'function') _nsPathRenderSuggestions();
|
|
1549
|
+
if (typeof _nsPathUpdateValidation === 'function') _nsPathUpdateValidation();
|
|
1550
|
+
return true;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
// Opened with the modal, so the machine list is fresh every time. A hub-less (older) server
|
|
1554
|
+
// answers 404 and the modal stays exactly as it was (AC-5a).
|
|
1555
|
+
function loadNewSessionMachines() {
|
|
1556
|
+
// D-1: a fresh modal starts from the plain top three — the popover pick belongs to the session it
|
|
1557
|
+
// was made in, never to the next one.
|
|
1558
|
+
_nsMachineState.pickerMachine = '';
|
|
1559
|
+
renderNewSessionMachineCards([]);
|
|
1560
|
+
const token = (typeof state !== 'undefined' && state.token) || '';
|
|
1561
|
+
fetch('/api/machines?token=' + encodeURIComponent(token))
|
|
1562
|
+
.then(function(r) { return r.ok ? r.json() : null; })
|
|
1563
|
+
.then(function(data) {
|
|
1564
|
+
const shown = renderNewSessionMachineCards(data && data.machines);
|
|
1565
|
+
if (shown) loadLocalRepoKeys();
|
|
1566
|
+
})
|
|
1567
|
+
.catch(function() { /* expected: no /api/machines on this server — nothing to render */ });
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
// Machine health / last-seen labels — the ONE copy lives in public/js/machine-pool-shared.js
|
|
1571
|
+
// (machineHealthLabel / machineLastSeenText), loaded by public/index.html before this file and by
|
|
1572
|
+
// public/m/index.html for the phone; the WP-4 picker cards, the WP-5 machines page and the WP-6
|
|
1573
|
+
// phone page all read that copy.
|
|
1574
|
+
|
|
1575
|
+
// ── AC-6d (client half): "Open on <machine>" on a resume-blocked card ────────────────────────
|
|
1576
|
+
// A resume-blocked wire that is parked on another machine MAY carry `openOnMachine`; the action
|
|
1577
|
+
// sends this session's normal resume, which WP-6 routes to that machine. Absent the field the
|
|
1578
|
+
// card's actions are untouched. The button is appended to the card runtime-20 painted (never a
|
|
1579
|
+
// second renderer) and is idempotent across repaints.
|
|
1580
|
+
// Called by runtime-20's resume-blocked painter on EVERY paint (first paint and remount both rebuild
|
|
1581
|
+
// the actions row from s._resumeBlockedMsg), so the button can never be lost to a repaint.
|
|
1582
|
+
function resumeBlockedOpenOnMachineAction(msg, actions, s) {
|
|
1583
|
+
const target = msg && msg.openOnMachine;
|
|
1584
|
+
if (!target || !target.instanceId || !actions || !s) return false;
|
|
1585
|
+
const previous = actions.querySelector('.machine-open-action');
|
|
1586
|
+
if (previous) previous.remove();
|
|
1587
|
+
const name = target.displayName || target.instanceId;
|
|
1588
|
+
const btn = document.createElement('button');
|
|
1589
|
+
btn.type = 'button';
|
|
1590
|
+
btn.className = 'snapshot-loading-btn machine-open-action';
|
|
1591
|
+
btn.textContent = 'Open on ' + name;
|
|
1592
|
+
btn.title = 'Resume this session on ' + name;
|
|
1593
|
+
btn.onclick = function() {
|
|
1594
|
+
if (typeof _retryBlockedResume !== 'function') return;
|
|
1595
|
+
btn.textContent = 'Opening on ' + name + '…';
|
|
1596
|
+
_retryBlockedResume(s);
|
|
1597
|
+
};
|
|
1598
|
+
actions.appendChild(btn);
|
|
1599
|
+
return true;
|
|
1600
|
+
}
|