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
|
@@ -6,6 +6,7 @@ const http = require('http');
|
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const { spawn } = require('child_process');
|
|
8
8
|
const { resolveRealNode } = require('./real-node');
|
|
9
|
+
const { isWritableOnlyByOwner } = require('../../shared/owner-write-safety');
|
|
9
10
|
const {
|
|
10
11
|
assertNotReserved,
|
|
11
12
|
withClientConfigLock,
|
|
@@ -1339,16 +1340,15 @@ function _ensurePrivateDirectory(dirPath, homeDir) {
|
|
|
1339
1340
|
stat = fs.lstatSync(current, { bigint: true });
|
|
1340
1341
|
}
|
|
1341
1342
|
if (stat.isSymbolicLink() || !stat.isDirectory()) throw new ClientConfigError('CLIENT_CONFIG_OWNERSHIP_LOST');
|
|
1342
|
-
const mode = Number(stat.mode & 0o777n);
|
|
1343
1343
|
const owned = current === ownedRoot || current.startsWith(`${ownedRoot}${path.sep}`);
|
|
1344
1344
|
if (owned) {
|
|
1345
|
-
if (stat.uid !== BigInt(process.getuid()) || (
|
|
1345
|
+
if (stat.uid !== BigInt(process.getuid()) || !isWritableOnlyByOwner(stat)) {
|
|
1346
1346
|
throw new ClientConfigError('CLIENT_CONFIG_OWNERSHIP_LOST');
|
|
1347
1347
|
}
|
|
1348
1348
|
continue;
|
|
1349
1349
|
}
|
|
1350
1350
|
if (stat.uid === 0n) continue; // system ancestor above the owned boundary
|
|
1351
|
-
if (stat.uid === BigInt(process.getuid()) && (
|
|
1351
|
+
if (stat.uid === BigInt(process.getuid()) && isWritableOnlyByOwner(stat)) continue;
|
|
1352
1352
|
throw new ClientConfigError('CLIENT_CONFIG_OWNERSHIP_LOST');
|
|
1353
1353
|
}
|
|
1354
1354
|
return dirPath;
|
|
@@ -1418,15 +1418,14 @@ function _admitHistoryDirectoryChain(dirPath, homeDir) {
|
|
|
1418
1418
|
throw new ClientConfigError('CLIENT_CONFIG_OWNERSHIP_LOST');
|
|
1419
1419
|
}
|
|
1420
1420
|
if (stat.isSymbolicLink() || !stat.isDirectory()) throw new ClientConfigError('CLIENT_CONFIG_OWNERSHIP_LOST');
|
|
1421
|
-
const mode = Number(stat.mode & 0o777n);
|
|
1422
1421
|
if (current === ownedRoot || current.startsWith(`${ownedRoot}${path.sep}`)) {
|
|
1423
|
-
if (stat.uid !== BigInt(process.getuid()) || (
|
|
1422
|
+
if (stat.uid !== BigInt(process.getuid()) || !isWritableOnlyByOwner(stat)) {
|
|
1424
1423
|
throw new ClientConfigError('CLIENT_CONFIG_OWNERSHIP_LOST');
|
|
1425
1424
|
}
|
|
1426
1425
|
continue;
|
|
1427
1426
|
}
|
|
1428
1427
|
if (stat.uid === 0n) continue; // system ancestor above the owned boundary
|
|
1429
|
-
if (stat.uid === BigInt(process.getuid()) && (
|
|
1428
|
+
if (stat.uid === BigInt(process.getuid()) && isWritableOnlyByOwner(stat)) continue;
|
|
1430
1429
|
throw new ClientConfigError('CLIENT_CONFIG_OWNERSHIP_LOST');
|
|
1431
1430
|
}
|
|
1432
1431
|
return true;
|
|
@@ -48,6 +48,7 @@ const SCHEDULER_JOB_LANES = Object.freeze({
|
|
|
48
48
|
'telemetry:cleanup': 'maintenance',
|
|
49
49
|
'agent-benchmark': 'llm_background',
|
|
50
50
|
'weekly-eval-loop': 'llm_background',
|
|
51
|
+
'helper-rules-probe': 'maintenance', // WP-5b §6.8: brain-write-only daily tick (same class as brain-retention)
|
|
51
52
|
'deferred:embeddings-init': 'embedding',
|
|
52
53
|
'deferred:temporal-backfill': 'maintenance',
|
|
53
54
|
'deferred:skill-reference-sync': 'io',
|
|
@@ -244,6 +244,11 @@ async function runSchedulerJob(jobName, payload = {}) {
|
|
|
244
244
|
result = await _measurePhase(phases, 'weeklyEval.run', () => runWeeklyEval(brain, { runAgentLoop }));
|
|
245
245
|
break;
|
|
246
246
|
}
|
|
247
|
+
case 'helper-rules-probe':
|
|
248
|
+
// WP-5b §6.8: this worker's brain handle is read-only, so the helper-rules record's
|
|
249
|
+
// daily probe crosses to the owner through _brainWrite (the same split brain-retention uses).
|
|
250
|
+
result = await _measurePhase(phases, 'helperRules.dailyTick', () => _brainWrite('helperRulesDailyTick'));
|
|
251
|
+
break;
|
|
247
252
|
case 'deferred:embeddings-init':
|
|
248
253
|
result = await _measurePhase(phases, 'embeddings.init', async () => {
|
|
249
254
|
await require('../embeddings').init();
|
|
@@ -347,6 +352,7 @@ function supportedSchedulerWorkerJobs() {
|
|
|
347
352
|
'telemetry:cleanup',
|
|
348
353
|
'agent-benchmark',
|
|
349
354
|
'weekly-eval-loop',
|
|
355
|
+
'helper-rules-probe',
|
|
350
356
|
'deferred:embeddings-init',
|
|
351
357
|
'deferred:temporal-backfill',
|
|
352
358
|
'deferred:skill-reference-sync',
|
|
@@ -14,6 +14,33 @@ const { createMigrationRunner, pragmaVersionStore } = require('../shared/db-migr
|
|
|
14
14
|
|
|
15
15
|
const TELEMETRY_DB_FILENAME = 'telemetry.db';
|
|
16
16
|
|
|
17
|
+
// WP-1 (blog-view-counts, design §5 D-2, lead review correction): the two
|
|
18
|
+
// additive blog-view tables (a running total per article and a bounded set of
|
|
19
|
+
// short-lived per-tab receipts) are DDL, so the walle-telemetry migration owner
|
|
20
|
+
// declares them HERE — the baseline creates them and migration 2 adds them to a
|
|
21
|
+
// v1 database, so a fresh install and an upgrade land on the same schema. The
|
|
22
|
+
// read/write path (lib/blog-view-store.js) owns only queries, never DDL.
|
|
23
|
+
const BLOG_VIEW_SCHEMA_SQL = `
|
|
24
|
+
CREATE TABLE IF NOT EXISTS blog_view_counts (
|
|
25
|
+
slug TEXT PRIMARY KEY,
|
|
26
|
+
views INTEGER NOT NULL DEFAULT 0 CHECK (views >= 0)
|
|
27
|
+
);
|
|
28
|
+
CREATE TABLE IF NOT EXISTS blog_view_receipts (
|
|
29
|
+
slug TEXT NOT NULL,
|
|
30
|
+
token TEXT NOT NULL,
|
|
31
|
+
expires_at_ms INTEGER NOT NULL,
|
|
32
|
+
PRIMARY KEY (slug, token)
|
|
33
|
+
);
|
|
34
|
+
CREATE INDEX IF NOT EXISTS idx_blog_view_expiry ON blog_view_receipts (expires_at_ms);
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
// Idempotent: every statement is IF NOT EXISTS, so re-running the baseline or
|
|
38
|
+
// migration 2 on an already-migrated database is a no-op. Called only from the
|
|
39
|
+
// two runner-owned spec entries below — never from a request path.
|
|
40
|
+
function createBlogViewTables(db) {
|
|
41
|
+
db.exec(BLOG_VIEW_SCHEMA_SQL);
|
|
42
|
+
}
|
|
43
|
+
|
|
17
44
|
function _telemetryColumnExists(db, table, column) {
|
|
18
45
|
return db.prepare(`PRAGMA table_info(${table})`).all().some((row) => row.name === column);
|
|
19
46
|
}
|
|
@@ -157,6 +184,9 @@ function telemetryBaseline(db) {
|
|
|
157
184
|
PRIMARY KEY(identity_key, content_hash)
|
|
158
185
|
);
|
|
159
186
|
`);
|
|
187
|
+
// WP-1 (blog-view-counts): durable per-article view counters live in this same
|
|
188
|
+
// database (design §5 D-2 — reuse the existing volume, no new open, no new job).
|
|
189
|
+
createBlogViewTables(db);
|
|
160
190
|
}
|
|
161
191
|
|
|
162
192
|
const TELEMETRY_MIGRATIONS = {
|
|
@@ -173,6 +203,11 @@ const TELEMETRY_MIGRATIONS = {
|
|
|
173
203
|
_telemetryAddColumn(d, 'feedback_reports', 'ip_first_seen', 'TEXT');
|
|
174
204
|
},
|
|
175
205
|
},
|
|
206
|
+
2: {
|
|
207
|
+
label: 'blog-view-tables',
|
|
208
|
+
rerunSafe: true, // CREATE TABLE/INDEX IF NOT EXISTS, safe to re-execute
|
|
209
|
+
up: (d) => createBlogViewTables(d),
|
|
210
|
+
},
|
|
176
211
|
};
|
|
177
212
|
|
|
178
213
|
/** Builds a fresh createMigrationRunner() instance for walle-telemetry. Callers that need
|
|
@@ -188,8 +223,10 @@ function createTelemetryMigrationRunner() {
|
|
|
188
223
|
}
|
|
189
224
|
|
|
190
225
|
module.exports = {
|
|
226
|
+
BLOG_VIEW_SCHEMA_SQL,
|
|
191
227
|
TELEMETRY_DB_FILENAME,
|
|
192
228
|
telemetryBaseline,
|
|
193
229
|
TELEMETRY_MIGRATIONS,
|
|
230
|
+
createBlogViewTables,
|
|
194
231
|
createTelemetryMigrationRunner,
|
|
195
232
|
};
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
const path = require('node:path');
|
|
4
4
|
const { fork } = require('node:child_process');
|
|
5
5
|
const { decodeIpcResult } = require('./ipc-result-codec');
|
|
6
|
+
// WP-1 (blog-view-counts): the ONE public counter path and its body cap have to
|
|
7
|
+
// be recognised on THIS side of the IPC boundary — the 1024-byte limit is
|
|
8
|
+
// enforced before a byte crosses it, and the counter's peer identity travels in
|
|
9
|
+
// its own field. Pure data/predicates, no env read at require time.
|
|
10
|
+
const { BLOG_VIEW_MAX_BODY_BYTES, isBlogViewsPath } = require('./blog-view-policy');
|
|
6
11
|
|
|
7
12
|
const MAX_REQUEST_BYTES = 2 * 1024 * 1024;
|
|
8
13
|
const MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
@@ -15,6 +20,38 @@ function codedError(code, message) {
|
|
|
15
20
|
return error;
|
|
16
21
|
}
|
|
17
22
|
|
|
23
|
+
// The TCP peer, as THIS process sees it — a transport fact, never a header. Only
|
|
24
|
+
// the parent can observe it (the worker's own socket is a pipe), so it is carried
|
|
25
|
+
// to the worker in its own IPC field and re-attached there as
|
|
26
|
+
// req.socket.remoteAddress. Absent/blank is passed through as '' so the worker
|
|
27
|
+
// can leave the request exactly as it was before this feature.
|
|
28
|
+
function readClientAddress(req) {
|
|
29
|
+
const address = req && req.socket ? req.socket.remoteAddress : undefined;
|
|
30
|
+
return typeof address === 'string' ? address : '';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// The counter's own refusal shape, written by this process when it refuses a
|
|
34
|
+
// request BEFORE the IPC hop (oversized body) or because the hop failed. Same
|
|
35
|
+
// headers and the same four generic messages the handler in the worker uses, so a
|
|
36
|
+
// caller cannot tell where the refusal came from, and no internal detail (a driver
|
|
37
|
+
// message, a timeout code, a stack) ever reaches the wire.
|
|
38
|
+
//
|
|
39
|
+
// `extraHeaders` exists for the one refusal that answers while the client may
|
|
40
|
+
// still be uploading (the 413): it asks for the connection to be closed by the
|
|
41
|
+
// normal HTTP means instead of tearing the socket down mid-body.
|
|
42
|
+
function sendCounterFailure(res, status, error, extraHeaders) {
|
|
43
|
+
if (res.headersSent) return;
|
|
44
|
+
const payload = JSON.stringify({ error });
|
|
45
|
+
res.writeHead(status, {
|
|
46
|
+
'Content-Type': 'application/json',
|
|
47
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
48
|
+
'Cache-Control': 'no-store',
|
|
49
|
+
'Vary': 'Origin',
|
|
50
|
+
...(extraHeaders || {}),
|
|
51
|
+
});
|
|
52
|
+
res.end(payload);
|
|
53
|
+
}
|
|
54
|
+
|
|
18
55
|
class TelemetryProcessClient {
|
|
19
56
|
constructor(options = {}) {
|
|
20
57
|
this.workerPath = options.workerPath || path.join(__dirname, '..', 'workers', 'telemetry-worker.js');
|
|
@@ -155,6 +192,11 @@ class TelemetryProcessClient {
|
|
|
155
192
|
}
|
|
156
193
|
|
|
157
194
|
proxy(req, res, url) {
|
|
195
|
+
// The public blog counter shares this transport but not its limits: its body
|
|
196
|
+
// cap (1024 B) is enforced here, on the parent side, BEFORE any IPC work —
|
|
197
|
+
// the generic telemetry routes keep the 2 MiB bound unchanged.
|
|
198
|
+
const counterRoute = isBlogViewsPath(url.pathname);
|
|
199
|
+
const maxRequestBytes = counterRoute ? BLOG_VIEW_MAX_BODY_BYTES : MAX_REQUEST_BYTES;
|
|
158
200
|
const chunks = [];
|
|
159
201
|
let bytes = 0;
|
|
160
202
|
let rejected = false;
|
|
@@ -171,12 +213,27 @@ class TelemetryProcessClient {
|
|
|
171
213
|
'x-forwarded-for': req.headers['x-forwarded-for'],
|
|
172
214
|
'x-real-ip': req.headers['x-real-ip'],
|
|
173
215
|
origin: req.headers.origin,
|
|
216
|
+
// WP-1: the counter's bot filter reads the UA and its limiter prefers
|
|
217
|
+
// Fly-Client-IP when (and only when) the child really is a Fly app.
|
|
218
|
+
// Forwarded for this one route only, so the existing telemetry routes
|
|
219
|
+
// keep answering from exactly the headers they saw before.
|
|
220
|
+
...(counterRoute ? {
|
|
221
|
+
'user-agent': req.headers['user-agent'],
|
|
222
|
+
'fly-client-ip': req.headers['fly-client-ip'],
|
|
223
|
+
} : {}),
|
|
174
224
|
},
|
|
225
|
+
...(counterRoute ? { clientAddress: readClientAddress(req) } : {}),
|
|
175
226
|
body: chunks.length ? Buffer.concat(chunks).toString('base64') : '',
|
|
176
227
|
});
|
|
177
228
|
res.writeHead(response.status || 200, response.headers || {});
|
|
178
229
|
res.end(response.body || '');
|
|
179
230
|
} catch (error) {
|
|
231
|
+
if (counterRoute) {
|
|
232
|
+
// A transport/queue/timeout failure is a 503 with the counter's generic
|
|
233
|
+
// body — never the internal error code or message.
|
|
234
|
+
sendCounterFailure(res, 503, 'View count unavailable');
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
180
237
|
const status = error.code === 'WALL_E_TELEMETRY_REQUEST_TOO_LARGE' ? 413 : 503;
|
|
181
238
|
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
182
239
|
res.end(JSON.stringify({ error: error.message, code: error.code, retry_after_ms: 500 }));
|
|
@@ -185,8 +242,20 @@ class TelemetryProcessClient {
|
|
|
185
242
|
req.on('data', chunk => {
|
|
186
243
|
if (rejected) return;
|
|
187
244
|
bytes += chunk.length;
|
|
188
|
-
if (bytes >
|
|
245
|
+
if (bytes > maxRequestBytes) {
|
|
189
246
|
rejected = true;
|
|
247
|
+
if (counterRoute) {
|
|
248
|
+
// Over-cap counter body: the refusal is written and the connection is
|
|
249
|
+
// ended by the protocol, NEVER by destroying the socket out from under
|
|
250
|
+
// the response. `req.destroy()` here raced the JSON write — a client on
|
|
251
|
+
// a real connection could see a TCP reset instead of the complete
|
|
252
|
+
// 'payload_too_large' document. Connection: close is enough: the
|
|
253
|
+
// `rejected` flag above already stops every further chunk from being
|
|
254
|
+
// buffered, so the unread remainder cannot grow this process, and Node
|
|
255
|
+
// tears the socket down itself once the response is flushed.
|
|
256
|
+
sendCounterFailure(res, 413, 'payload_too_large', { Connection: 'close' });
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
190
259
|
res.writeHead(413, { 'Content-Type': 'application/json' });
|
|
191
260
|
res.end(JSON.stringify({ error: 'Telemetry request exceeds 2 MiB' }));
|
|
192
261
|
req.destroy();
|
|
@@ -198,6 +267,12 @@ class TelemetryProcessClient {
|
|
|
198
267
|
req.on('error', error => {
|
|
199
268
|
if (rejected) return;
|
|
200
269
|
rejected = true;
|
|
270
|
+
if (counterRoute) {
|
|
271
|
+
// An aborted upload is a client problem: the counter's malformed-payload
|
|
272
|
+
// answer, with no detail about the transport failure.
|
|
273
|
+
sendCounterFailure(res, 400, 'invalid_payload');
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
201
276
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
202
277
|
res.end(JSON.stringify({ error: error.message }));
|
|
203
278
|
});
|
|
@@ -523,6 +523,10 @@ module.exports = {
|
|
|
523
523
|
shouldFallThroughClass,
|
|
524
524
|
_configFromRow,
|
|
525
525
|
_hasEnvKey,
|
|
526
|
+
// The provider-type -> credential-env-var map _hasEnvKey reads. Exported so a fixture can
|
|
527
|
+
// clear exactly the keys that decide whether an env-only provider candidate exists (the D-2
|
|
528
|
+
// seed reads the same map through routing-policy.js envCandidate).
|
|
529
|
+
_ENV_KEY_BY_TYPE,
|
|
526
530
|
TRANSIENT_CLASSES,
|
|
527
531
|
FALLTHROUGH_CLASSES,
|
|
528
532
|
},
|
|
@@ -19,7 +19,7 @@ const modelFactsCore = require('../lib/model-facts-core');
|
|
|
19
19
|
const { resolveReasoningControl, intersectReasoningControls } = require('./reasoning-control');
|
|
20
20
|
const { policyRankFor: routeRankPolicyStage } = require('./route-rank');
|
|
21
21
|
// D-5: llm/worker-route.js is a cycle-free leaf (node builtins + ./portkey + ../../shared/*).
|
|
22
|
-
const { reasoningParamsForRoute, routeReasoningLevels } = require('./worker-route');
|
|
22
|
+
const { reasoningParamsForRoute, routeReasoningLevels, routeModelReasoningLevels } = require('./worker-route');
|
|
23
23
|
const {
|
|
24
24
|
KNOWN_MAKER_TYPES,
|
|
25
25
|
canonicalModelId,
|
|
@@ -273,8 +273,15 @@ function groupSessionRows(models, providerById, live) {
|
|
|
273
273
|
// the registry row; a row without it (join absent, non-identity row)
|
|
274
274
|
// carries ''.
|
|
275
275
|
region: String(row.identity_region || ''),
|
|
276
|
-
// D-
|
|
277
|
-
|
|
276
|
+
// D-1/D-2 (§6): the route's CLI effort levels for THIS route's model id,
|
|
277
|
+
// off the one producer (llm/worker-route.js routeModelReasoningLevels) --
|
|
278
|
+
// an anthropic-messages passthrough route publishes the Claude matrix for
|
|
279
|
+
// `modelId` instead of the empty chat-translate fragment map.
|
|
280
|
+
reasoning_levels: routeModelReasoningLevels({
|
|
281
|
+
type: String(provider.type || '').toLowerCase(),
|
|
282
|
+
upstream_apis: Array.isArray(provider.upstream_apis) ? provider.upstream_apis : [],
|
|
283
|
+
model_id: modelId,
|
|
284
|
+
}),
|
|
278
285
|
reasoning_control: resolveReasoningControl({ providerType, modelId }),
|
|
279
286
|
};
|
|
280
287
|
|
|
@@ -442,8 +449,10 @@ function normalizeSessionProvider(provider = {}) {
|
|
|
442
449
|
// is routable at all. getProviderWithMeta (http/model-admin.js) stamps
|
|
443
450
|
// both on every provider row before it reaches the catalog build.
|
|
444
451
|
upstream_apis: Array.isArray(provider.upstream_apis) ? provider.upstream_apis.slice() : [],
|
|
445
|
-
// D-5 (§6.3): the CLI effort levels this
|
|
452
|
+
// D-5 (§6.3): the CLI effort levels this provider can carry upstream — the one
|
|
446
453
|
// definition (llm/worker-route.js routeReasoningLevels) over its upstream APIs.
|
|
454
|
+
// Route-wide and model-less on purpose (D-3): the per-model value the picker
|
|
455
|
+
// reads lives on each catalog route instead (groupSessionRows above).
|
|
447
456
|
reasoning_levels: routeReasoningLevels(reasoningParamsForRoute(String(provider.type || '').toLowerCase(), Array.isArray(provider.upstream_apis) ? provider.upstream_apis : [])),
|
|
448
457
|
routable: provider.routable !== false,
|
|
449
458
|
// D-7 (§6.3): why a routable:false provider cannot serve a worker — the
|
|
@@ -35,6 +35,8 @@ const TASK_ROLE = Object.freeze({
|
|
|
35
35
|
'approval-review': 'background',
|
|
36
36
|
'approval-ai-refinement': 'background',
|
|
37
37
|
'approval-scenario-classify': 'background',
|
|
38
|
+
'lead-wait-watch': 'background', // WP-1 worker-wait watcher tick (pinned to the worker's own route:model; the role is never consulted)
|
|
39
|
+
'explore-handoff-grade': 'background', // WP-6 spot-check grader (pinned to helper_rules.grader_model; the role is never consulted)
|
|
38
40
|
// user-triggered, single completion
|
|
39
41
|
'meeting-notes': 'chat',
|
|
40
42
|
'dictation-refine': 'chat',
|
|
@@ -261,6 +263,10 @@ async function completeForRole({
|
|
|
261
263
|
const role = resolution.role;
|
|
262
264
|
const ledgerWrite = _writeLedger(brain, {
|
|
263
265
|
source, feature, requestId, providerType, providerId, modelId, usage: response?.usage, latencyMs,
|
|
266
|
+
// WP-4b (D-9, S-19): the caller's sessionId (CTM sends the watched worker's ctm_session_id for
|
|
267
|
+
// a `lead-wait-watch` tick) is stored on the row, so watcher spend is readable per cell. A
|
|
268
|
+
// caller that passes nothing keeps today's NULL.
|
|
269
|
+
sessionId,
|
|
264
270
|
stopReason: response?.stopReason || null,
|
|
265
271
|
metadata: { taskType: task, role, tier, pinSource: resolution.source, fallbackFrom },
|
|
266
272
|
});
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Portkey HTTP transport: one bounded, redirect-safe, retrying JSON fetch for every
|
|
3
|
+
// request in the Portkey path (sync, discovery, catalog, resolver). Extracted verbatim
|
|
4
|
+
// from llm/portkey-sync.js (WP-0 of docs/codex-routed-fork-cold-restore-guard-2026-10-22.html)
|
|
5
|
+
// so that file stays under its size ceiling; the contract (D-1 transient classes,
|
|
6
|
+
// D-2 retry engine + test seam, manual redirects) is unchanged.
|
|
7
|
+
const { redactSecrets } = require('./provider-error');
|
|
8
|
+
const { createRetryPolicy } = require('../lib/kernel/retry-policy');
|
|
9
|
+
|
|
10
|
+
// D-2: TOTAL attempts for a transient failure, with the backoff between them.
|
|
11
|
+
const PORTKEY_REQUEST_ATTEMPTS = 3;
|
|
12
|
+
const PORTKEY_RETRY_DELAYS_MS = [1000, 3000];
|
|
13
|
+
const PORTKEY_RETRY_AFTER_MAX_MS = 30 * 1000;
|
|
14
|
+
|
|
15
|
+
// WP-1 review R6: this is the ONLY request budget in the Portkey path, and it
|
|
16
|
+
// is per REQUEST, not per host — callers that must bound a whole host (the
|
|
17
|
+
// resolver) also cap the number of pages. There is deliberately no
|
|
18
|
+
// AbortController+setTimeout fallback when `AbortSignal.timeout` is missing:
|
|
19
|
+
// that would be a recovery timer, which `stabilization-lint` requires to be
|
|
20
|
+
// registered in release/ownership-registry.json and which this feature's
|
|
21
|
+
// design forbids. `AbortSignal.timeout` is Node >= 17.3 and this repo pins
|
|
22
|
+
// v25.2.1, so the fallback branch below is unreachable in practice; if it is
|
|
23
|
+
// ever reached the request is unbounded and the caller's page cap is what
|
|
24
|
+
// stops it.
|
|
25
|
+
function withTimeout(timeoutMs) {
|
|
26
|
+
if (typeof AbortSignal !== 'undefined' && AbortSignal.timeout && Number.isFinite(timeoutMs)) {
|
|
27
|
+
return { signal: AbortSignal.timeout(timeoutMs) };
|
|
28
|
+
}
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function safeJson(response) {
|
|
33
|
+
try { return await response.json(); } catch { return null; /* expected: non-JSON or empty error body; callers fall back to a generic message */ }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// D-2 test seam: when set, createRetryPolicy uses it instead of the kernel's
|
|
37
|
+
// own registered timer. null (default) means "use the kernel sleep".
|
|
38
|
+
let _retrySleepOverride = null;
|
|
39
|
+
|
|
40
|
+
function _setRetrySleepForTest(fn) {
|
|
41
|
+
_retrySleepOverride = typeof fn === 'function' ? fn : null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// D-1: TRANSIENT iff no response at all (status 0) or 408/429/5xx; every other
|
|
45
|
+
// status (3xx/401/403/AB03/033/other 4xx) is DEFINITIVE and never retried.
|
|
46
|
+
function isTransientStatus(status) {
|
|
47
|
+
if (status === 0) return true;
|
|
48
|
+
const code = Number(status);
|
|
49
|
+
return code === 408 || code === 429 || (code >= 500 && code <= 599);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// D-2: honour Retry-After only as integer seconds ≤ 30 s; else fixed backoff.
|
|
53
|
+
function retryAfterMs(response) {
|
|
54
|
+
if (!response || typeof response.headers?.get !== 'function') return null;
|
|
55
|
+
const raw = response.headers.get('retry-after');
|
|
56
|
+
if (raw == null) return null;
|
|
57
|
+
const seconds = Number(String(raw).trim());
|
|
58
|
+
if (!Number.isFinite(seconds) || seconds < 0) return null;
|
|
59
|
+
const ms = seconds * 1000;
|
|
60
|
+
return ms <= PORTKEY_RETRY_AFTER_MAX_MS ? ms : null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function errorCodeFromBody(body) {
|
|
64
|
+
if (!body || typeof body !== 'object') return '';
|
|
65
|
+
if (body.data && body.data.errorCode) return String(body.data.errorCode);
|
|
66
|
+
if (body.error && body.error.code) return String(body.error.code);
|
|
67
|
+
return '';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function errorMessageFromBody(body, fallback) {
|
|
71
|
+
if (body && typeof body === 'object') {
|
|
72
|
+
if (body.data && body.data.message) return String(body.data.message);
|
|
73
|
+
if (body.message) return String(body.message);
|
|
74
|
+
}
|
|
75
|
+
return fallback;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// `redirect: 'manual'` is the DEFAULT here, for EVERY request in the Portkey
|
|
79
|
+
// path — not only the resolver's probe (final review, finding #3). Every one
|
|
80
|
+
// carries the full gateway key in `x-portkey-api-key`, and undici keeps that
|
|
81
|
+
// header across a cross-origin redirect, so following a 3xx would hand the key
|
|
82
|
+
// to a host the user never named (the axios GHSA-jr5f-v2jv-69x6 class). With
|
|
83
|
+
// `manual` a 3xx is not followed and `response.ok` is false, so it surfaces as
|
|
84
|
+
// `{ok:false, status:3xx}` — a verdict, never a second request.
|
|
85
|
+
// `fetchInit` (WP-1): extra init the CALLER owns — today the resolver's
|
|
86
|
+
// per-host AbortController signal. Spread last so it replaces withTimeout's.
|
|
87
|
+
// D-2: ONE attempt; returns today's ok/failure shape plus the response's
|
|
88
|
+
// Retry-After (null when the fetch threw — a transport failure has no headers).
|
|
89
|
+
async function fetchJsonOnce(fetchFn, url, { headers, timeoutMs, fallbackMessage, fetchInit } = {}) {
|
|
90
|
+
try {
|
|
91
|
+
const response = await fetchFn(url, { headers, redirect: 'manual', ...withTimeout(timeoutMs), ...(fetchInit || {}) });
|
|
92
|
+
const body = await safeJson(response);
|
|
93
|
+
if (response.ok) return { result: { ok: true, status: response.status, body }, retryAfter: null };
|
|
94
|
+
return {
|
|
95
|
+
result: {
|
|
96
|
+
ok: false,
|
|
97
|
+
status: response.status,
|
|
98
|
+
code: errorCodeFromBody(body),
|
|
99
|
+
message: redactSecrets(errorMessageFromBody(body, fallbackMessage(response.status))),
|
|
100
|
+
},
|
|
101
|
+
retryAfter: retryAfterMs(response),
|
|
102
|
+
};
|
|
103
|
+
} catch (err) {
|
|
104
|
+
// D-1: no HTTP response at all (status 0 — transport/timeout) is transient.
|
|
105
|
+
const message = err && err.message ? err.message : String(err);
|
|
106
|
+
return { result: { ok: false, status: 0, code: '', message: redactSecrets(message) }, retryAfter: null };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// D-2: transient failures are retried through wall-e's ONE retry engine
|
|
111
|
+
// (lib/kernel/retry-policy.js); a caller owning its own abort signal (the
|
|
112
|
+
// resolver's single probe) is single-shot and never retries.
|
|
113
|
+
async function fetchJsonOrError(fetchFn, url, opts = {}) {
|
|
114
|
+
if (opts.fetchInit) {
|
|
115
|
+
const { result } = await fetchJsonOnce(fetchFn, url, opts);
|
|
116
|
+
if (result.ok) return result;
|
|
117
|
+
const transient = isTransientStatus(result.status);
|
|
118
|
+
return transient ? { ...result, transient, attempts: 1 } : { ...result, transient };
|
|
119
|
+
}
|
|
120
|
+
let nextDelay = null;
|
|
121
|
+
let lastAttempt = 1;
|
|
122
|
+
const policyOptions = {
|
|
123
|
+
maxAttempts: PORTKEY_REQUEST_ATTEMPTS,
|
|
124
|
+
jitter: 0,
|
|
125
|
+
delayFn: (attempt) => nextDelay ?? PORTKEY_RETRY_DELAYS_MS[attempt - 1],
|
|
126
|
+
classify: (err) => (err && err.portkeyTransient ? 'transient' : 'permanent'),
|
|
127
|
+
layer: 'portkey-sync',
|
|
128
|
+
operation: 'fetch',
|
|
129
|
+
};
|
|
130
|
+
// Only override the kernel's own registered timer when a test seam is set.
|
|
131
|
+
if (_retrySleepOverride) policyOptions.sleep = (ms) => _retrySleepOverride(ms);
|
|
132
|
+
const policy = createRetryPolicy(policyOptions);
|
|
133
|
+
try {
|
|
134
|
+
return await policy.run(async (attempt) => {
|
|
135
|
+
const { result, retryAfter } = await fetchJsonOnce(fetchFn, url, opts);
|
|
136
|
+
if (result.ok || !isTransientStatus(result.status)) {
|
|
137
|
+
return result.ok ? result : { ...result, transient: false };
|
|
138
|
+
}
|
|
139
|
+
nextDelay = retryAfter;
|
|
140
|
+
lastAttempt = attempt;
|
|
141
|
+
throw Object.assign(new Error(result.message), { portkeyTransient: true, result });
|
|
142
|
+
});
|
|
143
|
+
} catch (err) {
|
|
144
|
+
// Only our transient sentinel is converted; anything else propagates.
|
|
145
|
+
if (!err || !err.portkeyTransient) throw err;
|
|
146
|
+
return { ...err.result, transient: true, attempts: lastAttempt };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = { fetchJsonOrError, fetchJsonOnce, safeJson, isTransientStatus, _setRetrySleepForTest };
|