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
|
@@ -185,6 +185,13 @@
|
|
|
185
185
|
"human",
|
|
186
186
|
false
|
|
187
187
|
],
|
|
188
|
+
[
|
|
189
|
+
"DELETE",
|
|
190
|
+
"/api/sessions/:p1/image-drafts",
|
|
191
|
+
"respond",
|
|
192
|
+
"human",
|
|
193
|
+
false
|
|
194
|
+
],
|
|
188
195
|
[
|
|
189
196
|
"DELETE",
|
|
190
197
|
"/api/sessions/:p1/image-drafts/:p2",
|
|
@@ -3280,6 +3287,13 @@
|
|
|
3280
3287
|
"human",
|
|
3281
3288
|
false
|
|
3282
3289
|
],
|
|
3290
|
+
[
|
|
3291
|
+
"POST",
|
|
3292
|
+
"/api/providers/:p1/login/code",
|
|
3293
|
+
"admin",
|
|
3294
|
+
"human",
|
|
3295
|
+
false
|
|
3296
|
+
],
|
|
3283
3297
|
[
|
|
3284
3298
|
"POST",
|
|
3285
3299
|
"/api/queues",
|
|
@@ -900,9 +900,17 @@ function createPaintedRowRecorder(options) {
|
|
|
900
900
|
// The floor never exceeds top: after a move back down (or rows revealed above the stretch start) nothing below top is servable.
|
|
901
901
|
// R-F2: `below` = how many rows the app holds BELOW its screen, measured at the screen BOTTOM (`bot`) so a pin change cannot move it.
|
|
902
902
|
// `top` is the first content row, so it moves when the pin count changes; the screen's bottom edge does not.
|
|
903
|
+
// Clock-free tests (D-2/D-4, WP-6): `samples` (the same monotone count as `stats().samples`, +1 on EVERY observe at the top of
|
|
904
|
+
// observeSample -- a sample that changes nothing counts too) and `hold` ride this object, so they travel wherever the recorder's
|
|
905
|
+
// state travels (the frame's `painted` field, mirror-status). `hold` describes the samples the recorder has REJECTED and not
|
|
906
|
+
// committed -- the same fact as the `held` flag beside it (non-null exactly when `held` is true: the samples since the last
|
|
907
|
+
// adopted screen, cleared by an adoption and by the seal that opens the next stretch). `kind` is the last rejection's kind
|
|
908
|
+
// ('ambiguous' | 'inPlace'), `rows` how many samples are outstanding, `top` the index they would have been adopted at. A reader
|
|
909
|
+
// can then wait on "this write has been sampled" (and on the hold it produced) instead of sleeping a window.
|
|
903
910
|
function state() {
|
|
904
911
|
const f = docFloor();
|
|
905
|
-
|
|
912
|
+
const heldLast = holds > 0 && held.length ? held[held.length - 1] : null;
|
|
913
|
+
return { epoch, top, pinned, floor: f, recorded: top - f, held: holds > 0, hold: heldLast ? { kind: heldLast.kind || null, rows: held.length, top } : null, samples: stats.samples, revision: revisionSeq, revisedFrom, breaks: breaks.map((b) => ({ at: b.at, reason: b.reason })), below: botMax === null ? 0 : Math.max(0, botMax - bot) }; // R-F2: rows below the screen bottom edge (0 at rest, after a seal or a jump)
|
|
906
914
|
}
|
|
907
915
|
|
|
908
916
|
// The lowest index revised by an observe after revision `seq`; null when none. When the bounded event list no longer reaches back to
|
|
@@ -22,12 +22,27 @@ const SUBMISSION_SOURCES = new Set([
|
|
|
22
22
|
'skill',
|
|
23
23
|
'unknown',
|
|
24
24
|
]);
|
|
25
|
+
// WP-0 (queue-delivery-recovery): the closed vocabulary of recovery actions a
|
|
26
|
+
// client may offer next to a status frame. It is additive to the historical
|
|
27
|
+
// `retryable` boolean, which stays exactly as it was (including its legacy
|
|
28
|
+
// `retryable: true` => `retry` meaning).
|
|
29
|
+
const RECOVERY_ACTIONS = new Set(['retry', 'resend', 'review', 'none']);
|
|
25
30
|
|
|
26
31
|
function normalizeSubmissionSource(source) {
|
|
27
32
|
const clean = String(source || '').trim().toLowerCase();
|
|
28
33
|
return SUBMISSION_SOURCES.has(clean) ? clean : 'unknown';
|
|
29
34
|
}
|
|
30
35
|
|
|
36
|
+
// Missing or unrecognized -> 'none' ("nothing for you to do"), except the
|
|
37
|
+
// historical `retryable: true` verdict, which has always meant "resending the
|
|
38
|
+
// same text can succeed once the condition clears" -> 'retry'. A value is never
|
|
39
|
+
// invented from a code or a message string.
|
|
40
|
+
function normalizeRecoveryAction(value, retryable) {
|
|
41
|
+
const clean = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
42
|
+
if (RECOVERY_ACTIONS.has(clean)) return clean;
|
|
43
|
+
return retryable === true ? 'retry' : 'none';
|
|
44
|
+
}
|
|
45
|
+
|
|
31
46
|
function publicSubmissionStatus(remoteStatus, result = {}) {
|
|
32
47
|
const knownStatus = Object.prototype.hasOwnProperty.call(TERMINAL_STATUS_MAP, String(remoteStatus || ''));
|
|
33
48
|
const mapped = knownStatus ? TERMINAL_STATUS_MAP[String(remoteStatus || '')] : { status: 'needs_attention' };
|
|
@@ -37,12 +52,16 @@ function publicSubmissionStatus(remoteStatus, result = {}) {
|
|
|
37
52
|
reason: String(result.reason || (knownStatus ? '' : 'unknown_status')),
|
|
38
53
|
message: String(result.message || ''),
|
|
39
54
|
source: String(result.source || ''),
|
|
40
|
-
// Whether
|
|
55
|
+
// Whether retrying the SAME admitted request can succeed once the condition clears
|
|
41
56
|
// (agent busy, another prompt in flight, session still restoring) versus
|
|
42
57
|
// needing the user to act first (answer an approval, revive the session).
|
|
43
58
|
// Carried here so EVERY status frame agrees — the direct WS reply and the
|
|
44
59
|
// broadcast both reach the client and the later one overwrites the record.
|
|
45
60
|
retryable: result.retryable === true,
|
|
61
|
+
// Additive recovery verdict, same reasoning as `retryable`: the direct reply,
|
|
62
|
+
// the broadcast, the persisted result and the stored projection all run
|
|
63
|
+
// through this one function so they cannot disagree.
|
|
64
|
+
recoveryAction: normalizeRecoveryAction(result.recoveryAction, result.retryable === true),
|
|
46
65
|
};
|
|
47
66
|
}
|
|
48
67
|
|
|
@@ -151,6 +170,7 @@ module.exports = {
|
|
|
151
170
|
createSubmissionCounters,
|
|
152
171
|
executeTerminalWriteSequence,
|
|
153
172
|
isConfirmedSubmissionRecord,
|
|
173
|
+
normalizeRecoveryAction,
|
|
154
174
|
normalizeSubmissionSource,
|
|
155
175
|
publicSubmissionStatus,
|
|
156
176
|
runtimeResultForSubmissionStatus,
|
|
@@ -4,6 +4,8 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const crypto = require('crypto');
|
|
6
6
|
|
|
7
|
+
const { createLoginCapture, isLoopbackUrl, loopbackPortOf } = require('./provider-login-capture');
|
|
8
|
+
|
|
7
9
|
const {
|
|
8
10
|
browserShimPath,
|
|
9
11
|
loginTabSpec,
|
|
@@ -46,6 +48,45 @@ const DEFAULT_PROVIDERS = Object.freeze({
|
|
|
46
48
|
},
|
|
47
49
|
});
|
|
48
50
|
|
|
51
|
+
// WP-1 (design §6, D-6): the ONE accepted shape of a returned sign-in code —
|
|
52
|
+
// `<code>#<state>`, both halves drawn from the vendor's own URL-safe alphabet. The
|
|
53
|
+
// code half is capped at 512 chars and the state half at 256 so a hostile body cannot
|
|
54
|
+
// smuggle a multi-kilobyte line into the tab's stdin.
|
|
55
|
+
const LOGIN_CODE_RE = /^([A-Za-z0-9._~-]{8,512})#([A-Za-z0-9._~-]{8,256})$/;
|
|
56
|
+
// The whole input is bounded before anything else runs: the route reads at most 16 KiB,
|
|
57
|
+
// and no legitimate paste (a bare code or a full callback URL with scopes) approaches this.
|
|
58
|
+
const LOGIN_CODE_MAX_CHARS = 4096;
|
|
59
|
+
|
|
60
|
+
// D-6 input normalisation, pure and exported for the unit table (AC-1a/AC-1b).
|
|
61
|
+
// Shape A `<code>#<state>` -> { code, state }
|
|
62
|
+
// Shape B an http(s) URL whose query has `code`/`state` -> { code, state }
|
|
63
|
+
// A loopback callback URL is its OWN refusal: its code was issued for the automatic
|
|
64
|
+
// `http://localhost:<port>/callback` redirect, while a pasted code is always exchanged
|
|
65
|
+
// with the manual redirect — the CLI would answer "Invalid code" for a code the card
|
|
66
|
+
// could have refused up front (§5 D-5).
|
|
67
|
+
function normalizeLoginCode(raw) {
|
|
68
|
+
const text = String(raw == null ? '' : raw).trim();
|
|
69
|
+
if (!text || text.length > LOGIN_CODE_MAX_CHARS) return { error: 'code_format_invalid' };
|
|
70
|
+
const direct = LOGIN_CODE_RE.exec(text);
|
|
71
|
+
if (direct) return { code: direct[1], state: direct[2] };
|
|
72
|
+
let parsed = null;
|
|
73
|
+
try {
|
|
74
|
+
parsed = new URL(text);
|
|
75
|
+
} catch {
|
|
76
|
+
/* expected: the trimmed string is neither a `code#state` pair nor a URL */
|
|
77
|
+
}
|
|
78
|
+
if (!parsed || (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')) {
|
|
79
|
+
return { error: 'code_format_invalid' };
|
|
80
|
+
}
|
|
81
|
+
const code = parsed.searchParams.get('code') || '';
|
|
82
|
+
const state = parsed.searchParams.get('state') || '';
|
|
83
|
+
if (!code || !state) return { error: 'code_format_invalid' };
|
|
84
|
+
if (isLoopbackUrl(text)) return { error: 'code_from_loopback_redirect' };
|
|
85
|
+
const split = LOGIN_CODE_RE.exec(`${code}#${state}`);
|
|
86
|
+
if (split) return { code: split[1], state: split[2] };
|
|
87
|
+
return { error: 'code_format_invalid' };
|
|
88
|
+
}
|
|
89
|
+
|
|
49
90
|
function _nowIso() {
|
|
50
91
|
return new Date().toISOString();
|
|
51
92
|
}
|
|
@@ -105,7 +146,13 @@ class ProviderLoginBroker {
|
|
|
105
146
|
// cache invalidation (lib/coding-agent-auth.invalidate).
|
|
106
147
|
this.resolveCli = options.resolveCli || null;
|
|
107
148
|
this.invalidateAccount = options.invalidateAccount || null;
|
|
149
|
+
// WP-1 (D-4): the ONE PTY write primitive a returned sign-in code travels through —
|
|
150
|
+
// server.js wires `(session, data) => _writeLifecyclePty(session, data, 'provider-login-code')`.
|
|
151
|
+
// Null in a pure unit context, where submitCode then answers 503 rather than writing.
|
|
152
|
+
this.writeToLoginTab = options.writeToLoginTab || null;
|
|
108
153
|
this.subscriptions = new Map();
|
|
154
|
+
// WP-0: one output capture subscriber per open flow, keyed by sessionId, disposed on exit.
|
|
155
|
+
this.captures = new Map();
|
|
109
156
|
}
|
|
110
157
|
|
|
111
158
|
// One probeAll for the whole state read (the lib caches per agent for CACHE_MS).
|
|
@@ -140,9 +187,12 @@ class ProviderLoginBroker {
|
|
|
140
187
|
_provider(id) {
|
|
141
188
|
const provider = DEFAULT_PROVIDERS[String(id || '').trim().toLowerCase()];
|
|
142
189
|
if (!provider) return null;
|
|
190
|
+
// WP-0 test seam (D-10): CTM_PROVIDER_LOGIN_<ID>_CLI pins the exact CLI path the login
|
|
191
|
+
// shell `exec`s, so a fixture CLI never depends on the agent-cli-cache/PATH order.
|
|
192
|
+
const cliOverride = String(this.env[`CTM_PROVIDER_LOGIN_${provider.id.toUpperCase()}_CLI`] || '').trim();
|
|
143
193
|
return {
|
|
144
194
|
...provider,
|
|
145
|
-
cli: this._resolveCli((provider.command || [])[0]),
|
|
195
|
+
cli: cliOverride || this._resolveCli((provider.command || [])[0]),
|
|
146
196
|
credentialPath: this.env[`CTM_PROVIDER_LOGIN_${provider.id.toUpperCase()}_CREDENTIAL_PATH`] || provider.credentialPath,
|
|
147
197
|
};
|
|
148
198
|
}
|
|
@@ -198,19 +248,27 @@ class ProviderLoginBroker {
|
|
|
198
248
|
|
|
199
249
|
_savedFlow(providerId, saved, browserUrl) {
|
|
200
250
|
if (!saved.flow) {
|
|
201
|
-
|
|
251
|
+
// D-1: a loopback-only sink row (the CLI's automatic URL, which no remote browser can
|
|
252
|
+
// complete) must not fabricate an awaiting_user flow out of thin air.
|
|
253
|
+
const synthetic = browserUrl && !isLoopbackUrl(browserUrl.url) ? browserUrl : null;
|
|
254
|
+
return synthetic ? {
|
|
202
255
|
id: `${providerId}-browser-url`,
|
|
203
256
|
sessionId: '',
|
|
204
257
|
status: 'awaiting_user',
|
|
205
|
-
verificationUrl:
|
|
258
|
+
verificationUrl: synthetic.url,
|
|
206
259
|
userCode: '',
|
|
207
|
-
|
|
208
|
-
|
|
260
|
+
state: '',
|
|
261
|
+
callbackPort: loopbackPortOf(synthetic.url),
|
|
262
|
+
codeEntry: '',
|
|
263
|
+
verificationUrlSource: '',
|
|
264
|
+
startedAt: synthetic.at,
|
|
265
|
+
updatedAt: synthetic.at,
|
|
209
266
|
exitedAt: '',
|
|
210
267
|
exitCode: null,
|
|
211
268
|
signal: '',
|
|
212
269
|
error: '',
|
|
213
270
|
outputPreview: '',
|
|
271
|
+
codeSubmittedAt: '',
|
|
214
272
|
} : null;
|
|
215
273
|
}
|
|
216
274
|
// The sink keeps the LAST URL per provider across flows, so a URL recorded before
|
|
@@ -218,12 +276,23 @@ class ProviderLoginBroker {
|
|
|
218
276
|
// card would offer a dead "Open sign-in page" before the CLI printed anything.
|
|
219
277
|
const startedAt = saved.flow.startedAt || '';
|
|
220
278
|
const urlForThisFlow = browserUrl && (!startedAt || String(browserUrl.at || '') >= startedAt) ? browserUrl : null;
|
|
279
|
+
const sinkUrl = urlForThisFlow ? String(urlForThisFlow.url || '') : '';
|
|
280
|
+
// D-1: a loopback sink URL (own host or redirect_uri) never becomes the link — it is the
|
|
281
|
+
// port evidence the card explains the manual paste with. A non-loopback sink URL keeps
|
|
282
|
+
// today's back-fill behaviour for the case where the capture found nothing.
|
|
283
|
+
const loopbackSink = sinkUrl ? isLoopbackUrl(sinkUrl) : false;
|
|
221
284
|
const flow = {
|
|
222
285
|
id: saved.flow.id,
|
|
223
286
|
sessionId: saved.flow.sessionId || '',
|
|
224
287
|
status: saved.flow.status,
|
|
225
|
-
verificationUrl: saved.flow.verificationUrl ||
|
|
288
|
+
verificationUrl: saved.flow.verificationUrl || (loopbackSink ? '' : sinkUrl),
|
|
226
289
|
userCode: saved.flow.userCode || '',
|
|
290
|
+
state: saved.flow.state || '',
|
|
291
|
+
callbackPort: typeof saved.flow.callbackPort === 'number'
|
|
292
|
+
? saved.flow.callbackPort
|
|
293
|
+
: (loopbackSink ? loopbackPortOf(sinkUrl) : null),
|
|
294
|
+
codeEntry: saved.flow.codeEntry || '',
|
|
295
|
+
verificationUrlSource: saved.flow.verificationUrlSource || '',
|
|
227
296
|
startedAt,
|
|
228
297
|
updatedAt: saved.flow.updatedAt || urlForThisFlow?.at || '',
|
|
229
298
|
exitedAt: saved.flow.exitedAt || '',
|
|
@@ -231,10 +300,13 @@ class ProviderLoginBroker {
|
|
|
231
300
|
signal: saved.flow.signal || '',
|
|
232
301
|
error: saved.flow.error || '',
|
|
233
302
|
outputPreview: _redact(saved.flow.outputPreview || '').slice(-1200),
|
|
303
|
+
// WP-1: WHEN the user handed the code back. A timestamp only — the code and its
|
|
304
|
+
// state are never persisted (§5 D-5).
|
|
305
|
+
codeSubmittedAt: saved.flow.codeSubmittedAt || '',
|
|
234
306
|
};
|
|
235
|
-
// A
|
|
236
|
-
// the truth about the flow's status until its exit handler settles it.
|
|
237
|
-
if (flow.verificationUrl && OPEN_FLOW_STATUSES.includes(flow.status) && flow.status !== 'awaiting_user') {
|
|
307
|
+
// A captured link or a Codex code is proof the CLI is waiting for the human — the tab
|
|
308
|
+
// tells the truth about the flow's status until its exit handler settles it.
|
|
309
|
+
if ((flow.verificationUrl || flow.userCode) && OPEN_FLOW_STATUSES.includes(flow.status) && flow.status !== 'awaiting_user') {
|
|
238
310
|
flow.status = 'awaiting_user';
|
|
239
311
|
}
|
|
240
312
|
return flow;
|
|
@@ -305,6 +377,11 @@ class ProviderLoginBroker {
|
|
|
305
377
|
if (settled) return;
|
|
306
378
|
settled = true;
|
|
307
379
|
this.subscriptions.delete(sessionId);
|
|
380
|
+
const capture = this.captures.get(sessionId);
|
|
381
|
+
if (capture) {
|
|
382
|
+
if (typeof capture.dispose === 'function') capture.dispose();
|
|
383
|
+
this.captures.delete(sessionId);
|
|
384
|
+
}
|
|
308
385
|
const next = this._readState();
|
|
309
386
|
const current = next[provider.id]?.flow;
|
|
310
387
|
// Superseded (a newer flow for this provider) or already final: nothing to say.
|
|
@@ -335,6 +412,50 @@ class ProviderLoginBroker {
|
|
|
335
412
|
this._writeState(next);
|
|
336
413
|
}
|
|
337
414
|
|
|
415
|
+
// WP-0 (D-2): the login tab's OWN output is where the CLI prints the link and the code.
|
|
416
|
+
// One subscriber per flow, seeded with the session's flushed tail (the CLI may have
|
|
417
|
+
// printed before createSession() resolved — §5 OQ-1), disposed by _watchLoginTab.settle.
|
|
418
|
+
_attachCapture(provider, session, flow) {
|
|
419
|
+
const host = session && (session.ptyProcess || session.pty);
|
|
420
|
+
if (!host || typeof host.onData !== 'function') return;
|
|
421
|
+
let subscription = null;
|
|
422
|
+
try {
|
|
423
|
+
const capture = createLoginCapture({
|
|
424
|
+
providerId: provider.id,
|
|
425
|
+
seed: String(session._textTail || ''),
|
|
426
|
+
onUpdate: (fields) => this._recordCaptured(provider, flow.sessionId, fields),
|
|
427
|
+
});
|
|
428
|
+
subscription = host.onData((data) => capture.feed(data));
|
|
429
|
+
} catch {
|
|
430
|
+
/* expected: a host that refuses a data subscriber — the flow still settles on exit */
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
this.captures.set(flow.sessionId, subscription);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Merges what the capture froze into the persisted flow. A flow that is gone, superseded
|
|
437
|
+
// or already final is left alone: capture is evidence, never a state resurrection.
|
|
438
|
+
_recordCaptured(provider, sessionId, fields) {
|
|
439
|
+
const state = this._readState();
|
|
440
|
+
const current = state[provider.id]?.flow;
|
|
441
|
+
if (!current || current.sessionId !== sessionId) return;
|
|
442
|
+
if (current.status === 'completed' || current.status === 'failed') return;
|
|
443
|
+
const next = { ...current };
|
|
444
|
+
if (fields.verificationUrl && !isLoopbackUrl(fields.verificationUrl)) {
|
|
445
|
+
next.verificationUrl = fields.verificationUrl;
|
|
446
|
+
next.verificationUrlSource = 'cli-print';
|
|
447
|
+
}
|
|
448
|
+
if (fields.state) next.state = fields.state;
|
|
449
|
+
if (fields.userCode) next.userCode = fields.userCode;
|
|
450
|
+
if (typeof fields.callbackPort === 'number') next.callbackPort = fields.callbackPort;
|
|
451
|
+
if (fields.codeEntry) next.codeEntry = fields.codeEntry;
|
|
452
|
+
if ((next.status === 'starting' || next.status === 'running') && (next.verificationUrl || next.userCode)) {
|
|
453
|
+
next.status = 'awaiting_user';
|
|
454
|
+
}
|
|
455
|
+
next.updatedAt = _nowIso();
|
|
456
|
+
this._writeFlow(provider, next);
|
|
457
|
+
}
|
|
458
|
+
|
|
338
459
|
// POST /api/providers/:id/login — opens the provider's login tab.
|
|
339
460
|
async start(id, opts = {}) {
|
|
340
461
|
const provider = this._provider(id);
|
|
@@ -369,6 +490,10 @@ class ProviderLoginBroker {
|
|
|
369
490
|
status: 'starting',
|
|
370
491
|
verificationUrl: '',
|
|
371
492
|
userCode: '',
|
|
493
|
+
state: '',
|
|
494
|
+
callbackPort: null,
|
|
495
|
+
codeEntry: '',
|
|
496
|
+
verificationUrlSource: '',
|
|
372
497
|
startedAt,
|
|
373
498
|
updatedAt: startedAt,
|
|
374
499
|
exitedAt: '',
|
|
@@ -376,6 +501,7 @@ class ProviderLoginBroker {
|
|
|
376
501
|
signal: '',
|
|
377
502
|
error: '',
|
|
378
503
|
outputPreview: '',
|
|
504
|
+
codeSubmittedAt: '',
|
|
379
505
|
};
|
|
380
506
|
this._writeFlow(provider, flow);
|
|
381
507
|
|
|
@@ -407,8 +533,64 @@ class ProviderLoginBroker {
|
|
|
407
533
|
const running = { ...flow, status: 'running', updatedAt: _nowIso() };
|
|
408
534
|
this._writeFlow(provider, running);
|
|
409
535
|
this._watchLoginTab(provider, session, sessionId);
|
|
536
|
+
this._attachCapture(provider, session, running);
|
|
410
537
|
return { ok: true, provider: provider.id, sessionId, flow: running };
|
|
411
538
|
}
|
|
539
|
+
|
|
540
|
+
// POST /api/providers/:id/login/code — hands a code the user pasted on the card back to
|
|
541
|
+
// the flow's OWN login tab (§5 D-4/D-5). Every refusal is checked BEFORE the write, and the
|
|
542
|
+
// write itself happens EXACTLY ONCE, here: `<code>#<state>\r` through the injected
|
|
543
|
+
// `writeToLoginTab` (server.js binds it to `_writeLifecyclePty(session, data,
|
|
544
|
+
// 'provider-login-code')`, so the user-recency clocks stay untouched). No keystroke replay, no
|
|
545
|
+
// retry, no write to any other session, and the code/state never enter a response, a log line
|
|
546
|
+
// or the flow record.
|
|
547
|
+
async submitCode(id, { code } = {}) {
|
|
548
|
+
const provider = this._provider(id);
|
|
549
|
+
if (!provider) return { ok: false, status: 404, error: 'provider_not_found' };
|
|
550
|
+
const saved = this._readState()[provider.id] || {};
|
|
551
|
+
const flow = saved.flow || null;
|
|
552
|
+
if (!flow || !OPEN_FLOW_STATUSES.includes(flow.status)) {
|
|
553
|
+
return { ok: false, status: 409, error: 'provider_login_not_open', provider: provider.id };
|
|
554
|
+
}
|
|
555
|
+
const session = this.getSession(flow.sessionId);
|
|
556
|
+
if (!loginTabAlive(session)) {
|
|
557
|
+
return { ok: false, status: 409, error: 'login_tab_gone', provider: provider.id, sessionId: flow.sessionId };
|
|
558
|
+
}
|
|
559
|
+
// D-3: only a provider whose code travels browser -> card -> tab can accept one. Codex
|
|
560
|
+
// ('browser') and a not-yet-captured flow ('') are refused with their own answer.
|
|
561
|
+
if (flow.codeEntry !== 'paste') {
|
|
562
|
+
return {
|
|
563
|
+
ok: false,
|
|
564
|
+
status: 409,
|
|
565
|
+
error: 'code_entry_not_supported',
|
|
566
|
+
provider: provider.id,
|
|
567
|
+
codeEntry: flow.codeEntry || '',
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
const normalized = normalizeLoginCode(code);
|
|
571
|
+
if (normalized.error) return { ok: false, status: 400, error: normalized.error };
|
|
572
|
+
if (flow.state && normalized.state !== flow.state) {
|
|
573
|
+
return { ok: false, status: 400, error: 'code_state_mismatch' };
|
|
574
|
+
}
|
|
575
|
+
if (!this.writeToLoginTab) {
|
|
576
|
+
return { ok: false, status: 503, error: 'provider_login_unavailable', provider: provider.id };
|
|
577
|
+
}
|
|
578
|
+
const written = this.writeToLoginTab(session, `${normalized.code}#${normalized.state}\r`);
|
|
579
|
+
if (!written || written.posted === false) {
|
|
580
|
+
return { ok: false, status: 409, error: 'login_tab_gone', provider: provider.id, sessionId: flow.sessionId };
|
|
581
|
+
}
|
|
582
|
+
const next = { ...flow };
|
|
583
|
+
next.updatedAt = _nowIso();
|
|
584
|
+
next.codeSubmittedAt = next.updatedAt;
|
|
585
|
+
this._writeFlow(provider, next);
|
|
586
|
+
return {
|
|
587
|
+
ok: true,
|
|
588
|
+
status: 200,
|
|
589
|
+
provider: provider.id,
|
|
590
|
+
sessionId: flow.sessionId,
|
|
591
|
+
flow: this._savedFlow(provider.id, { flow: next }, null),
|
|
592
|
+
};
|
|
593
|
+
}
|
|
412
594
|
}
|
|
413
595
|
|
|
414
596
|
module.exports = {
|
|
@@ -416,5 +598,6 @@ module.exports = {
|
|
|
416
598
|
DEFAULT_PROVIDERS,
|
|
417
599
|
OPEN_FLOW_STATUSES,
|
|
418
600
|
browserShimPath,
|
|
601
|
+
normalizeLoginCode,
|
|
419
602
|
_deriveStatus,
|
|
420
603
|
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-0 (design docs/remote-agent-sign-in-from-hub-browser-2026-10-25.html §5 D-1/D-2/D-3,
|
|
4
|
+
// §6, §9 S-1): what a provider sign-in CLI PRINTS on its own PTY is the truth for the
|
|
5
|
+
// card's link and code. `claude auth login` prints its manual authorize URL (the automatic
|
|
6
|
+
// one carries a loopback `redirect_uri` and is useless to a remote browser); `codex login
|
|
7
|
+
// --device-auth` prints a device URL plus a one-time code and never calls a browser.
|
|
8
|
+
//
|
|
9
|
+
// Pure by contract: no fs, no timers, no process.env -- the rolling window is the only
|
|
10
|
+
// state, so a URL split across PTY chunks still matches once both halves are in it.
|
|
11
|
+
//
|
|
12
|
+
// A loopback URL (`localhost` / `127.0.0.1` / `::1`, as the URL's own host or as its
|
|
13
|
+
// `redirect_uri` host) is NEVER a verification URL (D-1); it contributes the callback port
|
|
14
|
+
// only, which the card uses to explain why the automatic link cannot sign a remote user in.
|
|
15
|
+
|
|
16
|
+
const { stripAnsi } = require('./login-tripwire');
|
|
17
|
+
|
|
18
|
+
const CAPTURE_WINDOW_CHARS = 8192;
|
|
19
|
+
const AUTHORIZE_URL_RE = /https:\/\/[^\s"'<>]+\/oauth\/authorize\?[^\s"'<>]+/g; // host-agnostic on purpose
|
|
20
|
+
const DEVICE_URL_RE = /https:\/\/[^\s"'<>]+\/codex\/device\b[^\s"'<>]*/;
|
|
21
|
+
// The code must be followed by whitespace (Codex prints a newline after it): a PTY chunk that
|
|
22
|
+
// ends inside the code (`A12B-C`) must not yield its first group as a complete code.
|
|
23
|
+
const DEVICE_CODE_LINE_RE = /Enter this one-time code[^\n]*\n\s*([A-Z0-9]{4,}(?:-[A-Z0-9]{4,})*)(?=\s)/;
|
|
24
|
+
const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1', '[::1]']);
|
|
25
|
+
const CAPTURE_FIELDS = Object.freeze(['verificationUrl', 'state', 'userCode', 'callbackPort', 'codeEntry']);
|
|
26
|
+
|
|
27
|
+
function _urlOf(value) {
|
|
28
|
+
try {
|
|
29
|
+
return new URL(String(value));
|
|
30
|
+
} catch {
|
|
31
|
+
/* expected: a relative or truncated string is not a URL */
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// `new URL('[::1]')` keeps the brackets in hostname; the set carries both spellings, and the
|
|
37
|
+
// comparison is case-insensitive (hostnames are).
|
|
38
|
+
function _hostOf(parsed) {
|
|
39
|
+
return String(parsed.hostname || '').replace(/^\[|\]$/g, '').toLowerCase();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function _isLoopbackHost(parsed) {
|
|
43
|
+
return parsed ? LOOPBACK_HOSTS.has(_hostOf(parsed)) : false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isLoopbackUrl(value) {
|
|
47
|
+
const parsed = _urlOf(value);
|
|
48
|
+
if (!parsed) return false;
|
|
49
|
+
if (_isLoopbackHost(parsed)) return true;
|
|
50
|
+
const redirect = parsed.searchParams.get('redirect_uri');
|
|
51
|
+
return redirect ? _isLoopbackHost(_urlOf(redirect)) : false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function loopbackPortOf(value) {
|
|
55
|
+
const parsed = _urlOf(value);
|
|
56
|
+
if (!parsed) return null;
|
|
57
|
+
const redirect = parsed.searchParams.get('redirect_uri');
|
|
58
|
+
const redirectParsed = redirect ? _urlOf(redirect) : null;
|
|
59
|
+
if (_isLoopbackHost(redirectParsed) && redirectParsed.port) return Number(redirectParsed.port);
|
|
60
|
+
if (_isLoopbackHost(parsed) && parsed.port) return Number(parsed.port);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function _stateParamOf(value) {
|
|
65
|
+
const parsed = _urlOf(value);
|
|
66
|
+
return parsed ? (parsed.searchParams.get('state') || '') : '';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// A match that runs to the very end of what we have may be truncated by the chunk boundary
|
|
70
|
+
// (a PTY chunk is arbitrary bytes, and stripAnsi cannot know an OSC hyperlink finished).
|
|
71
|
+
// feed() re-parses the window on every chunk, so waiting one chunk for the rest loses
|
|
72
|
+
// nothing -- and it keeps a half-received URL from being frozen as the card's link.
|
|
73
|
+
function _isCompleteMatch(match, source) {
|
|
74
|
+
return match.index + match[0].length < source.length;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// text is ALREADY stripped (use stripAnsi first). providerId only decides precedence when
|
|
78
|
+
// both shapes appear: claude -> the authorize URL wins, codex -> the device URL wins.
|
|
79
|
+
function parseLoginOutput(text, providerId) {
|
|
80
|
+
const source = String(text == null ? '' : text);
|
|
81
|
+
const provider = String(providerId == null ? '' : providerId).trim().toLowerCase();
|
|
82
|
+
const result = { verificationUrl: '', state: '', userCode: '', callbackPort: null, codeEntry: '' };
|
|
83
|
+
|
|
84
|
+
let authorizeUrl = '';
|
|
85
|
+
for (const match of source.matchAll(AUTHORIZE_URL_RE)) {
|
|
86
|
+
if (!_isCompleteMatch(match, source)) continue;
|
|
87
|
+
const url = match[0];
|
|
88
|
+
// The automatic URL the CLI hands to BROWSER is loopback-redirected: port evidence only (D-1).
|
|
89
|
+
if (isLoopbackUrl(url)) {
|
|
90
|
+
if (result.callbackPort === null) result.callbackPort = loopbackPortOf(url);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (!authorizeUrl) authorizeUrl = url;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const deviceMatch = DEVICE_URL_RE.exec(source);
|
|
97
|
+
const deviceUrl = deviceMatch && _isCompleteMatch(deviceMatch, source) ? deviceMatch[0] : '';
|
|
98
|
+
const codeMatch = DEVICE_CODE_LINE_RE.exec(source);
|
|
99
|
+
const userCode = codeMatch ? codeMatch[1] : '';
|
|
100
|
+
|
|
101
|
+
const chosen = provider === 'codex' ? (deviceUrl || authorizeUrl) : (authorizeUrl || deviceUrl);
|
|
102
|
+
result.verificationUrl = chosen;
|
|
103
|
+
result.state = chosen ? _stateParamOf(chosen) : '';
|
|
104
|
+
result.codeEntry = !chosen ? '' : (chosen === authorizeUrl ? 'paste' : 'browser');
|
|
105
|
+
result.userCode = userCode;
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* createLoginCapture({ providerId, seed, onUpdate }) -> { feed(chunk), snapshot() }
|
|
111
|
+
* feed() appends to the rolling window and re-parses it; every field that changes
|
|
112
|
+
* from ''/null to a value is FROZEN (a later, different value never overwrites it) and the
|
|
113
|
+
* whole set of fields changed by this feed() is passed to onUpdate() exactly once.
|
|
114
|
+
* `seed` (the session's `_textTail`) is fed synchronously at creation, closing the
|
|
115
|
+
* attach-after-first-bytes race (§5 OQ-1).
|
|
116
|
+
*
|
|
117
|
+
* The window keeps RAW bytes and is stripped at parse time, not per chunk: a PTY chunk is
|
|
118
|
+
* arbitrary bytes, so an OSC-8 hyperlink (which the CLI wraps its URL in) can be split
|
|
119
|
+
* across two chunks -- stripping the halves separately would eat the `ESC ]` but leave
|
|
120
|
+
* `8;;` glued in front of the payload and the payload glued to the following text, and the
|
|
121
|
+
* card would get `…&state=<43>If`. Stripping the reassembled window removes the whole
|
|
122
|
+
* sequence, payload included (proved by the AC-0d chunk-split test).
|
|
123
|
+
*/
|
|
124
|
+
function createLoginCapture({ providerId, seed = '', onUpdate } = {}) {
|
|
125
|
+
let window = '';
|
|
126
|
+
const frozen = { verificationUrl: '', state: '', userCode: '', callbackPort: null, codeEntry: '' };
|
|
127
|
+
const notify = typeof onUpdate === 'function' ? onUpdate : null;
|
|
128
|
+
|
|
129
|
+
const feed = (chunk) => {
|
|
130
|
+
window = `${window}${String(chunk == null ? '' : chunk)}`.slice(-CAPTURE_WINDOW_CHARS);
|
|
131
|
+
const parsed = parseLoginOutput(stripAnsi(window), providerId);
|
|
132
|
+
const changed = {};
|
|
133
|
+
for (const field of CAPTURE_FIELDS) {
|
|
134
|
+
if (frozen[field] !== '' && frozen[field] !== null) continue; // frozen once set
|
|
135
|
+
const next = parsed[field];
|
|
136
|
+
if (next === '' || next === null || next === undefined) continue;
|
|
137
|
+
frozen[field] = next;
|
|
138
|
+
changed[field] = next;
|
|
139
|
+
}
|
|
140
|
+
if (notify && Object.keys(changed).length) notify(changed);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
if (seed) feed(seed);
|
|
144
|
+
return { feed, snapshot: () => ({ ...frozen }) };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = {
|
|
148
|
+
CAPTURE_WINDOW_CHARS,
|
|
149
|
+
isLoopbackUrl,
|
|
150
|
+
loopbackPortOf,
|
|
151
|
+
parseLoginOutput,
|
|
152
|
+
createLoginCapture,
|
|
153
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// R-1/R-2 (docs/clock-free-tests-followups-r5-2026-10-25.html §5, §6):
|
|
4
|
+
// the queue-draft GET overlays the durable, in-memory queue-engine item status
|
|
5
|
+
// onto the stored draft snapshot, so every reader (desktop, phone) sees the
|
|
6
|
+
// same truth without the GET writing anything back (D-4). Pure: no requires,
|
|
7
|
+
// no I/O, never mutates the draft it is handed.
|
|
8
|
+
|
|
9
|
+
// Only durable statuses that mean "the engine owns this item's fate" overlay a
|
|
10
|
+
// draft. A durable `pending` is deliberately excluded: the draft's
|
|
11
|
+
// queued/pending/confirming vocabulary stays client-owned (R-2, D-5).
|
|
12
|
+
const OVERLAY_STATUSES = new Set(['sending', 'sent', 'skipped', 'failed']);
|
|
13
|
+
|
|
14
|
+
// Missing / non-numeric / non-positive timestamps count as 0 (R-2).
|
|
15
|
+
function _draftStatusTimestamp(value) {
|
|
16
|
+
const n = Number(value);
|
|
17
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function overlayQueueDraftStatuses(draft, statusMap) {
|
|
21
|
+
if (!draft || typeof draft !== 'object' || !Array.isArray(draft.items)) return draft;
|
|
22
|
+
if (!statusMap || statusMap.size === 0) return { ...draft, items: draft.items.slice() };
|
|
23
|
+
const items = draft.items.map((item) => {
|
|
24
|
+
if (!item || typeof item !== 'object' || !item.id) return item;
|
|
25
|
+
const durable = statusMap.get(item.id);
|
|
26
|
+
if (!durable || !OVERLAY_STATUSES.has(durable.status)) return item;
|
|
27
|
+
const durableAt = _draftStatusTimestamp(durable.statusUpdatedAt);
|
|
28
|
+
const draftAt = _draftStatusTimestamp(item.statusUpdatedAt);
|
|
29
|
+
// Newest timestamp wins (A5), tie goes to the durable status (AC-1a).
|
|
30
|
+
if (durableAt < draftAt) return item;
|
|
31
|
+
return { ...item, status: durable.status, statusUpdatedAt: durable.statusUpdatedAt };
|
|
32
|
+
});
|
|
33
|
+
return { ...draft, items };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = { overlayQueueDraftStatuses, OVERLAY_STATUSES };
|
|
@@ -23,6 +23,7 @@ const MAX_CURSOR_BYTES = 16 * 1024;
|
|
|
23
23
|
// A request keeps consuming blocks until it holds maxRows rows or has read maxBytes; this bounds
|
|
24
24
|
// the work when many consecutive blocks hold only hidden record kinds.
|
|
25
25
|
const MAX_FILL_BLOCKS = 16;
|
|
26
|
+
const MAX_EMPTY_RUN_BLOCKS = 256; // D-1: while no row is collected a fill keeps consuming up to 256 × BLOCK_BYTES = 8 MiB of hidden records before answering an empty page
|
|
26
27
|
// leadPrompt: when no loaded unit before the page holds a prompt, scan this many EARLIER blocks (2 MiB) for one.
|
|
27
28
|
const LEAD_SCAN_MAX_BLOCKS = 64;
|
|
28
29
|
// Per-process LRU of scan answers. A page inside one long turn would otherwise re-scan the same blocks on every
|
|
@@ -323,7 +324,7 @@ function getRawCodexHistoryWindow(sources, options = {}, fileOps = transcriptSou
|
|
|
323
324
|
// maxBytes budgets the source bytes CONSUMED (the logical blocks); readRawHistoryBlock also reads a
|
|
324
325
|
// block of look-around each side to resolve record ownership, so counting raw reads would stop at ~3 blocks.
|
|
325
326
|
const consumedBytes = () => segments.reduce((sum, one) => sum + (one.block.end - one.block.start), 0);
|
|
326
|
-
while (all.length < maxRows && !(all.length && consumedBytes() >= maxBytes) && segments.length < MAX_FILL_BLOCKS) {
|
|
327
|
+
while (all.length < maxRows && !(all.length && consumedBytes() >= maxBytes) && segments.length < (all.length ? MAX_FILL_BLOCKS : MAX_EMPTY_RUN_BLOCKS)) {
|
|
327
328
|
const at = forward ? nextBlockOf(seg, all.at(-1)) : prevBlockOf(seg);
|
|
328
329
|
if (!at) break;
|
|
329
330
|
seg = loadBlock(at.x, at.b);
|
|
@@ -466,7 +467,8 @@ function getRawCodexHistoryWindow(sources, options = {}, fileOps = transcriptSou
|
|
|
466
467
|
// A block with only non-display records must still advance rather than claim an edge.
|
|
467
468
|
if (!items.length && all.length) return base('retryable-error', generation, 'response-budget-too-small');
|
|
468
469
|
if (!items.length) {
|
|
469
|
-
// Every block consumed was hidden-only: hand back
|
|
470
|
+
// Every block consumed (up to MAX_EMPTY_RUN_BLOCKS while no row was collected) was hidden-only: hand back
|
|
471
|
+
// cursors just past the oldest/newest one read, so the next request resumes after the hidden run it walked.
|
|
470
472
|
const oldest = forward ? segments[0] : segments.at(-1); const newest = forward ? segments.at(-1) : segments[0];
|
|
471
473
|
const older = prevBlockOf(oldest); const newer = nextBlockOf(newest, null);
|
|
472
474
|
response.olderCursor = cursorAt(older && { ...older, u: Number.MAX_SAFE_INTEGER, f: Number.MAX_SAFE_INTEGER });
|
|
@@ -484,4 +486,4 @@ function getRawCodexHistoryWindow(sources, options = {}, fileOps = transcriptSou
|
|
|
484
486
|
return response;
|
|
485
487
|
}
|
|
486
488
|
|
|
487
|
-
module.exports = { FAMILY, BLOCK_BYTES, encodeRawCodexHistoryCursor, decodeRawCodexHistoryCursor, isRawCodexHistoryCursor, getRawCodexHistoryWindow, historyRowKindOfRole };
|
|
489
|
+
module.exports = { FAMILY, BLOCK_BYTES, MAX_EMPTY_RUN_BLOCKS, encodeRawCodexHistoryCursor, decodeRawCodexHistoryCursor, isRawCodexHistoryCursor, getRawCodexHistoryWindow, historyRowKindOfRole };
|
|
@@ -1105,8 +1105,11 @@ class CtmReadPoolClient {
|
|
|
1105
1105
|
const timer = setTimeout(resolve, 500);
|
|
1106
1106
|
timer.unref?.();
|
|
1107
1107
|
});
|
|
1108
|
-
|
|
1109
|
-
|
|
1108
|
+
// Only a slot with an isolate can hold a read connection; a retired / never-started slot is
|
|
1109
|
+
// already in a reader gap (sqlite.org/wal.html "reader gaps").
|
|
1110
|
+
const live = this._workers.filter((worker) => worker.isLive);
|
|
1111
|
+
const results = await Promise.all(live.map((worker) => worker.requestReadonlyRecycle(reason)));
|
|
1112
|
+
return { requested: live.length, recycled: results.filter(Boolean).length, skippedNoIsolate: this._workers.length - live.length };
|
|
1110
1113
|
}
|
|
1111
1114
|
|
|
1112
1115
|
// `idleMs` (WP-4): when set, only workers idle (no completed op) at least this long
|