create-walle 0.10.2 → 0.10.3
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 +3 -1
- package/lib/release-schema.json +2 -2
- package/lib/runtime-closure-policy.json +8 -7
- package/package.json +1 -1
- package/template/coding-task-manager/api-prompts.js +45 -7
- package/template/coding-task-manager/bin/claude-hook.js +55 -1
- package/template/coding-task-manager/bin/ctm-relay-client.js +35 -12
- package/template/coding-task-manager/bin/land.js +2069 -0
- package/template/coding-task-manager/bin/memory-census.js +4 -2
- package/template/coding-task-manager/bin/memory-sync-verify.js +151 -0
- package/template/coding-task-manager/bin/probe-refusal-scope.js +530 -0
- package/template/coding-task-manager/db.js +156 -32
- package/template/coding-task-manager/lib/acp/acp-client.js +383 -0
- package/template/coding-task-manager/lib/acp/dsh-context-truth.js +176 -0
- package/template/coding-task-manager/lib/acp/dsh-launch-model.js +302 -0
- package/template/coding-task-manager/lib/acp/dsh-mcp-servers.js +194 -0
- package/template/coding-task-manager/lib/acp/dsh-prompt-lane.js +616 -0
- package/template/coding-task-manager/lib/acp/dsh-route-patch.js +308 -0
- package/template/coding-task-manager/lib/acp/dsh-session-runtime.js +522 -0
- package/template/coding-task-manager/lib/acp/dsh-transcript.js +395 -0
- package/template/coding-task-manager/lib/acp/dsh-usage-log.js +162 -0
- package/template/coding-task-manager/lib/acp/ndjson-stdio-transport.js +225 -0
- package/template/coding-task-manager/lib/adopted-host-redial.js +47 -11
- package/template/coding-task-manager/lib/adopted-launch-environment.js +123 -0
- package/template/coding-task-manager/lib/agent-auto-update-runtime.js +0 -3
- package/template/coding-task-manager/lib/agent-auto-update.js +26 -10
- package/template/coding-task-manager/lib/agent-bundle/build.js +657 -0
- package/template/coding-task-manager/lib/agent-bundle/codex-install.js +558 -0
- package/template/coding-task-manager/lib/agent-bundle/drift.js +120 -0
- package/template/coding-task-manager/lib/agent-bundle/gc.js +522 -0
- package/template/coding-task-manager/lib/agent-bundle/inventory.js +346 -0
- package/template/coding-task-manager/lib/agent-bundle/push.js +717 -0
- package/template/coding-task-manager/lib/agent-bundle/skill-transfer.js +724 -0
- package/template/coding-task-manager/lib/agent-bundle/spawn.js +119 -0
- package/template/coding-task-manager/lib/agent-bundle/store.js +568 -0
- package/template/coding-task-manager/lib/agent-capabilities.js +142 -12
- package/template/coding-task-manager/lib/agent-cli-cache.js +30 -5
- package/template/coding-task-manager/lib/agent-definitions.js +19 -0
- package/template/coding-task-manager/lib/agent-hooks-codex-toml.js +28 -16
- package/template/coding-task-manager/lib/agent-hooks-codex-workspace.js +9 -1
- package/template/coding-task-manager/lib/agent-hooks-installer.js +23 -8
- package/template/coding-task-manager/lib/agent-hooks-prune.js +37 -11
- package/template/coding-task-manager/lib/agent-hooks-shim.js +105 -0
- package/template/coding-task-manager/lib/agent-presets.js +84 -1
- package/template/coding-task-manager/lib/agent-upgrade-jobs.js +12 -1
- package/template/coding-task-manager/lib/agent-version-service.js +74 -11
- package/template/coding-task-manager/lib/all-sessions-query.js +6 -2
- package/template/coding-task-manager/lib/api/agent-bundles.js +436 -0
- package/template/coding-task-manager/lib/api/backup-retention.js +83 -0
- package/template/coding-task-manager/lib/api/conversation-sync-routes.js +124 -0
- package/template/coding-task-manager/lib/api/diagnostics.js +549 -69
- package/template/coding-task-manager/lib/api/fleet.js +118 -0
- package/template/coding-task-manager/lib/api/hub-bridge.js +243 -0
- package/template/coding-task-manager/lib/api/ingress.js +9 -1
- package/template/coding-task-manager/lib/api/landing.js +403 -0
- package/template/coding-task-manager/lib/api/machines.js +691 -2
- package/template/coding-task-manager/lib/api/memory-sync-routes.js +335 -0
- package/template/coding-task-manager/lib/api/misc-ops.js +8 -3
- package/template/coding-task-manager/lib/api/remote-sessions.js +1326 -0
- package/template/coding-task-manager/lib/api/session-attach.js +623 -0
- package/template/coding-task-manager/lib/api/session-messages.js +1384 -0
- package/template/coding-task-manager/lib/api/sessions.js +77 -51
- package/template/coding-task-manager/lib/api/setup-network.js +544 -16
- package/template/coding-task-manager/lib/api/setup-wizard-handlers.js +884 -0
- package/template/coding-task-manager/lib/api/setup.js +829 -10
- package/template/coding-task-manager/lib/api/walle-bridge.js +9 -1
- package/template/coding-task-manager/lib/attachment-reconciler-wiring.js +5 -2
- package/template/coding-task-manager/lib/auth-rate-limit.js +23 -0
- package/template/coding-task-manager/lib/auth-rules.js +292 -21
- package/template/coding-task-manager/lib/background-stop.js +369 -0
- package/template/coding-task-manager/lib/best-of-attempts.js +4 -1
- package/template/coding-task-manager/lib/best-of-launch-plan.js +52 -0
- package/template/coding-task-manager/lib/box-keep-awake.js +89 -0
- package/template/coding-task-manager/lib/broadcast-reason-stats.js +152 -0
- package/template/coding-task-manager/lib/broadcast-serialize.js +5 -0
- package/template/coding-task-manager/lib/build-info.js +4 -2
- package/template/coding-task-manager/lib/claude-model-truth.js +13 -2
- package/template/coding-task-manager/lib/claude-transcript-terminal-renderer.js +17 -13
- package/template/coding-task-manager/lib/client-diag-handler.js +64 -2
- package/template/coding-task-manager/lib/codex-app-server-client.js +14 -16
- package/template/coding-task-manager/lib/codex-configuration-history.js +55 -0
- package/template/coding-task-manager/lib/codex-harness-qualification.js +43 -20
- package/template/coding-task-manager/lib/codex-rollout-history.js +743 -0
- package/template/coding-task-manager/lib/coding-agent-models.js +27 -12
- package/template/coding-task-manager/lib/coding-cli-skill-installer.js +44 -14
- package/template/coding-task-manager/lib/coding-session-model-adapters.js +43 -2
- package/template/coding-task-manager/lib/coding-session-model-owner.js +1 -1
- package/template/coding-task-manager/lib/coding-session-model-public.js +7 -1
- package/template/coding-task-manager/lib/coding-session-model-runtime.js +23 -4
- package/template/coding-task-manager/lib/coding-session-model-scan.js +31 -0
- package/template/coding-task-manager/lib/coding-session-model-state.js +7 -0
- package/template/coding-task-manager/lib/conversation-freshness-diagnostics.js +280 -0
- package/template/coding-task-manager/lib/conversation-item-clock.js +261 -0
- package/template/coding-task-manager/lib/conversation-source-registry.js +5 -0
- package/template/coding-task-manager/lib/conversation-store-v2.js +44 -0
- package/template/coding-task-manager/lib/conversation-sync.js +355 -0
- package/template/coding-task-manager/lib/create/resolve-launch-params.js +96 -12
- package/template/coding-task-manager/lib/create/worker-role-profile.js +4 -4
- package/template/coding-task-manager/lib/create-intent-lease.js +51 -0
- package/template/coding-task-manager/lib/ctm-session-context-api.js +97 -6
- package/template/coding-task-manager/lib/db-domain-tables.js +1 -0
- package/template/coding-task-manager/lib/db-domains.js +1 -0
- package/template/coding-task-manager/lib/db-owner-dispatch-probe.js +32 -0
- package/template/coding-task-manager/lib/db-owner-request-counts.js +178 -0
- package/template/coding-task-manager/lib/db-owner-worker-client.js +68 -14
- package/template/coding-task-manager/lib/db-schema-source-files.js +5 -0
- package/template/coding-task-manager/lib/deferred-restore-attempt.js +6 -2
- package/template/coding-task-manager/lib/feature-manifest.js +4 -0
- package/template/coding-task-manager/lib/fleet-conversation-route.js +97 -0
- package/template/coding-task-manager/lib/fleet-fanout.js +78 -0
- package/template/coding-task-manager/lib/fleet-hub.js +762 -0
- package/template/coding-task-manager/lib/fleet-origin-follower.js +252 -0
- package/template/coding-task-manager/lib/fleet-resolver-effects.js +147 -0
- package/template/coding-task-manager/lib/fleet-resolver-tick.js +148 -0
- package/template/coding-task-manager/lib/fleet-resolver.js +454 -0
- package/template/coding-task-manager/lib/fleet-session-list-routes.js +114 -0
- package/template/coding-task-manager/lib/fleet-session-lists.js +672 -0
- package/template/coding-task-manager/lib/fleet-update.js +798 -0
- package/template/coding-task-manager/lib/fs-watcher-root-watch.js +141 -0
- package/template/coding-task-manager/lib/fs-watcher.js +58 -42
- package/template/coding-task-manager/lib/harness-history-transition.js +50 -9
- package/template/coding-task-manager/lib/headless-term-service.js +84 -8
- package/template/coding-task-manager/lib/hermetic-agent-launch-guard.js +10 -0
- package/template/coding-task-manager/lib/history-transition-read-op.js +175 -8
- package/template/coding-task-manager/lib/host-cpu-categories.js +310 -0
- package/template/coding-task-manager/lib/host-fseventsd-sampler.js +399 -0
- package/template/coding-task-manager/lib/host-handoff.js +16 -19
- package/template/coding-task-manager/lib/host-stats.js +150 -3
- package/template/coding-task-manager/lib/hosted-sync-worker.js +43 -320
- package/template/coding-task-manager/lib/hub-bridge-access.js +798 -0
- package/template/coding-task-manager/lib/hub-bridge-box.js +409 -0
- package/template/coding-task-manager/lib/hub-bridge-mcp.js +380 -0
- package/template/coding-task-manager/lib/hub-bridge-mesh.js +313 -0
- package/template/coding-task-manager/lib/hub-bridge-ops.js +721 -0
- package/template/coding-task-manager/lib/hub-bridge.js +398 -0
- package/template/coding-task-manager/lib/idle-notify.js +491 -0
- package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +8 -0
- package/template/coding-task-manager/lib/instance-manifest.js +5 -1
- package/template/coding-task-manager/lib/instance-sync-status.js +18 -8
- package/template/coding-task-manager/lib/landing/agent-rule.txt +1 -0
- package/template/coding-task-manager/lib/landing/batch-runner.js +366 -0
- package/template/coding-task-manager/lib/landing/config.js +536 -0
- package/template/coding-task-manager/lib/landing/ctm-view.js +256 -0
- package/template/coding-task-manager/lib/landing/gate.js +360 -0
- package/template/coding-task-manager/lib/landing/git.js +344 -0
- package/template/coding-task-manager/lib/landing/lease.js +247 -0
- package/template/coding-task-manager/lib/landing/merge-delta.js +380 -0
- package/template/coding-task-manager/lib/landing/proof.js +223 -0
- package/template/coding-task-manager/lib/landing/queue.js +998 -0
- package/template/coding-task-manager/lib/landing/radar.js +489 -0
- package/template/coding-task-manager/lib/landing/resolve-cli.js +424 -0
- package/template/coding-task-manager/lib/landing/state.js +263 -0
- package/template/coding-task-manager/lib/landing/stop.js +49 -0
- package/template/coding-task-manager/lib/landing/sync-cli.js +475 -0
- package/template/coding-task-manager/lib/landing/sync.js +456 -0
- package/template/coding-task-manager/lib/landing/train.js +885 -0
- package/template/coding-task-manager/lib/landing/window.js +57 -0
- package/template/coding-task-manager/lib/launch-environment.js +121 -0
- package/template/coding-task-manager/lib/launch-model-override.js +36 -10
- package/template/coding-task-manager/lib/launch-plan-pool.js +2 -1
- package/template/coding-task-manager/lib/launch-plan-warm-cache.js +6 -0
- package/template/coding-task-manager/lib/lead-model-observer.js +57 -1
- package/template/coding-task-manager/lib/lead-model-options.js +160 -3
- package/template/coding-task-manager/lib/lead-model-restore-reconciliation.js +106 -24
- package/template/coding-task-manager/lib/lead-model-switch-runtime.js +352 -68
- package/template/coding-task-manager/lib/lead-model-switch.js +114 -10
- package/template/coding-task-manager/lib/lead-model-transition-runtime.js +14 -2
- package/template/coding-task-manager/lib/lead-model-transition-store.js +24 -6
- package/template/coding-task-manager/lib/lead-route-preflight.js +179 -0
- package/template/coding-task-manager/lib/lead-switch-history-mode.js +110 -0
- package/template/coding-task-manager/lib/lead-wire-agent.js +7 -0
- package/template/coding-task-manager/lib/lifecycle-wake-experiment.js +9 -2
- package/template/coding-task-manager/lib/live-defer-announce.js +45 -0
- package/template/coding-task-manager/lib/machine-registry.js +750 -5
- package/template/coding-task-manager/lib/macos-capabilities.js +32 -15
- package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +433 -53
- package/template/coding-task-manager/lib/mainloop-observability.js +14 -9
- package/template/coding-task-manager/lib/media-draft-owner.js +16 -3
- package/template/coding-task-manager/lib/memory-join.js +337 -0
- package/template/coding-task-manager/lib/memory-sync-verify.js +393 -0
- package/template/coding-task-manager/lib/memory-sync.js +565 -0
- package/template/coding-task-manager/lib/migrations/v57-delegate-pref-mirror-opencode.js +68 -0
- package/template/coding-task-manager/lib/migrations/v58-subagent-launch-native-subagents.js +42 -0
- package/template/coding-task-manager/lib/migrations/v59-coding-session-model-state-dsh.js +73 -0
- package/template/coding-task-manager/lib/migrations/v60-delegate-pref-mirror-dsh.js +78 -0
- package/template/coding-task-manager/lib/migrations/v61-conversation-item-clock.js +71 -0
- package/template/coding-task-manager/lib/model-availability.js +727 -0
- package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +4 -6
- package/template/coding-task-manager/lib/model-host-receipt.js +6 -3
- package/template/coding-task-manager/lib/model-id-rule.js +19 -0
- package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
- package/template/coding-task-manager/lib/model-proxy/auto-mode-review.js +110 -23
- package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
- package/template/coding-task-manager/lib/model-proxy/generation-probe.js +60 -17
- package/template/coding-task-manager/lib/model-proxy/historical-tool-declarations.js +37 -0
- package/template/coding-task-manager/lib/model-proxy/lead-request-validation.js +1 -0
- package/template/coding-task-manager/lib/model-proxy/lead-token-admin.js +130 -0
- package/template/coding-task-manager/lib/model-proxy/refusal-scope.js +151 -0
- package/template/coding-task-manager/lib/model-proxy/request-budget-store.js +11 -2
- package/template/coding-task-manager/lib/model-proxy/tokens.js +69 -5
- package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
- package/template/coding-task-manager/lib/native-agent-model-args.js +25 -22
- package/template/coding-task-manager/lib/native-history-control.js +6 -4
- package/template/coding-task-manager/lib/native-lead-settings-client.js +44 -4
- package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +173 -36
- package/template/coding-task-manager/lib/online-backup-worker-client.js +2 -0
- package/template/coding-task-manager/lib/opencode-cli.js +480 -0
- package/template/coding-task-manager/lib/opencode-launch-model.js +471 -0
- package/template/coding-task-manager/lib/opencode-v1-plugin/model-carrier.mjs +35 -0
- package/template/coding-task-manager/lib/opencode-v2-plugin/server.js +118 -0
- package/template/coding-task-manager/lib/owner-action-manifest-data.json +1 -1
- package/template/coding-task-manager/lib/park-composer-draft.js +54 -0
- package/template/coding-task-manager/lib/park-fresh-relaunch.js +41 -10
- package/template/coding-task-manager/lib/perf-phase-trace.js +72 -0
- package/template/coding-task-manager/lib/perf-summary.js +7 -0
- package/template/coding-task-manager/lib/perf-tracker.js +63 -2
- package/template/coding-task-manager/lib/project-config.js +7 -0
- package/template/coding-task-manager/lib/prompt-purge-v2.js +52 -1
- package/template/coding-task-manager/lib/provider-login-broker.js +72 -15
- package/template/coding-task-manager/lib/provider-login-tab.js +10 -2
- package/template/coding-task-manager/lib/read-pool-client.js +89 -2
- package/template/coding-task-manager/lib/read-pool-worker-handle.js +29 -4
- package/template/coding-task-manager/lib/recent-catalog-query.js +15 -0
- package/template/coding-task-manager/lib/recent-session-catalog-response.js +67 -10
- package/template/coding-task-manager/lib/recovery-evidence.js +26 -11
- package/template/coding-task-manager/lib/relay-client.js +57 -3
- package/template/coding-task-manager/lib/relink-audit.js +34 -5
- package/template/coding-task-manager/lib/remote-machine-catalog.js +321 -26
- package/template/coding-task-manager/lib/remote-machine-create.js +576 -36
- package/template/coding-task-manager/lib/remote-machine-link.js +546 -59
- package/template/coding-task-manager/lib/remote-machine-proxy.js +143 -9
- package/template/coding-task-manager/lib/remote-session-forward.js +65 -7
- package/template/coding-task-manager/lib/remote-terminal-target.js +9 -2
- package/template/coding-task-manager/lib/restart-guard.js +18 -6
- package/template/coding-task-manager/lib/restore-seed-modes.js +87 -0
- package/template/coding-task-manager/lib/restored-scrollback-hydration-runtime.js +22 -2
- package/template/coding-task-manager/lib/resume-admission.js +17 -3
- package/template/coding-task-manager/lib/review-repo-registry.js +58 -19
- package/template/coding-task-manager/lib/root-delivery-runtime.js +2 -2
- package/template/coding-task-manager/lib/root-prompt-flow.js +1 -1
- package/template/coding-task-manager/lib/runtime-contract.js +3 -1
- package/template/coding-task-manager/lib/schema-indexes.js +1 -0
- package/template/coding-task-manager/lib/session-context-locator.js +742 -0
- package/template/coding-task-manager/lib/session-create/attachment.js +2 -2
- package/template/coding-task-manager/lib/session-create/create-phases.js +118 -0
- package/template/coding-task-manager/lib/session-create/delegate-worker.js +86 -16
- package/template/coding-task-manager/lib/session-create/dsh-session.js +930 -0
- package/template/coding-task-manager/lib/session-create/lead-route-launch.js +186 -13
- package/template/coding-task-manager/lib/session-create/native-lead-lifetime.js +55 -3
- package/template/coding-task-manager/lib/session-create/opencode-lead-provider.js +50 -0
- package/template/coding-task-manager/lib/session-create/prepared.js +162 -38
- package/template/coding-task-manager/lib/session-create/provider-process.js +160 -28
- package/template/coding-task-manager/lib/session-create/response.js +7 -7
- package/template/coding-task-manager/lib/session-create/structured-session.js +27 -0
- package/template/coding-task-manager/lib/session-create/subagent-pick-table.js +3 -1
- package/template/coding-task-manager/lib/session-create/subagent-pick.js +458 -9
- package/template/coding-task-manager/lib/session-create/transcript.js +1 -1
- package/template/coding-task-manager/lib/session-create/worker-budget-setup.js +21 -2
- package/template/coding-task-manager/lib/session-create/worker-continuation.js +7 -1
- package/template/coding-task-manager/lib/session-create/worker-launch-sandbox.js +39 -10
- package/template/coding-task-manager/lib/session-create/worker-observability.js +2 -2
- package/template/coding-task-manager/lib/session-empty.js +13 -10
- package/template/coding-task-manager/lib/session-host-manager.js +60 -0
- package/template/coding-task-manager/lib/session-jobs.js +3 -1
- package/template/coding-task-manager/lib/session-lifecycle.js +10 -4
- package/template/coding-task-manager/lib/session-model-action-preconditions.js +6 -2
- package/template/coding-task-manager/lib/session-model-intent-contract.js +11 -8
- package/template/coding-task-manager/lib/session-park.js +738 -0
- package/template/coding-task-manager/lib/session-path-grant.js +13 -4
- package/template/coding-task-manager/lib/session-status-ledger.js +106 -4
- package/template/coding-task-manager/lib/session-transport.js +17 -0
- package/template/coding-task-manager/lib/session-workspace-projection.js +54 -5
- package/template/coding-task-manager/lib/session-workspace-tracking-runtime.js +213 -1
- package/template/coding-task-manager/lib/skill-autocomplete-query.js +120 -3
- package/template/coding-task-manager/lib/skill-autocomplete.js +15 -1
- package/template/coding-task-manager/lib/sqlite-file-set.js +39 -0
- package/template/coding-task-manager/lib/standup-search-adapter.js +16 -2
- package/template/coding-task-manager/lib/state-sync/client-ledger.js +12 -0
- package/template/coding-task-manager/lib/state-sync/frame-emitter.js +319 -100
- package/template/coding-task-manager/lib/storage/coding-session-model-store.js +4 -1
- package/template/coding-task-manager/lib/storage/delegate-pref-mirror-store.js +32 -9
- package/template/coding-task-manager/lib/structured-session-boot-sweep.js +235 -0
- package/template/coding-task-manager/lib/submission-ack.js +90 -2
- package/template/coding-task-manager/lib/systemd-scope.js +115 -0
- package/template/coding-task-manager/lib/telemetry-receiver.js +39 -0
- package/template/coding-task-manager/lib/terminal-attach-geometry.js +985 -0
- package/template/coding-task-manager/lib/terminal-bootstrap.js +30 -0
- package/template/coding-task-manager/lib/terminal-draft-operation.js +357 -0
- package/template/coding-task-manager/lib/terminal-input-admission.js +17 -1
- package/template/coding-task-manager/lib/terminal-input-prepared.js +563 -0
- package/template/coding-task-manager/lib/transcript-cache-temps.js +171 -0
- package/template/coding-task-manager/lib/transcript-frame-cache.js +8 -3
- package/template/coding-task-manager/lib/transcript-source.js +36 -4
- package/template/coding-task-manager/lib/transcript-store.js +9 -0
- package/template/coding-task-manager/lib/typing-interaction-trace.js +106 -3
- package/template/coding-task-manager/lib/usage-ledger/backfill.js +40 -5
- package/template/coding-task-manager/lib/usage-ledger/client.js +4 -1
- package/template/coding-task-manager/lib/usage-ledger/dsh-parse.js +152 -0
- package/template/coding-task-manager/lib/usage-ledger/dsh-read.js +73 -0
- package/template/coding-task-manager/lib/usage-ledger/ingest.js +34 -6
- package/template/coding-task-manager/lib/usage-ledger/live.js +35 -8
- package/template/coding-task-manager/lib/usage-ledger/read.js +29 -20
- package/template/coding-task-manager/lib/usage-ledger/schema.js +61 -1
- package/template/coding-task-manager/lib/usage-ledger/task-coordinator.js +4 -2
- package/template/coding-task-manager/lib/wake-skip-reasons.js +1 -0
- package/template/coding-task-manager/lib/walle/turn.js +1 -1
- package/template/coding-task-manager/lib/walle-history-serve.js +838 -0
- package/template/coding-task-manager/lib/walle-kill-authority.js +2 -2
- package/template/coding-task-manager/lib/walle-log-rotation.js +164 -1
- package/template/coding-task-manager/lib/walle-mcp-auto-config.js +56 -2
- package/template/coding-task-manager/lib/walle-row-history.js +495 -0
- package/template/coding-task-manager/lib/walle-supervisor.js +33 -14
- package/template/coding-task-manager/lib/watch-worker-client.js +203 -0
- package/template/coding-task-manager/lib/worker-terminal-output.js +5 -2
- package/template/coding-task-manager/lib/ws/hub-bridge.js +35 -0
- package/template/coding-task-manager/lib/ws/lifecycle.js +6 -11
- package/template/coding-task-manager/lib/ws/misc.js +1 -1
- package/template/coding-task-manager/lib/ws/state-sync.js +106 -4
- package/template/coding-task-manager/lib/ws/terminal-attach.js +91 -0
- package/template/coding-task-manager/oauth-proxy.js +112 -115
- package/template/coding-task-manager/package-lock.json +10 -94
- package/template/coding-task-manager/package.json +4 -3
- package/template/coding-task-manager/protocol/message-table.json +10 -0
- package/template/coding-task-manager/protocol/messages.js +10 -0
- package/template/coding-task-manager/protocol/ws-schema.json +20 -0
- package/template/coding-task-manager/protocol/ws-schema.md +2 -0
- package/template/coding-task-manager/public/css/app.css +170 -0
- package/template/coding-task-manager/public/css/walle-session.css +4 -0
- package/template/coding-task-manager/public/index.html +473 -243
- package/template/coding-task-manager/public/js/agent-command.js +86 -0
- package/template/coding-task-manager/public/js/app/backups-panel.js +111 -5
- package/template/coding-task-manager/public/js/app/coding-agent-upgrade.js +103 -4
- package/template/coding-task-manager/public/js/app/machines-page.js +1328 -8
- package/template/coding-task-manager/public/js/app/models-tab.js +113 -38
- package/template/coding-task-manager/public/js/app/restart-banner.js +53 -0
- package/template/coding-task-manager/public/js/app/worktrees-panel.js +184 -2
- package/template/coding-task-manager/public/js/background-work.js +196 -27
- package/template/coding-task-manager/public/js/coding-session-model-presentation.js +138 -10
- package/template/coding-task-manager/public/js/core/active-session-groups.js +179 -0
- package/template/coding-task-manager/public/js/core/pending-create.js +238 -0
- package/template/coding-task-manager/public/js/core/recent-machine-source.js +228 -0
- package/template/coding-task-manager/public/js/core/session-commands.js +180 -14
- package/template/coding-task-manager/public/js/dashboard-client-function-order.json +8 -0
- package/template/coding-task-manager/public/js/document-review-links.js +9 -1
- package/template/coding-task-manager/public/js/file-resolution-client.js +52 -1
- package/template/coding-task-manager/public/js/history-app-seam.js +22 -2
- package/template/coding-task-manager/public/js/machine-pool-shared.js +558 -3
- package/template/coding-task-manager/public/js/message-renderer.js +4 -3
- package/template/coding-task-manager/public/js/model-picker.js +3 -1
- package/template/coding-task-manager/public/js/native-subagent-review.js +15 -2
- package/template/coding-task-manager/public/js/new-session-agent-grid.js +346 -0
- package/template/coding-task-manager/public/js/pasted-content.js +236 -0
- package/template/coding-task-manager/public/js/pe-private-content-lifetime.js +39 -7
- package/template/coding-task-manager/public/js/private-model-lifetime.js +6 -2
- package/template/coding-task-manager/public/js/prompts.js +11 -10
- package/template/coding-task-manager/public/js/protocol.js +10 -0
- package/template/coding-task-manager/public/js/restart-banner-policy.js +29 -1
- package/template/coding-task-manager/public/js/session-model-picker.js +447 -57
- package/template/coding-task-manager/public/js/session-search-utils.js +18 -2
- package/template/coding-task-manager/public/js/session-usage-view.js +2 -1
- package/template/coding-task-manager/public/js/session-workspace-tracking.js +51 -1
- package/template/coding-task-manager/public/js/state-sync-client.js +10 -1
- package/template/coding-task-manager/public/js/stream-view.js +6 -5
- package/template/coding-task-manager/public/js/terminal/conv-only-session.js +287 -0
- package/template/coding-task-manager/public/js/terminal/runtime-09.js +2 -0
- package/template/coding-task-manager/public/js/terminal/runtime-10.js +5 -5
- package/template/coding-task-manager/public/js/terminal/runtime-12.js +8 -1
- package/template/coding-task-manager/public/js/terminal/runtime-16.js +4 -0
- package/template/coding-task-manager/public/js/terminal/runtime-20.js +1 -1
- package/template/coding-task-manager/public/js/terminal-attachment-draft.js +67 -13
- package/template/coding-task-manager/public/js/terminal-image-orchestration.js +52 -6
- package/template/coding-task-manager/public/js/terminal-wheel.js +23 -3
- package/template/coding-task-manager/public/js/walle/chat-conversation.js +4 -4
- package/template/coding-task-manager/public/js/walle.js +6 -0
- package/template/coding-task-manager/public/js/ws/live-dispatch.js +10 -0
- package/template/coding-task-manager/public/m/app.css +140 -0
- package/template/coding-task-manager/public/m/app.js +507 -68
- package/template/coding-task-manager/public/m/index.html +17 -0
- package/template/coding-task-manager/public/m/machines.js +838 -4
- package/template/coding-task-manager/public/m/private-api.js +31 -1
- package/template/coding-task-manager/public/usage.html +1 -0
- package/template/coding-task-manager/scripts/dev-route-fixture/synthetic-route-upstream.js +104 -0
- package/template/coding-task-manager/scripts/dev-route-fixture.js +339 -0
- package/template/coding-task-manager/scripts/opencode-lead-route-probe.js +195 -0
- package/template/coding-task-manager/scripts/opencode-v2-contract-probe/fake-anthropic.js +168 -0
- package/template/coding-task-manager/scripts/opencode-v2-contract-probe/probe-plugin/server.js +95 -0
- package/template/coding-task-manager/scripts/opencode-v2-contract-probe.js +673 -0
- package/template/coding-task-manager/server.js +1950 -11679
- package/template/coding-task-manager/session-integrity.js +64 -4
- package/template/coding-task-manager/workers/db-owner-worker.js +19 -2
- package/template/coding-task-manager/workers/online-backup-worker.js +1 -0
- package/template/coding-task-manager/workers/read-pool-model-collector.js +51 -3
- package/template/coding-task-manager/workers/read-pool-worker.js +76 -6
- package/template/coding-task-manager/workers/session-host-pool-process.js +6 -6
- package/template/coding-task-manager/workers/session-host-process.js +5 -5
- package/template/coding-task-manager/workers/watch-worker.js +105 -0
- package/template/coding-task-manager/workers/worktree-worker.js +168 -33
- package/template/ctm-relay/lib/protocol.js +14 -1
- package/template/package-lock.json +2 -2
- package/template/package.json +1 -1
- package/template/shared/backup-retention.js +82 -0
- package/template/shared/conversation-admission.js +2 -0
- package/template/shared/conversation-provenance.js +3 -7
- package/template/shared/conversation-storage-v2-contract.js +5 -0
- package/template/shared/linked-worktree.js +51 -0
- package/template/shared/macos-fda-target.js +57 -0
- package/template/shared/orphan-parents.js +55 -0
- package/template/shared/release-schema.json +2 -2
- package/template/shared/storage/contract.js +3 -0
- package/template/wall-e/adapters/imessage.js +12 -0
- package/template/wall-e/agent.js +71 -4
- package/template/wall-e/api-walle.js +21 -1
- package/template/wall-e/bin/train-gemma-e4b-tooluse.js +2 -2
- package/template/wall-e/bin/walle-mcp-stdio.js +5 -5
- package/template/wall-e/brain/brain-join.js +859 -0
- package/template/wall-e/brain/brain-sync-membership.js +20 -0
- package/template/wall-e/brain/brain-sync-store.js +131 -77
- package/template/wall-e/brain/brain-sync-v2-schema.js +203 -0
- package/template/wall-e/brain/brain-sync-v2.js +578 -0
- package/template/wall-e/brain/delegate-model-prefs-store.js +13 -4
- package/template/wall-e/brain/deterministic-id.js +51 -0
- package/template/wall-e/brain/memories.js +2 -2
- package/template/wall-e/brain/migrations.js +40 -6
- package/template/wall-e/brain/model-identity-migration.js +1 -1
- package/template/wall-e/brain/owner-call-whitelist.js +23 -7
- package/template/wall-e/brain/schema.js +45 -85
- package/template/wall-e/brain.js +171 -35
- package/template/wall-e/chat.js +3 -3
- package/template/wall-e/coding/agent-shell-env.js +7 -2
- package/template/wall-e/coding/prompt-bundle.js +66 -21
- package/template/wall-e/config/main-loop-exemptions.json +2 -2
- package/template/wall-e/http/admin-api.js +37 -0
- package/template/wall-e/http/gws-api.js +4 -0
- package/template/wall-e/http/helper-rules-api.js +7 -1
- package/template/wall-e/http/model-admin.js +8 -1
- package/template/wall-e/http/model-delegate-pref-admin.js +3 -3
- package/template/wall-e/http/model-proxy-providers.js +174 -1
- package/template/wall-e/http/model-route-health.js +157 -0
- package/template/wall-e/http/sync-api.js +278 -464
- package/template/wall-e/jobs/job-runner.js +28 -1
- package/template/wall-e/lib/brain-owner-op-priority.js +9 -1
- package/template/wall-e/lib/ctm-data-dir.js +26 -0
- package/template/wall-e/lib/derivation-home.js +147 -0
- package/template/wall-e/lib/helper-rules/evaluate.js +50 -9
- package/template/wall-e/lib/helper-rules/grade.js +51 -8
- package/template/wall-e/lib/helper-rules/lead-call-hook.js +38 -5
- package/template/wall-e/lib/helper-rules/store.js +4 -2
- package/template/wall-e/lib/landing-mcp-tools.js +170 -0
- package/template/wall-e/lib/mcp-client-config.js +133 -14
- package/template/wall-e/lib/mcp-gateway-migrate.js +28 -64
- package/template/wall-e/lib/mcp-integration.js +58 -13
- package/template/wall-e/lib/parent-death.js +7 -1
- package/template/wall-e/lib/scheduler-worker-jobs.js +6 -4
- package/template/wall-e/llm/delegate-lead-prefs.js +16 -1
- package/template/wall-e/llm/delegate-vendor-default.js +12 -5
- package/template/wall-e/llm/model-roles.js +1 -1
- package/template/wall-e/llm/native-delegate-selection.js +56 -8
- package/template/wall-e/llm/worker-delegate-args.js +72 -7
- package/template/wall-e/llm/worker-mcp-routing.js +66 -17
- package/template/wall-e/loops/brain-custom-writes.js +19 -0
- package/template/wall-e/loops/ingest.js +17 -1
- package/template/wall-e/loops/think.js +19 -4
- package/template/wall-e/mcp-contract.js +222 -6
- package/template/wall-e/mcp-protocol-contract.js +40 -5
- package/template/wall-e/mcp-server.js +128 -29
- package/template/wall-e/mcp-worker-schemas.js +5 -5
- package/template/wall-e/memory/ctm-context-client.js +85 -0
- package/template/wall-e/memory/ctm-session-context.js +9 -3
- package/template/wall-e/memory/hub-bridge-client.js +388 -0
- package/template/wall-e/memory/replica-origin.js +56 -0
- package/template/wall-e/memory/session-pool.js +135 -0
- package/template/wall-e/runtime/live-capabilities.js +8 -3
- package/template/wall-e/server.js +4 -2
- package/template/wall-e/shared/release-schema.json +2 -2
- package/template/wall-e/shared/sqlite-file-set.js +39 -0
- package/template/wall-e/shared/storage/contract.js +3 -0
- package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
- package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +7 -1
- package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +4 -4
- package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +6 -6
- package/template/wall-e/skills/_templates/fleet-sync/VERSION +1 -0
- package/template/wall-e/skills/_templates/fleet-sync/released-sha256.json +16 -0
- package/template/wall-e/skills/_templates/fleet-sync/templates/claude/SKILL.md +64 -0
- package/template/wall-e/skills/_templates/fleet-sync/templates/codex/SKILL.md +64 -0
- package/template/wall-e/tools/chrome-automation.js +6 -0
- package/template/wall-e/training/setup-cuda-train-env.sh +2 -2
- package/template/wall-e/workers/brain-owner-worker.js +18 -4
- package/template/wall-e/workers/mcp-gateway-process.js +5 -5
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-3 (docs/remote-session-hub-bridge-2026-10-30.html §3 P-2, D-4/D-7, §5 R-4, §6.4, §8 WP-3):
|
|
4
|
+
// the HUB-side MCP passthrough — the hub's OWN MCP servers, reached by a box session over the
|
|
5
|
+
// WP-0 lane (lib/hub-bridge.js `handleRequest` -> lib/hub-bridge-ops.js `mcp.list` / `mcp.call`).
|
|
6
|
+
//
|
|
7
|
+
// Two servers are reachable, and only two:
|
|
8
|
+
//
|
|
9
|
+
// playwright the Wall-E MCP gateway's Playwright server, over Streamable HTTP at
|
|
10
|
+
// http://127.0.0.1:<WALLE_MCP_GATEWAY_PORT|3458>/mcp/playwright. A SEPARATE
|
|
11
|
+
// automation browser — good for localhost pages, never the user's own Chrome.
|
|
12
|
+
// chrome-devtools chrome-devtools-mcp --autoConnect, spawned with `npx`, attached to the USER'S
|
|
13
|
+
// real Chrome (Chrome asks the user to allow the attach the first time).
|
|
14
|
+
//
|
|
15
|
+
// Everything else is `mcp_server_not_allowed`: the allowlist is a frozen constant
|
|
16
|
+
// (`serverConfigs(env)`), NOT a caller-supplied command line, so a box can never talk this process
|
|
17
|
+
// into spawning an arbitrary server (that is `exec`'s job, behind its own, separate grant).
|
|
18
|
+
//
|
|
19
|
+
// D-4/§6.4: ONE connected MCP `Client` per server name, created LAZILY on first use and closed
|
|
20
|
+
// after `idleMs` (default 10 min) of inactivity or by `closeAll()` (server.js shutdown). The idle
|
|
21
|
+
// timer is unref'd so it never keeps the process alive. The connect is bounded by `connectTimeoutMs`
|
|
22
|
+
// (default 15 s): a dead gateway answers `mcp_server_unavailable` quickly instead of hanging the
|
|
23
|
+
// box's tool call until its own deadline.
|
|
24
|
+
//
|
|
25
|
+
// The SDK is required LAZILY (see `_sdk`) so a node_modules hiccup cannot break CTM boot, and every
|
|
26
|
+
// seam is injectable (`configure`) — the integration test drives a stub Streamable-HTTP server on a
|
|
27
|
+
// leased port and NEVER touches the real 3458 gateway or a real Chrome.
|
|
28
|
+
|
|
29
|
+
const DEFAULT_GATEWAY_PORT = 3458;
|
|
30
|
+
const DEFAULT_CONNECT_TIMEOUT_MS = 15000;
|
|
31
|
+
const DEFAULT_IDLE_MS = 10 * 60 * 1000;
|
|
32
|
+
// §6.4/R-8: the lane caps a result at 1 MiB. The bridge enforces the same ceiling a second time
|
|
33
|
+
// (lib/hub-bridge.js `_capResult`), but truncating HERE is what keeps an oversized image payload
|
|
34
|
+
// from ever being serialised into a frame — the module says `truncated: true` when it does.
|
|
35
|
+
const RESULT_MAX_BYTES = 1024 * 1024;
|
|
36
|
+
|
|
37
|
+
// OQ-1/anti-goal: "chrome-devtools-mcp version drift. Pin the version, and update it deliberately."
|
|
38
|
+
// `npm view chrome-devtools-mcp version` -> 1.12.0 at the time of writing (2026-10-30).
|
|
39
|
+
const CHROME_DEVTOOLS_MCP_VERSION = '1.12.0';
|
|
40
|
+
|
|
41
|
+
const GLOBAL_TIMERS = Object.freeze({
|
|
42
|
+
setTimeout: (fn, ms) => setTimeout(fn, ms),
|
|
43
|
+
clearTimeout: (timer) => clearTimeout(timer),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/** The gateway port: `WALLE_MCP_GATEWAY_PORT` when it parses to a positive number, else 3458. */
|
|
47
|
+
function resolveGatewayPort(env = process.env) {
|
|
48
|
+
const port = Number(env && env.WALLE_MCP_GATEWAY_PORT);
|
|
49
|
+
return Number.isFinite(port) && port > 0 ? port : DEFAULT_GATEWAY_PORT;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The frozen allowlist (§6.4). Built from `env` so the gateway port is never hard-coded at the call
|
|
54
|
+
* site; tests inject their own `serverConfigs` instead of overriding this one.
|
|
55
|
+
*/
|
|
56
|
+
function serverConfigs(env = process.env) {
|
|
57
|
+
const port = resolveGatewayPort(env);
|
|
58
|
+
return Object.freeze({
|
|
59
|
+
playwright: Object.freeze({
|
|
60
|
+
transport: 'http',
|
|
61
|
+
url: `http://127.0.0.1:${port}/mcp/playwright`,
|
|
62
|
+
}),
|
|
63
|
+
'chrome-devtools': Object.freeze({
|
|
64
|
+
transport: 'stdio',
|
|
65
|
+
command: 'npx',
|
|
66
|
+
args: Object.freeze(['-y', `chrome-devtools-mcp@${CHROME_DEVTOOLS_MCP_VERSION}`, '--autoConnect']),
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const DEFAULT_DEPS = {
|
|
72
|
+
env: process.env,
|
|
73
|
+
// (env) => frozen allowlist. Tests replace this to point at a stub server.
|
|
74
|
+
serverConfigs: null,
|
|
75
|
+
connectTimeoutMs: DEFAULT_CONNECT_TIMEOUT_MS,
|
|
76
|
+
idleMs: DEFAULT_IDLE_MS,
|
|
77
|
+
timers: GLOBAL_TIMERS,
|
|
78
|
+
// { Client, StreamableHTTPClientTransport, StdioClientTransport } — injected by tests, else lazily
|
|
79
|
+
// required from @modelcontextprotocol/sdk (see `_sdk`).
|
|
80
|
+
sdk: null,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
let _deps = null;
|
|
84
|
+
// name -> { client, transport, timer, connecting }. One connected client per server name.
|
|
85
|
+
const _entries = new Map();
|
|
86
|
+
let _sdkCache = null;
|
|
87
|
+
|
|
88
|
+
function configure(deps = {}) {
|
|
89
|
+
_deps = { ...DEFAULT_DEPS, ...(deps || {}) };
|
|
90
|
+
return _deps;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function _d() {
|
|
94
|
+
return _deps || DEFAULT_DEPS;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function _timers() {
|
|
98
|
+
const timers = _d().timers;
|
|
99
|
+
return timers && typeof timers.setTimeout === 'function' ? timers : GLOBAL_TIMERS;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The allowlist in force: the injected builder when present, else `serverConfigs(env)`. */
|
|
103
|
+
function _configs() {
|
|
104
|
+
const d = _d();
|
|
105
|
+
const build = typeof d.serverConfigs === 'function' ? d.serverConfigs : serverConfigs;
|
|
106
|
+
const configs = build(d.env);
|
|
107
|
+
return configs && typeof configs === 'object' ? configs : {};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The SDK, required lazily and memoized; tests inject `deps.sdk` instead. */
|
|
111
|
+
function _sdk() {
|
|
112
|
+
const injected = _d().sdk;
|
|
113
|
+
if (injected && typeof injected === 'object') return injected;
|
|
114
|
+
if (!_sdkCache) {
|
|
115
|
+
_sdkCache = {
|
|
116
|
+
Client: require('@modelcontextprotocol/sdk/client/index.js').Client,
|
|
117
|
+
StreamableHTTPClientTransport: require('@modelcontextprotocol/sdk/client/streamableHttp.js').StreamableHTTPClientTransport,
|
|
118
|
+
StdioClientTransport: require('@modelcontextprotocol/sdk/client/stdio.js').StdioClientTransport,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return _sdkCache;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** A refusal as this module speaks it: the code rides the error, never the message. */
|
|
125
|
+
function _namedError(code, message) {
|
|
126
|
+
return Object.assign(new Error(message), { code });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** `mcp_server_unavailable` carrying the FIRST LINE of the underlying error (§6.3). */
|
|
130
|
+
function _unavailable(name, error) {
|
|
131
|
+
const raw = String((error && error.message) || error || 'unknown error');
|
|
132
|
+
const firstLine = raw.split('\n')[0].trim().slice(0, 300);
|
|
133
|
+
return _namedError('mcp_server_unavailable', `${name}: ${firstLine || 'connection failed'}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function _createTransport(config, sdk) {
|
|
137
|
+
const kind = String((config && config.transport) || '');
|
|
138
|
+
if (kind === 'http') {
|
|
139
|
+
return new sdk.StreamableHTTPClientTransport(new URL(String(config.url)));
|
|
140
|
+
}
|
|
141
|
+
if (kind === 'stdio') {
|
|
142
|
+
const args = Array.isArray(config.args) ? [...config.args] : [];
|
|
143
|
+
return new sdk.StdioClientTransport({ command: String(config.command), args });
|
|
144
|
+
}
|
|
145
|
+
throw _namedError('mcp_server_unavailable', `unsupported transport: ${kind || '(none)'}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Unref a timer when the runtime gave us a real one (a fake timer in a test simply has no unref). */
|
|
149
|
+
function _unref(timer) {
|
|
150
|
+
if (timer && typeof timer.unref === 'function') {
|
|
151
|
+
try { timer.unref(); } catch { /* expected: a fake timer has no unref */ }
|
|
152
|
+
}
|
|
153
|
+
return timer;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** (Re)arm the idle close for `name`. Every successful use pushes the close out by `idleMs`. */
|
|
157
|
+
function _armIdle(name, entry) {
|
|
158
|
+
const { setTimeout: setT, clearTimeout: clrT } = _timers();
|
|
159
|
+
if (entry.timer) {
|
|
160
|
+
try { clrT(entry.timer); } catch { /* expected: the timer already fired */ }
|
|
161
|
+
}
|
|
162
|
+
const idleMs = Math.max(1, Number(_d().idleMs) || DEFAULT_IDLE_MS);
|
|
163
|
+
entry.timer = _unref(setT(() => {
|
|
164
|
+
// Idle close is fire-and-forget: a failure here must never surface as an unhandled rejection.
|
|
165
|
+
closeServer(name).catch(() => { /* expected: a client that already died has nothing to close */ });
|
|
166
|
+
}, idleMs));
|
|
167
|
+
return entry.timer;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Connect one MCP client for `name`. The connect is raced against `connectTimeoutMs` so a dead or
|
|
172
|
+
* unresponsive server answers `mcp_server_unavailable` at the deadline rather than hanging.
|
|
173
|
+
*/
|
|
174
|
+
async function _connect(name, config, entry) {
|
|
175
|
+
const sdk = _sdk();
|
|
176
|
+
const { setTimeout: setT, clearTimeout: clrT } = _timers();
|
|
177
|
+
let transport;
|
|
178
|
+
try {
|
|
179
|
+
transport = _createTransport(config, sdk);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
throw _unavailable(name, error);
|
|
182
|
+
}
|
|
183
|
+
const client = new sdk.Client({ name: 'ctm-hub-bridge', version: '1.0.0' }, { capabilities: {} });
|
|
184
|
+
const timeoutMs = Math.max(1, Number(_d().connectTimeoutMs) || DEFAULT_CONNECT_TIMEOUT_MS);
|
|
185
|
+
let timer = null;
|
|
186
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
187
|
+
timer = _unref(setT(() => reject(_namedError('mcp_server_unavailable', `${name}: connect timed out after ${timeoutMs} ms`)), timeoutMs));
|
|
188
|
+
});
|
|
189
|
+
try {
|
|
190
|
+
await Promise.race([client.connect(transport), timeout]);
|
|
191
|
+
} catch (error) {
|
|
192
|
+
try { clrT(timer); } catch { /* expected: the timeout already fired */ }
|
|
193
|
+
try { await client.close(); } catch { /* expected: a failed connect may leave nothing to close */ }
|
|
194
|
+
try { await transport.close(); } catch { /* expected: the transport may already be closed */ }
|
|
195
|
+
throw error && error.code === 'mcp_server_unavailable' ? error : _unavailable(name, error);
|
|
196
|
+
}
|
|
197
|
+
try { clrT(timer); } catch { /* expected: the timeout already cleared itself */ }
|
|
198
|
+
entry.client = client;
|
|
199
|
+
entry.transport = transport;
|
|
200
|
+
_armIdle(name, entry);
|
|
201
|
+
return client;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* The connected client for `server`, connecting lazily on first use. Unknown server ->
|
|
206
|
+
* `mcp_server_not_allowed`; a connect/spawn failure -> `mcp_server_unavailable`.
|
|
207
|
+
*/
|
|
208
|
+
async function _clientFor(server) {
|
|
209
|
+
const name = String(server || '');
|
|
210
|
+
const config = _configs()[name];
|
|
211
|
+
if (!config) {
|
|
212
|
+
throw _namedError('mcp_server_not_allowed', `MCP server '${name || '(none)'}' is not in the hub allowlist`);
|
|
213
|
+
}
|
|
214
|
+
const existing = _entries.get(name);
|
|
215
|
+
if (existing) {
|
|
216
|
+
if (existing.client) {
|
|
217
|
+
_armIdle(name, existing);
|
|
218
|
+
return existing.client;
|
|
219
|
+
}
|
|
220
|
+
if (existing.connecting) return existing.connecting;
|
|
221
|
+
}
|
|
222
|
+
const fresh = existing || { client: null, transport: null, timer: null, connecting: null };
|
|
223
|
+
_entries.set(name, fresh);
|
|
224
|
+
fresh.connecting = _connect(name, config, fresh);
|
|
225
|
+
try {
|
|
226
|
+
const client = await fresh.connecting;
|
|
227
|
+
fresh.connecting = null;
|
|
228
|
+
return client;
|
|
229
|
+
} catch (error) {
|
|
230
|
+
fresh.connecting = null;
|
|
231
|
+
// A failed connect must not leave a half-open entry behind: the next call retries from scratch.
|
|
232
|
+
if (_entries.get(name) === fresh) _entries.delete(name);
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* A THROWN list/call error is a protocol or transport failure (a tool's own failure comes back as
|
|
239
|
+
* `isError` in the result), so the cached client is dead: drop it, so the next call reconnects
|
|
240
|
+
* instead of reusing it until the idle close (which every retry would keep pushing out).
|
|
241
|
+
*/
|
|
242
|
+
async function _evict(name, client) {
|
|
243
|
+
const entry = _entries.get(name);
|
|
244
|
+
if (entry && entry.client === client) await closeServer(name);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** §6.4: the tool list of one allowlisted server, projected to `{name, description, inputSchema}`. */
|
|
248
|
+
async function listTools(server) {
|
|
249
|
+
const client = await _clientFor(server);
|
|
250
|
+
let result;
|
|
251
|
+
try {
|
|
252
|
+
result = await client.listTools();
|
|
253
|
+
} catch (error) {
|
|
254
|
+
await _evict(String(server || ''), client);
|
|
255
|
+
throw _unavailable(String(server || ''), error);
|
|
256
|
+
}
|
|
257
|
+
const tools = result && Array.isArray(result.tools) ? result.tools : [];
|
|
258
|
+
return tools.map((tool) => ({
|
|
259
|
+
name: String((tool && tool.name) || ''),
|
|
260
|
+
description: tool && tool.description !== undefined ? String(tool.description) : undefined,
|
|
261
|
+
inputSchema: (tool && tool.inputSchema) || { type: 'object' },
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* §6.4: call one tool on one allowlisted server, returning the MCP result `{content, isError}` with
|
|
267
|
+
* `truncated: true` when the 1 MiB ceiling forced a payload to be trimmed or dropped.
|
|
268
|
+
*/
|
|
269
|
+
async function callTool(server, tool, args) {
|
|
270
|
+
const name = String(server || '');
|
|
271
|
+
const client = await _clientFor(name);
|
|
272
|
+
const toolName = String(tool || '');
|
|
273
|
+
if (!toolName) throw _namedError('mcp_tool_required', 'tool is required');
|
|
274
|
+
let result;
|
|
275
|
+
try {
|
|
276
|
+
result = await client.callTool({
|
|
277
|
+
name: toolName,
|
|
278
|
+
arguments: args && typeof args === 'object' ? args : {},
|
|
279
|
+
});
|
|
280
|
+
} catch (error) {
|
|
281
|
+
await _evict(name, client);
|
|
282
|
+
throw _unavailable(name, error);
|
|
283
|
+
}
|
|
284
|
+
return _capResult(result);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* The 1 MiB ceiling (§6.4/R-8). Under the cap the result is returned as-is; over it, content items
|
|
289
|
+
* are kept while they fit, a `text` item is sliced to fit, and anything else (image/blob data) is
|
|
290
|
+
* dropped — `truncated: true` says so, and the bridge's own `_capResult` is the second guard.
|
|
291
|
+
*/
|
|
292
|
+
function _capResult(result) {
|
|
293
|
+
const value = result && typeof result === 'object' ? result : {};
|
|
294
|
+
const isError = value.isError === true;
|
|
295
|
+
const content = Array.isArray(value.content) ? value.content : [];
|
|
296
|
+
let serialised;
|
|
297
|
+
try {
|
|
298
|
+
serialised = JSON.stringify({ ...value, content });
|
|
299
|
+
} catch {
|
|
300
|
+
// REPORT via truncated: a non-serialisable result can never cross the lane — say so rather than throw.
|
|
301
|
+
return { content: [{ type: 'text', text: '[hub MCP result was not serialisable]' }], isError, truncated: true };
|
|
302
|
+
}
|
|
303
|
+
if (Buffer.byteLength(serialised, 'utf8') <= RESULT_MAX_BYTES) return { content, isError };
|
|
304
|
+
|
|
305
|
+
const out = { content: [], isError, truncated: true };
|
|
306
|
+
let used = Buffer.byteLength(JSON.stringify(out), 'utf8');
|
|
307
|
+
for (const item of content) {
|
|
308
|
+
let text;
|
|
309
|
+
try {
|
|
310
|
+
text = JSON.stringify(item);
|
|
311
|
+
} catch {
|
|
312
|
+
continue; // expected: an unserialisable item is dropped; `truncated` already carries the truth
|
|
313
|
+
}
|
|
314
|
+
const bytes = Buffer.byteLength(text, 'utf8');
|
|
315
|
+
const budget = RESULT_MAX_BYTES - used;
|
|
316
|
+
if (bytes <= budget) {
|
|
317
|
+
out.content.push(item);
|
|
318
|
+
used += bytes;
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
if (item && item.type === 'text' && typeof item.text === 'string' && budget > 128) {
|
|
322
|
+
const narrowed = { ...item, text: Buffer.from(item.text, 'utf8').subarray(0, budget - 128).toString('utf8') };
|
|
323
|
+
out.content.push(narrowed);
|
|
324
|
+
used += Buffer.byteLength(JSON.stringify(narrowed), 'utf8');
|
|
325
|
+
}
|
|
326
|
+
// An image/blob item that cannot fit is dropped whole (its bytes are not sliceable mid-base64).
|
|
327
|
+
}
|
|
328
|
+
return out;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** Close the client for `name`, clearing its idle timer. Safe to call when nothing is open. */
|
|
332
|
+
async function closeServer(server) {
|
|
333
|
+
const name = String(server || '');
|
|
334
|
+
const entry = _entries.get(name);
|
|
335
|
+
if (!entry) return false;
|
|
336
|
+
_entries.delete(name);
|
|
337
|
+
if (entry.timer) {
|
|
338
|
+
try { _timers().clearTimeout(entry.timer); } catch { /* expected: the timer already fired */ }
|
|
339
|
+
}
|
|
340
|
+
try { if (entry.client) await entry.client.close(); } catch { /* expected: a dead client has nothing to close */ }
|
|
341
|
+
try { if (entry.transport && typeof entry.transport.close === 'function') await entry.transport.close(); } catch { /* expected: the transport may already be closed */ }
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** Close every connected client (server.js shutdown). Never throws. */
|
|
346
|
+
async function closeAll() {
|
|
347
|
+
const names = [..._entries.keys()];
|
|
348
|
+
// closeServer() already swallows everything it can; the extra guard is only so one dead client
|
|
349
|
+
// can never stop the others from closing.
|
|
350
|
+
await Promise.all(names.map((name) => closeServer(name).catch(() => { /* expected: one dead client must not block the rest */ })));
|
|
351
|
+
_entries.clear();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** Open server names — the test seam for "the client really closed after idle". */
|
|
355
|
+
function _openServers() {
|
|
356
|
+
return [..._entries.keys()].sort();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function _resetForTests() {
|
|
360
|
+
_deps = null;
|
|
361
|
+
_entries.clear();
|
|
362
|
+
_sdkCache = null;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
module.exports = {
|
|
366
|
+
CHROME_DEVTOOLS_MCP_VERSION,
|
|
367
|
+
DEFAULT_CONNECT_TIMEOUT_MS,
|
|
368
|
+
DEFAULT_GATEWAY_PORT,
|
|
369
|
+
DEFAULT_IDLE_MS,
|
|
370
|
+
RESULT_MAX_BYTES,
|
|
371
|
+
resolveGatewayPort,
|
|
372
|
+
serverConfigs,
|
|
373
|
+
configure,
|
|
374
|
+
listTools,
|
|
375
|
+
callTool,
|
|
376
|
+
closeServer,
|
|
377
|
+
closeAll,
|
|
378
|
+
_openServers,
|
|
379
|
+
_resetForTests,
|
|
380
|
+
};
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-5 (docs/remote-session-hub-bridge-2026-10-30.html §5 R-12, D-9, §8 AC-5a): trusted-pool mesh links.
|
|
4
|
+
//
|
|
5
|
+
// R-12 says the bridge is SYMMETRIC: a member's role (hub or box) comes only from who opened the
|
|
6
|
+
// CONTROL link, so any member can serve a request. This module is what makes that reachable without a
|
|
7
|
+
// new listener: the CTM keeps a CONTROL link OPEN (lib/remote-machine-link.js `openLink`) to every
|
|
8
|
+
// pool member it TRUSTS ('always', R-11 — a per-machine row, else the file default), so every trusted
|
|
9
|
+
// member can ask every other one.
|
|
10
|
+
//
|
|
11
|
+
// reconcile() desired = rosterMembers() - self, filtered by trustFor(id) === 'always'
|
|
12
|
+
// for each desired member whose registry row is enabled + reachable:
|
|
13
|
+
// openLink(row) (idempotent — the link module reuses a live control link)
|
|
14
|
+
// fetchCatalog(row) once, so the R-2 ownership check knows that member's sessions
|
|
15
|
+
// links THIS module opened for a member that is no longer desired are closed
|
|
16
|
+
// (closeLink); a link it did not open is never touched.
|
|
17
|
+
// A member that cannot be reached simply has no link — the 30 s tick is the retry,
|
|
18
|
+
// so there is no per-member reconnect storm here.
|
|
19
|
+
//
|
|
20
|
+
// The link module's own 60 s idle close is a non-issue by construction: reconcile runs every 30 s and
|
|
21
|
+
// each fetchCatalog is a catalog user, which clears the idle timer. A machine that the operator stops
|
|
22
|
+
// trusting drops out of the desired set on the next tick and its link is closed by us at once; an
|
|
23
|
+
// untrusted-by-default pool (the default 'ask') opens nothing at all (D-9: "an untrusted one keeps
|
|
24
|
+
// per-session asks").
|
|
25
|
+
//
|
|
26
|
+
// Everything is injectable so the unit test never boots a server, opens a socket or reads a db.
|
|
27
|
+
|
|
28
|
+
const { boolFlag, registerFlag } = require('./env-flags');
|
|
29
|
+
|
|
30
|
+
const MESH_INTERVAL_MS = 30000;
|
|
31
|
+
|
|
32
|
+
// Default ON (design §5 R-12 / §8 WP-5): `CTM_HUB_BRIDGE_MESH=0` keeps the pre-WP-5 behaviour — only
|
|
33
|
+
// the links something else asked for (a viewer, a create, a catalog sweep) exist.
|
|
34
|
+
registerFlag('CTM_HUB_BRIDGE_MESH', {
|
|
35
|
+
kind: 'bool',
|
|
36
|
+
default: true,
|
|
37
|
+
desc: 'Trusted-pool mesh links: keep a CONTROL link to every pool member this machine trusts (\'always\'), so any trusted member can serve any other (docs/remote-session-hub-bridge-2026-10-30.html R-12); 0 disables the mesh',
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
function _string(value, max = 200) {
|
|
41
|
+
return String(value == null ? '' : value).trim().slice(0, max);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Production defaults. Every one of them is a lazy require, so a unit test that injects its own deps
|
|
45
|
+
// never loads the link/catalog/pool modules (the lib/** dependency-cycle ceiling and the hermetic rule).
|
|
46
|
+
const DEFAULT_DEPS = {
|
|
47
|
+
selfInstanceId: () => '',
|
|
48
|
+
rosterMembers: () => require('./api/machine-pool').rosterMembers(),
|
|
49
|
+
trustFor: (machineInstanceId) => require('./hub-bridge-access').trustFor(machineInstanceId),
|
|
50
|
+
machineRow: (machineInstanceId) => {
|
|
51
|
+
const rows = require('./remote-machine-catalog').machineRows() || [];
|
|
52
|
+
const id = _string(machineInstanceId, 160);
|
|
53
|
+
return rows.find((row) => _string(row && row.instanceId, 160) === id) || null;
|
|
54
|
+
},
|
|
55
|
+
openLink: (row, deps) => require('./remote-machine-link').openLink(row, deps),
|
|
56
|
+
linkFor: (machineInstanceId) => require('./remote-machine-link').linkFor(machineInstanceId),
|
|
57
|
+
closeLink: (link, deps) => require('./remote-machine-link').closeLink(link, deps),
|
|
58
|
+
fetchCatalog: (machineOrId, deps) => require('./remote-machine-catalog').fetchCatalog(machineOrId, deps),
|
|
59
|
+
setIntervalFn: (fn, ms) => setInterval(fn, ms),
|
|
60
|
+
clearIntervalFn: (timer) => clearInterval(timer),
|
|
61
|
+
log: console,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
let _deps = null;
|
|
65
|
+
let _timer = null;
|
|
66
|
+
// machineInstanceId -> the link THIS module opened. Only rows in here are ever closed by stop() or
|
|
67
|
+
// by a reconcile that no longer desires the member (R-12: never close a link we did not open).
|
|
68
|
+
const _opened = new Map();
|
|
69
|
+
|
|
70
|
+
function configure(deps = {}) {
|
|
71
|
+
_deps = { ...DEFAULT_DEPS, ...(deps || {}) };
|
|
72
|
+
return _deps;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function _d() {
|
|
76
|
+
return _deps || DEFAULT_DEPS;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Flag read at CALL time so a test can flip CTM_HUB_BRIDGE_MESH per-process (never at module load). */
|
|
80
|
+
function isEnabled() {
|
|
81
|
+
return boolFlag('CTM_HUB_BRIDGE_MESH', true);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function _report(message) {
|
|
85
|
+
const log = _d().log;
|
|
86
|
+
if (log && typeof log.warn === 'function') log.warn(`[hub-bridge-mesh] ${message}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The members this machine wants a mesh link to: every OTHER pool member whose trust is 'always'
|
|
91
|
+
* (R-11 — `trustFor` already falls back to the file default, so `default:'always'` covers every
|
|
92
|
+
* member). A roster or trust fault reads as "not desired" (fail to the ask flow, never to ambient
|
|
93
|
+
* trust), and self is always excluded.
|
|
94
|
+
*/
|
|
95
|
+
function desiredMembers() {
|
|
96
|
+
const d = _d();
|
|
97
|
+
let roster = [];
|
|
98
|
+
try {
|
|
99
|
+
roster = d.rosterMembers() || [];
|
|
100
|
+
} catch (error) {
|
|
101
|
+
_report(`roster read failed: ${error && error.message ? error.message : error}`);
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
const self = _string(typeof d.selfInstanceId === 'function' ? d.selfInstanceId() : '', 160);
|
|
105
|
+
const out = [];
|
|
106
|
+
for (const member of Array.isArray(roster) ? roster : []) {
|
|
107
|
+
const id = _string(member && member.instanceId, 160);
|
|
108
|
+
if (!id || id === self || out.includes(id)) continue;
|
|
109
|
+
let trust = 'ask';
|
|
110
|
+
try {
|
|
111
|
+
trust = _string(d.trustFor(id), 40) || 'ask';
|
|
112
|
+
} catch (error) {
|
|
113
|
+
trust = 'ask'; // REPORT via the skip: an unreadable trust file is not a grant (R-11)
|
|
114
|
+
_report(`trust read failed for ${id}: ${error && error.message ? error.message : error}`);
|
|
115
|
+
}
|
|
116
|
+
if (trust === 'always') out.push(id);
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function _rowFor(machineInstanceId) {
|
|
122
|
+
const d = _d();
|
|
123
|
+
if (typeof d.machineRow !== 'function') return null;
|
|
124
|
+
try {
|
|
125
|
+
return d.machineRow(machineInstanceId) || null;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
_report(`machine row read failed for ${machineInstanceId}: ${error && error.message ? error.message : error}`);
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* A row this module may dial: registered, enabled and judged reachable by the probe sweep. `unknown`
|
|
134
|
+
* is NOT dialable here — the mesh must not keep a link (and a 30 s retry) for a machine the registry
|
|
135
|
+
* has never proved, which is what keeps a NAT-blocked member at zero cost.
|
|
136
|
+
*/
|
|
137
|
+
function _dialable(row) {
|
|
138
|
+
if (!row) return false;
|
|
139
|
+
if (row.enabled === false || row.enabled === 0) return false;
|
|
140
|
+
return _string(row.health, 40) === 'reachable';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** The link this module currently holds for `machineInstanceId`, or null when it holds none. */
|
|
144
|
+
function _heldLink(machineInstanceId) {
|
|
145
|
+
const mine = _opened.get(machineInstanceId);
|
|
146
|
+
if (!mine || mine.closed) return null;
|
|
147
|
+
const d = _d();
|
|
148
|
+
let current = null;
|
|
149
|
+
try {
|
|
150
|
+
current = typeof d.linkFor === 'function' ? d.linkFor(machineInstanceId) : null;
|
|
151
|
+
} catch {
|
|
152
|
+
current = null; // expected: an unreadable link map means "cannot prove it is still ours"
|
|
153
|
+
}
|
|
154
|
+
// Only the link we opened, and only while the link module still publishes it for this machine.
|
|
155
|
+
return !current || current === mine ? mine : null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function _closeHeld(machineInstanceId, link) {
|
|
159
|
+
_opened.delete(machineInstanceId);
|
|
160
|
+
const d = _d();
|
|
161
|
+
try {
|
|
162
|
+
d.closeLink(link);
|
|
163
|
+
return true;
|
|
164
|
+
} catch (error) {
|
|
165
|
+
_report(`close failed for ${machineInstanceId}: ${error && error.message ? error.message : error}`);
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** One catalog fetch, fire-and-forget: a failed fetch is `{ok:false}` by contract, never a throw. */
|
|
171
|
+
function _fetchOnce(row) {
|
|
172
|
+
const d = _d();
|
|
173
|
+
try {
|
|
174
|
+
const pending = d.fetchCatalog(row);
|
|
175
|
+
if (pending && typeof pending.then === 'function') {
|
|
176
|
+
return pending.then(
|
|
177
|
+
(result) => result,
|
|
178
|
+
(error) => ({ ok: false, code: 'machine_unreachable', error: _string(error && error.message, 300) }),
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
} catch (error) {
|
|
182
|
+
// REPORT via the resolved value: the link is up, the list is a freshness bonus (R-2 owns the rest).
|
|
183
|
+
return Promise.resolve({ ok: false, code: 'machine_unreachable', error: _string(error && error.message, 300) });
|
|
184
|
+
}
|
|
185
|
+
return Promise.resolve({ ok: false, code: 'machine_unreachable', error: 'catalog_not_wired' });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* One pass: close what is no longer desired, open (and refresh) what is. Never throws — a reconcile
|
|
190
|
+
* runs on a timer, and a fault in one member must not stop the others. Resolves to a small summary
|
|
191
|
+
* (`{ok, enabled, linked, closed}`) for tests and diagnostics.
|
|
192
|
+
*/
|
|
193
|
+
async function reconcile() {
|
|
194
|
+
if (!isEnabled()) {
|
|
195
|
+
// The flag is the operator's "no mesh" switch: links this module opened are released, links it
|
|
196
|
+
// did not open are untouched.
|
|
197
|
+
const released = [];
|
|
198
|
+
for (const [id, link] of [..._opened]) {
|
|
199
|
+
if (_closeHeld(id, link)) released.push(id);
|
|
200
|
+
}
|
|
201
|
+
return { ok: true, enabled: false, linked: [], closed: released };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const desired = desiredMembers();
|
|
205
|
+
const closed = [];
|
|
206
|
+
for (const [id, link] of [..._opened]) {
|
|
207
|
+
if (desired.includes(id)) continue;
|
|
208
|
+
if (_closeHeld(id, link)) closed.push(id);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const linked = [];
|
|
212
|
+
const fetches = [];
|
|
213
|
+
for (const id of desired) {
|
|
214
|
+
const held = _heldLink(id);
|
|
215
|
+
const row = _rowFor(id);
|
|
216
|
+
if (!held && !_dialable(row)) continue; // no link, and no attempt until the next tick
|
|
217
|
+
if (held) {
|
|
218
|
+
// Keep the link warm (and the member's session list fresh) — see the header on the idle close.
|
|
219
|
+
linked.push(id);
|
|
220
|
+
fetches.push(_fetchOnce(row || id));
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
// openLink REUSES a link that already exists for this machine (the catalog's or a relay's). A
|
|
224
|
+
// link someone else opened is used, never adopted: recording it as ours would let a later trust
|
|
225
|
+
// flip close a link the catalog or a live viewer depends on.
|
|
226
|
+
let existing = null;
|
|
227
|
+
try {
|
|
228
|
+
existing = typeof _d().linkFor === 'function' ? _d().linkFor(id) : null;
|
|
229
|
+
} catch {
|
|
230
|
+
existing = null; // expected: an unreadable link map reads as "no link yet"
|
|
231
|
+
}
|
|
232
|
+
if (existing) {
|
|
233
|
+
linked.push(id);
|
|
234
|
+
fetches.push(_fetchOnce(row));
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
let link = null;
|
|
238
|
+
try {
|
|
239
|
+
link = _d().openLink(row);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
_report(`openLink failed for ${id}: ${error && error.message ? error.message : error}`);
|
|
242
|
+
link = null;
|
|
243
|
+
}
|
|
244
|
+
if (!link) continue;
|
|
245
|
+
_opened.set(id, link);
|
|
246
|
+
linked.push(id);
|
|
247
|
+
// R-2: learn that member's sessions once the link exists — the ownership check needs them.
|
|
248
|
+
fetches.push(_fetchOnce(row));
|
|
249
|
+
}
|
|
250
|
+
await Promise.allSettled(fetches);
|
|
251
|
+
return { ok: true, enabled: true, linked, closed };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Start the mesh: one reconcile now (so a boot with links already trusted does not wait a tick), then
|
|
256
|
+
* one unref'd 30 s interval. Idempotent — a second start() while running is a no-op.
|
|
257
|
+
*/
|
|
258
|
+
function start() {
|
|
259
|
+
if (_timer) return;
|
|
260
|
+
reconcile().catch(() => {
|
|
261
|
+
// expected: reconcile never rejects; a defensive catch keeps a timer callback from throwing.
|
|
262
|
+
});
|
|
263
|
+
const d = _d();
|
|
264
|
+
const timer = d.setIntervalFn(() => {
|
|
265
|
+
reconcile().catch(() => {
|
|
266
|
+
// expected: as above — a tick is fire-and-forget.
|
|
267
|
+
});
|
|
268
|
+
}, MESH_INTERVAL_MS);
|
|
269
|
+
if (timer && typeof timer.unref === 'function') timer.unref();
|
|
270
|
+
_timer = timer;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Stop the mesh: clear the tick and release every link this module opened. */
|
|
274
|
+
function stop() {
|
|
275
|
+
const d = _d();
|
|
276
|
+
if (_timer) {
|
|
277
|
+
try {
|
|
278
|
+
d.clearIntervalFn(_timer);
|
|
279
|
+
} catch {
|
|
280
|
+
// expected: the injected timer is already cleared.
|
|
281
|
+
}
|
|
282
|
+
_timer = null;
|
|
283
|
+
}
|
|
284
|
+
for (const [id, link] of [..._opened]) _closeHeld(id, link);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function isRunning() {
|
|
288
|
+
return !!_timer;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** The machine ids this module currently holds a mesh link to (diagnostics + tests). */
|
|
292
|
+
function openedMachineIds() {
|
|
293
|
+
return [..._opened.keys()].sort();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function _resetForTests() {
|
|
297
|
+
stop();
|
|
298
|
+
_deps = null;
|
|
299
|
+
_opened.clear();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
module.exports = {
|
|
303
|
+
MESH_INTERVAL_MS,
|
|
304
|
+
configure,
|
|
305
|
+
isEnabled,
|
|
306
|
+
desiredMembers,
|
|
307
|
+
reconcile,
|
|
308
|
+
start,
|
|
309
|
+
stop,
|
|
310
|
+
isRunning,
|
|
311
|
+
openedMachineIds,
|
|
312
|
+
_resetForTests,
|
|
313
|
+
};
|