create-walle 0.10.1 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/lib/owner-write-safety.js +135 -0
- package/lib/public-runtime.js +13 -5
- package/lib/release-schema.json +3 -2
- package/lib/runtime-closure-policy.json +6 -4
- package/package.json +1 -1
- package/template/bin/deps-doctor.js +1 -1
- package/template/bin/node-bin.sh +11 -0
- package/template/coding-task-manager/api-prompts.js +10 -22
- package/template/coding-task-manager/bin/claude-hook.js +7 -3
- package/template/coding-task-manager/bin/claude-table-parity.js +307 -0
- package/template/coding-task-manager/bin/conversation-storage-auto-migration-lib.js +16 -1
- package/template/coding-task-manager/bin/conversation-storage-generation-evidence.js +9 -2
- package/template/coding-task-manager/bin/conversation-storage-service-entry-lib.js +16 -1
- package/template/coding-task-manager/bin/conversation-storage-walle-owner.js +247 -0
- package/template/coding-task-manager/bin/ctm-machine-pair.js +160 -0
- package/template/coding-task-manager/bin/lead-wait-census.js +1348 -0
- package/template/coding-task-manager/bin/repair-foreign-clone.js +564 -0
- package/template/coding-task-manager/bin/repair-resume-echo-titles.js +248 -0
- package/template/coding-task-manager/bin/write-lock-bench.js +111 -0
- package/template/coding-task-manager/db.js +705 -286
- package/template/coding-task-manager/git-utils.js +44 -0
- package/template/coding-task-manager/lib/adopted-host-redial.js +4 -0
- package/template/coding-task-manager/lib/agent-presets.js +15 -0
- package/template/coding-task-manager/lib/agent-title-apply.js +88 -0
- package/template/coding-task-manager/lib/agent-title-backfill-read.js +92 -0
- package/template/coding-task-manager/lib/api/host-stats.js +98 -0
- package/template/coding-task-manager/lib/api/ingress.js +35 -12
- package/template/coding-task-manager/lib/api/machine-pairing.js +285 -0
- package/template/coding-task-manager/lib/api/machine-pool.js +307 -0
- package/template/coding-task-manager/lib/api/machines.js +461 -0
- package/template/coding-task-manager/lib/api/misc-ops.js +20 -5
- package/template/coding-task-manager/lib/api/providers.js +53 -6
- package/template/coding-task-manager/lib/api/resume-from-store.js +25 -0
- package/template/coding-task-manager/lib/api/sessions.js +160 -8
- package/template/coding-task-manager/lib/api/transcript-history.js +17 -4
- package/template/coding-task-manager/lib/api/worktrees.js +38 -37
- package/template/coding-task-manager/lib/approval-drift.js +16 -1
- package/template/coding-task-manager/lib/approval-logical-frame.js +5 -1
- package/template/coding-task-manager/lib/approval-scope.js +68 -4
- package/template/coding-task-manager/lib/auth-context.js +32 -2
- package/template/coding-task-manager/lib/auth-rate-limit.js +41 -1
- package/template/coding-task-manager/lib/auth-rules.js +186 -45
- package/template/coding-task-manager/lib/broadcast-serialize.js +2 -1
- package/template/coding-task-manager/lib/browser-session-broker.js +9 -1
- package/template/coding-task-manager/lib/browser-use-adapter.js +33 -2
- package/template/coding-task-manager/lib/claude-markdown-ansi.js +27 -85
- package/template/coding-task-manager/lib/claude-session-title.js +6 -30
- package/template/coding-task-manager/lib/claude-table-layout.js +322 -0
- package/template/coding-task-manager/lib/client-diag-handler.js +5 -0
- package/template/coding-task-manager/lib/codex-config-guard.js +33 -3
- package/template/coding-task-manager/lib/codex-harness-qualification.js +12 -0
- package/template/coding-task-manager/lib/codex-launch-health.js +29 -2
- package/template/coding-task-manager/lib/codex-link-preparation.js +9 -8
- package/template/coding-task-manager/lib/codex-paths.js +2 -1
- package/template/coding-task-manager/lib/coding-agent-models.js +21 -30
- package/template/coding-task-manager/lib/coding-session-model-adapters.js +27 -2
- package/template/coding-task-manager/lib/coding-session-model-projection.js +5 -0
- package/template/coding-task-manager/lib/coding-session-model-runtime.js +1 -2
- package/template/coding-task-manager/lib/coding-session-model-state.js +10 -0
- package/template/coding-task-manager/lib/coding-session-model-sync-state.js +0 -1
- package/template/coding-task-manager/lib/conversation-import/cursor-guard-owner.js +10 -0
- package/template/coding-task-manager/lib/create/resolve-launch-params.js +8 -1
- package/template/coding-task-manager/lib/ctm-boot-lifecycle.js +17 -1
- package/template/coding-task-manager/lib/db-domain-tables.js +7 -0
- package/template/coding-task-manager/lib/db-domains.js +21 -2
- package/template/coding-task-manager/lib/db-owner-op-registry.js +7 -0
- package/template/coding-task-manager/lib/db-schema-source-files.js +1 -0
- package/template/coding-task-manager/lib/feature-manifest.js +1 -1
- package/template/coding-task-manager/lib/fs-watcher.js +3 -2
- package/template/coding-task-manager/lib/harness-history-transition.js +3 -3
- package/template/coding-task-manager/lib/headless-inflight-ops.js +203 -0
- package/template/coding-task-manager/lib/headless-missing-reply.js +42 -0
- package/template/coding-task-manager/lib/headless-term-service.js +55 -17
- package/template/coding-task-manager/lib/host-control-dial.js +167 -0
- package/template/coding-task-manager/lib/host-handoff-orchestrator.js +16 -2
- package/template/coding-task-manager/lib/host-handoff.js +51 -13
- package/template/coding-task-manager/lib/host-health-ledger.js +164 -12
- package/template/coding-task-manager/lib/host-self-truth-watchdog.js +4 -0
- package/template/coding-task-manager/lib/host-stats.js +328 -0
- package/template/coding-task-manager/lib/host-threadpool-evacuation.js +160 -0
- package/template/coding-task-manager/lib/host-threadpool-monitor.js +14 -1
- package/template/coding-task-manager/lib/hosted-input-dispatch.js +203 -0
- package/template/coding-task-manager/lib/hosted-sync-worker.js +113 -5
- package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +4 -0
- package/template/coding-task-manager/lib/instance-manifest-runtime.js +3 -0
- package/template/coding-task-manager/lib/instance-manifest.js +13 -1
- package/template/coding-task-manager/lib/instance-sync-status.js +15 -0
- package/template/coding-task-manager/lib/launch-model-override.js +160 -1
- package/template/coding-task-manager/lib/launch-plan-worker.js +59 -2
- package/template/coding-task-manager/lib/lead-model-switch-runtime.js +57 -4
- package/template/coding-task-manager/lib/lead-model-switch.js +3 -2
- package/template/coding-task-manager/lib/lead-model-transition-store.js +61 -3
- package/template/coding-task-manager/lib/legacy-draft-cancellation-recovery.js +111 -0
- package/template/coding-task-manager/lib/login-tripwire.js +1 -0
- package/template/coding-task-manager/lib/machine-pairing.js +238 -0
- package/template/coding-task-manager/lib/machine-pool.js +409 -0
- package/template/coding-task-manager/lib/machine-registry.js +749 -0
- package/template/coding-task-manager/lib/machine-self.js +66 -0
- package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +52 -4
- package/template/coding-task-manager/lib/media-journal-control-reader.js +473 -0
- package/template/coding-task-manager/lib/media-journal.js +84 -27
- package/template/coding-task-manager/lib/media-store-options.js +43 -15
- package/template/coding-task-manager/lib/migrations/v56-host-samples-and-machine-pool.js +76 -0
- package/template/coding-task-manager/lib/mirror-feed-sanitize.js +12 -3
- package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
- package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
- package/template/coding-task-manager/lib/model-proxy/handoff.js +494 -0
- package/template/coding-task-manager/lib/model-proxy/lead-worker-picks.js +25 -0
- package/template/coding-task-manager/lib/model-proxy/native-lead-listener.js +61 -7
- package/template/coding-task-manager/lib/model-proxy/proxy-cli.js +200 -0
- package/template/coding-task-manager/lib/model-proxy/proxy-instance-record.js +257 -0
- package/template/coding-task-manager/lib/model-proxy/stream-accounting.js +205 -0
- package/template/coding-task-manager/lib/model-proxy/tokens.js +78 -0
- package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
- package/template/coding-task-manager/lib/native-history-control.js +2 -2
- package/template/coding-task-manager/lib/native-lead-settings-client.js +2 -1
- package/template/coding-task-manager/lib/native-lead-tls.js +18 -4
- package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +628 -64
- package/template/coding-task-manager/lib/owner-action-manifest-data.json +14 -0
- package/template/coding-task-manager/lib/painted-row-recorder.js +9 -1
- package/template/coding-task-manager/lib/prompt-submission-contract.js +21 -1
- package/template/coding-task-manager/lib/provider-login-broker.js +192 -9
- package/template/coding-task-manager/lib/provider-login-capture.js +153 -0
- package/template/coding-task-manager/lib/queue-draft-status-overlay.js +36 -0
- package/template/coding-task-manager/lib/raw-codex-history-window.js +5 -3
- package/template/coding-task-manager/lib/read-pool-client.js +5 -2
- package/template/coding-task-manager/lib/read-pool-op-registry.js +10 -1
- package/template/coding-task-manager/lib/recent-catalog-query.js +26 -0
- package/template/coding-task-manager/lib/recent-session-catalog-response.js +9 -0
- package/template/coding-task-manager/lib/remote-command-context.js +12 -0
- package/template/coding-task-manager/lib/remote-command-runtime.js +13 -1
- package/template/coding-task-manager/lib/remote-machine-catalog.js +1062 -0
- package/template/coding-task-manager/lib/remote-machine-create.js +693 -0
- package/template/coding-task-manager/lib/remote-machine-link.js +939 -0
- package/template/coding-task-manager/lib/remote-machine-proxy.js +520 -0
- package/template/coding-task-manager/lib/remote-session-forward.js +306 -0
- package/template/coding-task-manager/lib/remote-submission-status.js +6 -0
- package/template/coding-task-manager/lib/restart-outcome-truth.js +20 -3
- package/template/coding-task-manager/lib/restart-preflight.js +9 -2
- package/template/coding-task-manager/lib/restore-backoff-gate.js +10 -2
- package/template/coding-task-manager/lib/restore-prepare-retry.js +12 -5
- package/template/coding-task-manager/lib/restore-resume-batch.js +22 -1
- package/template/coding-task-manager/lib/resume-blocked-wire.js +62 -1
- package/template/coding-task-manager/lib/resume-cwd.js +13 -0
- package/template/coding-task-manager/lib/resume-from-store.js +12 -1
- package/template/coding-task-manager/lib/resume-origin-facts.js +38 -1
- package/template/coding-task-manager/lib/resume-preflight.js +21 -16
- package/template/coding-task-manager/lib/review-repo-admission.js +39 -6
- package/template/coding-task-manager/lib/review-repo-registry.js +17 -0
- package/template/coding-task-manager/lib/root-delivery-runtime.js +72 -15
- package/template/coding-task-manager/lib/root-delivery-selection.js +37 -1
- package/template/coding-task-manager/lib/root-prompt-admission.js +33 -2
- package/template/coding-task-manager/lib/root-prompt-flow.js +167 -33
- package/template/coding-task-manager/lib/scrollback-archive-store.js +7 -4
- package/template/coding-task-manager/lib/session-analysis-memory.js +2 -0
- package/template/coding-task-manager/lib/session-create/codex-native-provider-gate.js +218 -0
- package/template/coding-task-manager/lib/session-create/codex-resume-exit-classify.js +52 -0
- package/template/coding-task-manager/lib/session-create/delegate-worker.js +95 -6
- package/template/coding-task-manager/lib/session-create/identity.js +20 -1
- package/template/coding-task-manager/lib/session-create/prepared.js +69 -1
- package/template/coding-task-manager/lib/session-create/provider-launch-context.js +77 -0
- package/template/coding-task-manager/lib/session-create/provider-process.js +34 -37
- package/template/coding-task-manager/lib/session-create/response.js +30 -12
- package/template/coding-task-manager/lib/session-create/transcript.js +13 -4
- package/template/coding-task-manager/lib/session-create/worker-continuation.js +43 -1
- package/template/coding-task-manager/lib/session-create/worker-notices.js +94 -5
- package/template/coding-task-manager/lib/session-create/worker-observability.js +99 -7
- package/template/coding-task-manager/lib/session-create/worker-stop.js +11 -3
- package/template/coding-task-manager/lib/session-create/worker-wait-watcher.js +315 -0
- package/template/coding-task-manager/lib/session-geometry-authority-handler.js +10 -1
- package/template/coding-task-manager/lib/session-host-manager.js +152 -206
- package/template/coding-task-manager/lib/session-jobs.js +17 -9
- package/template/coding-task-manager/lib/session-lifecycle.js +18 -0
- package/template/coding-task-manager/lib/session-model-action-preconditions.js +9 -0
- package/template/coding-task-manager/lib/session-resource-sampler.js +19 -9
- package/template/coding-task-manager/lib/session-standup.js +67 -4
- package/template/coding-task-manager/lib/session-status-ledger.js +42 -3
- package/template/coding-task-manager/lib/session-status-runtime.js +45 -1
- package/template/coding-task-manager/lib/session-title-mirror.js +25 -57
- package/template/coding-task-manager/lib/session-transcript-trash.js +8 -1
- package/template/coding-task-manager/lib/startup-pointer-write.js +57 -0
- package/template/coding-task-manager/lib/state-sync/painted-frame-field.js +11 -0
- package/template/coding-task-manager/lib/storage/coding-session-model-store.js +21 -22
- package/template/coding-task-manager/lib/surviving-owner-bootstrap.js +7 -2
- package/template/coding-task-manager/lib/surviving-owner-recovery.js +2 -1
- package/template/coding-task-manager/lib/terminal-draft-http-status.js +25 -0
- package/template/coding-task-manager/lib/transcript-archiver.js +229 -229
- package/template/coding-task-manager/lib/transcript-frame-cache.js +12 -2
- package/template/coding-task-manager/lib/transcript-guard-job.js +55 -4
- package/template/coding-task-manager/lib/transcript-link.js +77 -0
- package/template/coding-task-manager/lib/transcript-manifest-store.js +8 -4
- package/template/coding-task-manager/lib/transcript-source.js +8 -3
- package/template/coding-task-manager/lib/transcript-store.js +40 -0
- package/template/coding-task-manager/lib/tunnel-lane-governor.js +6 -3
- package/template/coding-task-manager/lib/typing-interaction-trace.js +92 -0
- package/template/coding-task-manager/lib/typing-worker-stages.js +60 -0
- package/template/coding-task-manager/lib/visible-revision-watchdog.js +8 -12
- package/template/coding-task-manager/lib/wake-skip-reasons.js +46 -0
- package/template/coding-task-manager/lib/walle-supervisor.js +98 -14
- package/template/coding-task-manager/lib/worker-failure-classify.js +13 -1
- package/template/coding-task-manager/lib/ws/state-sync.js +19 -2
- package/template/coding-task-manager/lib/ws/terminal-attach.js +32 -1
- package/template/coding-task-manager/lib/ws-router.js +68 -1
- package/template/coding-task-manager/lib/ws-send-admission.js +22 -4
- package/template/coding-task-manager/oauth-proxy.js +99 -55
- package/template/coding-task-manager/package-lock.json +24 -0
- package/template/coding-task-manager/package.json +3 -0
- package/template/coding-task-manager/providers/codex-mcp-extract.js +121 -19
- package/template/coding-task-manager/providers/codex-mcp.js +14 -5
- package/template/coding-task-manager/public/css/app.css +279 -1
- package/template/coding-task-manager/public/css/reviews.css +4 -0
- package/template/coding-task-manager/public/index.html +294 -223
- package/template/coding-task-manager/public/js/app/machines-page.js +1252 -0
- package/template/coding-task-manager/public/js/app/models-tab.js +546 -36
- package/template/coding-task-manager/public/js/app/worktrees-panel.js +7 -5
- package/template/coding-task-manager/public/js/background-work.js +13 -7
- package/template/coding-task-manager/public/js/coding-session-model-presentation.js +44 -4
- package/template/coding-task-manager/public/js/core/session-commands.js +676 -0
- package/template/coding-task-manager/public/js/core/session-selectors.js +5 -1
- package/template/coding-task-manager/public/js/history-app-seam.js +26 -2
- package/template/coding-task-manager/public/js/history-painted-rows.js +17 -2
- package/template/coding-task-manager/public/js/history-reader-park.js +13 -1
- package/template/coding-task-manager/public/js/history-scroller.js +187 -9
- package/template/coding-task-manager/public/js/history-transcript-pages.js +5 -5
- package/template/coding-task-manager/public/js/machine-pool-shared.js +436 -0
- package/template/coding-task-manager/public/js/private-state-scope.js +31 -3
- package/template/coding-task-manager/public/js/prompt-submission-client.js +173 -8
- package/template/coding-task-manager/public/js/prompt-submission-tray.js +281 -0
- package/template/coding-task-manager/public/js/queue-panel-submission-reconcile.js +174 -1
- package/template/coding-task-manager/public/js/reviews.js +7 -3
- package/template/coding-task-manager/public/js/root-draft-lifetime.js +139 -2
- package/template/coding-task-manager/public/js/root-queue-builder-lifetime.js +16 -0
- package/template/coding-task-manager/public/js/row-join.js +4 -2
- package/template/coding-task-manager/public/js/session-model-picker.js +1 -1
- package/template/coding-task-manager/public/js/session-search-utils.js +46 -19
- package/template/coding-task-manager/public/js/state-sync-client.js +4 -0
- package/template/coding-task-manager/public/js/terminal/runtime-02.js +16 -7
- package/template/coding-task-manager/public/js/terminal/runtime-03.js +41 -0
- package/template/coding-task-manager/public/js/terminal/runtime-04.js +41 -4
- package/template/coding-task-manager/public/js/terminal/runtime-12.js +52 -2
- package/template/coding-task-manager/public/js/terminal/runtime-16.js +12 -12
- package/template/coding-task-manager/public/js/terminal/runtime-18.js +14 -0
- package/template/coding-task-manager/public/js/terminal/runtime-19.js +8 -4
- package/template/coding-task-manager/public/js/terminal/runtime-20.js +210 -73
- package/template/coding-task-manager/public/js/terminal/runtime-21.js +19 -6
- package/template/coding-task-manager/public/js/terminal/runtime-22.js +86 -0
- package/template/coding-task-manager/public/js/terminal-history-viewport.js +1 -1
- package/template/coding-task-manager/public/js/terminal-wheel.js +39 -2
- package/template/coding-task-manager/public/js/walle/chat-conversation.js +11 -3
- package/template/coding-task-manager/public/js/walle-session.js +30 -7
- package/template/coding-task-manager/public/m/app.css +226 -8
- package/template/coding-task-manager/public/m/app.js +502 -9
- package/template/coding-task-manager/public/m/index.html +38 -1
- package/template/coding-task-manager/public/m/machines.js +1313 -0
- package/template/coding-task-manager/public/m/pool-failover.js +391 -0
- package/template/coding-task-manager/public/m/private-queue-actions.js +26 -7
- package/template/coding-task-manager/public/m/sw.js +5 -0
- package/template/coding-task-manager/queue-engine.js +13 -0
- package/template/coding-task-manager/server/approvals.js +5 -6
- package/template/coding-task-manager/server/boot.js +136 -17
- package/template/coding-task-manager/server.js +902 -239
- package/template/coding-task-manager/session-integrity.js +7 -9
- package/template/coding-task-manager/workers/db-owner-worker.js +53 -7
- package/template/coding-task-manager/workers/read-pool-worker.js +17 -6
- package/template/coding-task-manager/workers/session-host-pool-process.js +18 -4
- package/template/coding-task-manager/workers/session-host-process.js +14 -3
- package/template/coding-task-manager/workers/transcript-archive-worker.js +1 -1
- package/template/coding-task-manager/workers/worktree-worker.js +34 -0
- package/template/package-lock.json +2 -2
- package/template/package.json +1 -1
- package/template/scripts/conversation-storage-v2-migration.js +78 -11
- package/template/shared/conversation-storage-v2-contract.js +42 -6
- package/template/shared/fixtures/worker-status-payloads.json +392 -0
- package/template/shared/git-remote-key.js +83 -0
- package/template/shared/host-identity.js +98 -2
- package/template/shared/owner-write-safety.js +135 -0
- package/template/shared/public-runtime.js +13 -5
- package/template/shared/release-schema.json +3 -2
- package/template/shared/report-citations.js +14 -0
- package/template/shared/sqlite-write-lock.js +361 -223
- package/template/shared/storage/contract.js +35 -3
- package/template/wall-e/agent.js +21 -0
- package/template/wall-e/api-walle.js +5 -0
- package/template/wall-e/bin/brain-purge-legacy-ctm.js +3 -2
- package/template/wall-e/bin/walle-mcp-stdio.js +12 -6
- package/template/wall-e/brain/helper-rules-schema.js +129 -0
- package/template/wall-e/brain/migrations.js +51 -22
- package/template/wall-e/brain/model-deployment-facts.js +31 -1
- package/template/wall-e/brain/owner-call-whitelist.js +16 -0
- package/template/wall-e/brain/schema-ensures.js +22 -0
- package/template/wall-e/brain.js +1 -1
- package/template/wall-e/coding/journal-dispatcher.js +51 -0
- package/template/wall-e/coding/session-journal.js +28 -40
- package/template/wall-e/coding-orchestrator.js +23 -26
- package/template/wall-e/http/auth.js +18 -1
- package/template/wall-e/http/blog-views-api.js +318 -0
- package/template/wall-e/http/helper-rules-api.js +321 -0
- package/template/wall-e/http/model-proxy-providers.js +3 -1
- package/template/wall-e/http/telemetry-api.js +12 -0
- package/template/wall-e/lib/blog-view-policy.js +88 -0
- package/template/wall-e/lib/blog-view-store.js +169 -0
- package/template/wall-e/lib/client-config-lock.js +2 -1
- package/template/wall-e/lib/coding-journal-writer-child-client.js +67 -18
- package/template/wall-e/lib/error-kinds.js +10 -1
- package/template/wall-e/lib/feature-manifest.js +1 -1
- package/template/wall-e/lib/helper-rules/cell-state.js +123 -0
- package/template/wall-e/lib/helper-rules/evaluate.js +357 -0
- package/template/wall-e/lib/helper-rules/explore-rubric.md +12 -0
- package/template/wall-e/lib/helper-rules/grade.js +277 -0
- package/template/wall-e/lib/helper-rules/label.js +247 -0
- package/template/wall-e/lib/helper-rules/lead-call-hook.js +522 -0
- package/template/wall-e/lib/helper-rules/store.js +667 -0
- package/template/wall-e/lib/mcp-integration.js +5 -6
- package/template/wall-e/lib/runtime-worker-pool.js +1 -0
- package/template/wall-e/lib/scheduler-worker-jobs.js +6 -0
- package/template/wall-e/lib/telemetry-db-schema.js +37 -0
- package/template/wall-e/lib/telemetry-process-client.js +76 -1
- package/template/wall-e/llm/default-fallback.js +4 -0
- package/template/wall-e/llm/model-catalog.js +13 -4
- package/template/wall-e/llm/model-roles.js +6 -0
- package/template/wall-e/llm/portkey-http.js +150 -0
- package/template/wall-e/llm/portkey-sync.js +15 -145
- package/template/wall-e/llm/worker-mcp-routing.js +61 -0
- package/template/wall-e/llm/worker-route.js +43 -1
- package/template/wall-e/loops/brain-custom-writes.js +45 -0
- package/template/wall-e/mcp-protocol-contract.js +41 -1
- package/template/wall-e/mcp-server.js +13 -2
- package/template/wall-e/mcp-worker-schemas.js +20 -2
- package/template/wall-e/memory/ctm-context-client.js +25 -5
- package/template/wall-e/shared/release-schema.json +3 -2
- package/template/wall-e/shared/sqlite-write-lock.js +361 -223
- package/template/wall-e/shared/storage/contract.js +35 -3
- package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
- package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +6 -0
- package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +7 -2
- package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +7 -2
- package/template/wall-e/tools/local-tools.js +6 -4
- package/template/wall-e/workers/brain-owner-worker.js +9 -0
- package/template/wall-e/workers/coding-journal-writer-child.js +8 -1
- package/template/wall-e/workers/telemetry-worker.js +8 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-1 (docs/machine-pool-picker-pairing-dashboard-2026-10-24.html §5 D-10/D-11/D-15/D-16, §6.1,
|
|
4
|
+
// §8 WP-1, §9 S-1/S-2): the host-stats primitive. One instance per CTM process owns "what is this
|
|
5
|
+
// host doing right now", fed by the EXISTING 60 s resource tick (no new timer) and retained 24 h in
|
|
6
|
+
// this machine's own DB (D-11: the box is the truth; there is no hub-side time-series).
|
|
7
|
+
//
|
|
8
|
+
// Every number here is the machine describing itself, so the same sample serves the local picker
|
|
9
|
+
// ("This Mac") and a peer that dialed this box through the existing ?machine= proxy (D-12).
|
|
10
|
+
//
|
|
11
|
+
// Two rules shape the implementation:
|
|
12
|
+
// * `record()` NEVER throws. It runs from a lifecycle sweep, so a failing sub-read (a missing
|
|
13
|
+
// /proc, an absent nvidia-smi, a closed DB handle during teardown) must degrade to a `null`
|
|
14
|
+
// FIELD, never to an aborted sweep. Each read is individually guarded.
|
|
15
|
+
// * All I/O is ASYNC (main-thread lint): statfs/readFile are the promise forms and the only
|
|
16
|
+
// process launch is one async, 3 s-bounded nvidia-smi probe on linux.
|
|
17
|
+
//
|
|
18
|
+
// The injected `execFile` is called in METHOD position on the `io` bag below rather than as a bare
|
|
19
|
+
// `execFile(...)`. That is the same convention lib/session-resource-sampler.js uses for its own
|
|
20
|
+
// injected runner (`this.exec(...)`): bin/main-thread-lint.js's bare-launch rule reads a bare
|
|
21
|
+
// `execFile(` as a new, unledgered main-thread spawn site, and this WP does not own the exemption
|
|
22
|
+
// ledger. Behavior is identical — one async execFile with a fixed argv and a 3 s timeout.
|
|
23
|
+
|
|
24
|
+
const os = require('node:os');
|
|
25
|
+
const fs = require('node:fs');
|
|
26
|
+
const childProcess = require('node:child_process');
|
|
27
|
+
const { promisify } = require('node:util');
|
|
28
|
+
|
|
29
|
+
const DEFAULT_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
30
|
+
const DEFAULT_HISTORY_LIMIT = 1440;
|
|
31
|
+
|
|
32
|
+
// §6.1 (exact argv), observed on the DGX Spark's GB10: memory.used/memory.total are `[N/A]` on
|
|
33
|
+
// unified memory, so `unifiedMemory: true` is derived from exactly that.
|
|
34
|
+
const NVIDIA_SMI_ARGS = Object.freeze([
|
|
35
|
+
'--query-gpu=name,utilization.gpu,memory.used,memory.total,temperature.gpu,power.draw',
|
|
36
|
+
'--format=csv,noheader,nounits',
|
|
37
|
+
]);
|
|
38
|
+
const NVIDIA_SMI_TIMEOUT_MS = 3000;
|
|
39
|
+
|
|
40
|
+
const BYTES_PER_MIB = 1024 * 1024;
|
|
41
|
+
const BYTES_PER_KIB = 1024;
|
|
42
|
+
|
|
43
|
+
/** `null` for anything that is not a finite number — the one way a missing reading enters a field. */
|
|
44
|
+
function _numOrNull(value) {
|
|
45
|
+
// An absent/blank value must be NULL, never 0: `Number('')` is 0, and a fabricated 0 on a missing
|
|
46
|
+
// reading is exactly the "no total, no swap" lie this module refuses to tell about os.freemem().
|
|
47
|
+
const text = String(value == null ? '' : value).trim();
|
|
48
|
+
if (!text) return null;
|
|
49
|
+
const n = Number(text);
|
|
50
|
+
return Number.isFinite(n) ? n : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** nvidia-smi prints `[N/A]` (or `[Not Supported]`) for a metric the driver cannot read. */
|
|
54
|
+
function _smiOrNull(raw) {
|
|
55
|
+
const text = String(raw == null ? '' : raw).trim();
|
|
56
|
+
if (!text || /^\[.*\]$/.test(text) || /^N\/A$/i.test(text)) return null;
|
|
57
|
+
return _numOrNull(text);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** §6.1: `MemTotal − MemAvailable`, plus swap, from /proc/meminfo (linux only). */
|
|
61
|
+
function parseMeminfo(text) {
|
|
62
|
+
const fields = {};
|
|
63
|
+
for (const line of String(text || '').split('\n')) {
|
|
64
|
+
const match = /^([A-Za-z_()]+):\s*(\d+)\s*kB\s*$/.exec(line.trim());
|
|
65
|
+
if (match) fields[match[1]] = Number(match[2]);
|
|
66
|
+
}
|
|
67
|
+
const total = Number.isFinite(fields.MemTotal) ? fields.MemTotal * BYTES_PER_KIB : null;
|
|
68
|
+
const available = Number.isFinite(fields.MemAvailable) ? fields.MemAvailable * BYTES_PER_KIB : null;
|
|
69
|
+
const swapTotal = Number.isFinite(fields.SwapTotal) ? fields.SwapTotal * BYTES_PER_KIB : null;
|
|
70
|
+
const swapFree = Number.isFinite(fields.SwapFree) ? fields.SwapFree * BYTES_PER_KIB : null;
|
|
71
|
+
return {
|
|
72
|
+
memTotalBytes: total,
|
|
73
|
+
memUsedBytes: total != null && available != null ? Math.max(0, total - available) : null,
|
|
74
|
+
swapUsedBytes: swapTotal != null && swapFree != null ? Math.max(0, swapTotal - swapFree) : null,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** §6.1: first line only, comma separated, MiB for the two memory columns → bytes. */
|
|
79
|
+
function parseNvidiaSmi(stdout) {
|
|
80
|
+
const line = String(stdout || '').split('\n').map(part => part.trim()).find(part => part.length > 0);
|
|
81
|
+
if (!line) return null;
|
|
82
|
+
const parts = line.split(',').map(part => part.trim());
|
|
83
|
+
const memUsedMiB = _smiOrNull(parts[2]);
|
|
84
|
+
const memTotalMiB = _smiOrNull(parts[3]);
|
|
85
|
+
return {
|
|
86
|
+
name: String(parts[0] || '').slice(0, 120),
|
|
87
|
+
utilPct: _smiOrNull(parts[1]),
|
|
88
|
+
tempC: _smiOrNull(parts[4]),
|
|
89
|
+
powerW: _smiOrNull(parts[5]),
|
|
90
|
+
memUsedBytes: memUsedMiB == null ? null : Math.round(memUsedMiB * BYTES_PER_MIB),
|
|
91
|
+
memTotalBytes: memTotalMiB == null ? null : Math.round(memTotalMiB * BYTES_PER_MIB),
|
|
92
|
+
// P (unified memory): no readable memory split ⇒ the host memory above IS the GPU's.
|
|
93
|
+
unifiedMemory: memUsedMiB == null || memTotalMiB == null,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Cumulative cpu times, summed over cores — the delta between two calls is the util numerator. */
|
|
98
|
+
function cpuTimes(cpuList) {
|
|
99
|
+
let idle = 0;
|
|
100
|
+
let total = 0;
|
|
101
|
+
for (const cpu of Array.isArray(cpuList) ? cpuList : []) {
|
|
102
|
+
const times = cpu && cpu.times;
|
|
103
|
+
if (!times) continue;
|
|
104
|
+
const values = ['user', 'nice', 'sys', 'idle', 'irq'].map(key => Number(times[key]) || 0);
|
|
105
|
+
idle += values[3];
|
|
106
|
+
total += values.reduce((sum, value) => sum + value, 0);
|
|
107
|
+
}
|
|
108
|
+
return { idle, total };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function createHostStats({
|
|
112
|
+
db,
|
|
113
|
+
platform = process.platform,
|
|
114
|
+
now = () => Date.now(),
|
|
115
|
+
cpus = () => os.cpus(),
|
|
116
|
+
loadavg = () => os.loadavg(),
|
|
117
|
+
statfs = (p) => fs.promises.statfs(p),
|
|
118
|
+
readFile = (p) => fs.promises.readFile(p, 'utf8'),
|
|
119
|
+
execFile = promisify(childProcess.execFile),
|
|
120
|
+
diskPath = os.homedir(),
|
|
121
|
+
nvidiaSmiPath = '/usr/bin/nvidia-smi',
|
|
122
|
+
countTasks = () => ({ running: 0, needsYou: 0, idle: 0, workers: 0 }),
|
|
123
|
+
lastCreateAt = () => 0,
|
|
124
|
+
retentionMs = DEFAULT_RETENTION_MS,
|
|
125
|
+
} = {}) {
|
|
126
|
+
// Method-position calls only (see the header): the lint's bare-launch rule scans for a bare
|
|
127
|
+
// `execFile(`, not an injected runner reached through its owning bag.
|
|
128
|
+
const io = { statfs, readFile, execFile };
|
|
129
|
+
|
|
130
|
+
let lastCpu = null;
|
|
131
|
+
let latestSample = null;
|
|
132
|
+
// nvidia-smi presence is probed ONCE per process: the boot image of a box does not change under it.
|
|
133
|
+
let nvidiaSmiAvailability = null;
|
|
134
|
+
|
|
135
|
+
// D-10: a delta of two cumulative samples; the first tick has no previous, so it reports null
|
|
136
|
+
// rather than a fabricated 0.
|
|
137
|
+
function _cpuPct() {
|
|
138
|
+
let current = null;
|
|
139
|
+
try {
|
|
140
|
+
current = cpuTimes(cpus());
|
|
141
|
+
} catch {
|
|
142
|
+
return null; // expected: os.cpus() unavailable in a restricted sandbox
|
|
143
|
+
}
|
|
144
|
+
if (!current || current.total <= 0) return null;
|
|
145
|
+
const previous = lastCpu;
|
|
146
|
+
lastCpu = current;
|
|
147
|
+
if (!previous) return null;
|
|
148
|
+
const totalDelta = current.total - previous.total;
|
|
149
|
+
const idleDelta = current.idle - previous.idle;
|
|
150
|
+
if (!(totalDelta > 0)) return null;
|
|
151
|
+
return Math.max(0, Math.min(100, Math.round((1 - idleDelta / totalDelta) * 10000) / 100));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function _load() {
|
|
155
|
+
try {
|
|
156
|
+
const values = loadavg() || [];
|
|
157
|
+
return [Number(values[0]) || 0, Number(values[1]) || 0, Number(values[2]) || 0];
|
|
158
|
+
} catch {
|
|
159
|
+
return [0, 0, 0]; // expected: a platform without load-average support
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function _cores() {
|
|
164
|
+
try {
|
|
165
|
+
return Math.max(1, (cpus() || []).length);
|
|
166
|
+
} catch {
|
|
167
|
+
return 1; // expected: os.cpus() unavailable
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// D-10: darwin reads the sampler's vm_stat host fields; linux reads /proc/meminfo; anything else
|
|
172
|
+
// is null. `os.freemem()` is never used — it reports one number with no total and no swap.
|
|
173
|
+
async function _memory(snapshot) {
|
|
174
|
+
if (platform === 'darwin') {
|
|
175
|
+
const host = (snapshot && snapshot.host) || {};
|
|
176
|
+
return {
|
|
177
|
+
memUsedBytes: _numOrNull(host.memoryUsedBytes),
|
|
178
|
+
memTotalBytes: _numOrNull(host.memoryTotalBytes),
|
|
179
|
+
swapUsedBytes: _numOrNull(host.swapUsedBytes),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if (platform === 'linux') {
|
|
183
|
+
try {
|
|
184
|
+
return parseMeminfo(await io.readFile('/proc/meminfo'));
|
|
185
|
+
} catch {
|
|
186
|
+
return { memUsedBytes: null, memTotalBytes: null, swapUsedBytes: null }; // expected: no /proc in a container
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return { memUsedBytes: null, memTotalBytes: null, swapUsedBytes: null }; // expected: platform without a memory contract
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function _disk() {
|
|
193
|
+
try {
|
|
194
|
+
const stats = await io.statfs(diskPath);
|
|
195
|
+
const blockSize = Number(stats && stats.bsize) || 0;
|
|
196
|
+
if (!blockSize) return { diskFreeBytes: null, diskTotalBytes: null };
|
|
197
|
+
return {
|
|
198
|
+
// bavail (free to an unprivileged writer), not bfree — the number a user can act on.
|
|
199
|
+
diskFreeBytes: Math.max(0, (Number(stats.bavail) || 0) * blockSize),
|
|
200
|
+
diskTotalBytes: Math.max(0, (Number(stats.blocks) || 0) * blockSize),
|
|
201
|
+
};
|
|
202
|
+
} catch {
|
|
203
|
+
return { diskFreeBytes: null, diskTotalBytes: null }; // expected: home dir unreadable/statfs unsupported
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// D-10: linux only, one async execFile with a 3 s timeout. Absence of nvidia-smi is a normal
|
|
208
|
+
// answer (`gpu: null`), not an error, and the probe itself is cached per process.
|
|
209
|
+
async function _gpu() {
|
|
210
|
+
if (platform !== 'linux') return null; // expected: GPU probe is linux-only by design
|
|
211
|
+
if (nvidiaSmiAvailability === null) {
|
|
212
|
+
try {
|
|
213
|
+
await fs.promises.access(nvidiaSmiPath, fs.constants.X_OK);
|
|
214
|
+
nvidiaSmiAvailability = true;
|
|
215
|
+
} catch {
|
|
216
|
+
nvidiaSmiAvailability = false; // expected: no nvidia-smi on this box
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (!nvidiaSmiAvailability) return null;
|
|
220
|
+
try {
|
|
221
|
+
const { stdout } = await io.execFile(nvidiaSmiPath, [...NVIDIA_SMI_ARGS], { timeout: NVIDIA_SMI_TIMEOUT_MS });
|
|
222
|
+
return parseNvidiaSmi(stdout);
|
|
223
|
+
} catch {
|
|
224
|
+
return null; // expected: driver absent, or the query timed out on a wedged GPU
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// D-10: the sampler's own process tree rss, in bytes.
|
|
229
|
+
function _ctmRssBytes(snapshot) {
|
|
230
|
+
const rssMB = snapshot && snapshot.processTree ? Number(snapshot.processTree.rssMB) : NaN;
|
|
231
|
+
return Number.isFinite(rssMB) ? Math.max(0, Math.round(rssMB * BYTES_PER_MIB)) : null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// D-15: injected by server.js, computed from the session-status ledger. A throwing counter must
|
|
235
|
+
// not lose the rest of the sample.
|
|
236
|
+
function _tasks() {
|
|
237
|
+
try {
|
|
238
|
+
const counts = countTasks() || {};
|
|
239
|
+
return {
|
|
240
|
+
running: Number(counts.running) || 0,
|
|
241
|
+
needsYou: Number(counts.needsYou) || 0,
|
|
242
|
+
idle: Number(counts.idle) || 0,
|
|
243
|
+
workers: Number(counts.workers) || 0,
|
|
244
|
+
};
|
|
245
|
+
} catch {
|
|
246
|
+
return { running: 0, needsYou: 0, idle: 0, workers: 0 }; // expected: ledger unavailable during teardown
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// D-16: the newest session create on THIS machine, so Recent agrees on every primary.
|
|
251
|
+
function _lastUsedAt() {
|
|
252
|
+
try {
|
|
253
|
+
return Number(lastCreateAt()) || 0;
|
|
254
|
+
} catch {
|
|
255
|
+
return 0; // expected: session map unavailable during teardown
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* One sample per resource tick. Never throws and never rejects: every sub-read is guarded and a
|
|
261
|
+
* failed one contributes `null` (or the all-zero task bucket) to the sample.
|
|
262
|
+
*/
|
|
263
|
+
async function record(resourceSnapshot) {
|
|
264
|
+
const at = now();
|
|
265
|
+
const [memory, disk, gpu] = await Promise.all([
|
|
266
|
+
_memory(resourceSnapshot),
|
|
267
|
+
_disk(),
|
|
268
|
+
_gpu(),
|
|
269
|
+
]);
|
|
270
|
+
const load = _load();
|
|
271
|
+
const sample = {
|
|
272
|
+
at,
|
|
273
|
+
cpuPct: _cpuPct(),
|
|
274
|
+
load1: load[0],
|
|
275
|
+
load5: load[1],
|
|
276
|
+
load15: load[2],
|
|
277
|
+
cores: _cores(),
|
|
278
|
+
memUsedBytes: memory.memUsedBytes,
|
|
279
|
+
memTotalBytes: memory.memTotalBytes,
|
|
280
|
+
swapUsedBytes: memory.swapUsedBytes,
|
|
281
|
+
diskFreeBytes: disk.diskFreeBytes,
|
|
282
|
+
diskTotalBytes: disk.diskTotalBytes,
|
|
283
|
+
gpu,
|
|
284
|
+
ctmRssBytes: _ctmRssBytes(resourceSnapshot),
|
|
285
|
+
tasks: _tasks(),
|
|
286
|
+
lastUsedAt: _lastUsedAt(),
|
|
287
|
+
};
|
|
288
|
+
// Memory FIRST, then the write: a reader that pairs latest() with history() (lib/api/host-stats.js
|
|
289
|
+
// does, and asserts latest.at >= samples[last].at) must never see a stored row newer than the
|
|
290
|
+
// in-memory sample beside it. record()'s row is committed before this await resolves, so setting
|
|
291
|
+
// latestSample afterwards leaves exactly that window open.
|
|
292
|
+
latestSample = sample;
|
|
293
|
+
try {
|
|
294
|
+
const sampleJson = JSON.stringify(sample);
|
|
295
|
+
if (db && typeof db.recordHostSample === 'function') {
|
|
296
|
+
await db.recordHostSample({ at, sampleJson, purgeBeforeMs: at - retentionMs });
|
|
297
|
+
}
|
|
298
|
+
} catch {
|
|
299
|
+
// HANDLE: history is best-effort; the sample is still returned and held in memory, so a
|
|
300
|
+
// full/closed DB costs this box its time series, never the live read a picker makes.
|
|
301
|
+
// expected: the DB handle is closed or full (teardown, disk exhaustion)
|
|
302
|
+
}
|
|
303
|
+
return sample;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function latest() {
|
|
307
|
+
return latestSample;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Oldest first, the same order the wire shape documents (§6.1). */
|
|
311
|
+
async function history({ sinceMs = now() - retentionMs, limit = DEFAULT_HISTORY_LIMIT } = {}) {
|
|
312
|
+
if (!db || typeof db.listHostSamples !== 'function') return [];
|
|
313
|
+
const rows = await db.listHostSamples({ sinceMs, limit });
|
|
314
|
+
const samples = [];
|
|
315
|
+
for (const row of Array.isArray(rows) ? rows : []) {
|
|
316
|
+
try {
|
|
317
|
+
samples.push(JSON.parse(row.sample_json));
|
|
318
|
+
} catch {
|
|
319
|
+
// expected: a row written by an older/newer shape is skipped, never fatal
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return samples;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return { record, latest, history };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
module.exports = { createHostStats, parseMeminfo, parseNvidiaSmi, cpuTimes, NVIDIA_SMI_ARGS, DEFAULT_RETENTION_MS };
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-3 (stale-pty-host-evacuation design doc, §5 D-3, §6): the supervisor action
|
|
4
|
+
// for a host whose libuv thread pool is dead. The monitor report is a fact, the
|
|
5
|
+
// readiness ledger's recordThreadpool turns that fact into ONE verdict per
|
|
6
|
+
// stall episode (D-2), and this module is what acts on it:
|
|
7
|
+
//
|
|
8
|
+
// evacuate first, revive second, never keep.
|
|
9
|
+
//
|
|
10
|
+
// Evacuation is a single handoffHost(..., { reason: 'evacuate-threadpool' })
|
|
11
|
+
// attempt -- NEVER handoffHostWithRetry: the retry exists for transient spawn
|
|
12
|
+
// races on healthy hosts, and on a stalled host the second attempt just repeats
|
|
13
|
+
// the same wait (I-1: 14:18:17 -> 14:18:27 in the design doc's evidence). A
|
|
14
|
+
// successful handoff keeps the agents (their PTYs are inherited by the
|
|
15
|
+
// successor, which has a fresh pool); anything else falls through to the
|
|
16
|
+
// existing revive path, which kills the wedged host and lets its sessions cold
|
|
17
|
+
// resume.
|
|
18
|
+
//
|
|
19
|
+
// A stalled host is NEVER marked staleHost and NEVER left on the 10-minute retry
|
|
20
|
+
// (D-3): this module writes no stale flag and schedules nothing. It also never
|
|
21
|
+
// declares a host dead itself -- that is the orchestrator's `successor-lost-
|
|
22
|
+
// after-release` path, whose outcome is reported here as 'lost'.
|
|
23
|
+
//
|
|
24
|
+
// Dependency-injected exactly like lib/host-health-revive.js: no process globals,
|
|
25
|
+
// no server.js require, every collaborator passed in. Single-flight per hostId
|
|
26
|
+
// (a Map<hostId, Promise> that exists only for the duration of one active call),
|
|
27
|
+
// so two stall frames for the same host cannot start two handoffs.
|
|
28
|
+
|
|
29
|
+
function createEvacuateStalledHost({
|
|
30
|
+
manager,
|
|
31
|
+
handoffHost,
|
|
32
|
+
reviveUnresponsiveHost,
|
|
33
|
+
recordSessionDiagnostic,
|
|
34
|
+
hostHealth = null,
|
|
35
|
+
log = console.warn,
|
|
36
|
+
handoffOpts = {},
|
|
37
|
+
} = {}) {
|
|
38
|
+
const _inFlight = new Map(); // hostId -> Promise<outcome>
|
|
39
|
+
|
|
40
|
+
// The sessions this host is routing RIGHT NOW -- captured before the handoff,
|
|
41
|
+
// which is what re-points (or tears down) their adapters.
|
|
42
|
+
function routedSessionIds(hostId) {
|
|
43
|
+
const ids = [];
|
|
44
|
+
for (const [sessionId, adapter] of manager.hosts) {
|
|
45
|
+
if (adapter && adapter._hostId === hostId && !adapter.closed) ids.push(sessionId);
|
|
46
|
+
}
|
|
47
|
+
return ids;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function evacuateOnce(id, verdict) {
|
|
51
|
+
const sessionIds = routedSessionIds(id);
|
|
52
|
+
const oldestPendingMs = Number(verdict && verdict.oldestPendingMs) || 0;
|
|
53
|
+
let result = null;
|
|
54
|
+
let handoffReason = null;
|
|
55
|
+
try {
|
|
56
|
+
result = await handoffHost(manager, id, {
|
|
57
|
+
...handoffOpts, reason: 'evacuate-threadpool', hostHealth, recordSessionDiagnostic,
|
|
58
|
+
});
|
|
59
|
+
handoffReason = result && typeof result.reason === 'string' ? result.reason : null;
|
|
60
|
+
} catch (err) {
|
|
61
|
+
// handoffHost is documented never to reject (it returns { ok:false, attempted, reason });
|
|
62
|
+
// defend anyway and let the catch-all branch below decide (revive).
|
|
63
|
+
log(`[host-evacuate-error] host=${id} handoffHost threw: ${err?.message || err}`); // REPORT: treated as an unattempted failure below
|
|
64
|
+
result = null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let outcome;
|
|
68
|
+
if (result && result.ok === true) {
|
|
69
|
+
outcome = 'evacuated';
|
|
70
|
+
} else if (result && result.attempted === false && handoffReason === 'owner-recovery-pending') {
|
|
71
|
+
// A live owner is still recovering; nothing to do yet. Re-arm the ledger's
|
|
72
|
+
// episode evaluation (episode start is kept) so the next stalled frame
|
|
73
|
+
// decides again the moment the owner is done.
|
|
74
|
+
outcome = 'deferred';
|
|
75
|
+
hostHealth?.resetThreadpoolEpisode?.(id);
|
|
76
|
+
} else if (result && result.ok === false && handoffReason === 'successor-lost-after-release') {
|
|
77
|
+
// The orchestrator already declared this host dead and tore its adapters
|
|
78
|
+
// down; its sessions cold resume. Reviving would be a second kill.
|
|
79
|
+
outcome = 'lost';
|
|
80
|
+
} else {
|
|
81
|
+
// Any other ok:false (attempted or not) -- the old host cannot be replaced,
|
|
82
|
+
// so fall back to the terminal path: kill it and let the sessions resume.
|
|
83
|
+
let revived = null;
|
|
84
|
+
try {
|
|
85
|
+
revived = await reviveUnresponsiveHost(id, {
|
|
86
|
+
misses: 0,
|
|
87
|
+
stall: { op: 'threadpool', sessionId8: '', durationMs: oldestPendingMs },
|
|
88
|
+
});
|
|
89
|
+
} catch (err) {
|
|
90
|
+
log(`[host-evacuate-error] host=${id} reviveUnresponsiveHost threw: ${err?.message || err}`); // REPORT: outcome stays unknown below
|
|
91
|
+
}
|
|
92
|
+
if (revived === 'killed') outcome = 'revived';
|
|
93
|
+
else if (revived === 'capped') outcome = 'capped';
|
|
94
|
+
else outcome = 'unknown-host'; // 'unknown-host', a throw, or any unrecognized result
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
log(`[host-evacuate] host=${id} outcome=${outcome} reason=${handoffReason || '-'} sessions=${sessionIds.length} oldestPendingMs=${oldestPendingMs}`);
|
|
98
|
+
for (const sessionId of sessionIds) {
|
|
99
|
+
recordSessionDiagnostic?.(sessionId, 'host-evacuate', { hostId: id, outcome, reason: handoffReason, oldestPendingMs });
|
|
100
|
+
}
|
|
101
|
+
return outcome;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return async function evacuateStalledHost(hostId, verdict) {
|
|
105
|
+
const id = String(hostId || '');
|
|
106
|
+
if (!id) return 'unknown-host';
|
|
107
|
+
const existing = _inFlight.get(id);
|
|
108
|
+
if (existing) return existing;
|
|
109
|
+
const p = evacuateOnce(id, verdict).finally(() => { _inFlight.delete(id); });
|
|
110
|
+
_inFlight.set(id, p);
|
|
111
|
+
return p;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// WP-3 (design doc §5 D-4, §8 WP-3, §9 S-8): the BOOT half of the escalation.
|
|
116
|
+
// Reconcile has just adopted every surviving host, so each adopted host's own
|
|
117
|
+
// state-reply threadpool snapshot is fed to the same readiness ledger the live
|
|
118
|
+
// onThreadpoolReport path feeds, and the ids the ledger verdicts 'evacuate'
|
|
119
|
+
// right now are returned -- the caller passes that Set as
|
|
120
|
+
// runUpgradePendingHandoffs({ skipHostIds }) so a host already on the
|
|
121
|
+
// evacuation path is never ALSO handed off by the upgrade pass.
|
|
122
|
+
//
|
|
123
|
+
// A missing/shapeless snapshot is a pre-WP-3 host (its state reply carried no
|
|
124
|
+
// threadpool field): today's behaviour, untouched. A host the manager no longer
|
|
125
|
+
// routes is skipped too -- the ledger is keyed by hostId, so acting on a stale
|
|
126
|
+
// id could only arm a timer for a host nobody serves.
|
|
127
|
+
//
|
|
128
|
+
// A stalled snapshot that is not due YET (oldestPendingMs under the deadline)
|
|
129
|
+
// returns null and arms the ledger's own timer, whose onEvacuate fires later:
|
|
130
|
+
// that host is deliberately NOT in the skip set -- its upgrade handoff may
|
|
131
|
+
// still run, and the orchestrator's single-flight channel is per-host while a
|
|
132
|
+
// failed upgrade handoff already ends in stale/retry (D-4).
|
|
133
|
+
function evacuateStalledAdoptedHosts({
|
|
134
|
+
hostHealth,
|
|
135
|
+
evacuateStalledHost,
|
|
136
|
+
threadpoolByHostId,
|
|
137
|
+
ownsHostId,
|
|
138
|
+
log = console.warn,
|
|
139
|
+
} = {}) {
|
|
140
|
+
const skipHostIds = new Set();
|
|
141
|
+
if (!hostHealth || typeof hostHealth.recordThreadpool !== 'function') return skipHostIds;
|
|
142
|
+
if (!threadpoolByHostId || typeof threadpoolByHostId.entries !== 'function') return skipHostIds;
|
|
143
|
+
// REPORT disposition for both failure shapes (a rejection and a synchronous
|
|
144
|
+
// throw): boot must not block on -- or be aborted by -- one evacuation.
|
|
145
|
+
const report = (id, err) => log(`[host-evacuate-boot] host=${id} evacuation failed: ${err?.message || err}`);
|
|
146
|
+
for (const [hostId, snapshot] of threadpoolByHostId) {
|
|
147
|
+
const id = String(hostId || '');
|
|
148
|
+
const snap = snapshot && typeof snapshot === 'object' ? snapshot : null;
|
|
149
|
+
if (!id || !snap || (snap.state !== 'ok' && snap.state !== 'stalled')) continue; // pre-WP-3 host: no state change
|
|
150
|
+
if (typeof ownsHostId === 'function' && !ownsHostId(id)) continue; // no longer routed: nothing to evacuate
|
|
151
|
+
const verdict = hostHealth.recordThreadpool(id, snap);
|
|
152
|
+
if (!verdict) continue; // not due yet (the ledger's own timer may fire later) -- never skipped
|
|
153
|
+
skipHostIds.add(id);
|
|
154
|
+
try { Promise.resolve(evacuateStalledHost(id, verdict)).catch((err) => report(id, err)); }
|
|
155
|
+
catch (err) { report(id, err); } // REPORT: evacuateStalledHost never rejects, but a synchronous throw must not abort boot
|
|
156
|
+
}
|
|
157
|
+
return skipHostIds;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = { createEvacuateStalledHost, evacuateStalledAdoptedHosts };
|
|
@@ -69,6 +69,11 @@ function createHostThreadpoolMonitor({
|
|
|
69
69
|
setTimeoutFn = setTimeout,
|
|
70
70
|
clearTimeoutFn = clearTimeout,
|
|
71
71
|
onFrame = () => {},
|
|
72
|
+
// WP-0 (D-6/S-14): the frame's per-session view. A caller-provided provider
|
|
73
|
+
// (the pool wires the headless service's oldest in-flight ops here) is merged
|
|
74
|
+
// into EVERY emitted frame. Report-only: a throwing provider is swallowed --
|
|
75
|
+
// it can never change `state`, fail a probe, or stop a frame going out.
|
|
76
|
+
extraSnapshot = () => ({}),
|
|
72
77
|
// Injectable seam for unit tests only (defaults to the real module): lets a
|
|
73
78
|
// test simulate a request the real threadpool would never finish (init
|
|
74
79
|
// fires, destroy never does) without needing an actual stuck libuv worker.
|
|
@@ -134,7 +139,15 @@ function createHostThreadpoolMonitor({
|
|
|
134
139
|
}
|
|
135
140
|
|
|
136
141
|
function emitFrame(extra = {}) {
|
|
137
|
-
|
|
142
|
+
// WP-0: a report-only extraSnapshot provider is merged shallowly into every
|
|
143
|
+
// frame, but `state` is re-asserted from the monitor's own snapshot last, so
|
|
144
|
+
// a provider can never change what the detector decided.
|
|
145
|
+
let extraFields = {};
|
|
146
|
+
try {
|
|
147
|
+
extraFields = extraSnapshot() || {};
|
|
148
|
+
} catch { /* expected: a report-only provider must not break the frame */ }
|
|
149
|
+
const base = snapshot();
|
|
150
|
+
onFrame({ ...base, ...extraFields, ...extra, state: base.state });
|
|
138
151
|
}
|
|
139
152
|
|
|
140
153
|
function resetWindow() {
|