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
|
@@ -26,6 +26,24 @@
|
|
|
26
26
|
// The consecutive-miss counters are deliberately MODULE state, not columns: they are probe
|
|
27
27
|
// bookkeeping, they die with the process, and they must never look like fact to a reader.
|
|
28
28
|
const { dispatchOwnerMutation } = require('./owner-mutation-disposition');
|
|
29
|
+
const { createErrorSink } = require('./error-sink');
|
|
30
|
+
// WP-3 rev 3 (§13.6 D-17): the hub-side `proxy.sh` the installer writes. Node builtins only.
|
|
31
|
+
const fs = require('node:fs');
|
|
32
|
+
const os = require('node:os');
|
|
33
|
+
const path = require('node:path');
|
|
34
|
+
// WP-7 (docs/remote-agent-skills-bundles-2026-10-25.html §5 D-11, §6.5): the pure classification of
|
|
35
|
+
// a box's own skills against the hub's copy and the base the box reported. Required at the top
|
|
36
|
+
// level on purpose — it is a leaf (build/store/codex-install only), so it introduces no cycle, and a
|
|
37
|
+
// probe-time require would hide the edge from the dependency lint.
|
|
38
|
+
const bundleInventory = require('./agent-bundle/inventory');
|
|
39
|
+
// WP-1 (docs/remote-box-model-access-2026-10-26.html §6.2, §5.3 D-2/D-9): the box's OWN model
|
|
40
|
+
// availability, read on the same probe tick as everything else. Only the TTL constant is needed from
|
|
41
|
+
// the module (the block is derived here, on the hub, from the box's §6.1 body) — the module is a
|
|
42
|
+
// leaf (http/https only), so the edge introduces no cycle.
|
|
43
|
+
const { ORIGIN_TTL_MS } = require('./model-availability');
|
|
44
|
+
|
|
45
|
+
/** REPORT sink for a probe-sweep fault that must not fail the sweep (WP-7's hub-side build). */
|
|
46
|
+
const errorSink = createErrorSink({ name: 'machine-registry' });
|
|
29
47
|
|
|
30
48
|
const DEFAULT_TIMEOUT_MS = 3000;
|
|
31
49
|
// D-4/P3 grace: a machine with no successful probe for this long reads `unreachable`.
|
|
@@ -57,6 +75,14 @@ const _missStreak = new Map();
|
|
|
57
75
|
// with a freshness stamp, it dies with the process, and a stale copy must never look like fact to a
|
|
58
76
|
// reader (a restart reads `unknown`, which never refuses a forward).
|
|
59
77
|
const _agentAuth = new Map();
|
|
78
|
+
// instance_id -> { presence: { dsh: boolean }, checkedAt } (WP-13b, D-15). The box's OWN
|
|
79
|
+
// available-agents answer as this hub last read it from ITS /api/models/available-agents, reduced to
|
|
80
|
+
// the compact presence the new-session card and the machine row need: whether the box has the
|
|
81
|
+
// DeepSeek Harness. Nothing else from the body is kept — not paths, not versions, not the other
|
|
82
|
+
// agents. Same module-state contract as _agentAuth: a stale copy must never look like fact to a
|
|
83
|
+
// reader (a restart reads "never read", which refuses nothing), and a failed/404/unparseable read
|
|
84
|
+
// KEEPS the previous value (P3, "Unknown != Dead").
|
|
85
|
+
const _agentPresence = new Map();
|
|
60
86
|
// instance_id -> { sample, checkedAt } (D-12, D-16). The box's own host sample as the hub last read
|
|
61
87
|
// it from ITS /api/host/stats?latest=1, plus the hub-clock stamp that gates the next re-read. Same
|
|
62
88
|
// module-state contract as _agentAuth: a stale number must never look fresh, and a box that stops
|
|
@@ -67,9 +93,47 @@ const _hostStats = new Map();
|
|
|
67
93
|
// its os/arch/hostname live — the roster deliberately carries none of them (P-1) — so the sweep
|
|
68
94
|
// caches what the probe already fetched instead of asking again.
|
|
69
95
|
const _manifestHost = new Map();
|
|
96
|
+
// instance_id -> the `build` block of the last manifest this hub read from the box (WP-0,
|
|
97
|
+
// docs/fleet-auto-update-pooled-machines-2026-10-26.html S-1/S-2). Same lifetime and the same write
|
|
98
|
+
// sites as `_manifestHost`: the manifest the probe already fetched is the version truth the fleet
|
|
99
|
+
// reconciler and the Machines card read ("which build is that box running?"), so nothing re-asks.
|
|
100
|
+
const _manifestBuild = new Map();
|
|
101
|
+
// instance_id -> the agent-skill push result (WP-3, docs/remote-agent-skills-bundles-2026-10-25.html
|
|
102
|
+
// §6.5). Module state for the same reason as _agentAuth: it describes the LAST push this process
|
|
103
|
+
// made, it is not durable truth about the box, and a restart must read "never pushed" rather than a
|
|
104
|
+
// stale tree. `pushError` is cleared by the next success and kept otherwise; `sentTreeHash`/
|
|
105
|
+
// `sentAt`/`sentCount`/`heldBack` always describe the last SUCCESSFUL push (D-6: a failure never
|
|
106
|
+
// erases what the box is actually running).
|
|
107
|
+
const _agentBundlePush = new Map();
|
|
108
|
+
// instance_id -> { onBox, checkedAt } (WP-7 §6.5/D-11). The box's OWN skill inventory as this hub
|
|
109
|
+
// last read it from ITS /api/agent-skills/inventory, classified against the hub's copy and the base
|
|
110
|
+
// the BOX reported. Module state for the same reason as _agentAuth: it is the last read this process
|
|
111
|
+
// made, and a box that stops answering keeps its entry with a FROZEN onBox — never blanked to "no
|
|
112
|
+
// skills", which would read as "the box lost everything". `checkedAt` is the hub clock stamp that
|
|
113
|
+
// gates the next read (SAME 60 s constant, SAME tick — this module still owns ONE timer site).
|
|
114
|
+
const _agentSkills = new Map();
|
|
115
|
+
// WP-1 §6.2: instance_id -> { block, checkedAt }. The box's OWN model availability (§6.1) as this
|
|
116
|
+
// hub last read it from ITS /api/models/availability, plus the hub-clock stamp that gates the next
|
|
117
|
+
// read. D-9: the read rides the EXISTING 60 s probe tick and is refreshed at most once per
|
|
118
|
+
// ORIGIN_TTL_MS (10 min) per machine — no second timer, no poll. Module state for the same reason as
|
|
119
|
+
// _agentAuth. `block === null` is a REAL answer ("this box predates WP-1"), not "never read" —
|
|
120
|
+
// `checkedAt` is what tells the two apart, and a failed read keeps the previous block untouched.
|
|
121
|
+
const _machineModels = new Map();
|
|
122
|
+
// WP-1 §6.2: how long a box's availability answer is trusted. Same value as the box's own per-origin
|
|
123
|
+
// cache, on purpose: the hub must not re-ask more often than the box would re-probe.
|
|
124
|
+
const MODELS_REFRESH_MS = ORIGIN_TTL_MS;
|
|
125
|
+
// platform -> { items, at }: the hub's own item list (`{agent, kind, name, itemHash}`) as WP-1's
|
|
126
|
+
// builder produced it for that platform. Memoized for AGENT_AUTH_REFRESH_MS so N boxes of one
|
|
127
|
+
// platform cost ONE build per minute. Only `items` is kept — `blobsByHash` is hub-local absolute
|
|
128
|
+
// paths and would pin the whole hub tree in memory for a classification that never reads it.
|
|
129
|
+
const _hubItemCache = new Map();
|
|
70
130
|
// The last sweep's self-suspicion verdict, surfaced by GET /api/machines. Not persisted per
|
|
71
131
|
// machine (design §7 "not persisted per machine") — it is a hub-wide observation about one sweep.
|
|
72
132
|
let _lastSweep = { hubNetworkSuspect: false, at: 0 };
|
|
133
|
+
// WP-3 rev 3 (§13.6 D-17): does THIS hub have an away path? The installer writes its hub half to
|
|
134
|
+
// `~/.config/ctm-away-path/proxy.sh` (bin/away-path.sh cmd_install_hub). Read once per sweep, off
|
|
135
|
+
// the request path (see machineAwayReason); `null` = this process has not swept yet.
|
|
136
|
+
let _awayPathProxyExists = null;
|
|
73
137
|
// Bumped by every registry write this process makes (register, remove, seed, a probe that changed
|
|
74
138
|
// a row). Readers that keep a snapshot (lib/remote-machine-catalog.js) compare it instead of
|
|
75
139
|
// trusting a TTL alone: a snapshot taken a moment before a registration must not hide the new
|
|
@@ -127,10 +191,36 @@ function _manifestHostBlock(body) {
|
|
|
127
191
|
};
|
|
128
192
|
}
|
|
129
193
|
|
|
194
|
+
/**
|
|
195
|
+
* WP-0 (S-1): the manifest's `build` block — the box's own version truth (`lib/instance-manifest.js`
|
|
196
|
+
* publishes `{ version, sha, builtAt, release }`). Each field is bounded; a manifest without the
|
|
197
|
+
* block (an older box) reads as three empty strings, never undefined, so a reader can tell "no
|
|
198
|
+
* build published" from "never probed" (`null`).
|
|
199
|
+
*/
|
|
200
|
+
function _manifestBuildBlock(body) {
|
|
201
|
+
const manifest = (body && (body.manifest || body.instance)) || body || {};
|
|
202
|
+
const build = manifest.build || {};
|
|
203
|
+
return {
|
|
204
|
+
sha: _string(build.sha, 64),
|
|
205
|
+
version: _string(build.version, 64),
|
|
206
|
+
release: _string(build.release, 64),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
130
210
|
function _manifestDisplayName(body) {
|
|
131
211
|
return _manifestHostBlock(body).displayName;
|
|
132
212
|
}
|
|
133
213
|
|
|
214
|
+
/**
|
|
215
|
+
* D-9/WP-3: the cached manifest host block for an instance (the box's `os` is what the bundle
|
|
216
|
+
* build targets), or null when this hub has never read that box's manifest. A COPY: the cache is
|
|
217
|
+
* module state and a reader must not be able to mutate it.
|
|
218
|
+
*/
|
|
219
|
+
function manifestHostFor(instanceId) {
|
|
220
|
+
const host = _manifestHost.get(_string(instanceId, 160)) || null;
|
|
221
|
+
return host ? { ...host } : null;
|
|
222
|
+
}
|
|
223
|
+
|
|
134
224
|
function _hostLabel(url) {
|
|
135
225
|
try { return _string(new URL(url).host, 160); } catch { return _string(url, 160); }
|
|
136
226
|
}
|
|
@@ -172,6 +262,60 @@ function _dialUrls(machine = {}) {
|
|
|
172
262
|
return pinned ? [pinned] : [];
|
|
173
263
|
}
|
|
174
264
|
|
|
265
|
+
// WP-3 rev 3 (§13.6 D-17): is this host one only the home LAN (or this machine) can name? A URL
|
|
266
|
+
// that does not parse is NOT local-only — "would this work from another network?" would be unknown.
|
|
267
|
+
function _isLocalOnlyHost(hostname) {
|
|
268
|
+
const host = String(hostname || '').toLowerCase();
|
|
269
|
+
if (!host) return false;
|
|
270
|
+
if (host === 'localhost' || host.endsWith('.localhost')) return true;
|
|
271
|
+
if (host.endsWith('.local')) return true;
|
|
272
|
+
// `new URL('http://[::1]:3456').hostname` keeps the brackets; strip them before parsing.
|
|
273
|
+
const ip = host.startsWith('[') && host.endsWith(']') ? host.slice(1, -1) : host;
|
|
274
|
+
if (ip.includes(':')) {
|
|
275
|
+
if (ip === '::1') return true;
|
|
276
|
+
const first = parseInt(ip.split(':')[0] || '', 16);
|
|
277
|
+
if (!Number.isInteger(first)) return false;
|
|
278
|
+
// fc00::/7 (unique-local) and fe80::/10 (link-local).
|
|
279
|
+
return (first >= 0xfc00 && first <= 0xfdff) || (first >= 0xfe80 && first <= 0xfebf);
|
|
280
|
+
}
|
|
281
|
+
const octets = ip.split('.');
|
|
282
|
+
if (octets.length !== 4) return false;
|
|
283
|
+
const nums = octets.map((part) => (/^\d{1,3}$/.test(part) ? Number(part) : NaN));
|
|
284
|
+
if (nums.some((n) => !Number.isInteger(n) || n > 255)) return false;
|
|
285
|
+
if (nums[0] === 127 || nums[0] === 10) return true; // 127/8 loopback, 10/8 private
|
|
286
|
+
if (nums[0] === 172 && nums[1] >= 16 && nums[1] <= 31) return true; // 172.16/12
|
|
287
|
+
return nums[0] === 192 && nums[1] === 168; // 192.168/16
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* WP-3 rev 3 (docs/off-lan-hub-to-box-access-2026-10-31.html §13.6 D-17): WHY an `unreachable` box
|
|
292
|
+
* whose every dial URL is local-only cannot be dialled from here, or null when the hub cannot know.
|
|
293
|
+
* The one readable fact is whether this hub has an away path at all — the hub-side
|
|
294
|
+
* `~/.config/ctm-away-path/proxy.sh` (bin/away-path.sh cmd_install_hub): no proxy ⇒ nothing could
|
|
295
|
+
* ever route to the box from off-LAN; proxy present ⇒ this box's tunnel or forward is the down half.
|
|
296
|
+
*
|
|
297
|
+
* That fact is read ONCE PER SWEEP (`probeMachinesOnce`), never on the request path: /api/machines
|
|
298
|
+
* renders on the main thread. `null` (module state) = this process has not swept yet — an honest
|
|
299
|
+
* "not known", and `unreachable` is only ever written BY a sweep.
|
|
300
|
+
*/
|
|
301
|
+
function machineAwayReason(machine = {}, deps = {}) {
|
|
302
|
+
const row = machine || {};
|
|
303
|
+
if (row.health !== 'unreachable') return null;
|
|
304
|
+
const urls = _dialUrls(row);
|
|
305
|
+
if (!urls.length) return null;
|
|
306
|
+
const localOnly = urls.every((raw) => {
|
|
307
|
+
try {
|
|
308
|
+
return _isLocalOnlyHost(new URL(raw).hostname);
|
|
309
|
+
} catch {
|
|
310
|
+
return false; // expected: an unparsable URL is not local-only (see _isLocalOnlyHost)
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
if (!localOnly) return null;
|
|
314
|
+
const proxyExists = typeof deps.awayPathProxyExists === 'boolean' ? deps.awayPathProxyExists : _awayPathProxyExists;
|
|
315
|
+
if (proxyExists === null) return null; // this hub has not swept yet, so it does not know
|
|
316
|
+
return proxyExists ? 'away_path_down' : 'no_away_path';
|
|
317
|
+
}
|
|
318
|
+
|
|
175
319
|
// One JSON GET against a box, with the AbortController + unref'd timer shape server.js's golive
|
|
176
320
|
// probe uses, so a hung box can never hold the tick open past timeoutMs. Shared by the manifest and
|
|
177
321
|
// the agent-sign-in probes: this is the ONE timer site in the file (a second copy would be a new
|
|
@@ -215,7 +359,7 @@ async function _probeManifest(url, options = {}) {
|
|
|
215
359
|
// Reachable but degraded: the box answered and told us nothing we can pin or trust.
|
|
216
360
|
if (!instanceId) return { ok: false, code: 'machine_manifest_invalid', error: 'manifest_instance_id_missing', body: fetched.body };
|
|
217
361
|
const host = _manifestHostBlock(fetched.body);
|
|
218
|
-
return { ok: true, instanceId, displayName: host.displayName, host };
|
|
362
|
+
return { ok: true, instanceId, displayName: host.displayName, host, build: _manifestBuildBlock(fetched.body) };
|
|
219
363
|
}
|
|
220
364
|
|
|
221
365
|
/**
|
|
@@ -351,6 +495,18 @@ function _cacheManifestHost(instanceId, host) {
|
|
|
351
495
|
}
|
|
352
496
|
}
|
|
353
497
|
|
|
498
|
+
/**
|
|
499
|
+
* WP-0 (S-2): cache the manifest's `build` block beside `_manifestHost` — written by the same single
|
|
500
|
+
* caller (the sweep), cleared by the same test seam. A build change is deliberately NOT registry
|
|
501
|
+
* news: it is a version stamp the fleet reconciler reads, not machine identity, and bumping the
|
|
502
|
+
* revision on every restart of every box would wake every snapshot reader for nothing.
|
|
503
|
+
*/
|
|
504
|
+
function _cacheManifestBuild(instanceId, build) {
|
|
505
|
+
const id = _string(instanceId, 160);
|
|
506
|
+
if (!id || !build) return;
|
|
507
|
+
_manifestBuild.set(id, build);
|
|
508
|
+
}
|
|
509
|
+
|
|
354
510
|
/** A box's logged-in flag -> the hub's three-state verdict (unknown for anything unexpected). */
|
|
355
511
|
function _agentLoginState(signedIn) {
|
|
356
512
|
if (signedIn === true) return 'signed_in';
|
|
@@ -378,6 +534,240 @@ async function _probeAgentAuth(url, options = {}) {
|
|
|
378
534
|
return { claude: _agentLoginState(signedInFor('claude')), codex: _agentLoginState(signedInFor('codex')) };
|
|
379
535
|
}
|
|
380
536
|
|
|
537
|
+
/**
|
|
538
|
+
* WP-13b (D-15): the box's OWN available-agents answer, reduced to the one compact presence the hub
|
|
539
|
+
* surfaces — `{ dsh: boolean }`.
|
|
540
|
+
*
|
|
541
|
+
* The route (`GET /api/models/available-agents`) answers `agentCliCache.getAvailableAgents()`'s
|
|
542
|
+
* body: `{ ok, agents: { <agent>: { available, … } }, <agent>: boolean }`. This read keeps ONLY
|
|
543
|
+
* whether `dsh` is present; paths and every other agent are dropped at the door, so nothing the box
|
|
544
|
+
* printed about its filesystem is ever cached hub-side.
|
|
545
|
+
*
|
|
546
|
+
* Three outcomes, matching the sign-in/skills reads:
|
|
547
|
+
* 'block' — a 200 that carries an explicit dsh verdict: store the compact presence.
|
|
548
|
+
* 'keep' — a failed read, a 404 (a box older than this route), a non-object/unparseable body, or
|
|
549
|
+
* a body that names no dsh at all: KEEP the previous value (P3, "Unknown != Dead"). Only
|
|
550
|
+
* an explicit answer is news; a silent box is never read as "the box lost dsh".
|
|
551
|
+
*/
|
|
552
|
+
async function _probeAgentAvailability(url, options = {}) {
|
|
553
|
+
const fetched = await _getJsonBox(url, '/api/models/available-agents', options);
|
|
554
|
+
if (!fetched.ok) return { state: 'keep' };
|
|
555
|
+
const presence = _dshPresenceFromBody(fetched.body);
|
|
556
|
+
if (!presence) return { state: 'keep' };
|
|
557
|
+
return { state: 'block', presence };
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* The `dsh` verdict in a `/api/models/available-agents` body, or null when the body carries none.
|
|
562
|
+
* Accepted shapes (the hub must not care which one its box serves): the route's own
|
|
563
|
+
* `{ dsh: <boolean>, agents: { dsh: { available } } }`, and the bare `{ agents: {…} }` / `{ dsh }`.
|
|
564
|
+
*/
|
|
565
|
+
function _dshPresenceFromBody(body) {
|
|
566
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) return null;
|
|
567
|
+
const agents = body.agents && typeof body.agents === 'object' && !Array.isArray(body.agents) ? body.agents : null;
|
|
568
|
+
const detail = agents ? agents.dsh : undefined;
|
|
569
|
+
if (detail !== undefined) {
|
|
570
|
+
if (typeof detail === 'boolean') return { dsh: detail };
|
|
571
|
+
if (detail && typeof detail === 'object' && typeof detail.available === 'boolean') return { dsh: detail.available };
|
|
572
|
+
return null; // present but not a verdict — "no answer", never "not installed"
|
|
573
|
+
}
|
|
574
|
+
if (typeof body.dsh === 'boolean') return { dsh: body.dsh };
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Store one box's agent presence. `noteRegistryChanged()` fires only when the SURFACED presence
|
|
580
|
+
* actually moved, exactly like the sign-in read above: a first read is not news (registration already
|
|
581
|
+
* bumped the revision), and an identical re-read under a newer `checkedAt` is not either.
|
|
582
|
+
*/
|
|
583
|
+
function _setAgentPresence(instanceId, presence, now) {
|
|
584
|
+
const id = _string(instanceId, 160);
|
|
585
|
+
if (!id || !presence) return null;
|
|
586
|
+
const previous = _agentPresence.get(id) || null;
|
|
587
|
+
const next = { presence: { dsh: presence.dsh === true }, checkedAt: now };
|
|
588
|
+
_agentPresence.set(id, next);
|
|
589
|
+
if (previous && previous.presence.dsh !== next.presence.dsh) noteRegistryChanged();
|
|
590
|
+
return next;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* WP-7 (§6.5/AC-7a): read a box's OWN skill inventory from ITS `GET /api/agent-skills/inventory`.
|
|
595
|
+
*
|
|
596
|
+
* Never throws and never reports anything but the parsed body or null. A box running an older CTM
|
|
597
|
+
* answers 404, a silent box never answers, and an unparseable body is not an inventory — all three
|
|
598
|
+
* read "no inventory this time", which the caller answers with KEEP THE LAST VALUE. It is
|
|
599
|
+
* deliberately not "an empty inventory": that would claim the box lost every skill it had.
|
|
600
|
+
*/
|
|
601
|
+
async function _probeAgentSkills(url, options = {}) {
|
|
602
|
+
const fetched = await _getJsonBox(url, '/api/agent-skills/inventory', options);
|
|
603
|
+
if (!fetched.ok) return null;
|
|
604
|
+
const body = fetched.body;
|
|
605
|
+
return body && typeof body === 'object' && !Array.isArray(body) ? body : null;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* WP-1 (§6.2/§6.1): read a box's OWN model availability from ITS `GET /api/models/availability`.
|
|
610
|
+
*
|
|
611
|
+
* Three outcomes, and the difference between them is the whole contract:
|
|
612
|
+
* 'block' — the box answered with a §6.1 body: store the derived §6.2 block.
|
|
613
|
+
* 'null' — the box answered 404: it predates WP-1, so `models` is null and the row renders
|
|
614
|
+
* exactly as it did before (AC-1e). Stamped, so a pre-WP-1 box is asked once per TTL.
|
|
615
|
+
* 'keep' — anything else (silent box, non-JSON answer, transport throw): KEEP the previous block
|
|
616
|
+
* and move only the stamp (the remote-skills WP-7 rule). Never blank a block we hold.
|
|
617
|
+
*/
|
|
618
|
+
async function _probeModelsAvailability(url, options = {}) {
|
|
619
|
+
const fetched = await _getJsonBox(url, '/api/models/availability', options);
|
|
620
|
+
if (fetched.ok) {
|
|
621
|
+
const body = fetched.body;
|
|
622
|
+
// A 200 that is not a §6.1 body (no `routes` array) is not an answer to this question: KEEP.
|
|
623
|
+
if (!body || typeof body !== 'object' || Array.isArray(body) || !Array.isArray(body.routes)) {
|
|
624
|
+
return { state: 'keep' };
|
|
625
|
+
}
|
|
626
|
+
return { state: 'block', block: _modelsBlockFrom(body) };
|
|
627
|
+
}
|
|
628
|
+
if (fetched.status === 404) return { state: 'null', block: null };
|
|
629
|
+
return { state: 'keep' };
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* §6.2: the block the machine row surfaces, derived from the box's §6.1 body. `usable` is the
|
|
634
|
+
* launch-able count the card states (enabled, and either keyed or a local runtime, and reachable);
|
|
635
|
+
* `unreachable` lists the enabled rows the box cannot get to; `delegate` mirrors the box's own
|
|
636
|
+
* /delegate picks with the verdict of the route each one names.
|
|
637
|
+
*/
|
|
638
|
+
function _modelsBlockFrom(body) {
|
|
639
|
+
const routes = (Array.isArray(body.routes) ? body.routes : []).filter((row) => row && typeof row === 'object');
|
|
640
|
+
const enabled = routes.filter((row) => row.enabled !== false);
|
|
641
|
+
const isOllama = (row) => _string(row.type, 60).toLowerCase() === 'ollama';
|
|
642
|
+
const usable = enabled.filter((row) => row.reach === 'reachable' && (row.keyed === true || isOllama(row))).length;
|
|
643
|
+
const unreachable = enabled
|
|
644
|
+
.filter((row) => row.reach === 'unreachable' || row.reach === 'no_server')
|
|
645
|
+
.map((row) => ({
|
|
646
|
+
id: _string(row.id, 200),
|
|
647
|
+
type: _string(row.type, 60),
|
|
648
|
+
origin: row.origin ? _string(row.origin, 300) : null,
|
|
649
|
+
}));
|
|
650
|
+
// WP-5 (§6.4/R-6): the box's own route list, so the hub can offer "Use on <box>" for the routes
|
|
651
|
+
// the box does NOT have and refuse one whose origin that same list calls unreachable. THREE fields
|
|
652
|
+
// only — id, origin and the reach verdict — never a name, a type, a credential flag or a key: the
|
|
653
|
+
// hub needs "does this box already have this id" and "can this box reach this origin", nothing
|
|
654
|
+
// else. A row with no id is not a route the hub can match, so it is skipped rather than surfaced
|
|
655
|
+
// as an empty id.
|
|
656
|
+
const routeRows = routes
|
|
657
|
+
.map((row) => ({
|
|
658
|
+
id: _string(row.id, 200),
|
|
659
|
+
origin: row.origin ? _string(row.origin, 300) : null,
|
|
660
|
+
reach: _string(row.reach, 20) || 'unknown',
|
|
661
|
+
}))
|
|
662
|
+
.filter((row) => row.id);
|
|
663
|
+
const delegate = (Array.isArray(body.delegateDefaults) ? body.delegateDefaults : [])
|
|
664
|
+
.filter((pref) => pref && typeof pref === 'object')
|
|
665
|
+
.map((pref) => ({
|
|
666
|
+
agent: _string(pref.agent, 20),
|
|
667
|
+
routeId: _string(pref.routeId, 200),
|
|
668
|
+
model: _string(pref.model, 200),
|
|
669
|
+
reach: _string(pref.reach, 20) || 'unknown',
|
|
670
|
+
}));
|
|
671
|
+
const nativeOf = (agent) => ({
|
|
672
|
+
signedIn: !!(body.native && body.native[agent] && body.native[agent].signedIn === true),
|
|
673
|
+
});
|
|
674
|
+
return {
|
|
675
|
+
checkedAt: _string(body.checkedAt, 60),
|
|
676
|
+
usable,
|
|
677
|
+
unreachable,
|
|
678
|
+
routes: routeRows,
|
|
679
|
+
delegate,
|
|
680
|
+
native: { claude: nativeOf('claude'), codex: nativeOf('codex') },
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Store one box's availability block. `noteRegistryChanged()` fires only when the SURFACED block
|
|
686
|
+
* moved — a first read or a real change is news, an identical re-read under a newer `checkedAt` is
|
|
687
|
+
* not, or every 60 s tick would invalidate every reader's snapshot for nothing.
|
|
688
|
+
*/
|
|
689
|
+
function _setMachineModels(instanceId, block, now) {
|
|
690
|
+
const id = _string(instanceId, 160);
|
|
691
|
+
if (!id) return null;
|
|
692
|
+
const previous = _machineModels.get(id) || null;
|
|
693
|
+
const next = { block, checkedAt: now };
|
|
694
|
+
_machineModels.set(id, next);
|
|
695
|
+
if (!previous || JSON.stringify(previous.block) !== JSON.stringify(next.block)) noteRegistryChanged();
|
|
696
|
+
return next;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/** WP-1 §6.2: the box's availability block as this hub last read it, or null (never read / pre-WP-1). */
|
|
700
|
+
function modelsFor(instanceId) {
|
|
701
|
+
const entry = _machineModels.get(_string(instanceId, 160));
|
|
702
|
+
return entry ? entry.block : null;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* WP-7 (§6.5): the hub's own item list for one box platform, memoized for AGENT_AUTH_REFRESH_MS.
|
|
707
|
+
*
|
|
708
|
+
* `deps.buildAgentBundle` is injectable so a unit test never reads the real home; production falls
|
|
709
|
+
* back to WP-1's builder. A build failure is REPORTed and answered with null — "the hub cannot read
|
|
710
|
+
* its own skills" and "the hub has no skills" are DIFFERENT claims, and only the second one may
|
|
711
|
+
* rewrite a box's classification to `new_on_box`.
|
|
712
|
+
*
|
|
713
|
+
* @returns {Promise<{items: object[]}|null>}
|
|
714
|
+
*/
|
|
715
|
+
async function _hubItemsForPlatform(platform, { deps = {}, now = Date.now() } = {}) {
|
|
716
|
+
const key = _string(platform, 40) || 'unknown';
|
|
717
|
+
const cached = _hubItemCache.get(key) || null;
|
|
718
|
+
if (cached && now - cached.at < AGENT_AUTH_REFRESH_MS) return { items: cached.items };
|
|
719
|
+
const build = typeof deps.buildAgentBundle === 'function'
|
|
720
|
+
? deps.buildAgentBundle
|
|
721
|
+
: require('./agent-bundle/build').buildAgentBundle;
|
|
722
|
+
try {
|
|
723
|
+
const bundle = await build({ targetPlatform: key });
|
|
724
|
+
const items = (bundle && Array.isArray(bundle.items) ? bundle.items : []).map((item) => ({
|
|
725
|
+
agent: item.agent, kind: item.kind, name: item.name, itemHash: item.itemHash,
|
|
726
|
+
}));
|
|
727
|
+
_hubItemCache.set(key, { items, at: now });
|
|
728
|
+
return { items };
|
|
729
|
+
} catch (error) {
|
|
730
|
+
// REPORT: a hub-side build fault leaves every box's `onBox` exactly as it was (see the caller).
|
|
731
|
+
errorSink.report('agent-skill-inventory-build', error);
|
|
732
|
+
return null;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* WP-7 (§6.5/AC-7a): classify one box's inventory read. `boxSkills` come from the box, `baseItems`
|
|
738
|
+
* from the base the BOX reported (that is the design adjustment: the hub's push record is
|
|
739
|
+
* in-process, so classification survives a hub restart only if the box carries the base back).
|
|
740
|
+
*
|
|
741
|
+
* @returns {Promise<object[]|null>} the classification rows, or null when the hub could not read its
|
|
742
|
+
* own skills — the caller then keeps the previous `onBox` rather than publishing a wrong one.
|
|
743
|
+
*/
|
|
744
|
+
async function _classifyInventory(inventory, { deps = {}, now = Date.now() } = {}) {
|
|
745
|
+
const boxSkills = Array.isArray(inventory.skills) ? inventory.skills : [];
|
|
746
|
+
// §6.5: only a box holding at least one UNMANAGED skill is worth the hub's own build — a box whose
|
|
747
|
+
// skills are all CTM's (AC-7d) has nothing to classify, so the hub never walks its own tree for it.
|
|
748
|
+
const unmanaged = boxSkills.some((skill) => skill && (skill.managed === null || skill.managed === undefined));
|
|
749
|
+
if (!unmanaged) return [];
|
|
750
|
+
const hub = await _hubItemsForPlatform(inventory.platform, { deps, now });
|
|
751
|
+
if (!hub) return null;
|
|
752
|
+
const baseItems = inventory.base && Array.isArray(inventory.base.items) ? inventory.base.items : [];
|
|
753
|
+
return bundleInventory.classifyBoxSkills({ hubItems: hub.items, baseItems, boxSkills });
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Store one box's classification. `noteRegistryChanged()` fires when the SURFACED block actually
|
|
758
|
+
* moved — a first read (the row goes from "no inventory" to a review list) is news, an identical
|
|
759
|
+
* re-read with only a newer `checkedAt` is not, or every 15 s tick would invalidate every snapshot.
|
|
760
|
+
*/
|
|
761
|
+
function _setAgentSkills(instanceId, onBox, now) {
|
|
762
|
+
const id = _string(instanceId, 160);
|
|
763
|
+
if (!id) return null;
|
|
764
|
+
const previous = _agentSkills.get(id) || null;
|
|
765
|
+
const next = { onBox, checkedAt: now };
|
|
766
|
+
_agentSkills.set(id, next);
|
|
767
|
+
if (!previous || JSON.stringify(previous.onBox) !== JSON.stringify(next.onBox)) noteRegistryChanged();
|
|
768
|
+
return next;
|
|
769
|
+
}
|
|
770
|
+
|
|
381
771
|
function _rows(deps = {}) {
|
|
382
772
|
const db = deps.db;
|
|
383
773
|
if (!db || typeof db.listMachineRows !== 'function') return [];
|
|
@@ -394,7 +784,7 @@ function _isPinned(row = {}) {
|
|
|
394
784
|
return Number(row.lastSeenAt || row.last_seen_at || 0) > 0;
|
|
395
785
|
}
|
|
396
786
|
|
|
397
|
-
function _machineView(row = {}) {
|
|
787
|
+
function _machineView(row = {}, deps = {}) {
|
|
398
788
|
const lastSeenAt = Number(row.lastSeenAt ?? row.last_seen_at ?? 0) || 0;
|
|
399
789
|
const enabled = Number(row.enabled ?? 1) !== 0;
|
|
400
790
|
const health = _string(row.health, 40) || 'unknown';
|
|
@@ -403,11 +793,14 @@ function _machineView(row = {}) {
|
|
|
403
793
|
// manifest, which the sweep caches per instance. The registry row's own label is the fallback for
|
|
404
794
|
// a box this hub has not read a manifest from yet.
|
|
405
795
|
const manifest = _manifestHost.get(instanceId) || null;
|
|
796
|
+
// WP-0 (S-1/S-2): the build stamp the box published in the manifest this hub last read, or null
|
|
797
|
+
// when it has never answered (never a fabricated {sha:''} row pretending to be a version).
|
|
798
|
+
const build = _manifestBuild.get(instanceId) || null;
|
|
406
799
|
// D-12/D-16: the box's own host sample (load, memory, disk, GPU, tasks) and its newest session
|
|
407
800
|
// create. `checkedAt` is the HUB clock stamp of the read, so a reader can label a stale number
|
|
408
801
|
// instead of showing it as current.
|
|
409
802
|
const stats = _hostStats.get(instanceId) || null;
|
|
410
|
-
|
|
803
|
+
const view = {
|
|
411
804
|
instanceId,
|
|
412
805
|
displayName: _string((manifest && manifest.displayName) || row.displayName || row.display_name, 200),
|
|
413
806
|
url: _string(row.url, 1000),
|
|
@@ -425,18 +818,33 @@ function _machineView(row = {}) {
|
|
|
425
818
|
os: _string(manifest && manifest.os, 40),
|
|
426
819
|
arch: _string(manifest && manifest.arch, 40),
|
|
427
820
|
hostname: _string(manifest && manifest.hostname, 160),
|
|
821
|
+
// WP-0 (S-1): the box's own version truth (the manifest's build block), null until it answers.
|
|
822
|
+
build,
|
|
428
823
|
// null = this hub has never read a sample for this box (never a zeroed-out sample).
|
|
429
824
|
stats: stats ? { sample: stats.sample, checkedAt: stats.checkedAt } : null,
|
|
825
|
+
// WP-3 (§6.5) + WP-7 (§6.5/D-11) + WP-8 (§6.5): the skills push state for this box — the last
|
|
826
|
+
// successful tree and the last failure if there is one — together with the classification of the
|
|
827
|
+
// box's OWN skills (`onBox`, `checkedAt`) and this machine's per-machine plugin exclusions.
|
|
828
|
+
// null = this process neither pushed to, read this box, nor excluded anything for it.
|
|
829
|
+
skills: _skillsView(instanceId, deps),
|
|
830
|
+
// WP-1 (§6.2): the box's own model availability, read on the existing tick. null = this hub has
|
|
831
|
+
// never read it, or the box predates WP-1 — the row then renders exactly as it did before.
|
|
832
|
+
models: modelsFor(instanceId),
|
|
430
833
|
lastUsedAt: Number((stats && stats.sample && stats.sample.lastUsedAt) || 0) || 0,
|
|
431
834
|
// D-5: "unknown" until the box has ever answered; a disabled row is never a picker
|
|
432
835
|
// candidate, and only reachable/unhealthy ever render as cards.
|
|
433
836
|
pickerState: !enabled ? 'unknown' : health,
|
|
434
837
|
visible: enabled && (health === 'reachable' || health === 'unhealthy'),
|
|
435
838
|
};
|
|
839
|
+
// WP-3 rev 3 (§13.6 D-17): computed LAST, on the view itself, so the reason is derived from the
|
|
840
|
+
// exact `health` / `url` / `urls` the card renders (never a second, possibly-different reading).
|
|
841
|
+
// null for every row the hub cannot speak for; the Machines card then renders no note at all.
|
|
842
|
+
view.awayReason = machineAwayReason(view, deps);
|
|
843
|
+
return view;
|
|
436
844
|
}
|
|
437
845
|
|
|
438
846
|
function listMachines(deps = {}) {
|
|
439
|
-
const machines = _rows(deps).map((row) => _machineView(row));
|
|
847
|
+
const machines = _rows(deps).map((row) => _machineView(row, deps));
|
|
440
848
|
return { machines, hubNetworkSuspect: !!_lastSweep.hubNetworkSuspect };
|
|
441
849
|
}
|
|
442
850
|
|
|
@@ -531,6 +939,13 @@ async function probeMachinesOnce(options = {}, deps = {}) {
|
|
|
531
939
|
return { ok: true, probed: 0, reachable: 0, unknown: 0, unreachable: 0, unhealthy: 0, hubNetworkSuspect: false, results: [] };
|
|
532
940
|
}
|
|
533
941
|
|
|
942
|
+
// WP-3 rev 3 (§13.6 D-17): ONE away-path read per sweep, OFF the request path (see
|
|
943
|
+
// machineAwayReason). A missing proxy.sh IS the false answer; `deps` may inject the verdict.
|
|
944
|
+
_awayPathProxyExists = typeof deps.awayPathProxyExists === 'boolean'
|
|
945
|
+
? deps.awayPathProxyExists
|
|
946
|
+
: await fs.promises.access(path.join(os.homedir(), '.config', 'ctm-away-path', 'proxy.sh'))
|
|
947
|
+
.then(() => true, () => false /* expected: an absent proxy.sh is the "no away path" answer */);
|
|
948
|
+
|
|
534
949
|
// P-8: one credential PER TARGET (pool secret for a member, the legacy env bearer for a box that
|
|
535
950
|
// never joined), so the header builder is resolved per machine, not once per sweep.
|
|
536
951
|
const headersFor = _peerHeadersFor(deps);
|
|
@@ -604,6 +1019,9 @@ async function probeMachinesOnce(options = {}, deps = {}) {
|
|
|
604
1019
|
// D-4: the manifest we just read IS the machine's settings + host identity. Cached here so
|
|
605
1020
|
// /api/machines serves the machine-owned displayName/favorite/hidden and os/arch/hostname.
|
|
606
1021
|
_cacheManifestHost(machine.instanceId, probe.host);
|
|
1022
|
+
// WP-0 (S-2): the same manifest read carries the box's build stamp; cache it here, the one place
|
|
1023
|
+
// the hub learns it, so /api/machines can serve build.sha without a second fetch.
|
|
1024
|
+
_cacheManifestBuild(machine.instanceId, probe.build);
|
|
607
1025
|
|
|
608
1026
|
const previous = _agentAuth.get(machine.instanceId);
|
|
609
1027
|
if (!previous || now - previous.checkedAt >= AGENT_AUTH_REFRESH_MS) {
|
|
@@ -614,6 +1032,50 @@ async function probeMachinesOnce(options = {}, deps = {}) {
|
|
|
614
1032
|
if (previous && (previous.claude !== probed.claude || previous.codex !== probed.codex)) noteRegistryChanged();
|
|
615
1033
|
}
|
|
616
1034
|
|
|
1035
|
+
// WP-13b (D-15): the box's OWN available-agents answer, on the SAME tick and under the SAME
|
|
1036
|
+
// per-instance 60 s stamp as the sign-in read — the New Session card and the machine row need to
|
|
1037
|
+
// know whether the box has the DeepSeek Harness BEFORE a create is dialled. A failed / 404 /
|
|
1038
|
+
// unparseable read keeps the last value (P3): only an explicit answer moves it.
|
|
1039
|
+
const presenceEntry = _agentPresence.get(machine.instanceId) || null;
|
|
1040
|
+
if (!presenceEntry || now - presenceEntry.checkedAt >= AGENT_AUTH_REFRESH_MS) {
|
|
1041
|
+
const read = await _probeAgentAvailability(url, { fetchImpl, timeoutMs, peerHeaders: machineHeaders });
|
|
1042
|
+
if (read.state === 'block') _setAgentPresence(machine.instanceId, read.presence, now);
|
|
1043
|
+
// A failed / 404 / unparseable read KEEPS the last value and moves only the stamp, so a box
|
|
1044
|
+
// without this route is asked once a minute rather than on every tick. A FIRST failure stores
|
|
1045
|
+
// nothing at all — there is no previous value to keep, and "never read" is the honest answer.
|
|
1046
|
+
else if (presenceEntry) _setAgentPresence(machine.instanceId, presenceEntry.presence, now);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// WP-7 (§6.5/AC-7a/AC-7c): the box's own skill inventory, on the SAME tick and under the SAME
|
|
1050
|
+
// per-instance 60 s stamp as the sign-in read — one pass over `results`, no timer of this
|
|
1051
|
+
// module's own (AC-7b), so a box-created skill shows up on the row within 60 s + one 15 s tick.
|
|
1052
|
+
const skillsEntry = _agentSkills.get(machine.instanceId) || null;
|
|
1053
|
+
if (!skillsEntry || now - skillsEntry.checkedAt >= AGENT_AUTH_REFRESH_MS) {
|
|
1054
|
+
const inventory = await _probeAgentSkills(url, { fetchImpl, timeoutMs, peerHeaders: machineHeaders });
|
|
1055
|
+
if (inventory) {
|
|
1056
|
+
const onBox = await _classifyInventory(inventory, { deps, now });
|
|
1057
|
+
// null = the hub could not read its OWN skills: keep what we hold (a build fault is not news
|
|
1058
|
+
// about the box), and move only the stamp so the next read waits its full 60 s.
|
|
1059
|
+
_setAgentSkills(machine.instanceId, onBox === null ? (skillsEntry ? skillsEntry.onBox : null) : onBox, now);
|
|
1060
|
+
} else if (skillsEntry) {
|
|
1061
|
+
// An older box (404) or a failed read: KEEP the last value, no error noise — and move the
|
|
1062
|
+
// stamp, so a box without this route is asked once a minute rather than on every tick.
|
|
1063
|
+
_setAgentSkills(machine.instanceId, skillsEntry.onBox, now);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// WP-1 (§6.2/D-9): the box's OWN model availability, on the SAME tick and under its own per-
|
|
1068
|
+
// instance ORIGIN_TTL_MS (10 min) stamp — the hub must not ask more often than the box would
|
|
1069
|
+
// re-probe. A failure keeps the last block (never blanked), and a 404 records that the box
|
|
1070
|
+
// predates WP-1 (models: null, the row renders exactly as today).
|
|
1071
|
+
const modelsEntry = _machineModels.get(machine.instanceId) || null;
|
|
1072
|
+
if (!modelsEntry || now - modelsEntry.checkedAt >= MODELS_REFRESH_MS) {
|
|
1073
|
+
const read = await _probeModelsAvailability(url, { fetchImpl, timeoutMs, peerHeaders: machineHeaders });
|
|
1074
|
+
if (read.state === 'block') _setMachineModels(machine.instanceId, read.block, now);
|
|
1075
|
+
else if (read.state === 'null') _setMachineModels(machine.instanceId, null, now);
|
|
1076
|
+
else if (modelsEntry) _setMachineModels(machine.instanceId, modelsEntry.block, now);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
617
1079
|
// D-12/P-5: the box's own host sample and its roster version, refreshed on the SAME tick and
|
|
618
1080
|
// under the SAME per-instance 60 s stamp as the sign-in read — one pass over `results`, no
|
|
619
1081
|
// timer of this module's own. Both are asked ONLY of a box whose manifest probe just answered
|
|
@@ -711,6 +1173,204 @@ function agentAuthFor(instanceId) {
|
|
|
711
1173
|
return _agentAuth.get(_string(instanceId, 160)) || null;
|
|
712
1174
|
}
|
|
713
1175
|
|
|
1176
|
+
/**
|
|
1177
|
+
* WP-13b (D-15): the box's last-read agent presence as `{ dsh: { available: boolean } }`, or null
|
|
1178
|
+
* when this process has never read it. The shape is the one `lib/remote-machine-create.js`'s
|
|
1179
|
+
* `_boxAgentMissing` already accepts verbatim — the gate asks THIS module, never the box, so a
|
|
1180
|
+
* never-read (or restarted) hub holds no verdict and refuses nothing (P3).
|
|
1181
|
+
*/
|
|
1182
|
+
function agentAvailabilityFor(instanceId) {
|
|
1183
|
+
const entry = _agentPresence.get(_string(instanceId, 160));
|
|
1184
|
+
if (!entry) return null;
|
|
1185
|
+
return { dsh: { available: entry.presence.dsh === true } };
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
/**
|
|
1189
|
+
* The §6.5 `skills` block for a machine row, or null when this process neither pushed nor read an
|
|
1190
|
+
* inventory for this box.
|
|
1191
|
+
*
|
|
1192
|
+
* WP-7: the block is the UNION of the two module truths — the last push (§6.5, WP-3) and the last
|
|
1193
|
+
* inventory read (D-11/§6.5). A box this hub has read an inventory from but never pushed to still
|
|
1194
|
+
* gets a block (`sentTreeHash: null`, `onBox` populated); that is the WP-9 review card's whole input.
|
|
1195
|
+
* `onBox`/`checkedAt` are null — never absent — when only one half is known, so a reader can always
|
|
1196
|
+
* ask "have we read this box's own skills, and when".
|
|
1197
|
+
*/
|
|
1198
|
+
/**
|
|
1199
|
+
* WP-8 (§6.5): this machine's per-machine plugin exclusions, read through the caller's provider.
|
|
1200
|
+
*
|
|
1201
|
+
* The provider is INJECTED (`deps.excludePluginsFor`), never imported: the real reader is a hub-side
|
|
1202
|
+
* JSON file that lib/api/machines.js supplies, so this module — which must run in unit tests with a
|
|
1203
|
+
* temp db and no data dir — never reads a real one, and a default `[]` keeps every existing caller
|
|
1204
|
+
* (and the probe sweep) exactly as it was.
|
|
1205
|
+
*/
|
|
1206
|
+
function _excludePluginsFor(deps = {}, instanceId) {
|
|
1207
|
+
const provider = deps.excludePluginsFor;
|
|
1208
|
+
if (typeof provider !== 'function') return [];
|
|
1209
|
+
try {
|
|
1210
|
+
const list = provider(instanceId);
|
|
1211
|
+
return Array.isArray(list) ? list.map((id) => _string(id, 200)).filter(Boolean) : [];
|
|
1212
|
+
} catch {
|
|
1213
|
+
return []; // expected: an unreadable exclusion file is "nothing excluded", never a failed row read
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
/**
|
|
1218
|
+
* WP-9 (§9 S-9): the PLUGIN names the hub's last bundle carried — the Review dialog's exclusion
|
|
1219
|
+
* checklist is "the plugins the hub sends to this box", so it is derived from the push entry's own
|
|
1220
|
+
* item list (never re-read from disk here: this module holds no home dir, and the push already
|
|
1221
|
+
* decided what went out).
|
|
1222
|
+
*
|
|
1223
|
+
* push.js's result reports `sentCount`/`heldBack` and NO items in this release, so the reader accepts
|
|
1224
|
+
* every shape a caller could hand it (`items`, a nested `bundle.items`, or a plain `plugins` list) and
|
|
1225
|
+
* otherwise falls back to what the PREVIOUS push recorded — the same rule the rest of
|
|
1226
|
+
* `recordAgentBundlePush` follows: a failed push must not empty a checklist the user is looking at.
|
|
1227
|
+
*/
|
|
1228
|
+
function _pluginNamesFromItems(items) {
|
|
1229
|
+
const names = new Set();
|
|
1230
|
+
for (const item of Array.isArray(items) ? items : []) {
|
|
1231
|
+
if (!item || typeof item !== 'object') continue;
|
|
1232
|
+
if (_string(item.kind, 40) !== 'plugin') continue;
|
|
1233
|
+
const name = _string(item.name, 200);
|
|
1234
|
+
if (name) names.add(name);
|
|
1235
|
+
}
|
|
1236
|
+
return [...names].sort();
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
function _pluginsFromResult(result, previous) {
|
|
1240
|
+
const nested = result && result.bundle && result.bundle.items;
|
|
1241
|
+
if (Array.isArray(result && result.items)) return _pluginNamesFromItems(result.items);
|
|
1242
|
+
if (Array.isArray(nested)) return _pluginNamesFromItems(nested);
|
|
1243
|
+
if (Array.isArray(result && result.plugins)) {
|
|
1244
|
+
const names = new Set();
|
|
1245
|
+
for (const value of result.plugins) {
|
|
1246
|
+
const name = _string(value && typeof value === 'object' ? value.name : value, 200);
|
|
1247
|
+
if (name) names.add(name);
|
|
1248
|
+
}
|
|
1249
|
+
return [...names].sort();
|
|
1250
|
+
}
|
|
1251
|
+
return previous && Array.isArray(previous.plugins) ? previous.plugins.slice() : [];
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
/**
|
|
1255
|
+
* WP-6 D-13 (§6.3): the per-phase timings a push result carries, kept as a COPY of only the keys
|
|
1256
|
+
* whose values are finite numbers — anything else (a missing object, a string, NaN) is dropped, and
|
|
1257
|
+
* a result that kept nothing reads as null. Recorded for an ok AND a failed push alike.
|
|
1258
|
+
*/
|
|
1259
|
+
function _pushTimingsOf(result) {
|
|
1260
|
+
const source = result && result.timings;
|
|
1261
|
+
if (!source || typeof source !== 'object') return null;
|
|
1262
|
+
const kept = {};
|
|
1263
|
+
let any = false;
|
|
1264
|
+
for (const key of Object.keys(source)) {
|
|
1265
|
+
const value = source[key];
|
|
1266
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
1267
|
+
kept[key] = value;
|
|
1268
|
+
any = true;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
return any ? kept : null;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
function _skillsView(instanceId, deps = {}) {
|
|
1275
|
+
const id = _string(instanceId, 160);
|
|
1276
|
+
return _skillsViewFrom(_agentBundlePush.get(id) || null, _agentSkills.get(id) || null, _excludePluginsFor(deps, id));
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/** The surfaced `skills` block, one shape for the row and the change check below. */
|
|
1280
|
+
function _skillsViewFrom(pushEntry, inventoryEntry = null, excludePlugins = []) {
|
|
1281
|
+
const exclusions = Array.isArray(excludePlugins) ? excludePlugins : [];
|
|
1282
|
+
// WP-8: a machine with nothing pushed, nothing read AND nothing excluded has no block at all — the
|
|
1283
|
+
// pre-WP-8 `null` (and every test that pins it) is preserved. A machine with ONLY exclusions does get
|
|
1284
|
+
// a block, because the exclusions are the hub's own setting for it and a reader must be able to see
|
|
1285
|
+
// them before the first probe ever answers.
|
|
1286
|
+
if (!pushEntry && !inventoryEntry && !exclusions.length) return null;
|
|
1287
|
+
return {
|
|
1288
|
+
sentTreeHash: pushEntry ? pushEntry.sentTreeHash : null,
|
|
1289
|
+
sentAt: pushEntry ? pushEntry.sentAt : 0,
|
|
1290
|
+
sentCount: pushEntry ? pushEntry.sentCount : 0,
|
|
1291
|
+
heldBack: pushEntry ? pushEntry.heldBack : [],
|
|
1292
|
+
pushError: pushEntry ? pushEntry.pushError : null,
|
|
1293
|
+
// WP-6 D-13 (§6.3): the per-phase timings of the LAST push — which phase cost the caller its wait.
|
|
1294
|
+
// Null when no push result has carried any yet (a pre-WP-6 push, or none at all).
|
|
1295
|
+
pushTimings: pushEntry && pushEntry.pushTimings ? pushEntry.pushTimings : null,
|
|
1296
|
+
onBox: inventoryEntry ? inventoryEntry.onBox : null,
|
|
1297
|
+
checkedAt: inventoryEntry ? inventoryEntry.checkedAt : null,
|
|
1298
|
+
excludePlugins: exclusions.slice(),
|
|
1299
|
+
// WP-9 (§9 S-9): the plugin names the last push carried, for the Review dialog's checklist.
|
|
1300
|
+
// Always an array on a non-null block, like `heldBack` — a reader never has to treat "no push"
|
|
1301
|
+
// and "no plugins" differently.
|
|
1302
|
+
plugins: pushEntry && Array.isArray(pushEntry.plugins) ? pushEntry.plugins.slice() : [],
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
/** A `skills` change is registry news — a reader keeping a snapshot must re-read (`noteRegistryChanged`). */
|
|
1307
|
+
function _skillsChanged(previousPush, nextPush, inventoryEntry) {
|
|
1308
|
+
return JSON.stringify(_skillsViewFrom(previousPush, inventoryEntry))
|
|
1309
|
+
!== JSON.stringify(_skillsViewFrom(nextPush, inventoryEntry));
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
/** WP-3: the last agent-skill push this process made to `instanceId`, or null when it made none. */
|
|
1313
|
+
function agentBundlePushFor(instanceId) {
|
|
1314
|
+
return _skillsView(instanceId);
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
/**
|
|
1318
|
+
* WP-3 (§6.5): record the outcome of a push to `instanceId`.
|
|
1319
|
+
*
|
|
1320
|
+
* ok -> sentTreeHash/sentAt/sentCount/heldBack describe THIS push and `pushError` is cleared.
|
|
1321
|
+
* ok:false-> the failure is stamped (`pushError: {code, message?, continuing?, at}` — `message` is
|
|
1322
|
+
* the box's own reason, `result.boxCode` from lib/agent-bundle/push.js D-4, present only
|
|
1323
|
+
* when the box named one; `continuing` is present only when the push outlived the
|
|
1324
|
+
* caller's wait and is still running, WP-6 D-11) while the last SUCCESSFUL push's fields
|
|
1325
|
+
* are kept: D-6's whole point is that a failed push changes nothing about the session,
|
|
1326
|
+
* and a reader must not conclude the box stopped running its last tree.
|
|
1327
|
+
*
|
|
1328
|
+
* WP-6 D-13: BOTH outcomes also store `pushTimings`, the per-phase timings the result carried (only
|
|
1329
|
+
* keys whose values are finite numbers; null when it carried none). A LATE ok result (D-11) clears
|
|
1330
|
+
* `pushError` exactly as an on-time one does — it is `recordAgentBundlePush` either way.
|
|
1331
|
+
*
|
|
1332
|
+
* Bumps the registry revision only when the surfaced `skills` block actually changed.
|
|
1333
|
+
*/
|
|
1334
|
+
function recordAgentBundlePush(instanceId, result, deps = {}) {
|
|
1335
|
+
const id = _string(instanceId, 160);
|
|
1336
|
+
if (!id) return null;
|
|
1337
|
+
const now = _now(deps);
|
|
1338
|
+
const previous = _agentBundlePush.get(id) || null;
|
|
1339
|
+
const ok = !!(result && result.ok);
|
|
1340
|
+
const pushTimings = _pushTimingsOf(result);
|
|
1341
|
+
const next = ok
|
|
1342
|
+
? {
|
|
1343
|
+
sentTreeHash: _string(result.treeHash, 80) || null,
|
|
1344
|
+
sentAt: now,
|
|
1345
|
+
sentCount: Number(result.sentCount) || 0,
|
|
1346
|
+
heldBack: Array.isArray(result.heldBack) ? result.heldBack : [],
|
|
1347
|
+
plugins: _pluginsFromResult(result, previous),
|
|
1348
|
+
pushError: null,
|
|
1349
|
+
pushTimings,
|
|
1350
|
+
}
|
|
1351
|
+
: {
|
|
1352
|
+
sentTreeHash: previous ? previous.sentTreeHash : null,
|
|
1353
|
+
sentAt: previous ? previous.sentAt : 0,
|
|
1354
|
+
sentCount: previous ? previous.sentCount : 0,
|
|
1355
|
+
heldBack: previous ? previous.heldBack : [],
|
|
1356
|
+
plugins: _pluginsFromResult(result, previous),
|
|
1357
|
+
pushError: {
|
|
1358
|
+
code: _string(result && result.code, 80) || 'agent_bundle_push_network',
|
|
1359
|
+
// D-4: the box's own reason, so the Machines line can say WHY (public/m/machines.js renders
|
|
1360
|
+
// `error.message || error.code`). Absent when the box named nothing — never an empty string.
|
|
1361
|
+
...(_string(result && result.boxCode, 80) ? { message: _string(result.boxCode, 80) } : {}),
|
|
1362
|
+
// WP-6 D-11: the push outlived the caller's wait and is running on — its result will arrive
|
|
1363
|
+
// later. Present ONLY when true, like every other optional key here.
|
|
1364
|
+
...(result && result.continuing === true ? { continuing: true } : {}),
|
|
1365
|
+
at: now,
|
|
1366
|
+
},
|
|
1367
|
+
pushTimings,
|
|
1368
|
+
};
|
|
1369
|
+
_agentBundlePush.set(id, next);
|
|
1370
|
+
if (_skillsChanged(previous, next, _agentSkills.get(id) || null)) noteRegistryChanged();
|
|
1371
|
+
return _skillsViewFrom(next, _agentSkills.get(id) || null);
|
|
1372
|
+
}
|
|
1373
|
+
|
|
714
1374
|
/**
|
|
715
1375
|
* D-12/D-16: the box's own host sample as this hub last read it, with the hub-clock stamp of that
|
|
716
1376
|
* read, or null when the box has never been asked successfully. Readers use `checkedAt` to label a
|
|
@@ -721,13 +1381,74 @@ function hostStatsFor(instanceId) {
|
|
|
721
1381
|
return entry ? { sample: entry.sample, checkedAt: entry.checkedAt } : null;
|
|
722
1382
|
}
|
|
723
1383
|
|
|
724
|
-
/**
|
|
1384
|
+
/**
|
|
1385
|
+
* WP-8 §6.5 step 7: drop the memoized hub item lists.
|
|
1386
|
+
*
|
|
1387
|
+
* A skill IMPORT changes the hub's own skill tree, and the cache is keyed only by platform — so without
|
|
1388
|
+
* this the hub would keep classifying every OTHER box against the pre-import list for up to 60 s. The
|
|
1389
|
+
* next read rebuilds from the (now-changed) tree.
|
|
1390
|
+
*/
|
|
1391
|
+
function invalidateHubItemCache() {
|
|
1392
|
+
_hubItemCache.clear();
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
/**
|
|
1396
|
+
* WP-8 §6.5 step 7: make ONE box's inventory stale, so the next probe tick re-reads it.
|
|
1397
|
+
*
|
|
1398
|
+
* The stamp (not the entry) is zeroed: the last classification stays visible — a reader sees the
|
|
1399
|
+
* pre-import review list rather than a row blanked to "no skills" — and `probeMachinesOnce`'s
|
|
1400
|
+
* `now - checkedAt >= AGENT_AUTH_REFRESH_MS` gate re-reads on the very next tick. Only a box this
|
|
1401
|
+
* process has actually read is touched; a never-read box has nothing to invalidate.
|
|
1402
|
+
*/
|
|
1403
|
+
function markAgentSkillsStale(instanceId) {
|
|
1404
|
+
const id = _string(instanceId, 160);
|
|
1405
|
+
if (!id) return;
|
|
1406
|
+
const entry = _agentSkills.get(id);
|
|
1407
|
+
if (entry) _agentSkills.set(id, { ...entry, checkedAt: 0 });
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
/**
|
|
1411
|
+
* WP-4: routes just deleted ON the box are gone from the hub's cached models block at once (the
|
|
1412
|
+
* Review dialog re-reads the list right after the remove), and the stamp is zeroed so the very next
|
|
1413
|
+
* tick re-reads the box's own truth. The block's other fields are left as read — only the box can
|
|
1414
|
+
* say what the removal did to `usable` or to a /delegate pick that named a removed route.
|
|
1415
|
+
*/
|
|
1416
|
+
function forgetMachineModelRoutes(instanceId, routeIds) {
|
|
1417
|
+
const id = _string(instanceId, 160);
|
|
1418
|
+
const entry = id ? _machineModels.get(id) : null;
|
|
1419
|
+
if (!entry) return;
|
|
1420
|
+
const gone = new Set((Array.isArray(routeIds) ? routeIds : []).map((route) => _string(route, 200)));
|
|
1421
|
+
const block = entry.block && Array.isArray(entry.block.unreachable)
|
|
1422
|
+
? { ...entry.block, unreachable: entry.block.unreachable.filter((row) => !gone.has(row.id)) }
|
|
1423
|
+
: entry.block;
|
|
1424
|
+
_machineModels.set(id, { block, checkedAt: 0 });
|
|
1425
|
+
noteRegistryChanged();
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
function markMachineModelsStale(instanceId) {
|
|
1429
|
+
const id = _string(instanceId, 160);
|
|
1430
|
+
if (!id) return;
|
|
1431
|
+
const entry = _machineModels.get(id);
|
|
1432
|
+
if (!entry) return;
|
|
1433
|
+
_machineModels.set(id, { block: entry.block, checkedAt: 0 });
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
/** Test seam: the miss counters, the agent-auth/host-stats/manifest caches, the skills push state
|
|
1437
|
+
* (WP-3) and the skill inventory + hub-item cache (WP-7), the sweep verdict, and the hub's cached
|
|
1438
|
+
* away-path read (WP-3 rev 3). */
|
|
725
1439
|
function _resetMachineRegistryStateForTests() {
|
|
726
1440
|
_missStreak.clear();
|
|
727
1441
|
_agentAuth.clear();
|
|
1442
|
+
_agentPresence.clear();
|
|
728
1443
|
_hostStats.clear();
|
|
729
1444
|
_manifestHost.clear();
|
|
1445
|
+
_manifestBuild.clear();
|
|
1446
|
+
_agentBundlePush.clear();
|
|
1447
|
+
_agentSkills.clear();
|
|
1448
|
+
_machineModels.clear();
|
|
1449
|
+
_hubItemCache.clear();
|
|
730
1450
|
_lastSweep = { hubNetworkSuspect: false, at: 0 };
|
|
1451
|
+
_awayPathProxyExists = null;
|
|
731
1452
|
}
|
|
732
1453
|
|
|
733
1454
|
module.exports = {
|
|
@@ -737,13 +1458,37 @@ module.exports = {
|
|
|
737
1458
|
AGENT_AUTH_REFRESH_MS,
|
|
738
1459
|
HOST_STATS_REFRESH_MS,
|
|
739
1460
|
agentAuthFor,
|
|
1461
|
+
// WP-13b (D-15): the box's own available-agents presence as this hub last read it
|
|
1462
|
+
// (`{ dsh: { available } }`), or null when it was never read — the gate in
|
|
1463
|
+
// lib/remote-machine-create.js and the machine row both read it through here.
|
|
1464
|
+
agentAvailabilityFor,
|
|
740
1465
|
hostStatsFor,
|
|
741
1466
|
listMachines,
|
|
1467
|
+
// WP-3 rev 3 (§13.6 D-17): why an unreachable box cannot be dialled from here (`no_away_path` /
|
|
1468
|
+
// `away_path_down`), or null when that is not this hub's to say. Read straight off the machine view.
|
|
1469
|
+
machineAwayReason,
|
|
742
1470
|
registryRevision,
|
|
743
1471
|
noteRegistryChanged,
|
|
744
1472
|
registerMachine,
|
|
745
1473
|
probeMachinesOnce,
|
|
746
1474
|
seedMachinesFromSyncPeers,
|
|
747
1475
|
summarizeMachines,
|
|
1476
|
+
// WP-3 (docs/remote-agent-skills-bundles-2026-10-25.html D-9/§6.5): the box's os (from the cached
|
|
1477
|
+
// manifest host block) and the agent-skill push state the machine row surfaces.
|
|
1478
|
+
manifestHostFor,
|
|
1479
|
+
recordAgentBundlePush,
|
|
1480
|
+
agentBundlePushFor,
|
|
1481
|
+
// WP-8 (§6.5 step 7): after an import/retire/exclude-plugins write the hub's own item list and this
|
|
1482
|
+
// box's inventory read are both stale — these two make the next read see the change.
|
|
1483
|
+
invalidateHubItemCache,
|
|
1484
|
+
markAgentSkillsStale,
|
|
1485
|
+
// WP-5 (§6.4): after a copy the box has one more row — zero the stamp so the next tick re-reads
|
|
1486
|
+
// it, without dropping the block (the removal path's `forgetMachineModelRoutes` drops ids, which
|
|
1487
|
+
// is the wrong move for an ADDITION: the hub cannot say what the new row's verdict is).
|
|
1488
|
+
markMachineModelsStale,
|
|
1489
|
+
forgetMachineModelRoutes,
|
|
1490
|
+
// WP-5 (§6.4): the §6.2 block as this hub last read it — the hub's "Use on <box>" list reads it
|
|
1491
|
+
// to drop the routes the box already has and the origins the box already said it cannot reach.
|
|
1492
|
+
modelsFor,
|
|
748
1493
|
_resetMachineRegistryStateForTests,
|
|
749
1494
|
};
|