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,672 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-1 (docs/fleet-session-lists-hub-search-2026-11-01.html §5 D-1..D-10, §6.2, §7, §8 WP-1):
|
|
4
|
+
// the fleet fan-out primitive. A session-LIST route (Recent, session search, PE conversations,
|
|
5
|
+
// standup search) answered with `?scope=fleet` returns THIS machine's rows plus every dialable
|
|
6
|
+
// pool member's rows, de-duplicated, stamped with provenance, and carrying a per-machine status
|
|
7
|
+
// map. The route wiring lives in WP-2..WP-4 — this module owns the primitive, the merge rules and
|
|
8
|
+
// the census seam.
|
|
9
|
+
//
|
|
10
|
+
// WP-3 adds the FUNCTION-LEVEL twin of `fleetRouted` for a route that calls a search function
|
|
11
|
+
// rather than a ServerResponse handler — `searchFleet` (S-11, the standup deep search). It runs the
|
|
12
|
+
// local search and the same fan-out CONCURRENTLY and merges through the same `mergeMemberLists` +
|
|
13
|
+
// `dedupeFleetRows` the route flow uses, so the two can never drift.
|
|
14
|
+
//
|
|
15
|
+
// Hard rules implemented here (never re-derived elsewhere):
|
|
16
|
+
// D-2 only a hub fans out, and never twice: every forwarded leg carries NO `scope`, and a
|
|
17
|
+
// request arriving with `X-CTM-Forwarded-By`/`X-CTM-Machine` is never a fleet request.
|
|
18
|
+
// D-4 dedup: the local id wins; a box row whose non-empty `ownerInstanceId` names another
|
|
19
|
+
// machine is dropped; among box rows with the same id the owner's wins.
|
|
20
|
+
// D-5 one deadline per fan-out (`FLEET_LIST_MEMBER_DEADLINE_MS`); a late answer never rewrites
|
|
21
|
+
// the result (snapshot on settle — the same shape lib/session-context-locator.js used).
|
|
22
|
+
// D-6 the fleet body is the local route's own envelope with its row array replaced, plus
|
|
23
|
+
// `machines` (self first) and `partial`.
|
|
24
|
+
// D-8 stale-if-error for UNFILTERED lists only, keyed `${route}\0${machine_id}\0${limit}`.
|
|
25
|
+
// D-10 every surviving box row is a NEW object stamped `machine`/`machineName`.
|
|
26
|
+
//
|
|
27
|
+
// Module graph (no cycle): this module requires lib/session-context-locator.js (for `otherMembers`/
|
|
28
|
+
// `isDialable`/`machineLabel`/`productionLocalIdentity`/`poolEnabled` — never re-typed) and the leaf
|
|
29
|
+
// lib/fleet-fanout.js; the locator requires only the leaf. Neither requires the other back.
|
|
30
|
+
|
|
31
|
+
const {
|
|
32
|
+
poolEnabled,
|
|
33
|
+
otherMembers,
|
|
34
|
+
isDialable,
|
|
35
|
+
machineLabel,
|
|
36
|
+
productionLocalIdentity,
|
|
37
|
+
} = require('./session-context-locator');
|
|
38
|
+
|
|
39
|
+
const FLEET_LIST_MEMBER_DEADLINE_MS = 2_000; // D-5/§1.3
|
|
40
|
+
const FLEET_SCOPE_PARAM = 'scope';
|
|
41
|
+
const FLEET_SCOPE_VALUE = 'fleet';
|
|
42
|
+
const FLEET_STATUSES = Object.freeze(['ok', 'stale', 'timeout', 'unreachable', 'error', 'skipped']);
|
|
43
|
+
/** Tags a handler with the route key it was built for (§7 census reads this). */
|
|
44
|
+
const FLEET_ROUTED = Symbol.for('ctm.fleetRouted');
|
|
45
|
+
|
|
46
|
+
// S-11: the one session-search path the standup deep search fans out over. Its route KEY is the
|
|
47
|
+
// FLEET_LIST_ROUTES key lib/fleet-session-list-routes.js registers the search spec under.
|
|
48
|
+
const SESSION_SEARCH_PATHNAME = '/api/sessions/search';
|
|
49
|
+
const SESSION_SEARCH_ROUTE = `GET ${SESSION_SEARCH_PATHNAME}`;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* §6.2/§7: filled by WP-2 (Recent + session search) and WP-4 (PE conversations) with
|
|
53
|
+
* `{ 'GET /api/...': spec }`. A plain exported object on purpose — a later WP assigns into it.
|
|
54
|
+
*/
|
|
55
|
+
const FLEET_LIST_ROUTES = {};
|
|
56
|
+
|
|
57
|
+
// D-2: a leg is only ever a READ of a peer's own list, so the two headers the relay sets on a
|
|
58
|
+
// forwarded request are the no-recursion markers (§5 P-3, §6.3).
|
|
59
|
+
const FORWARDED_BY_HEADER = 'x-ctm-forwarded-by';
|
|
60
|
+
const MACHINE_HEADER = 'x-ctm-machine';
|
|
61
|
+
|
|
62
|
+
/** Headers arrive either as a Node plain object (lower-cased keys) or a fetch-like `Headers`. */
|
|
63
|
+
function headerValue(req, name) {
|
|
64
|
+
const headers = req && req.headers;
|
|
65
|
+
if (!headers) return '';
|
|
66
|
+
if (typeof headers.get === 'function') return String(headers.get(name) || '');
|
|
67
|
+
const direct = headers[name];
|
|
68
|
+
if (direct !== undefined) return String(direct || '');
|
|
69
|
+
const upper = name.toUpperCase();
|
|
70
|
+
for (const key of Object.keys(headers)) {
|
|
71
|
+
if (String(key).toLowerCase() === name) return String(headers[key] || '');
|
|
72
|
+
if (key === upper) return String(headers[key] || '');
|
|
73
|
+
}
|
|
74
|
+
return '';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* D-1/D-2: is this request a fleet fan-out request for THIS machine? True iff `scope=fleet`, the
|
|
79
|
+
* pool flag is ON (`CTM_SESSION_CONTEXT_POOL`, default ON — the ONE kill switch for fleet reads),
|
|
80
|
+
* and the request is neither a forwarded leg nor a single-box proxy (`?machine=`).
|
|
81
|
+
*/
|
|
82
|
+
function isFleetRequest(req, url, env = process.env) {
|
|
83
|
+
if (!url || !url.searchParams) return false;
|
|
84
|
+
if (url.searchParams.get(FLEET_SCOPE_PARAM) !== FLEET_SCOPE_VALUE) return false;
|
|
85
|
+
if (!poolEnabled(env)) return false;
|
|
86
|
+
if (headerValue(req, FORWARDED_BY_HEADER)) return false;
|
|
87
|
+
if (headerValue(req, MACHINE_HEADER)) return false;
|
|
88
|
+
if (url.searchParams.get('machine')) return false;
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* D-2/D-3: the query string for one forwarded leg. Route-level params (`scope`, `token`,
|
|
94
|
+
* `machine`, `owner`) are dropped, `owner=self` is set, and everything else the caller asked for
|
|
95
|
+
* rides along unchanged (so each box answers ITS OWN top-N — the union of per-owner top-N lists is
|
|
96
|
+
* what contains the global top-N).
|
|
97
|
+
*/
|
|
98
|
+
function forwardSearchFor(url) {
|
|
99
|
+
const params = new URLSearchParams(url && url.searchParams ? url.searchParams : undefined);
|
|
100
|
+
for (const key of ['scope', 'token', 'machine', 'owner']) params.delete(key);
|
|
101
|
+
params.set('owner', 'self');
|
|
102
|
+
return `?${params.toString()}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* One member's forwarded answer -> `{ error: false, rows, body }` | `{ error: true, status }`.
|
|
107
|
+
*
|
|
108
|
+
* A transport failure is named by TYPE: `machine_unreachable` with `error === 'timeout'` is a
|
|
109
|
+
* member that did not answer in time, any other unreachable reason is `unreachable`, and anything
|
|
110
|
+
* else (unknown code, non-2xx, non-JSON, a body without the expected rows array) is `error`.
|
|
111
|
+
* Never throws, never returns a silent empty list.
|
|
112
|
+
*/
|
|
113
|
+
function parseListLeg(result, spec) {
|
|
114
|
+
if (!result || result.ok !== true) {
|
|
115
|
+
if (result && result.code === 'machine_unreachable') {
|
|
116
|
+
return { error: true, status: result.error === 'timeout' ? 'timeout' : 'unreachable' };
|
|
117
|
+
}
|
|
118
|
+
return { error: true, status: 'error' };
|
|
119
|
+
}
|
|
120
|
+
if (!(Number(result.status) >= 200 && Number(result.status) < 300)) return { error: true, status: 'error' };
|
|
121
|
+
let body;
|
|
122
|
+
try {
|
|
123
|
+
body = JSON.parse(String(result.text == null ? '' : result.text));
|
|
124
|
+
} catch {
|
|
125
|
+
/* expected: a non-JSON member body is a named per-machine 'error', never thrown at the caller */
|
|
126
|
+
return { error: true, status: 'error' };
|
|
127
|
+
}
|
|
128
|
+
const rows = spec && spec.rowsKey ? (body ? body[spec.rowsKey] : undefined) : body;
|
|
129
|
+
if (!Array.isArray(rows)) return { error: true, status: 'error' };
|
|
130
|
+
return { error: false, rows, body };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* S-6: the scatter-gather lives in the leaf lib/fleet-fanout.js; this wrapper only supplies the
|
|
135
|
+
* locator's `machineLabel`, so every caller labels members by the one rule.
|
|
136
|
+
*/
|
|
137
|
+
function fanOutMembers(machines, request, d = {}) {
|
|
138
|
+
return require('./fleet-fanout').fanOutMembers(machines, request, { label: machineLabel, ...d });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* §6.2: a minimal ServerResponse stand-in for the LOCAL leg of a fleet request, so the wrapper can
|
|
143
|
+
* run the route's own handler to completion (it is the only always-authoritative leg) and then
|
|
144
|
+
* decide whether to merge into it or to re-emit it verbatim.
|
|
145
|
+
*
|
|
146
|
+
* Resolves `{ status, headers, raw, json }` when `end()` is called — `json` is `JSON.parse(raw)`,
|
|
147
|
+
* or `null` for a body that is not JSON (the wrapper then writes the captured bytes verbatim).
|
|
148
|
+
* Rejects if `local` throws (or its returned promise rejects) before `end()`.
|
|
149
|
+
*/
|
|
150
|
+
function captureLocal(local, req, url) {
|
|
151
|
+
return new Promise((resolve, reject) => {
|
|
152
|
+
const chunks = [];
|
|
153
|
+
// Header NAMES keep the local leg's own casing, so the verbatim re-emit is faithful; the
|
|
154
|
+
// lowercase index is only how getHeader/removeHeader/setHeader find them again.
|
|
155
|
+
const headers = {};
|
|
156
|
+
const byLower = new Map();
|
|
157
|
+
let sent = false;
|
|
158
|
+
let done = false;
|
|
159
|
+
const setHeaderValue = (key, value) => {
|
|
160
|
+
const name = String(key);
|
|
161
|
+
const lower = name.toLowerCase();
|
|
162
|
+
const previous = byLower.get(lower);
|
|
163
|
+
if (previous !== undefined && previous !== name) delete headers[previous];
|
|
164
|
+
headers[name] = value;
|
|
165
|
+
byLower.set(lower, name);
|
|
166
|
+
};
|
|
167
|
+
const push = (chunk) => {
|
|
168
|
+
if (chunk === undefined || chunk === null) return;
|
|
169
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
170
|
+
};
|
|
171
|
+
const shim = {
|
|
172
|
+
statusCode: 200,
|
|
173
|
+
get headersSent() { return sent; },
|
|
174
|
+
writeHead(status, hdrs) {
|
|
175
|
+
shim.statusCode = Number(status) || 200;
|
|
176
|
+
sent = true;
|
|
177
|
+
if (hdrs && typeof hdrs === 'object') {
|
|
178
|
+
for (const key of Object.keys(hdrs)) setHeaderValue(key, hdrs[key]);
|
|
179
|
+
}
|
|
180
|
+
return shim;
|
|
181
|
+
},
|
|
182
|
+
setHeader(key, value) { if (!sent) setHeaderValue(key, value); return shim; },
|
|
183
|
+
getHeader(key) { return headers[byLower.get(String(key).toLowerCase())]; },
|
|
184
|
+
removeHeader(key) {
|
|
185
|
+
const name = byLower.get(String(key).toLowerCase());
|
|
186
|
+
if (name !== undefined) { delete headers[name]; byLower.delete(String(key).toLowerCase()); }
|
|
187
|
+
},
|
|
188
|
+
write(chunk) { push(chunk); sent = true; return true; },
|
|
189
|
+
end(chunk) {
|
|
190
|
+
if (done) return shim;
|
|
191
|
+
done = true;
|
|
192
|
+
sent = true;
|
|
193
|
+
push(chunk);
|
|
194
|
+
const raw = Buffer.concat(chunks);
|
|
195
|
+
let json = null;
|
|
196
|
+
try {
|
|
197
|
+
json = JSON.parse(raw.toString('utf8'));
|
|
198
|
+
} catch {
|
|
199
|
+
/* expected: a non-JSON local body is reported as json === null — the wrapper then writes
|
|
200
|
+
the captured bytes verbatim, with no merge */
|
|
201
|
+
json = null;
|
|
202
|
+
}
|
|
203
|
+
resolve({ status: shim.statusCode, headers, raw, json });
|
|
204
|
+
return shim;
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
let outcome;
|
|
208
|
+
try {
|
|
209
|
+
outcome = local(req, shim, url);
|
|
210
|
+
} catch (err) {
|
|
211
|
+
reject(err);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (outcome && typeof outcome.then === 'function') outcome.catch(reject);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** D-4's default key when a route does not supply `rowId`. */
|
|
219
|
+
function defaultRowId(row) {
|
|
220
|
+
return String((row && (row.sessionId || row.id || row.session_id || row.ctm_session_id)) || '');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function textOf(value) {
|
|
224
|
+
return String(value == null ? '' : value);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* D-4/D-10, exported for tests. Local rows come first and are kept AS IS (same objects — this
|
|
229
|
+
* machine's row is authoritative, §2.2); each surviving box row is a NEW object stamped
|
|
230
|
+
* `machine`/`machineName`. A box row is dropped when its id is a local id, and when its non-empty
|
|
231
|
+
* `ownerInstanceId` is one of `localOwnerIds` (this hub's own id + lineage): it is then a box's COPY
|
|
232
|
+
* of one of OUR sessions — the 3,775 Spark copies of §1.1 — even if this hub no longer lists it.
|
|
233
|
+
* A box row owned by any other id is kept: a box answering `owner=self` filters by its OWN lineage,
|
|
234
|
+
* so an owner id that differs from the box's registry id is that box's older lineage id, not a
|
|
235
|
+
* foreign row. Among box rows sharing an id the one whose `ownerInstanceId` is its own source
|
|
236
|
+
* machine wins, else the first seen. Rows with an empty id are never merged. Pure: no input mutated.
|
|
237
|
+
*/
|
|
238
|
+
function dedupeFleetRows(localRows, lists, rowId, localOwnerIds) {
|
|
239
|
+
const ours = localOwnerIds instanceof Set ? localOwnerIds : new Set(localOwnerIds || []);
|
|
240
|
+
const idOf = typeof rowId === 'function' ? rowId : defaultRowId;
|
|
241
|
+
const local = Array.isArray(localRows) ? localRows : [];
|
|
242
|
+
const localIds = new Set();
|
|
243
|
+
for (const row of local) {
|
|
244
|
+
const id = textOf(idOf(row));
|
|
245
|
+
if (id) localIds.add(id);
|
|
246
|
+
}
|
|
247
|
+
const out = local.slice();
|
|
248
|
+
const seen = new Map();
|
|
249
|
+
const order = [];
|
|
250
|
+
for (const list of Array.isArray(lists) ? lists : []) {
|
|
251
|
+
if (!list || typeof list !== 'object') continue;
|
|
252
|
+
const machineId = textOf(list.machine_id);
|
|
253
|
+
const rows = Array.isArray(list.rows) ? list.rows : [];
|
|
254
|
+
for (const row of rows) {
|
|
255
|
+
if (!row || typeof row !== 'object' || Array.isArray(row)) continue;
|
|
256
|
+
const owner = textOf(row.ownerInstanceId).trim();
|
|
257
|
+
if (owner && ours.has(owner)) continue; // a box's copy of one of OUR sessions never enters the answer
|
|
258
|
+
const placed = { ...row, machine: list.machine_id, machineName: list.machine_name };
|
|
259
|
+
const id = textOf(idOf(row));
|
|
260
|
+
if (!id) {
|
|
261
|
+
out.push(placed);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (localIds.has(id)) continue; // D-4: this machine's own row wins the id
|
|
265
|
+
const isOwnerRow = owner !== '' && owner === machineId;
|
|
266
|
+
const existing = seen.get(id);
|
|
267
|
+
if (!existing) {
|
|
268
|
+
seen.set(id, { row: placed, ownerRow: isOwnerRow });
|
|
269
|
+
order.push(id);
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (isOwnerRow && !existing.ownerRow) {
|
|
273
|
+
existing.row = placed;
|
|
274
|
+
existing.ownerRow = true;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
for (const id of order) out.push(seen.get(id).row);
|
|
279
|
+
return out;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** D-8: `${route}\0${machine_id}\0${limit}` (§6.2). */
|
|
283
|
+
function lastGoodKey(route, machineId, limit) {
|
|
284
|
+
return `${route}\u0000${machineId}\u0000${limit}`;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* D-8: per-(route, machine, limit) last-`ok` row list, in memory only.
|
|
289
|
+
* `get(key)` -> `{rows, at}|null`, `set(key, rows)` stamps `at` from the injected clock,
|
|
290
|
+
* `prune(liveMachineIds)` drops every entry whose machine is no longer in `machineRows()`.
|
|
291
|
+
*/
|
|
292
|
+
function createFleetLastGood({ now = () => Date.now() } = {}) {
|
|
293
|
+
const store = new Map();
|
|
294
|
+
return {
|
|
295
|
+
get(key) {
|
|
296
|
+
const entry = store.get(String(key));
|
|
297
|
+
return entry ? { rows: entry.rows, at: entry.at } : null;
|
|
298
|
+
},
|
|
299
|
+
set(key, rows) {
|
|
300
|
+
store.set(String(key), { rows: Array.isArray(rows) ? rows : [], at: Number(now()) });
|
|
301
|
+
},
|
|
302
|
+
prune(liveMachineIds) {
|
|
303
|
+
const live = liveMachineIds instanceof Set ? liveMachineIds : new Set(liveMachineIds || []);
|
|
304
|
+
for (const key of [...store.keys()]) {
|
|
305
|
+
const parts = String(key).split('\u0000');
|
|
306
|
+
if (!live.has(parts.length >= 2 ? parts[1] : '')) store.delete(key);
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Module-level default stale cache, so a hub keeps a member's last good list between requests.
|
|
313
|
+
const defaultLastGood = createFleetLastGood();
|
|
314
|
+
|
|
315
|
+
/** The literal response body is emitted with the local leg's status/headers and no merge (§6.2). */
|
|
316
|
+
function writeVerbatim(res, captured) {
|
|
317
|
+
if (captured.headers && Object.keys(captured.headers).length) res.writeHead(captured.status, captured.headers);
|
|
318
|
+
else res.writeHead(captured.status);
|
|
319
|
+
res.end(captured.raw);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function writeJson(res, status, body) {
|
|
323
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
324
|
+
res.end(JSON.stringify(body));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Every dependency of a fleet request, injectable so a unit test never touches the network or a DB. */
|
|
328
|
+
function resolveFleetDeps(deps = {}) {
|
|
329
|
+
const deadline = Number(deps.deadlineMs) > 0 ? Number(deps.deadlineMs) : FLEET_LIST_MEMBER_DEADLINE_MS;
|
|
330
|
+
return {
|
|
331
|
+
machineRows: deps.machineRows || (() => require('./remote-machine-catalog').machineRows()),
|
|
332
|
+
localIdentity: deps.localIdentity || productionLocalIdentity,
|
|
333
|
+
// D-4: this hub's own id + lineage — a box row owned by one of these is a copy of OUR session.
|
|
334
|
+
localOwnerIds: deps.localOwnerIds || (() => {
|
|
335
|
+
const identity = require('./instance-identity-access').getInstanceIdentity({ required: false });
|
|
336
|
+
return new Set([...(identity && identity.lineage ? identity.lineage : [])]);
|
|
337
|
+
}),
|
|
338
|
+
forward: deps.forward || ((request) => require('./remote-machine-proxy').forwardMachineHttp({
|
|
339
|
+
...request,
|
|
340
|
+
method: 'GET',
|
|
341
|
+
deps: { timeoutMs: deadline },
|
|
342
|
+
})),
|
|
343
|
+
setTimeoutFn: deps.setTimeoutFn || ((fn, ms) => setTimeout(fn, ms)),
|
|
344
|
+
clearTimeoutFn: deps.clearTimeoutFn || ((timer) => clearTimeout(timer)),
|
|
345
|
+
deadlineMs: deadline,
|
|
346
|
+
env: deps.env || process.env,
|
|
347
|
+
lastGood: deps.lastGood || defaultLastGood,
|
|
348
|
+
now: deps.now || (() => Date.now()),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** D-8: only an unfiltered list (`/api/recent-sessions`, `/api/conversations`) is cache-eligible. */
|
|
353
|
+
function isUnfiltered(spec, url) {
|
|
354
|
+
if (typeof spec.unfiltered !== 'function') return false;
|
|
355
|
+
try {
|
|
356
|
+
return spec.unfiltered(url) === true;
|
|
357
|
+
} catch {
|
|
358
|
+
/* expected: a spec that cannot evaluate its own filter predicate is simply not cache-eligible */
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function limitOf(spec, url) {
|
|
364
|
+
if (typeof spec.limitOf === 'function') {
|
|
365
|
+
const n = Number(spec.limitOf(url));
|
|
366
|
+
if (Number.isFinite(n) && n >= 0) return Math.trunc(n);
|
|
367
|
+
}
|
|
368
|
+
return 0;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* D-4/D-8/D-10: turn the fan-out's per-member answers into ONE deduped row list plus the
|
|
373
|
+
* per-member status entries of the `machines` envelope. Shared by the route flow (`runFleetUnguarded`)
|
|
374
|
+
* and the function-level `searchFleet` (S-11) so the two can never drift — a member is dialled only
|
|
375
|
+
* when `isDialable`, an undialled one is `skipped`, and only a `cacheable` route writes/reads D-8's
|
|
376
|
+
* last good copy.
|
|
377
|
+
*
|
|
378
|
+
* @param {{members:object[], fanout:{lists:object[],statuses:Map,counts:Map}, localRows:object[],
|
|
379
|
+
* rowId:Function, ours:Set<string>, cacheable:boolean, route:string, limit:number, d:object}} input
|
|
380
|
+
* @returns {{rows:object[], entries:object[]}} `rows` = the deduped union (local rows first),
|
|
381
|
+
* `entries` = one `{machine_id, machine_name, status, count, as_of?}` per member, in the order
|
|
382
|
+
* `machineRows()` returned them (the caller prepends this machine's own `ok` entry).
|
|
383
|
+
*/
|
|
384
|
+
function mergeMemberLists({ members, fanout, localRows, rowId, ours, cacheable, route, limit, d }) {
|
|
385
|
+
const lists = [];
|
|
386
|
+
const entries = [];
|
|
387
|
+
for (const row of members) {
|
|
388
|
+
const id = textOf(row.instanceId);
|
|
389
|
+
const name = machineLabel(row);
|
|
390
|
+
if (!isDialable(row)) {
|
|
391
|
+
// Never dialled (disabled/unreachable) is `skipped`, exactly like the context probe's
|
|
392
|
+
// `searched` — D-8 covers it with the stale copy when one is held.
|
|
393
|
+
entries.push(fillFromLastGood(d, cacheable, route, id, name, limit, 'skipped', 0, lists));
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
const status = fanout.statuses.get(row.instanceId) || 'timeout';
|
|
397
|
+
const count = fanout.counts.get(row.instanceId) || 0;
|
|
398
|
+
if (status === 'ok') {
|
|
399
|
+
const answered = fanout.lists.find((entry) => entry.machine_id === id);
|
|
400
|
+
const rows = answered ? answered.rows : [];
|
|
401
|
+
if (cacheable) d.lastGood.set(lastGoodKey(route, id, limit), rows);
|
|
402
|
+
lists.push({
|
|
403
|
+
machine_id: id,
|
|
404
|
+
machine_name: name,
|
|
405
|
+
owner: false,
|
|
406
|
+
rows,
|
|
407
|
+
body: answered ? answered.body : undefined,
|
|
408
|
+
});
|
|
409
|
+
entries.push({ machine_id: id, machine_name: name, status, count });
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
entries.push(fillFromLastGood(d, cacheable, route, id, name, limit, status, count, lists));
|
|
413
|
+
}
|
|
414
|
+
return { rows: dedupeFleetRows(localRows, lists, rowId, ours), entries };
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* The fleet request itself: the local leg and the fan-out run CONCURRENTLY (D-5: the local leg is
|
|
419
|
+
* never cut by the member deadline), then either the local answer is re-emitted verbatim (not 2xx,
|
|
420
|
+
* not JSON, or no rows array to merge into) or the merged envelope is written with the local
|
|
421
|
+
* leg's status.
|
|
422
|
+
*/
|
|
423
|
+
async function runFleet(route, local, spec, deps, req, res, url) {
|
|
424
|
+
try {
|
|
425
|
+
return await runFleetUnguarded(route, local, spec, deps, req, res, url);
|
|
426
|
+
} catch (error) {
|
|
427
|
+
// REPORT: the local handler threw before answering. Answer 500 like any route error rather
|
|
428
|
+
// than leaving the request open or rejecting into the router.
|
|
429
|
+
console.warn(`[fleet-session-lists] ${route} local leg failed: ${(error && error.message) || error}`);
|
|
430
|
+
if (!res.headersSent) writeJson(res, 500, { error: 'fleet_local_failed' });
|
|
431
|
+
return undefined;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async function runFleetUnguarded(route, local, spec, deps, req, res, url) {
|
|
436
|
+
const d = resolveFleetDeps(deps);
|
|
437
|
+
const self = d.localIdentity();
|
|
438
|
+
const members = otherMembers({ localIdentity: d.localIdentity, machineRows: d.machineRows }, '');
|
|
439
|
+
const limit = limitOf(spec, url);
|
|
440
|
+
const [captured, fanout] = await Promise.all([
|
|
441
|
+
captureLocal(local, req, url),
|
|
442
|
+
fanOutMembers(members.filter(isDialable), {
|
|
443
|
+
pathname: url.pathname,
|
|
444
|
+
search: forwardSearchFor(url),
|
|
445
|
+
parse: (result) => parseListLeg(result, spec),
|
|
446
|
+
}, {
|
|
447
|
+
forward: d.forward,
|
|
448
|
+
setTimeoutFn: d.setTimeoutFn,
|
|
449
|
+
clearTimeoutFn: d.clearTimeoutFn,
|
|
450
|
+
deadlineMs: d.deadlineMs,
|
|
451
|
+
}),
|
|
452
|
+
]);
|
|
453
|
+
|
|
454
|
+
const ok = Number(captured.status) >= 200 && Number(captured.status) < 300;
|
|
455
|
+
const localRows = captured.json == null ? null : (spec.rowsKey ? captured.json[spec.rowsKey] : captured.json);
|
|
456
|
+
if (!ok || captured.json == null || !Array.isArray(localRows)) {
|
|
457
|
+
writeVerbatim(res, captured);
|
|
458
|
+
return undefined;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const cacheable = spec.lastGood === true && isUnfiltered(spec, url);
|
|
462
|
+
// D-8: an entry whose machine left the registry is dropped on every fleet call.
|
|
463
|
+
d.lastGood.prune(new Set(members.map((row) => textOf(row.instanceId))));
|
|
464
|
+
|
|
465
|
+
const ours = new Set(d.localOwnerIds());
|
|
466
|
+
if (self && self.instanceId) ours.add(textOf(self.instanceId));
|
|
467
|
+
const { rows: merged, entries: memberEntries } = mergeMemberLists({
|
|
468
|
+
members,
|
|
469
|
+
fanout,
|
|
470
|
+
localRows,
|
|
471
|
+
rowId: spec.rowId,
|
|
472
|
+
ours,
|
|
473
|
+
cacheable,
|
|
474
|
+
route,
|
|
475
|
+
limit,
|
|
476
|
+
d,
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
const select = typeof spec.select === 'function' ? spec.select : ((rows) => rows);
|
|
480
|
+
const rows = select(merged, { limit, url });
|
|
481
|
+
const machines = [{
|
|
482
|
+
machine_id: textOf(self.instanceId),
|
|
483
|
+
machine_name: textOf(self.displayName),
|
|
484
|
+
status: 'ok',
|
|
485
|
+
count: localRows.length,
|
|
486
|
+
}, ...memberEntries];
|
|
487
|
+
|
|
488
|
+
const body = spec.rowsKey ? { ...captured.json, [spec.rowsKey]: rows } : { [spec.fleetRowsKey]: rows };
|
|
489
|
+
body.machines = machines;
|
|
490
|
+
body.partial = machines.some((entry) => entry.status !== 'ok');
|
|
491
|
+
if (spec.rowsKey && Object.prototype.hasOwnProperty.call(captured.json, 'total')) body.total = rows.length;
|
|
492
|
+
writeJson(res, captured.status, body);
|
|
493
|
+
return undefined;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* D-8: a member that did not answer `ok` contributes its last good rows with `stale: true` on every
|
|
498
|
+
* row and reports status `stale` + `as_of`; with no entry (or a non-cacheable route) the member is
|
|
499
|
+
* named with its own failure status and contributes nothing.
|
|
500
|
+
*/
|
|
501
|
+
function fillFromLastGood(d, cacheable, route, id, name, limit, status, count, lists) {
|
|
502
|
+
if (cacheable) {
|
|
503
|
+
const entry = d.lastGood.get(lastGoodKey(route, id, limit));
|
|
504
|
+
if (entry) {
|
|
505
|
+
lists.push({
|
|
506
|
+
machine_id: id,
|
|
507
|
+
machine_name: name,
|
|
508
|
+
owner: false,
|
|
509
|
+
rows: entry.rows.map((row) => ({ ...row, stale: true })),
|
|
510
|
+
});
|
|
511
|
+
return {
|
|
512
|
+
machine_id: id, machine_name: name, status: 'stale', count: entry.rows.length, as_of: entry.at,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return { machine_id: id, machine_name: name, status, count };
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* S-11/§6.3: the FUNCTION-LEVEL fleet search, for the one fleet caller that is not a ServerResponse
|
|
521
|
+
* route — the standup deep search (`server.js` `apiStandupSearch`), which calls a search function
|
|
522
|
+
* and serialises the object itself. It runs `localSearch(query, options)` (today's
|
|
523
|
+
* `buildSessionSearchResponse` shape `{ results, total?, sources, … }`) CONCURRENTLY with the same
|
|
524
|
+
* fan-out the route flow uses (D-5: the local leg is never cut by the member deadline).
|
|
525
|
+
*
|
|
526
|
+
* The forwarded leg is `GET /api/sessions/search?q=<query>&limit=<options.limit>&owner=self` — NO
|
|
527
|
+
* `scope`, so a box answers its own top-N and never fans out again (D-2/D-3). The merged rows are
|
|
528
|
+
* deduped by the search spec's `rowId` (a box's copy of one of OUR sessions is dropped, D-4),
|
|
529
|
+
* ordered by the search spec's own `select` (D-9: `SessionSearchUtils.compareSearchHits`) and
|
|
530
|
+
* sliced to the search spec's own clamp — so a merged fleet order equals single-DB order by
|
|
531
|
+
* construction.
|
|
532
|
+
*
|
|
533
|
+
* @param {(query:string, options:object) => Promise<object>} localSearch today's search function
|
|
534
|
+
* @param {string} query
|
|
535
|
+
* @param {{limit?:number}} [options] passed through to `localSearch` untouched
|
|
536
|
+
* @param {object} [deps] `resolveFleetDeps` keys (machineRows, localIdentity, forward, timers,
|
|
537
|
+
* deadlineMs, env, localOwnerIds, …) plus `spec` — the search route's spec
|
|
538
|
+
* (defaults to the one lib/fleet-session-list-routes.js registers in
|
|
539
|
+
* FLEET_LIST_ROUTES; read from the map because requiring that module here
|
|
540
|
+
* would form a lib cycle).
|
|
541
|
+
* @returns {Promise<object>} `{ ...localResult, results, machines, partial, total? }` — `total`
|
|
542
|
+
* only when the local result carried one. NEVER stale-if-error (D-8: search is never cached) and
|
|
543
|
+
* NO cache write. The pool kill switch (or a missing spec) returns `localSearch(query, options)`
|
|
544
|
+
* untouched; a `localSearch` rejection PROPAGATES (the standup adapter owns the `degraded`
|
|
545
|
+
* contract).
|
|
546
|
+
*/
|
|
547
|
+
async function searchFleet(localSearch, query, options = {}, deps = {}) {
|
|
548
|
+
const d = resolveFleetDeps(deps);
|
|
549
|
+
const spec = deps.spec || FLEET_LIST_ROUTES[SESSION_SEARCH_ROUTE];
|
|
550
|
+
if (!poolEnabled(d.env) || !spec) return localSearch(query, options);
|
|
551
|
+
|
|
552
|
+
const params = new URLSearchParams();
|
|
553
|
+
params.set('q', textOf(query));
|
|
554
|
+
if (options.limit != null) params.set('limit', String(options.limit));
|
|
555
|
+
params.set('owner', 'self');
|
|
556
|
+
const search = `?${params.toString()}`;
|
|
557
|
+
const legUrl = new URL(`${SESSION_SEARCH_PATHNAME}${search}`, 'http://fleet.local');
|
|
558
|
+
// The limit is the search spec's own clamp, applied to the caller's `limit` (default 50).
|
|
559
|
+
const limit = limitOf(spec, legUrl);
|
|
560
|
+
|
|
561
|
+
const self = d.localIdentity();
|
|
562
|
+
const members = otherMembers({ localIdentity: d.localIdentity, machineRows: d.machineRows }, '');
|
|
563
|
+
const ours = new Set(d.localOwnerIds());
|
|
564
|
+
if (self && self.instanceId) ours.add(textOf(self.instanceId));
|
|
565
|
+
|
|
566
|
+
const [localResult, fanout] = await Promise.all([
|
|
567
|
+
localSearch(query, options),
|
|
568
|
+
fanOutMembers(members.filter(isDialable), {
|
|
569
|
+
pathname: SESSION_SEARCH_PATHNAME,
|
|
570
|
+
search,
|
|
571
|
+
parse: (result) => parseListLeg(result, spec),
|
|
572
|
+
}, {
|
|
573
|
+
forward: d.forward,
|
|
574
|
+
setTimeoutFn: d.setTimeoutFn,
|
|
575
|
+
clearTimeoutFn: d.clearTimeoutFn,
|
|
576
|
+
deadlineMs: d.deadlineMs,
|
|
577
|
+
}),
|
|
578
|
+
]);
|
|
579
|
+
|
|
580
|
+
const localRows = localResult && Array.isArray(localResult.results) ? localResult.results : [];
|
|
581
|
+
const { rows: merged, entries } = mergeMemberLists({
|
|
582
|
+
members,
|
|
583
|
+
fanout,
|
|
584
|
+
localRows,
|
|
585
|
+
rowId: spec.rowId,
|
|
586
|
+
ours,
|
|
587
|
+
// D-8: a search hit list is query-keyed with a low hit rate — never read from, never written to,
|
|
588
|
+
// the last-good store.
|
|
589
|
+
cacheable: false,
|
|
590
|
+
route: SESSION_SEARCH_ROUTE,
|
|
591
|
+
limit,
|
|
592
|
+
d,
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
const select = typeof spec.select === 'function' ? spec.select : ((rows) => rows);
|
|
596
|
+
const results = select(merged, { limit, url: legUrl });
|
|
597
|
+
const machines = [{
|
|
598
|
+
machine_id: textOf(self.instanceId),
|
|
599
|
+
machine_name: textOf(self.displayName),
|
|
600
|
+
status: 'ok',
|
|
601
|
+
count: localRows.length,
|
|
602
|
+
}, ...entries];
|
|
603
|
+
|
|
604
|
+
const body = { ...localResult, results, machines, partial: machines.some((entry) => entry.status !== 'ok') };
|
|
605
|
+
if (localResult && Object.prototype.hasOwnProperty.call(localResult, 'total')) body.total = results.length;
|
|
606
|
+
return body;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* §6.2: the route wrapper. `!isFleetRequest` returns `local(req, res, url)` UNCHANGED (the same
|
|
611
|
+
* return value — every non-fleet caller keeps today's behaviour byte for byte).
|
|
612
|
+
*
|
|
613
|
+
* @param {string} route `'GET /api/recent-sessions'` etc. — the key of FLEET_LIST_ROUTES
|
|
614
|
+
* @param {(req:any, res:any, url:URL) => any} local today's handler, untouched
|
|
615
|
+
* @param {object} spec see the module docblock / §6.2 typedef
|
|
616
|
+
* @param {object} deps machineRows, localIdentity, forward, setTimeoutFn, clearTimeoutFn,
|
|
617
|
+
* deadlineMs, env, lastGood, now (all injectable for tests)
|
|
618
|
+
* @returns {(req:any, res:any, url:URL) => any} tagged with `handler[FLEET_ROUTED] = route`
|
|
619
|
+
*/
|
|
620
|
+
function fleetRouted(route, local, spec, deps = {}) {
|
|
621
|
+
// A missing or partial spec is a wiring bug (a misspelled export once made this route answer
|
|
622
|
+
// `{"undefined": [...]}` with no limit and no stale copy) — refuse it where the route is built,
|
|
623
|
+
// never degrade it to `{}` at request time.
|
|
624
|
+
assertFleetSpec(route, spec);
|
|
625
|
+
const handler = (req, res, url) => {
|
|
626
|
+
const requestUrl = url || safeUrl(req);
|
|
627
|
+
if (!isFleetRequest(req, requestUrl, deps.env || process.env)) return local(req, res, requestUrl);
|
|
628
|
+
return runFleet(route, local, spec, deps, req, res, requestUrl);
|
|
629
|
+
};
|
|
630
|
+
handler[FLEET_ROUTED] = route;
|
|
631
|
+
return handler;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/** §6.2 spec contract: the four functions every merge needs, and a key the merged rows go under. */
|
|
635
|
+
function assertFleetSpec(route, spec) {
|
|
636
|
+
const missing = [];
|
|
637
|
+
if (!spec || typeof spec !== 'object') missing.push('spec');
|
|
638
|
+
else {
|
|
639
|
+
for (const fn of ['rowId', 'select', 'limitOf']) if (typeof spec[fn] !== 'function') missing.push(fn);
|
|
640
|
+
if (spec.lastGood === true && typeof spec.unfiltered !== 'function') missing.push('unfiltered');
|
|
641
|
+
if (!spec.rowsKey && !spec.fleetRowsKey) missing.push('rowsKey|fleetRowsKey');
|
|
642
|
+
}
|
|
643
|
+
if (missing.length) throw new TypeError(`fleetRouted(${route}): incomplete spec (${missing.join(', ')})`);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function safeUrl(req) {
|
|
647
|
+
try {
|
|
648
|
+
return new URL((req && req.url) || '/', 'http://localhost');
|
|
649
|
+
} catch {
|
|
650
|
+
/* expected: an unparseable req.url can only fall back to a root URL, never throw into the route */
|
|
651
|
+
return new URL('/', 'http://localhost');
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
module.exports = {
|
|
656
|
+
FLEET_LIST_MEMBER_DEADLINE_MS,
|
|
657
|
+
FLEET_SCOPE_PARAM,
|
|
658
|
+
FLEET_STATUSES,
|
|
659
|
+
FLEET_ROUTED,
|
|
660
|
+
FLEET_LIST_ROUTES,
|
|
661
|
+
isFleetRequest,
|
|
662
|
+
forwardSearchFor,
|
|
663
|
+
parseListLeg,
|
|
664
|
+
fanOutMembers,
|
|
665
|
+
captureLocal,
|
|
666
|
+
dedupeFleetRows,
|
|
667
|
+
mergeMemberLists,
|
|
668
|
+
searchFleet,
|
|
669
|
+
createFleetLastGood,
|
|
670
|
+
fleetRouted,
|
|
671
|
+
assertFleetSpec,
|
|
672
|
+
};
|