create-walle 0.10.2 → 0.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/lib/release-schema.json +2 -2
- package/lib/runtime-closure-policy.json +8 -7
- package/package.json +1 -1
- package/template/coding-task-manager/api-prompts.js +45 -7
- package/template/coding-task-manager/bin/claude-hook.js +55 -1
- package/template/coding-task-manager/bin/ctm-relay-client.js +35 -12
- package/template/coding-task-manager/bin/land.js +2069 -0
- package/template/coding-task-manager/bin/memory-census.js +4 -2
- package/template/coding-task-manager/bin/memory-sync-verify.js +151 -0
- package/template/coding-task-manager/bin/probe-refusal-scope.js +530 -0
- package/template/coding-task-manager/db.js +156 -32
- package/template/coding-task-manager/lib/acp/acp-client.js +383 -0
- package/template/coding-task-manager/lib/acp/dsh-context-truth.js +176 -0
- package/template/coding-task-manager/lib/acp/dsh-launch-model.js +302 -0
- package/template/coding-task-manager/lib/acp/dsh-mcp-servers.js +194 -0
- package/template/coding-task-manager/lib/acp/dsh-prompt-lane.js +616 -0
- package/template/coding-task-manager/lib/acp/dsh-route-patch.js +308 -0
- package/template/coding-task-manager/lib/acp/dsh-session-runtime.js +522 -0
- package/template/coding-task-manager/lib/acp/dsh-transcript.js +395 -0
- package/template/coding-task-manager/lib/acp/dsh-usage-log.js +162 -0
- package/template/coding-task-manager/lib/acp/ndjson-stdio-transport.js +225 -0
- package/template/coding-task-manager/lib/adopted-host-redial.js +47 -11
- package/template/coding-task-manager/lib/adopted-launch-environment.js +123 -0
- package/template/coding-task-manager/lib/agent-auto-update-runtime.js +0 -3
- package/template/coding-task-manager/lib/agent-auto-update.js +26 -10
- package/template/coding-task-manager/lib/agent-bundle/build.js +657 -0
- package/template/coding-task-manager/lib/agent-bundle/codex-install.js +558 -0
- package/template/coding-task-manager/lib/agent-bundle/drift.js +120 -0
- package/template/coding-task-manager/lib/agent-bundle/gc.js +522 -0
- package/template/coding-task-manager/lib/agent-bundle/inventory.js +346 -0
- package/template/coding-task-manager/lib/agent-bundle/push.js +717 -0
- package/template/coding-task-manager/lib/agent-bundle/skill-transfer.js +724 -0
- package/template/coding-task-manager/lib/agent-bundle/spawn.js +119 -0
- package/template/coding-task-manager/lib/agent-bundle/store.js +568 -0
- package/template/coding-task-manager/lib/agent-capabilities.js +142 -12
- package/template/coding-task-manager/lib/agent-cli-cache.js +30 -5
- package/template/coding-task-manager/lib/agent-definitions.js +19 -0
- package/template/coding-task-manager/lib/agent-hooks-codex-toml.js +28 -16
- package/template/coding-task-manager/lib/agent-hooks-codex-workspace.js +9 -1
- package/template/coding-task-manager/lib/agent-hooks-installer.js +23 -8
- package/template/coding-task-manager/lib/agent-hooks-prune.js +37 -11
- package/template/coding-task-manager/lib/agent-hooks-shim.js +105 -0
- package/template/coding-task-manager/lib/agent-presets.js +84 -1
- package/template/coding-task-manager/lib/agent-upgrade-jobs.js +12 -1
- package/template/coding-task-manager/lib/agent-version-service.js +74 -11
- package/template/coding-task-manager/lib/all-sessions-query.js +6 -2
- package/template/coding-task-manager/lib/api/agent-bundles.js +436 -0
- package/template/coding-task-manager/lib/api/backup-retention.js +83 -0
- package/template/coding-task-manager/lib/api/conversation-sync-routes.js +124 -0
- package/template/coding-task-manager/lib/api/diagnostics.js +549 -69
- package/template/coding-task-manager/lib/api/fleet.js +118 -0
- package/template/coding-task-manager/lib/api/hub-bridge.js +243 -0
- package/template/coding-task-manager/lib/api/ingress.js +9 -1
- package/template/coding-task-manager/lib/api/landing.js +403 -0
- package/template/coding-task-manager/lib/api/machines.js +691 -2
- package/template/coding-task-manager/lib/api/memory-sync-routes.js +335 -0
- package/template/coding-task-manager/lib/api/misc-ops.js +8 -3
- package/template/coding-task-manager/lib/api/remote-sessions.js +1326 -0
- package/template/coding-task-manager/lib/api/session-attach.js +623 -0
- package/template/coding-task-manager/lib/api/session-messages.js +1384 -0
- package/template/coding-task-manager/lib/api/sessions.js +77 -51
- package/template/coding-task-manager/lib/api/setup-network.js +544 -16
- package/template/coding-task-manager/lib/api/setup-wizard-handlers.js +884 -0
- package/template/coding-task-manager/lib/api/setup.js +829 -10
- package/template/coding-task-manager/lib/api/walle-bridge.js +9 -1
- package/template/coding-task-manager/lib/attachment-reconciler-wiring.js +5 -2
- package/template/coding-task-manager/lib/auth-rate-limit.js +23 -0
- package/template/coding-task-manager/lib/auth-rules.js +292 -21
- package/template/coding-task-manager/lib/background-stop.js +369 -0
- package/template/coding-task-manager/lib/best-of-attempts.js +4 -1
- package/template/coding-task-manager/lib/best-of-launch-plan.js +52 -0
- package/template/coding-task-manager/lib/box-keep-awake.js +89 -0
- package/template/coding-task-manager/lib/broadcast-reason-stats.js +152 -0
- package/template/coding-task-manager/lib/broadcast-serialize.js +5 -0
- package/template/coding-task-manager/lib/build-info.js +4 -2
- package/template/coding-task-manager/lib/claude-model-truth.js +13 -2
- package/template/coding-task-manager/lib/claude-transcript-terminal-renderer.js +17 -13
- package/template/coding-task-manager/lib/client-diag-handler.js +64 -2
- package/template/coding-task-manager/lib/codex-app-server-client.js +14 -16
- package/template/coding-task-manager/lib/codex-configuration-history.js +55 -0
- package/template/coding-task-manager/lib/codex-harness-qualification.js +43 -20
- package/template/coding-task-manager/lib/codex-rollout-history.js +743 -0
- package/template/coding-task-manager/lib/coding-agent-models.js +27 -12
- package/template/coding-task-manager/lib/coding-cli-skill-installer.js +44 -14
- package/template/coding-task-manager/lib/coding-session-model-adapters.js +43 -2
- package/template/coding-task-manager/lib/coding-session-model-owner.js +1 -1
- package/template/coding-task-manager/lib/coding-session-model-public.js +7 -1
- package/template/coding-task-manager/lib/coding-session-model-runtime.js +23 -4
- package/template/coding-task-manager/lib/coding-session-model-scan.js +31 -0
- package/template/coding-task-manager/lib/coding-session-model-state.js +7 -0
- package/template/coding-task-manager/lib/conversation-freshness-diagnostics.js +280 -0
- package/template/coding-task-manager/lib/conversation-item-clock.js +261 -0
- package/template/coding-task-manager/lib/conversation-source-registry.js +5 -0
- package/template/coding-task-manager/lib/conversation-store-v2.js +44 -0
- package/template/coding-task-manager/lib/conversation-sync.js +355 -0
- package/template/coding-task-manager/lib/create/resolve-launch-params.js +96 -12
- package/template/coding-task-manager/lib/create/worker-role-profile.js +4 -4
- package/template/coding-task-manager/lib/create-intent-lease.js +51 -0
- package/template/coding-task-manager/lib/ctm-session-context-api.js +97 -6
- package/template/coding-task-manager/lib/db-domain-tables.js +1 -0
- package/template/coding-task-manager/lib/db-domains.js +1 -0
- package/template/coding-task-manager/lib/db-owner-dispatch-probe.js +32 -0
- package/template/coding-task-manager/lib/db-owner-request-counts.js +178 -0
- package/template/coding-task-manager/lib/db-owner-worker-client.js +68 -14
- package/template/coding-task-manager/lib/db-schema-source-files.js +5 -0
- package/template/coding-task-manager/lib/deferred-restore-attempt.js +6 -2
- package/template/coding-task-manager/lib/feature-manifest.js +4 -0
- package/template/coding-task-manager/lib/fleet-conversation-route.js +97 -0
- package/template/coding-task-manager/lib/fleet-fanout.js +78 -0
- package/template/coding-task-manager/lib/fleet-hub.js +762 -0
- package/template/coding-task-manager/lib/fleet-origin-follower.js +252 -0
- package/template/coding-task-manager/lib/fleet-resolver-effects.js +147 -0
- package/template/coding-task-manager/lib/fleet-resolver-tick.js +148 -0
- package/template/coding-task-manager/lib/fleet-resolver.js +454 -0
- package/template/coding-task-manager/lib/fleet-session-list-routes.js +114 -0
- package/template/coding-task-manager/lib/fleet-session-lists.js +672 -0
- package/template/coding-task-manager/lib/fleet-update.js +798 -0
- package/template/coding-task-manager/lib/fs-watcher-root-watch.js +141 -0
- package/template/coding-task-manager/lib/fs-watcher.js +58 -42
- package/template/coding-task-manager/lib/harness-history-transition.js +50 -9
- package/template/coding-task-manager/lib/headless-term-service.js +84 -8
- package/template/coding-task-manager/lib/hermetic-agent-launch-guard.js +10 -0
- package/template/coding-task-manager/lib/history-transition-read-op.js +175 -8
- package/template/coding-task-manager/lib/host-cpu-categories.js +310 -0
- package/template/coding-task-manager/lib/host-fseventsd-sampler.js +399 -0
- package/template/coding-task-manager/lib/host-handoff.js +16 -19
- package/template/coding-task-manager/lib/host-stats.js +150 -3
- package/template/coding-task-manager/lib/hosted-sync-worker.js +43 -320
- package/template/coding-task-manager/lib/hub-bridge-access.js +798 -0
- package/template/coding-task-manager/lib/hub-bridge-box.js +409 -0
- package/template/coding-task-manager/lib/hub-bridge-mcp.js +380 -0
- package/template/coding-task-manager/lib/hub-bridge-mesh.js +313 -0
- package/template/coding-task-manager/lib/hub-bridge-ops.js +721 -0
- package/template/coding-task-manager/lib/hub-bridge.js +398 -0
- package/template/coding-task-manager/lib/idle-notify.js +491 -0
- package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +8 -0
- package/template/coding-task-manager/lib/instance-manifest.js +5 -1
- package/template/coding-task-manager/lib/instance-sync-status.js +18 -8
- package/template/coding-task-manager/lib/landing/agent-rule.txt +1 -0
- package/template/coding-task-manager/lib/landing/batch-runner.js +366 -0
- package/template/coding-task-manager/lib/landing/config.js +536 -0
- package/template/coding-task-manager/lib/landing/ctm-view.js +256 -0
- package/template/coding-task-manager/lib/landing/gate.js +360 -0
- package/template/coding-task-manager/lib/landing/git.js +344 -0
- package/template/coding-task-manager/lib/landing/lease.js +247 -0
- package/template/coding-task-manager/lib/landing/merge-delta.js +380 -0
- package/template/coding-task-manager/lib/landing/proof.js +223 -0
- package/template/coding-task-manager/lib/landing/queue.js +998 -0
- package/template/coding-task-manager/lib/landing/radar.js +489 -0
- package/template/coding-task-manager/lib/landing/resolve-cli.js +424 -0
- package/template/coding-task-manager/lib/landing/state.js +263 -0
- package/template/coding-task-manager/lib/landing/stop.js +49 -0
- package/template/coding-task-manager/lib/landing/sync-cli.js +475 -0
- package/template/coding-task-manager/lib/landing/sync.js +456 -0
- package/template/coding-task-manager/lib/landing/train.js +885 -0
- package/template/coding-task-manager/lib/landing/window.js +57 -0
- package/template/coding-task-manager/lib/launch-environment.js +121 -0
- package/template/coding-task-manager/lib/launch-model-override.js +36 -10
- package/template/coding-task-manager/lib/launch-plan-pool.js +2 -1
- package/template/coding-task-manager/lib/launch-plan-warm-cache.js +6 -0
- package/template/coding-task-manager/lib/lead-model-observer.js +57 -1
- package/template/coding-task-manager/lib/lead-model-options.js +160 -3
- package/template/coding-task-manager/lib/lead-model-restore-reconciliation.js +106 -24
- package/template/coding-task-manager/lib/lead-model-switch-runtime.js +352 -68
- package/template/coding-task-manager/lib/lead-model-switch.js +114 -10
- package/template/coding-task-manager/lib/lead-model-transition-runtime.js +14 -2
- package/template/coding-task-manager/lib/lead-model-transition-store.js +24 -6
- package/template/coding-task-manager/lib/lead-route-preflight.js +179 -0
- package/template/coding-task-manager/lib/lead-switch-history-mode.js +110 -0
- package/template/coding-task-manager/lib/lead-wire-agent.js +7 -0
- package/template/coding-task-manager/lib/lifecycle-wake-experiment.js +9 -2
- package/template/coding-task-manager/lib/live-defer-announce.js +45 -0
- package/template/coding-task-manager/lib/machine-registry.js +750 -5
- package/template/coding-task-manager/lib/macos-capabilities.js +32 -15
- package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +433 -53
- package/template/coding-task-manager/lib/mainloop-observability.js +14 -9
- package/template/coding-task-manager/lib/media-draft-owner.js +16 -3
- package/template/coding-task-manager/lib/memory-join.js +337 -0
- package/template/coding-task-manager/lib/memory-sync-verify.js +393 -0
- package/template/coding-task-manager/lib/memory-sync.js +565 -0
- package/template/coding-task-manager/lib/migrations/v57-delegate-pref-mirror-opencode.js +68 -0
- package/template/coding-task-manager/lib/migrations/v58-subagent-launch-native-subagents.js +42 -0
- package/template/coding-task-manager/lib/migrations/v59-coding-session-model-state-dsh.js +73 -0
- package/template/coding-task-manager/lib/migrations/v60-delegate-pref-mirror-dsh.js +78 -0
- package/template/coding-task-manager/lib/migrations/v61-conversation-item-clock.js +71 -0
- package/template/coding-task-manager/lib/model-availability.js +727 -0
- package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +4 -6
- package/template/coding-task-manager/lib/model-host-receipt.js +6 -3
- package/template/coding-task-manager/lib/model-id-rule.js +19 -0
- package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
- package/template/coding-task-manager/lib/model-proxy/auto-mode-review.js +110 -23
- package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
- package/template/coding-task-manager/lib/model-proxy/generation-probe.js +60 -17
- package/template/coding-task-manager/lib/model-proxy/historical-tool-declarations.js +37 -0
- package/template/coding-task-manager/lib/model-proxy/lead-request-validation.js +1 -0
- package/template/coding-task-manager/lib/model-proxy/lead-token-admin.js +130 -0
- package/template/coding-task-manager/lib/model-proxy/refusal-scope.js +151 -0
- package/template/coding-task-manager/lib/model-proxy/request-budget-store.js +11 -2
- package/template/coding-task-manager/lib/model-proxy/tokens.js +69 -5
- package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
- package/template/coding-task-manager/lib/native-agent-model-args.js +25 -22
- package/template/coding-task-manager/lib/native-history-control.js +6 -4
- package/template/coding-task-manager/lib/native-lead-settings-client.js +44 -4
- package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +173 -36
- package/template/coding-task-manager/lib/online-backup-worker-client.js +2 -0
- package/template/coding-task-manager/lib/opencode-cli.js +480 -0
- package/template/coding-task-manager/lib/opencode-launch-model.js +471 -0
- package/template/coding-task-manager/lib/opencode-v1-plugin/model-carrier.mjs +35 -0
- package/template/coding-task-manager/lib/opencode-v2-plugin/server.js +118 -0
- package/template/coding-task-manager/lib/owner-action-manifest-data.json +1 -1
- package/template/coding-task-manager/lib/park-composer-draft.js +54 -0
- package/template/coding-task-manager/lib/park-fresh-relaunch.js +41 -10
- package/template/coding-task-manager/lib/perf-phase-trace.js +72 -0
- package/template/coding-task-manager/lib/perf-summary.js +7 -0
- package/template/coding-task-manager/lib/perf-tracker.js +63 -2
- package/template/coding-task-manager/lib/project-config.js +7 -0
- package/template/coding-task-manager/lib/prompt-purge-v2.js +52 -1
- package/template/coding-task-manager/lib/provider-login-broker.js +72 -15
- package/template/coding-task-manager/lib/provider-login-tab.js +10 -2
- package/template/coding-task-manager/lib/read-pool-client.js +89 -2
- package/template/coding-task-manager/lib/read-pool-worker-handle.js +29 -4
- package/template/coding-task-manager/lib/recent-catalog-query.js +15 -0
- package/template/coding-task-manager/lib/recent-session-catalog-response.js +67 -10
- package/template/coding-task-manager/lib/recovery-evidence.js +26 -11
- package/template/coding-task-manager/lib/relay-client.js +57 -3
- package/template/coding-task-manager/lib/relink-audit.js +34 -5
- package/template/coding-task-manager/lib/remote-machine-catalog.js +321 -26
- package/template/coding-task-manager/lib/remote-machine-create.js +576 -36
- package/template/coding-task-manager/lib/remote-machine-link.js +546 -59
- package/template/coding-task-manager/lib/remote-machine-proxy.js +143 -9
- package/template/coding-task-manager/lib/remote-session-forward.js +65 -7
- package/template/coding-task-manager/lib/remote-terminal-target.js +9 -2
- package/template/coding-task-manager/lib/restart-guard.js +18 -6
- package/template/coding-task-manager/lib/restore-seed-modes.js +87 -0
- package/template/coding-task-manager/lib/restored-scrollback-hydration-runtime.js +22 -2
- package/template/coding-task-manager/lib/resume-admission.js +17 -3
- package/template/coding-task-manager/lib/review-repo-registry.js +58 -19
- package/template/coding-task-manager/lib/root-delivery-runtime.js +2 -2
- package/template/coding-task-manager/lib/root-prompt-flow.js +1 -1
- package/template/coding-task-manager/lib/runtime-contract.js +3 -1
- package/template/coding-task-manager/lib/schema-indexes.js +1 -0
- package/template/coding-task-manager/lib/session-context-locator.js +742 -0
- package/template/coding-task-manager/lib/session-create/attachment.js +2 -2
- package/template/coding-task-manager/lib/session-create/create-phases.js +118 -0
- package/template/coding-task-manager/lib/session-create/delegate-worker.js +86 -16
- package/template/coding-task-manager/lib/session-create/dsh-session.js +930 -0
- package/template/coding-task-manager/lib/session-create/lead-route-launch.js +186 -13
- package/template/coding-task-manager/lib/session-create/native-lead-lifetime.js +55 -3
- package/template/coding-task-manager/lib/session-create/opencode-lead-provider.js +50 -0
- package/template/coding-task-manager/lib/session-create/prepared.js +162 -38
- package/template/coding-task-manager/lib/session-create/provider-process.js +160 -28
- package/template/coding-task-manager/lib/session-create/response.js +7 -7
- package/template/coding-task-manager/lib/session-create/structured-session.js +27 -0
- package/template/coding-task-manager/lib/session-create/subagent-pick-table.js +3 -1
- package/template/coding-task-manager/lib/session-create/subagent-pick.js +458 -9
- package/template/coding-task-manager/lib/session-create/transcript.js +1 -1
- package/template/coding-task-manager/lib/session-create/worker-budget-setup.js +21 -2
- package/template/coding-task-manager/lib/session-create/worker-continuation.js +7 -1
- package/template/coding-task-manager/lib/session-create/worker-launch-sandbox.js +39 -10
- package/template/coding-task-manager/lib/session-create/worker-observability.js +2 -2
- package/template/coding-task-manager/lib/session-empty.js +13 -10
- package/template/coding-task-manager/lib/session-host-manager.js +60 -0
- package/template/coding-task-manager/lib/session-jobs.js +3 -1
- package/template/coding-task-manager/lib/session-lifecycle.js +10 -4
- package/template/coding-task-manager/lib/session-model-action-preconditions.js +6 -2
- package/template/coding-task-manager/lib/session-model-intent-contract.js +11 -8
- package/template/coding-task-manager/lib/session-park.js +738 -0
- package/template/coding-task-manager/lib/session-path-grant.js +13 -4
- package/template/coding-task-manager/lib/session-status-ledger.js +106 -4
- package/template/coding-task-manager/lib/session-transport.js +17 -0
- package/template/coding-task-manager/lib/session-workspace-projection.js +54 -5
- package/template/coding-task-manager/lib/session-workspace-tracking-runtime.js +213 -1
- package/template/coding-task-manager/lib/skill-autocomplete-query.js +120 -3
- package/template/coding-task-manager/lib/skill-autocomplete.js +15 -1
- package/template/coding-task-manager/lib/sqlite-file-set.js +39 -0
- package/template/coding-task-manager/lib/standup-search-adapter.js +16 -2
- package/template/coding-task-manager/lib/state-sync/client-ledger.js +12 -0
- package/template/coding-task-manager/lib/state-sync/frame-emitter.js +319 -100
- package/template/coding-task-manager/lib/storage/coding-session-model-store.js +4 -1
- package/template/coding-task-manager/lib/storage/delegate-pref-mirror-store.js +32 -9
- package/template/coding-task-manager/lib/structured-session-boot-sweep.js +235 -0
- package/template/coding-task-manager/lib/submission-ack.js +90 -2
- package/template/coding-task-manager/lib/systemd-scope.js +115 -0
- package/template/coding-task-manager/lib/telemetry-receiver.js +39 -0
- package/template/coding-task-manager/lib/terminal-attach-geometry.js +985 -0
- package/template/coding-task-manager/lib/terminal-bootstrap.js +30 -0
- package/template/coding-task-manager/lib/terminal-draft-operation.js +357 -0
- package/template/coding-task-manager/lib/terminal-input-admission.js +17 -1
- package/template/coding-task-manager/lib/terminal-input-prepared.js +563 -0
- package/template/coding-task-manager/lib/transcript-cache-temps.js +171 -0
- package/template/coding-task-manager/lib/transcript-frame-cache.js +8 -3
- package/template/coding-task-manager/lib/transcript-source.js +36 -4
- package/template/coding-task-manager/lib/transcript-store.js +9 -0
- package/template/coding-task-manager/lib/typing-interaction-trace.js +106 -3
- package/template/coding-task-manager/lib/usage-ledger/backfill.js +40 -5
- package/template/coding-task-manager/lib/usage-ledger/client.js +4 -1
- package/template/coding-task-manager/lib/usage-ledger/dsh-parse.js +152 -0
- package/template/coding-task-manager/lib/usage-ledger/dsh-read.js +73 -0
- package/template/coding-task-manager/lib/usage-ledger/ingest.js +34 -6
- package/template/coding-task-manager/lib/usage-ledger/live.js +35 -8
- package/template/coding-task-manager/lib/usage-ledger/read.js +29 -20
- package/template/coding-task-manager/lib/usage-ledger/schema.js +61 -1
- package/template/coding-task-manager/lib/usage-ledger/task-coordinator.js +4 -2
- package/template/coding-task-manager/lib/wake-skip-reasons.js +1 -0
- package/template/coding-task-manager/lib/walle/turn.js +1 -1
- package/template/coding-task-manager/lib/walle-history-serve.js +838 -0
- package/template/coding-task-manager/lib/walle-kill-authority.js +2 -2
- package/template/coding-task-manager/lib/walle-log-rotation.js +164 -1
- package/template/coding-task-manager/lib/walle-mcp-auto-config.js +56 -2
- package/template/coding-task-manager/lib/walle-row-history.js +495 -0
- package/template/coding-task-manager/lib/walle-supervisor.js +33 -14
- package/template/coding-task-manager/lib/watch-worker-client.js +203 -0
- package/template/coding-task-manager/lib/worker-terminal-output.js +5 -2
- package/template/coding-task-manager/lib/ws/hub-bridge.js +35 -0
- package/template/coding-task-manager/lib/ws/lifecycle.js +6 -11
- package/template/coding-task-manager/lib/ws/misc.js +1 -1
- package/template/coding-task-manager/lib/ws/state-sync.js +106 -4
- package/template/coding-task-manager/lib/ws/terminal-attach.js +91 -0
- package/template/coding-task-manager/oauth-proxy.js +112 -115
- package/template/coding-task-manager/package-lock.json +10 -94
- package/template/coding-task-manager/package.json +4 -3
- package/template/coding-task-manager/protocol/message-table.json +10 -0
- package/template/coding-task-manager/protocol/messages.js +10 -0
- package/template/coding-task-manager/protocol/ws-schema.json +20 -0
- package/template/coding-task-manager/protocol/ws-schema.md +2 -0
- package/template/coding-task-manager/public/css/app.css +170 -0
- package/template/coding-task-manager/public/css/walle-session.css +4 -0
- package/template/coding-task-manager/public/index.html +473 -243
- package/template/coding-task-manager/public/js/agent-command.js +86 -0
- package/template/coding-task-manager/public/js/app/backups-panel.js +111 -5
- package/template/coding-task-manager/public/js/app/coding-agent-upgrade.js +103 -4
- package/template/coding-task-manager/public/js/app/machines-page.js +1328 -8
- package/template/coding-task-manager/public/js/app/models-tab.js +113 -38
- package/template/coding-task-manager/public/js/app/restart-banner.js +53 -0
- package/template/coding-task-manager/public/js/app/worktrees-panel.js +184 -2
- package/template/coding-task-manager/public/js/background-work.js +196 -27
- package/template/coding-task-manager/public/js/coding-session-model-presentation.js +138 -10
- package/template/coding-task-manager/public/js/core/active-session-groups.js +179 -0
- package/template/coding-task-manager/public/js/core/pending-create.js +238 -0
- package/template/coding-task-manager/public/js/core/recent-machine-source.js +228 -0
- package/template/coding-task-manager/public/js/core/session-commands.js +180 -14
- package/template/coding-task-manager/public/js/dashboard-client-function-order.json +8 -0
- package/template/coding-task-manager/public/js/document-review-links.js +9 -1
- package/template/coding-task-manager/public/js/file-resolution-client.js +52 -1
- package/template/coding-task-manager/public/js/history-app-seam.js +22 -2
- package/template/coding-task-manager/public/js/machine-pool-shared.js +558 -3
- package/template/coding-task-manager/public/js/message-renderer.js +4 -3
- package/template/coding-task-manager/public/js/model-picker.js +3 -1
- package/template/coding-task-manager/public/js/native-subagent-review.js +15 -2
- package/template/coding-task-manager/public/js/new-session-agent-grid.js +346 -0
- package/template/coding-task-manager/public/js/pasted-content.js +236 -0
- package/template/coding-task-manager/public/js/pe-private-content-lifetime.js +39 -7
- package/template/coding-task-manager/public/js/private-model-lifetime.js +6 -2
- package/template/coding-task-manager/public/js/prompts.js +11 -10
- package/template/coding-task-manager/public/js/protocol.js +10 -0
- package/template/coding-task-manager/public/js/restart-banner-policy.js +29 -1
- package/template/coding-task-manager/public/js/session-model-picker.js +447 -57
- package/template/coding-task-manager/public/js/session-search-utils.js +18 -2
- package/template/coding-task-manager/public/js/session-usage-view.js +2 -1
- package/template/coding-task-manager/public/js/session-workspace-tracking.js +51 -1
- package/template/coding-task-manager/public/js/state-sync-client.js +10 -1
- package/template/coding-task-manager/public/js/stream-view.js +6 -5
- package/template/coding-task-manager/public/js/terminal/conv-only-session.js +287 -0
- package/template/coding-task-manager/public/js/terminal/runtime-09.js +2 -0
- package/template/coding-task-manager/public/js/terminal/runtime-10.js +5 -5
- package/template/coding-task-manager/public/js/terminal/runtime-12.js +8 -1
- package/template/coding-task-manager/public/js/terminal/runtime-16.js +4 -0
- package/template/coding-task-manager/public/js/terminal/runtime-20.js +1 -1
- package/template/coding-task-manager/public/js/terminal-attachment-draft.js +67 -13
- package/template/coding-task-manager/public/js/terminal-image-orchestration.js +52 -6
- package/template/coding-task-manager/public/js/terminal-wheel.js +23 -3
- package/template/coding-task-manager/public/js/walle/chat-conversation.js +4 -4
- package/template/coding-task-manager/public/js/walle.js +6 -0
- package/template/coding-task-manager/public/js/ws/live-dispatch.js +10 -0
- package/template/coding-task-manager/public/m/app.css +140 -0
- package/template/coding-task-manager/public/m/app.js +507 -68
- package/template/coding-task-manager/public/m/index.html +17 -0
- package/template/coding-task-manager/public/m/machines.js +838 -4
- package/template/coding-task-manager/public/m/private-api.js +31 -1
- package/template/coding-task-manager/public/usage.html +1 -0
- package/template/coding-task-manager/scripts/dev-route-fixture/synthetic-route-upstream.js +104 -0
- package/template/coding-task-manager/scripts/dev-route-fixture.js +339 -0
- package/template/coding-task-manager/scripts/opencode-lead-route-probe.js +195 -0
- package/template/coding-task-manager/scripts/opencode-v2-contract-probe/fake-anthropic.js +168 -0
- package/template/coding-task-manager/scripts/opencode-v2-contract-probe/probe-plugin/server.js +95 -0
- package/template/coding-task-manager/scripts/opencode-v2-contract-probe.js +673 -0
- package/template/coding-task-manager/server.js +1950 -11679
- package/template/coding-task-manager/session-integrity.js +64 -4
- package/template/coding-task-manager/workers/db-owner-worker.js +19 -2
- package/template/coding-task-manager/workers/online-backup-worker.js +1 -0
- package/template/coding-task-manager/workers/read-pool-model-collector.js +51 -3
- package/template/coding-task-manager/workers/read-pool-worker.js +76 -6
- package/template/coding-task-manager/workers/session-host-pool-process.js +6 -6
- package/template/coding-task-manager/workers/session-host-process.js +5 -5
- package/template/coding-task-manager/workers/watch-worker.js +105 -0
- package/template/coding-task-manager/workers/worktree-worker.js +168 -33
- package/template/ctm-relay/lib/protocol.js +14 -1
- package/template/package-lock.json +2 -2
- package/template/package.json +1 -1
- package/template/shared/backup-retention.js +82 -0
- package/template/shared/conversation-admission.js +2 -0
- package/template/shared/conversation-provenance.js +3 -7
- package/template/shared/conversation-storage-v2-contract.js +5 -0
- package/template/shared/linked-worktree.js +51 -0
- package/template/shared/macos-fda-target.js +57 -0
- package/template/shared/orphan-parents.js +55 -0
- package/template/shared/release-schema.json +2 -2
- package/template/shared/storage/contract.js +3 -0
- package/template/wall-e/adapters/imessage.js +12 -0
- package/template/wall-e/agent.js +71 -4
- package/template/wall-e/api-walle.js +21 -1
- package/template/wall-e/bin/train-gemma-e4b-tooluse.js +2 -2
- package/template/wall-e/bin/walle-mcp-stdio.js +5 -5
- package/template/wall-e/brain/brain-join.js +859 -0
- package/template/wall-e/brain/brain-sync-membership.js +20 -0
- package/template/wall-e/brain/brain-sync-store.js +131 -77
- package/template/wall-e/brain/brain-sync-v2-schema.js +203 -0
- package/template/wall-e/brain/brain-sync-v2.js +578 -0
- package/template/wall-e/brain/delegate-model-prefs-store.js +13 -4
- package/template/wall-e/brain/deterministic-id.js +51 -0
- package/template/wall-e/brain/memories.js +2 -2
- package/template/wall-e/brain/migrations.js +40 -6
- package/template/wall-e/brain/model-identity-migration.js +1 -1
- package/template/wall-e/brain/owner-call-whitelist.js +23 -7
- package/template/wall-e/brain/schema.js +45 -85
- package/template/wall-e/brain.js +171 -35
- package/template/wall-e/chat.js +3 -3
- package/template/wall-e/coding/agent-shell-env.js +7 -2
- package/template/wall-e/coding/prompt-bundle.js +66 -21
- package/template/wall-e/config/main-loop-exemptions.json +2 -2
- package/template/wall-e/http/admin-api.js +37 -0
- package/template/wall-e/http/gws-api.js +4 -0
- package/template/wall-e/http/helper-rules-api.js +7 -1
- package/template/wall-e/http/model-admin.js +8 -1
- package/template/wall-e/http/model-delegate-pref-admin.js +3 -3
- package/template/wall-e/http/model-proxy-providers.js +174 -1
- package/template/wall-e/http/model-route-health.js +157 -0
- package/template/wall-e/http/sync-api.js +278 -464
- package/template/wall-e/jobs/job-runner.js +28 -1
- package/template/wall-e/lib/brain-owner-op-priority.js +9 -1
- package/template/wall-e/lib/ctm-data-dir.js +26 -0
- package/template/wall-e/lib/derivation-home.js +147 -0
- package/template/wall-e/lib/helper-rules/evaluate.js +50 -9
- package/template/wall-e/lib/helper-rules/grade.js +51 -8
- package/template/wall-e/lib/helper-rules/lead-call-hook.js +38 -5
- package/template/wall-e/lib/helper-rules/store.js +4 -2
- package/template/wall-e/lib/landing-mcp-tools.js +170 -0
- package/template/wall-e/lib/mcp-client-config.js +133 -14
- package/template/wall-e/lib/mcp-gateway-migrate.js +28 -64
- package/template/wall-e/lib/mcp-integration.js +58 -13
- package/template/wall-e/lib/parent-death.js +7 -1
- package/template/wall-e/lib/scheduler-worker-jobs.js +6 -4
- package/template/wall-e/llm/delegate-lead-prefs.js +16 -1
- package/template/wall-e/llm/delegate-vendor-default.js +12 -5
- package/template/wall-e/llm/model-roles.js +1 -1
- package/template/wall-e/llm/native-delegate-selection.js +56 -8
- package/template/wall-e/llm/worker-delegate-args.js +72 -7
- package/template/wall-e/llm/worker-mcp-routing.js +66 -17
- package/template/wall-e/loops/brain-custom-writes.js +19 -0
- package/template/wall-e/loops/ingest.js +17 -1
- package/template/wall-e/loops/think.js +19 -4
- package/template/wall-e/mcp-contract.js +222 -6
- package/template/wall-e/mcp-protocol-contract.js +40 -5
- package/template/wall-e/mcp-server.js +128 -29
- package/template/wall-e/mcp-worker-schemas.js +5 -5
- package/template/wall-e/memory/ctm-context-client.js +85 -0
- package/template/wall-e/memory/ctm-session-context.js +9 -3
- package/template/wall-e/memory/hub-bridge-client.js +388 -0
- package/template/wall-e/memory/replica-origin.js +56 -0
- package/template/wall-e/memory/session-pool.js +135 -0
- package/template/wall-e/runtime/live-capabilities.js +8 -3
- package/template/wall-e/server.js +4 -2
- package/template/wall-e/shared/release-schema.json +2 -2
- package/template/wall-e/shared/sqlite-file-set.js +39 -0
- package/template/wall-e/shared/storage/contract.js +3 -0
- package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
- package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +7 -1
- package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +4 -4
- package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +6 -6
- package/template/wall-e/skills/_templates/fleet-sync/VERSION +1 -0
- package/template/wall-e/skills/_templates/fleet-sync/released-sha256.json +16 -0
- package/template/wall-e/skills/_templates/fleet-sync/templates/claude/SKILL.md +64 -0
- package/template/wall-e/skills/_templates/fleet-sync/templates/codex/SKILL.md +64 -0
- package/template/wall-e/tools/chrome-automation.js +6 -0
- package/template/wall-e/training/setup-cuda-train-env.sh +2 -2
- package/template/wall-e/workers/brain-owner-worker.js +18 -4
- package/template/wall-e/workers/mcp-gateway-process.js +5 -5
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-1 (docs/shared-memory-every-machine-2026-10-31.html §5 D-3/D-4/D-8/D-9/D-10/D-12, §6.4):
|
|
4
|
+
// the storage half of the v2 change feed — a version-vector addressed feed plus the one
|
|
5
|
+
// transaction that applies a page of it.
|
|
6
|
+
//
|
|
7
|
+
// readVector(db) -> { instanceId, vector, clockSeedDone }
|
|
8
|
+
// changesSince(db, { since, limit }) -> { entries, more, clockSeedDone }
|
|
9
|
+
// applyChanges(db, { sourceInstanceId, entries }) -> { ok, applied, skipped, conflicts, vector }
|
|
10
|
+
// recordMemberVector(db, { memberInstanceId, vector }) -> { ok, ... }
|
|
11
|
+
// pruneTombstones(db, { now, rejoinAfterDays }) -> { ok, pruned }
|
|
12
|
+
// digest(db, { table }) -> { ok, table, count, buckets }
|
|
13
|
+
//
|
|
14
|
+
// Every entry point runs on the OWNER's write connection (workers/brain-owner-worker.js
|
|
15
|
+
// CUSTOM_BRAIN_CALLS → here) — the apply creates the connection-scoped TEMP sentinel the
|
|
16
|
+
// capture triggers test. The route handlers in http/sync-api.js stay thin: they
|
|
17
|
+
// authenticate, bound the body and hand the page to the owner through callLocalBrain.
|
|
18
|
+
//
|
|
19
|
+
// The v1 outbox transport (and its apply/LWW helpers in http/sync-api.js) was retired by
|
|
20
|
+
// WP-6b / brain migration 105; the LWW rule, row write and payload filters below are now the
|
|
21
|
+
// only copy.
|
|
22
|
+
|
|
23
|
+
const crypto = require('node:crypto');
|
|
24
|
+
const {
|
|
25
|
+
REPLICATED_TABLES,
|
|
26
|
+
readReplicatedRow,
|
|
27
|
+
knowledgeRowExists,
|
|
28
|
+
localBrainInstanceId,
|
|
29
|
+
clockSeedState,
|
|
30
|
+
} = require('./brain-sync-store');
|
|
31
|
+
const { createBrainSyncSentinel, dropBrainSyncSentinel } = require('./schema');
|
|
32
|
+
|
|
33
|
+
const DEFAULT_CHANGES_LIMIT = 500; // §6.4: one page is at most 500 clock rows
|
|
34
|
+
const MAX_CHANGES_LIMIT = 2000; // …and the route's hard cap
|
|
35
|
+
const MAX_APPLY_ENTRIES = 2000; // §6.4: one apply call carries at most this many entries
|
|
36
|
+
const MAX_TOMBSTONE_PRUNE = 5000; // per call, so a long backlog drains over several ticks
|
|
37
|
+
const MEMORY_WATCH_COLUMNS = Object.freeze(['content', 'content_raw', 'subject', 'metadata', 'importance', 'archived_at']);
|
|
38
|
+
const DIGEST_PAGE = 5000;
|
|
39
|
+
const MEMORY_HOST_LOCAL_RESET = Object.freeze({ extraction_attempts: 0, last_accessed: null, access_count: 0, embed_failed_count: 0 });
|
|
40
|
+
const MEMORY_HOST_LOCAL_COLUMNS = Object.freeze(Object.keys(MEMORY_HOST_LOCAL_RESET));
|
|
41
|
+
// The digest compares what replication keeps equal: the id, the watch columns and (memories)
|
|
42
|
+
// the immutable identity columns. Everything else may legitimately differ per machine.
|
|
43
|
+
const { BRAIN_SYNC_WATCH_COLUMNS } = require('./brain-sync-v2-schema');
|
|
44
|
+
const DIGEST_COLUMNS = Object.freeze({
|
|
45
|
+
memories: Object.freeze(['id', 'source', 'source_id', 'memory_type', 'timestamp', ...BRAIN_SYNC_WATCH_COLUMNS.memories]),
|
|
46
|
+
knowledge: Object.freeze(['id', ...BRAIN_SYNC_WATCH_COLUMNS.knowledge]),
|
|
47
|
+
entities: Object.freeze(['id', ...BRAIN_SYNC_WATCH_COLUMNS.entities]),
|
|
48
|
+
people: Object.freeze(['id', ...BRAIN_SYNC_WATCH_COLUMNS.people]),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
function _string(value, max = 4000) {
|
|
52
|
+
if (typeof value === 'string') return value.slice(0, max);
|
|
53
|
+
return value == null ? '' : String(value).slice(0, max);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function _parseSeq(value) {
|
|
57
|
+
const n = Number(value);
|
|
58
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function _tableColumns(db, table) {
|
|
62
|
+
try {
|
|
63
|
+
return db.prepare(`PRAGMA table_info("${table}")`).all().map((row) => row.name);
|
|
64
|
+
} catch {
|
|
65
|
+
return []; // HANDLE: a missing table yields no columns — the caller treats that as "nothing to write"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Payloads arrive from a peer: identifiers that reach SQL come only from the LOCAL table's
|
|
70
|
+
// column list, values are always bound parameters.
|
|
71
|
+
function _payloadFields(payload, allowedColumns) {
|
|
72
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return [];
|
|
73
|
+
const allowed = new Set(allowedColumns);
|
|
74
|
+
return Object.keys(payload).filter((key) => allowed.has(key));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function _parsePayload(raw) {
|
|
78
|
+
if (raw && typeof raw === 'object' && !Array.isArray(raw)) return raw;
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(String(raw == null ? '{}' : raw));
|
|
81
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
|
|
82
|
+
} catch {
|
|
83
|
+
return null; // HANDLE: an unparseable payload is skipped; the caller records it as skipped, never as a write
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// R-10/D-10: higher updated_at wins; a tie goes to the lexicographically smaller origin.
|
|
88
|
+
function _lwwWins(db, table, rowId, updatedAt, originInstanceId, localId) {
|
|
89
|
+
const local = db.prepare(
|
|
90
|
+
'SELECT updated_at, origin_instance_id FROM brain_row_clock WHERE table_name = ? AND row_id = ?'
|
|
91
|
+
).get(table, rowId);
|
|
92
|
+
if (!local) return true;
|
|
93
|
+
const localAt = _string(local.updated_at, 64);
|
|
94
|
+
const incomingAt = _string(updatedAt, 64);
|
|
95
|
+
if (incomingAt > localAt) return true;
|
|
96
|
+
if (incomingAt < localAt) return false;
|
|
97
|
+
const localOrigin = _string(local.origin_instance_id, 160) || _string(localId, 160);
|
|
98
|
+
return _string(originInstanceId, 160) < localOrigin;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// The row write: memories are append-only by id; every other
|
|
102
|
+
// replicated table upserts by id.
|
|
103
|
+
function _writeRow(db, table, fields, payload) {
|
|
104
|
+
const columns = fields.map((column) => `"${column}"`).join(', ');
|
|
105
|
+
const placeholders = fields.map(() => '?').join(', ');
|
|
106
|
+
const values = fields.map((column) => payload[column]);
|
|
107
|
+
if (table === 'memories') {
|
|
108
|
+
return db.prepare(`INSERT OR IGNORE INTO "memories" (${columns}) VALUES (${placeholders})`).run(...values);
|
|
109
|
+
}
|
|
110
|
+
const updates = fields.filter((column) => column !== 'id').map((column) => `"${column}" = excluded."${column}"`).join(', ');
|
|
111
|
+
const conflict = updates ? `ON CONFLICT("id") DO UPDATE SET ${updates}` : 'ON CONFLICT("id") DO NOTHING';
|
|
112
|
+
return db.prepare(`INSERT INTO "${table}" (${columns}) VALUES (${placeholders}) ${conflict}`).run(...values);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// The v2 clock row for one applied change: the same (table, row) key the capture trigger
|
|
116
|
+
// writes, carrying the PEER's origin/seq so the row stays addressable in that origin's stream.
|
|
117
|
+
function _stampClock(db, table, rowId, updatedAt, originInstanceId, originSeq, deleted) {
|
|
118
|
+
db.prepare(`
|
|
119
|
+
INSERT INTO brain_row_clock (table_name, row_id, updated_at, origin_instance_id, origin_seq, deleted)
|
|
120
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
121
|
+
ON CONFLICT(table_name, row_id) DO UPDATE SET
|
|
122
|
+
updated_at = excluded.updated_at,
|
|
123
|
+
origin_instance_id = excluded.origin_instance_id,
|
|
124
|
+
origin_seq = excluded.origin_seq,
|
|
125
|
+
deleted = excluded.deleted
|
|
126
|
+
`).run(table, rowId, _string(updatedAt, 64), _string(originInstanceId, 160), originSeq, deleted);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function _bumpVv(db, originInstanceId, seq) {
|
|
130
|
+
if (!(seq > 0)) return;
|
|
131
|
+
db.prepare(`
|
|
132
|
+
INSERT INTO brain_vv (origin_instance_id, max_seq) VALUES (?, ?)
|
|
133
|
+
ON CONFLICT(origin_instance_id) DO UPDATE SET max_seq = MAX(max_seq, excluded.max_seq)
|
|
134
|
+
`).run(originInstanceId, seq);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function _readVv(db) {
|
|
138
|
+
const vector = {};
|
|
139
|
+
for (const row of db.prepare('SELECT origin_instance_id, max_seq FROM brain_vv ORDER BY origin_instance_id ASC').all()) {
|
|
140
|
+
vector[String(row.origin_instance_id)] = Number(row.max_seq) || 0;
|
|
141
|
+
}
|
|
142
|
+
return vector;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// Feed
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
// GET /api/walle/sync/v2/vector — "what do I hold": this node's id, its vector and whether
|
|
150
|
+
// the WP-0 seed walk has finished (a peer must not pull a node whose clocks are incomplete).
|
|
151
|
+
function readVector(db) {
|
|
152
|
+
return {
|
|
153
|
+
instanceId: localBrainInstanceId(db),
|
|
154
|
+
vector: _readVv(db),
|
|
155
|
+
clockSeedDone: clockSeedState(db).done,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function _feedEntry(db, row) {
|
|
160
|
+
const deleted = Number(row.deleted) ? 1 : 0;
|
|
161
|
+
let payload = null;
|
|
162
|
+
if (!deleted) {
|
|
163
|
+
payload = readReplicatedRow(db, row.table_name, row.row_id);
|
|
164
|
+
}
|
|
165
|
+
// A clock that says "live" for a row that is gone is a race with a local delete (or a clock
|
|
166
|
+
// seeded before the row was written): ship it as a delete. Nothing is logged — the peer's
|
|
167
|
+
// apply is idempotent, and the clock row it writes is the same either way.
|
|
168
|
+
const isDeleted = deleted === 1 || !payload;
|
|
169
|
+
return {
|
|
170
|
+
table: row.table_name,
|
|
171
|
+
rowId: row.row_id,
|
|
172
|
+
origin: row.origin_instance_id,
|
|
173
|
+
originSeq: Number(row.origin_seq) || 0,
|
|
174
|
+
updatedAt: row.updated_at,
|
|
175
|
+
deleted: isDeleted ? 1 : 0,
|
|
176
|
+
payload: isDeleted ? null : payload,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// POST /api/walle/sync/v2/changes — every clock row whose origin_seq is beyond `since`, in
|
|
181
|
+
// ascending (origin, origin_seq) order, filling one page. An origin absent from `since` means
|
|
182
|
+
// 0; origin '' ("this host" before the identity was known, or a v1 row) is never shipped — an
|
|
183
|
+
// unaddressable row has no place in a feed a peer walks by (origin, seq).
|
|
184
|
+
function changesSince(db, { since = {}, limit = DEFAULT_CHANGES_LIMIT } = {}) {
|
|
185
|
+
const requested = Math.floor(Number(limit) || 0) || DEFAULT_CHANGES_LIMIT;
|
|
186
|
+
const budget = Math.max(1, Math.min(MAX_CHANGES_LIMIT, requested));
|
|
187
|
+
const sinceMap = since && typeof since === 'object' && !Array.isArray(since) ? since : {};
|
|
188
|
+
const origins = db.prepare('SELECT origin_instance_id FROM brain_vv ORDER BY origin_instance_id ASC').all()
|
|
189
|
+
.map((row) => String(row.origin_instance_id))
|
|
190
|
+
.filter((origin) => origin !== '');
|
|
191
|
+
const select = db.prepare(`
|
|
192
|
+
SELECT table_name, row_id, origin_instance_id, origin_seq, updated_at, deleted
|
|
193
|
+
FROM brain_row_clock
|
|
194
|
+
WHERE origin_instance_id = ? AND origin_seq > ?
|
|
195
|
+
ORDER BY origin_seq ASC
|
|
196
|
+
LIMIT ?
|
|
197
|
+
`);
|
|
198
|
+
const probe = db.prepare('SELECT 1 AS hit FROM brain_row_clock WHERE origin_instance_id = ? AND origin_seq > ? LIMIT 1');
|
|
199
|
+
const entries = [];
|
|
200
|
+
let pending = null;
|
|
201
|
+
for (const origin of origins) {
|
|
202
|
+
const after = Math.max(0, _parseSeq(sinceMap[origin]));
|
|
203
|
+
const remaining = budget - entries.length;
|
|
204
|
+
if (remaining <= 0) {
|
|
205
|
+
pending = { origin, after };
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
const rows = select.all(origin, after, remaining);
|
|
209
|
+
for (const row of rows) entries.push(_feedEntry(db, row));
|
|
210
|
+
if (rows.length < remaining) continue; // this origin drained
|
|
211
|
+
// The page filled exactly at this origin's boundary: ask whether it holds more.
|
|
212
|
+
const lastSeq = rows.length ? Number(rows[rows.length - 1].origin_seq) : after;
|
|
213
|
+
if (probe.get(origin, lastSeq)) {
|
|
214
|
+
pending = { origin, after: lastSeq };
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// more = some origin still holds rows past what this page returned (the pending origin, or any
|
|
219
|
+
// origin after it that the exhausted budget never reached).
|
|
220
|
+
let more = false;
|
|
221
|
+
if (pending) {
|
|
222
|
+
const startAt = origins.indexOf(pending.origin);
|
|
223
|
+
for (let index = startAt; index < origins.length && !more; index += 1) {
|
|
224
|
+
const origin = origins[index];
|
|
225
|
+
const after = index === startAt ? pending.after : Math.max(0, _parseSeq(sinceMap[origin]));
|
|
226
|
+
more = !!probe.get(origin, after);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
entries,
|
|
231
|
+
more,
|
|
232
|
+
clockSeedDone: clockSeedState(db).done,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
// Apply
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
|
|
240
|
+
// POST /api/walle/sync/v2/apply — one IMMEDIATE transaction for the whole page, sentinel on.
|
|
241
|
+
// Rules (§5 D-10): the clock decides first (same origin → higher seq wins), then LWW for
|
|
242
|
+
// non-memory rows, memories stay append-only by id (an existing memory only takes the six
|
|
243
|
+
// watch columns, and only when the incoming clock wins). Losses land in brain_sync_conflicts.
|
|
244
|
+
// Nothing in a page can stop the transaction: an unknown table is a conflict, a malformed
|
|
245
|
+
// entry is a skip.
|
|
246
|
+
function applyChanges(db, payload = {}) {
|
|
247
|
+
const sourceInstanceId = _string(payload.sourceInstanceId || payload.source_instance_id, 160).trim();
|
|
248
|
+
if (!sourceInstanceId) {
|
|
249
|
+
const error = new Error('brain_sync_source_instance_required');
|
|
250
|
+
error.code = 'BRAIN_SYNC_SOURCE_REQUIRED';
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
const list = Array.isArray(payload.entries) ? payload.entries : [];
|
|
254
|
+
if (list.length > MAX_APPLY_ENTRIES) {
|
|
255
|
+
const error = new Error('brain_sync_entries_too_large');
|
|
256
|
+
error.code = 'BRAIN_SYNC_ENTRIES_TOO_LARGE';
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
const localId = localBrainInstanceId(db);
|
|
260
|
+
const memberOf = _string(payload.memberOf, 160).trim();
|
|
261
|
+
// The CREATE TEMP TABLE text lives in brain/schema.js (the sanctioned DDL module) — this
|
|
262
|
+
// storage module owns no DDL (bin/ddl-outside-runner-lint.js).
|
|
263
|
+
createBrainSyncSentinel(db);
|
|
264
|
+
try {
|
|
265
|
+
return db.transaction(() => {
|
|
266
|
+
let applied = 0;
|
|
267
|
+
let skipped = 0;
|
|
268
|
+
let conflicts = 0;
|
|
269
|
+
const clockOf = db.prepare('SELECT origin_instance_id, origin_seq, updated_at, deleted FROM brain_row_clock WHERE table_name = ? AND row_id = ?');
|
|
270
|
+
const seenThrough = db.prepare('SELECT max_seq FROM brain_vv WHERE origin_instance_id = ?');
|
|
271
|
+
const recordConflict = db.prepare(`
|
|
272
|
+
INSERT INTO brain_sync_conflicts (at, reason, table_name, row_id, origin_instance_id, origin_seq, detail)
|
|
273
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
274
|
+
`);
|
|
275
|
+
const note = (reason, entry, detail = '') => {
|
|
276
|
+
conflicts += 1;
|
|
277
|
+
recordConflict.run(
|
|
278
|
+
new Date().toISOString(), reason,
|
|
279
|
+
_string(entry.table, 40), _string(entry.rowId, 160),
|
|
280
|
+
_string(entry.origin, 160), Number(entry.originSeq) || 0, _string(detail, 500),
|
|
281
|
+
);
|
|
282
|
+
};
|
|
283
|
+
for (const entry of list) {
|
|
284
|
+
const table = _string(entry?.table || entry?.table_name, 40).trim();
|
|
285
|
+
const rowId = _string(entry?.rowId || entry?.row_id, 160).trim();
|
|
286
|
+
const origin = _string(entry?.origin || entry?.origin_instance_id, 160);
|
|
287
|
+
const originSeq = _parseSeq(entry?.originSeq ?? entry?.origin_seq);
|
|
288
|
+
const updatedAt = _string(entry?.updatedAt || entry?.updated_at, 64);
|
|
289
|
+
const deleted = Number(entry?.deleted) ? 1 : 0;
|
|
290
|
+
const shape = { table, rowId, origin, originSeq };
|
|
291
|
+
if (!REPLICATED_TABLES.includes(table)) {
|
|
292
|
+
skipped += 1;
|
|
293
|
+
note('brain_table_unknown', shape);
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
if (!rowId || !origin) {
|
|
297
|
+
// No id or no origin is not addressable and cannot be LWW-compared; a skip, not a write.
|
|
298
|
+
skipped += 1;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
// Echo guard: a row this node produced is never taken back (the peer asked with its own
|
|
302
|
+
// maximum, so this should not happen — when it does, the local copy is authoritative).
|
|
303
|
+
if (origin === localId) {
|
|
304
|
+
skipped += 1;
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
// 0. already considered: this node's vector says it has seen this origin through a seq at
|
|
308
|
+
// or past this entry. Within the hub star each origin's changes arrive in ascending seq
|
|
309
|
+
// (the hub pulls each origin in order and serves its own clock in order), so a re-offer is
|
|
310
|
+
// never new information — skipping it keeps a decided conflict from being re-recorded.
|
|
311
|
+
const seen = seenThrough.get(origin);
|
|
312
|
+
if (seen && Number(seen.max_seq) >= originSeq) {
|
|
313
|
+
skipped += 1;
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
const local = clockOf.get(table, rowId);
|
|
317
|
+
const localOrigin = local ? _string(local.origin_instance_id, 160) : '';
|
|
318
|
+
const localSeq = local ? Number(local.origin_seq) || 0 : 0;
|
|
319
|
+
// 1. stale same-origin write (D-3: per-origin seq is the address).
|
|
320
|
+
if (local && localOrigin === origin && localSeq >= originSeq) {
|
|
321
|
+
skipped += 1;
|
|
322
|
+
_bumpVv(db, origin, originSeq); // considered: the vector means "seen through", not "holds"
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
// 2. LWW against a different origin, non-memory rows only.
|
|
326
|
+
if (table !== 'memories' && local && localOrigin !== origin && !_lwwWins(db, table, rowId, updatedAt, origin, localId)) {
|
|
327
|
+
skipped += 1;
|
|
328
|
+
note('lww_lost', shape);
|
|
329
|
+
_bumpVv(db, origin, originSeq); // a decided loss must not be re-offered every tick
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
// 3. a tombstone: the row goes, the clock stays.
|
|
333
|
+
if (deleted) {
|
|
334
|
+
db.prepare(`DELETE FROM "${table}" WHERE id = ?`).run(rowId);
|
|
335
|
+
_stampClock(db, table, rowId, updatedAt, origin, originSeq, 1);
|
|
336
|
+
_bumpVv(db, origin, originSeq);
|
|
337
|
+
applied += 1;
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const parsed = _parsePayload(entry?.payload);
|
|
341
|
+
if (!parsed) {
|
|
342
|
+
skipped += 1;
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
const columns = _tableColumns(db, table);
|
|
346
|
+
if (!columns.length) {
|
|
347
|
+
skipped += 1;
|
|
348
|
+
note('brain_table_unknown', shape);
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
const row = { ...parsed, id: parsed.id || rowId };
|
|
352
|
+
if (table === 'memories') {
|
|
353
|
+
// D-4: the (source, source_id) pair is the memory's real identity. An incoming row
|
|
354
|
+
// that pins the same pair to a different id is a fork, not an update — drop it.
|
|
355
|
+
const sourceId = row.source_id == null ? '' : String(row.source_id);
|
|
356
|
+
if (sourceId) {
|
|
357
|
+
const clash = db.prepare('SELECT id FROM memories WHERE source = ? AND source_id = ?').get(_string(row.source, 200), _string(sourceId, 400));
|
|
358
|
+
if (clash && String(clash.id) !== String(rowId)) {
|
|
359
|
+
skipped += 1;
|
|
360
|
+
note('memory_identity_conflict', shape, `local_id:${_string(clash.id, 160)}`);
|
|
361
|
+
_bumpVv(db, origin, originSeq); // recorded once; re-offering it every tick would grow the ledger forever
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const existed = !!db.prepare('SELECT 1 AS hit FROM memories WHERE id = ?').get(rowId);
|
|
366
|
+
if (existed) {
|
|
367
|
+
// An archive/edit travels as an update of the same id under LWW (§5 D-10); only the
|
|
368
|
+
// six watch columns move, so nothing local (extraction_status, embeddings pointers)
|
|
369
|
+
// is clobbered by a peer.
|
|
370
|
+
const wins = !local || localOrigin === origin || _lwwWins(db, table, rowId, updatedAt, origin, localId);
|
|
371
|
+
if (!wins) {
|
|
372
|
+
skipped += 1;
|
|
373
|
+
note('lww_lost', shape);
|
|
374
|
+
_bumpVv(db, origin, originSeq);
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
const watch = MEMORY_WATCH_COLUMNS.filter((column) => columns.includes(column) && row[column] !== undefined);
|
|
378
|
+
if (watch.length) {
|
|
379
|
+
db.prepare(`UPDATE memories SET ${watch.map((column) => `"${column}" = ?`).join(', ')} WHERE id = ?`)
|
|
380
|
+
.run(...watch.map((column) => row[column]), rowId);
|
|
381
|
+
}
|
|
382
|
+
} else {
|
|
383
|
+
// Follow-up #19 (v1 parity): a memory this host did not ingest is never extracted
|
|
384
|
+
// here — the origin host's extraction produced its knowledge rows, and those
|
|
385
|
+
// replicate on their own. Only the insert path labels the row.
|
|
386
|
+
row.extraction_status = 'replicated';
|
|
387
|
+
// Host-local bookkeeping starts fresh here: the origin's read stamps and retry
|
|
388
|
+
// counters describe ITS work (an origin embed_failed_count would stop this node's
|
|
389
|
+
// embedding backfill from ever trying the row).
|
|
390
|
+
for (const column of MEMORY_HOST_LOCAL_COLUMNS) if (column in row) row[column] = MEMORY_HOST_LOCAL_RESET[column];
|
|
391
|
+
const fields = _payloadFields(row, columns);
|
|
392
|
+
if (!fields.includes('id')) fields.unshift('id');
|
|
393
|
+
_writeRow(db, table, fields, row);
|
|
394
|
+
}
|
|
395
|
+
} else {
|
|
396
|
+
// Same repair the v1 apply makes for a dangling knowledge pointer: the target is not
|
|
397
|
+
// here, so the pointer is dropped (NULL) and the loss is recorded.
|
|
398
|
+
if (table === 'knowledge') {
|
|
399
|
+
const target = _string(row.superseded_by, 160).trim();
|
|
400
|
+
if (target && !knowledgeRowExists(db, target)) {
|
|
401
|
+
row.superseded_by = null;
|
|
402
|
+
note('brain_fk_dropped', shape, `referenced_id:${target}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const fields = _payloadFields(row, columns);
|
|
406
|
+
if (!fields.includes('id')) fields.unshift('id');
|
|
407
|
+
_writeRow(db, table, fields, row);
|
|
408
|
+
}
|
|
409
|
+
_stampClock(db, table, rowId, updatedAt, origin, originSeq, 0);
|
|
410
|
+
_bumpVv(db, origin, originSeq);
|
|
411
|
+
applied += 1;
|
|
412
|
+
}
|
|
413
|
+
if (memberOf) markMembership(db, { homeInstanceId: memberOf });
|
|
414
|
+
return { ok: true, applied, skipped, conflicts, vector: _readVv(db) };
|
|
415
|
+
}).immediate();
|
|
416
|
+
} finally {
|
|
417
|
+
dropBrainSyncSentinel(db);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// D-5: the durable "this node is a member" fact. Written when a home's sync round applies changes here
|
|
422
|
+
// (header X-CTM-Memory-Sync-Home), and by the WP-4 join. Host-local kv (never replicated). A node never
|
|
423
|
+
// marks itself.
|
|
424
|
+
const { MEMBERSHIP_KEY, readMembership } = require('./brain-sync-membership');
|
|
425
|
+
function markMembership(db, { homeInstanceId = '' } = {}) {
|
|
426
|
+
const home = _string(homeInstanceId, 160).trim();
|
|
427
|
+
if (!home || home === localBrainInstanceId(db)) return { ok: false, changed: false };
|
|
428
|
+
const current = readMembership(db);
|
|
429
|
+
if (current && current.role === 'member' && current.homeInstanceId === home) return { ok: true, changed: false };
|
|
430
|
+
db.prepare(`INSERT INTO kv_store (key, value, updated_at) VALUES (?, ?, datetime('now'))
|
|
431
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`)
|
|
432
|
+
.run(MEMBERSHIP_KEY, JSON.stringify({ role: 'member', homeInstanceId: home, since: new Date().toISOString() }));
|
|
433
|
+
return { ok: true, changed: true };
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ---------------------------------------------------------------------------
|
|
437
|
+
// Hub bookkeeping
|
|
438
|
+
// ---------------------------------------------------------------------------
|
|
439
|
+
|
|
440
|
+
// POST /api/walle/sync/v2/member-vector — the hub's record of what a member last reported.
|
|
441
|
+
// Monotonic per (member, origin): a stale or out-of-order report can only ever make the
|
|
442
|
+
// pruning floor more conservative, never less.
|
|
443
|
+
function recordMemberVector(db, { memberInstanceId = '', vector = {} } = {}) {
|
|
444
|
+
const member = _string(memberInstanceId, 160).trim();
|
|
445
|
+
if (!member) {
|
|
446
|
+
const error = new Error('brain_sync_member_instance_required');
|
|
447
|
+
error.code = 'BRAIN_SYNC_MEMBER_REQUIRED';
|
|
448
|
+
throw error;
|
|
449
|
+
}
|
|
450
|
+
const entries = vector && typeof vector === 'object' && !Array.isArray(vector) ? Object.entries(vector) : [];
|
|
451
|
+
const at = new Date().toISOString();
|
|
452
|
+
db.transaction(() => {
|
|
453
|
+
const upsert = db.prepare(`
|
|
454
|
+
INSERT INTO brain_member_vv (member_instance_id, origin_instance_id, max_seq, reported_at)
|
|
455
|
+
VALUES (?, ?, ?, ?)
|
|
456
|
+
ON CONFLICT(member_instance_id, origin_instance_id) DO UPDATE SET
|
|
457
|
+
max_seq = MAX(brain_member_vv.max_seq, excluded.max_seq),
|
|
458
|
+
reported_at = excluded.reported_at
|
|
459
|
+
`);
|
|
460
|
+
for (const [origin, seq] of entries) {
|
|
461
|
+
if (!origin) continue;
|
|
462
|
+
upsert.run(member, _string(origin, 160), Math.max(0, _parseSeq(seq)), at);
|
|
463
|
+
}
|
|
464
|
+
}).immediate();
|
|
465
|
+
return { ok: true, memberInstanceId: member, reportedAt: at };
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// POST /api/walle/sync/v2/prune-tombstones — §5 D-8. A tombstone goes only once EVERY member
|
|
469
|
+
// whose report is still fresh (within rejoinAfterDays) has a vector covering it: the floor is
|
|
470
|
+
// the MIN over those members for that origin, so one lagging member holds the tombstone back.
|
|
471
|
+
// No members at all → nothing to prune (a node that just joined must not eat the history it
|
|
472
|
+
// has not shipped yet).
|
|
473
|
+
function pruneTombstones(db, { now = Date.now(), rejoinAfterDays = 30 } = {}) {
|
|
474
|
+
const days = Number.isFinite(Number(rejoinAfterDays)) ? Math.max(0, Number(rejoinAfterDays)) : 30;
|
|
475
|
+
const cutoff = new Date(Number(now) - days * 24 * 60 * 60 * 1000).toISOString();
|
|
476
|
+
const hasMembers = !!db.prepare('SELECT 1 AS hit FROM brain_member_vv LIMIT 1').get();
|
|
477
|
+
if (!hasMembers) {
|
|
478
|
+
// A MEMBER (no member vectors: only the hub dials, D-2) prunes by age. Its tombstones matter
|
|
479
|
+
// only until the hub has pulled them, which a member silent for longer than the rejoin window
|
|
480
|
+
// forfeits anyway (D-8: it must re-join by snapshot). updated_at is the origin's ISO clock.
|
|
481
|
+
const aged = db.prepare(`
|
|
482
|
+
DELETE FROM brain_row_clock
|
|
483
|
+
WHERE rowid IN (SELECT rowid FROM brain_row_clock WHERE deleted = 1 AND updated_at < ? LIMIT ?)
|
|
484
|
+
`).run(cutoff, MAX_TOMBSTONE_PRUNE).changes;
|
|
485
|
+
return { ok: true, pruned: aged, mode: 'age' };
|
|
486
|
+
}
|
|
487
|
+
let pruned = 0;
|
|
488
|
+
db.transaction(() => {
|
|
489
|
+
const floors = db.prepare(`
|
|
490
|
+
SELECT origin_instance_id AS origin, MIN(max_seq) AS floor
|
|
491
|
+
FROM brain_member_vv
|
|
492
|
+
WHERE reported_at >= ?
|
|
493
|
+
GROUP BY origin_instance_id
|
|
494
|
+
`).all(cutoff);
|
|
495
|
+
const del = db.prepare(`
|
|
496
|
+
DELETE FROM brain_row_clock
|
|
497
|
+
WHERE rowid IN (
|
|
498
|
+
SELECT rowid FROM brain_row_clock
|
|
499
|
+
WHERE deleted = 1 AND origin_instance_id = ? AND origin_seq <= ?
|
|
500
|
+
LIMIT ?
|
|
501
|
+
)
|
|
502
|
+
`);
|
|
503
|
+
for (const row of floors) {
|
|
504
|
+
if (pruned >= MAX_TOMBSTONE_PRUNE) break;
|
|
505
|
+
pruned += del.run(_string(row.origin, 160), Math.max(0, Number(row.floor) || 0), MAX_TOMBSTONE_PRUNE - pruned).changes;
|
|
506
|
+
}
|
|
507
|
+
}).immediate();
|
|
508
|
+
return { ok: true, pruned };
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// ---------------------------------------------------------------------------
|
|
512
|
+
// Convergence digest (§6.5) — the comparison WP-6's verifier reads
|
|
513
|
+
// ---------------------------------------------------------------------------
|
|
514
|
+
|
|
515
|
+
function _canonicalJson(table, payload) {
|
|
516
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return 'null';
|
|
517
|
+
const ordered = {};
|
|
518
|
+
for (const key of [...(DIGEST_COLUMNS[table] || [])].sort()) ordered[key] = payload[key] === undefined ? null : payload[key];
|
|
519
|
+
return JSON.stringify(ordered);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// GET /api/walle/sync/v2/digest?table=memories — bucket = first byte of sha256(rowId);
|
|
523
|
+
// rowHash = first 8 bytes of sha256(`${table}|${rowId}|${deleted}|${canonicalJson(payload)}`),
|
|
524
|
+
// XOR-folded per bucket. Only DIGEST_COLUMNS are hashed: read stamps, retry counters and local
|
|
525
|
+
// classifier output are this host's bookkeeping, not the replicated state. CPU-bound, paged.
|
|
526
|
+
function digest(db, { table } = {}) {
|
|
527
|
+
const name = _string(table, 40).trim();
|
|
528
|
+
if (!REPLICATED_TABLES.includes(name)) {
|
|
529
|
+
const error = new Error(`brain_sync_digest_unknown_table: ${name}`);
|
|
530
|
+
error.code = 'BRAIN_SYNC_TABLE_UNKNOWN';
|
|
531
|
+
throw error;
|
|
532
|
+
}
|
|
533
|
+
const counts = new Array(256).fill(0);
|
|
534
|
+
const folds = new Array(256).fill(null);
|
|
535
|
+
// LIVE rows only (shared-memory WP-4c review): tombstones are bookkeeping that each node prunes on
|
|
536
|
+
// its own schedule (pruneTombstones), so hashing them would make converged nodes differ. A delete
|
|
537
|
+
// that never applied still shows: the lagging node keeps a live row the others do not have.
|
|
538
|
+
const page = db.prepare('SELECT rowid AS rid, row_id, deleted FROM brain_row_clock WHERE table_name = ? AND deleted = 0 AND rowid > ? ORDER BY rowid ASC LIMIT ?');
|
|
539
|
+
let afterRowid = 0;
|
|
540
|
+
let count = 0;
|
|
541
|
+
for (;;) {
|
|
542
|
+
const rows = page.all(name, afterRowid, DIGEST_PAGE);
|
|
543
|
+
if (!rows.length) break;
|
|
544
|
+
for (const clockRow of rows) {
|
|
545
|
+
afterRowid = Number(clockRow.rid);
|
|
546
|
+
const live = Number(clockRow.deleted) ? null : readReplicatedRow(db, name, clockRow.row_id);
|
|
547
|
+
const deleted = live ? 0 : 1;
|
|
548
|
+
const hash = crypto.createHash('sha256').update(`${name}|${clockRow.row_id}|${deleted}|${_canonicalJson(name, live)}`).digest();
|
|
549
|
+
const bucket = crypto.createHash('sha256').update(String(clockRow.row_id)).digest()[0];
|
|
550
|
+
counts[bucket] += 1;
|
|
551
|
+
count += 1;
|
|
552
|
+
const fold = folds[bucket] || Buffer.alloc(8);
|
|
553
|
+
for (let index = 0; index < 8; index += 1) fold[index] ^= hash[index];
|
|
554
|
+
folds[bucket] = fold;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
const buckets = [];
|
|
558
|
+
for (let bucket = 0; bucket < 256; bucket += 1) {
|
|
559
|
+
buckets.push({ b: bucket, count: counts[bucket], xor: (folds[bucket] || Buffer.alloc(8)).toString('hex') });
|
|
560
|
+
}
|
|
561
|
+
return { ok: true, table: name, count, buckets };
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
module.exports = {
|
|
565
|
+
markMembership,
|
|
566
|
+
readMembership,
|
|
567
|
+
MEMBERSHIP_KEY,
|
|
568
|
+
DEFAULT_CHANGES_LIMIT,
|
|
569
|
+
MAX_CHANGES_LIMIT,
|
|
570
|
+
MAX_APPLY_ENTRIES,
|
|
571
|
+
MEMORY_WATCH_COLUMNS,
|
|
572
|
+
readVector,
|
|
573
|
+
changesSince,
|
|
574
|
+
applyChanges,
|
|
575
|
+
recordMemberVector,
|
|
576
|
+
pruneTombstones,
|
|
577
|
+
digest,
|
|
578
|
+
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Delegate model preferences (lead-and-delegate-model-picker-ux-2026-10-08.html
|
|
4
4
|
// §5 D-9/D-11, §6.4): one row per scope in delegate_model_prefs --
|
|
5
5
|
// session:<ctm_session_id> the pick made for ONE lead session
|
|
6
|
-
// default:claude | default:codex the per-lead-agent default
|
|
6
|
+
// default:claude | default:codex | default:opencode | default:dsh the per-lead-agent default
|
|
7
7
|
// Every write to the table lives here. effectiveDelegatePref() walks the
|
|
8
8
|
// ladder session > default > vendor and names the winning rung as `source`.
|
|
9
9
|
// Migration 86 also lives here: it folds the retired model_defaults
|
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
const { foldDelegateLadder } = require('../llm/delegate-vendor-default');
|
|
12
12
|
|
|
13
13
|
const LANES = Object.freeze(['native', 'routed', 'inherit']);
|
|
14
|
-
|
|
14
|
+
// The lead agents the ladder is keyed on. WP-10/P-5 (deepseek-harness-acp-agent-2026-10-28.html
|
|
15
|
+
// §5b D-24): dsh joins as a first-class delegating lead -- its `default:dsh` rung is exactly as
|
|
16
|
+
// reachable as default:opencode's.
|
|
17
|
+
const AGENTS = Object.freeze(['claude', 'codex', 'opencode', 'dsh']);
|
|
15
18
|
const SESSION_SCOPE_RE = /^session:[A-Za-z0-9._-]{1,160}$/;
|
|
16
19
|
const FIELD_MAX = 200;
|
|
17
20
|
// '' = the model's own default. native: the Agent tool / spawn_agent accept up to max; routed:
|
|
@@ -54,7 +57,7 @@ function optionalString(value, field) {
|
|
|
54
57
|
function putDelegatePref(db, row, { now = () => new Date().toISOString() } = {}) {
|
|
55
58
|
const v = row && typeof row === 'object' && !Array.isArray(row) ? row : {};
|
|
56
59
|
if (!isValidScopeKey(v.scope_key)) {
|
|
57
|
-
throw prefError('scope_key', 'must be session:<ctm_session_id>, default:claude or default:
|
|
60
|
+
throw prefError('scope_key', 'must be session:<ctm_session_id>, default:claude, default:codex, default:opencode or default:dsh');
|
|
58
61
|
}
|
|
59
62
|
if (!LANES.includes(v.lane)) throw prefError('lane', 'must be native, routed or inherit');
|
|
60
63
|
const model = optionalString(v.model, 'model');
|
|
@@ -96,7 +99,10 @@ function deleteDelegatePref(db, scopeKey) {
|
|
|
96
99
|
* @returns {{effective:{lane,model,route_id,identity_id,display,effort,source}, ladder:Array}}
|
|
97
100
|
*/
|
|
98
101
|
function effectiveDelegatePref(db, { ctmSessionId, agent } = {}) {
|
|
99
|
-
|
|
102
|
+
// AGENTS is the one list of lead agents; an absent/unknown agent keeps the historical claude
|
|
103
|
+
// fallback. Reading `default:${leadAgent}` is what makes default:opencode / default:dsh
|
|
104
|
+
// reachable at all.
|
|
105
|
+
const leadAgent = AGENTS.includes(agent) ? agent : 'claude';
|
|
100
106
|
const sessionKey = ctmSessionId ? `session:${ctmSessionId}` : '';
|
|
101
107
|
return foldDelegateLadder({
|
|
102
108
|
session: isValidScopeKey(sessionKey) ? getDelegatePref(db, sessionKey) : null,
|
|
@@ -127,6 +133,9 @@ function migrateDelegatedWorkerDefault(d) {
|
|
|
127
133
|
VALUES (?, 'routed', ?, ?, ?, ?, ?)
|
|
128
134
|
`);
|
|
129
135
|
const now = new Date().toISOString();
|
|
136
|
+
// AC-4f / WP-10: `agents` comes from servableAgents(), which only ever yields claude/codex,
|
|
137
|
+
// so an 'opencode' or 'dsh' member of AGENTS is always skipped and this migration's
|
|
138
|
+
// behaviour is unchanged by either joining the list.
|
|
130
139
|
for (const agent of AGENTS) {
|
|
131
140
|
if (!agents.includes(agent)) continue;
|
|
132
141
|
const info = insert.run(`default:${agent}`, reg.model_id, identity ? identity.identity_id : '', reg.provider_id, reg.display_name || reg.model_id, now);
|