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
|
@@ -22,6 +22,11 @@ function closedReceipt(stream) {
|
|
|
22
22
|
return stream.closed ? Promise.resolve() : new Promise((resolve) => stream.once('close', resolve));
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
// A hit refreshes the entry's mtime (LRU order), but a shared 4 GiB cache does not need 5 s LRU
|
|
26
|
+
// granularity: touching at most once per minute per file drops mtime-only writes by ~12x while keeping
|
|
27
|
+
// the eviction order of multi-GB entries unchanged (fsevents-churn D-10b).
|
|
28
|
+
const TOUCH_MIN_INTERVAL_MS = 60_000;
|
|
29
|
+
|
|
25
30
|
function createDecodedFrameCache({ withDecoder, cacheRoot, cacheMax, now, frameFile, pruneCache, codeError }) {
|
|
26
31
|
const entries = new Map();
|
|
27
32
|
|
|
@@ -34,7 +39,12 @@ function createDecodedFrameCache({ withDecoder, cacheRoot, cacheMax, now, frameF
|
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
async function touch(handle, opts) {
|
|
37
|
-
const
|
|
42
|
+
const st = await handle.stat();
|
|
43
|
+
const at = now(opts);
|
|
44
|
+
// The inode's mtime IS the last-touch record: entries are dropped from the map when their last
|
|
45
|
+
// lease closes, so anything in memory would reset on every acquisition cycle (model-sync, 5 s).
|
|
46
|
+
if (at - st.mtimeMs < TOUCH_MIN_INTERVAL_MS) return; // fresh enough: a touch this minute already happened
|
|
47
|
+
const when = new Date(at);
|
|
38
48
|
await handle.utimes(when, when);
|
|
39
49
|
}
|
|
40
50
|
|
|
@@ -241,4 +251,4 @@ function createDecodedFrameCache({ withDecoder, cacheRoot, cacheMax, now, frameF
|
|
|
241
251
|
return { acquireDecodedFrame };
|
|
242
252
|
}
|
|
243
253
|
|
|
244
|
-
module.exports = { createDecodedFrameCache };
|
|
254
|
+
module.exports = { createDecodedFrameCache, TOUCH_MIN_INTERVAL_MS };
|
|
@@ -24,12 +24,35 @@ const ARCHIVE_INTERVAL_MS = 60 * 60 * 1000;
|
|
|
24
24
|
const SCRUB_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
25
25
|
const ARCHIVE_TIMEOUT_MS = 60 * 60 * 1000; // a multi-GB compress + verify outlasts the 10 minute default
|
|
26
26
|
|
|
27
|
+
// D-24/WP-6: the counters the `restoredOriginals` recovery result carries, flattened for the job result and
|
|
28
|
+
// its log line. A cycle that skipped the phase (or a guard that never reported one) counts as all zeros.
|
|
29
|
+
function _restoredCounts(restoredOriginals) {
|
|
30
|
+
const r = restoredOriginals || {};
|
|
31
|
+
const count = (v) => (Array.isArray(v) ? v.length : 0);
|
|
32
|
+
return {
|
|
33
|
+
units: Number(r.restoredUnits) || 0,
|
|
34
|
+
files: Number(r.restoredFiles) || 0,
|
|
35
|
+
linked: Number(r.linkedFromTrash) || 0,
|
|
36
|
+
decodedBytes: Number(r.decodedBytes) || 0,
|
|
37
|
+
deferredLowDisk: Number(r.deferredLowDisk) || 0,
|
|
38
|
+
targetExists: count(r.targetExists),
|
|
39
|
+
failed: count(r.failed),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
27
43
|
// Scheduler job `transcript-archive` (D-16..D-19, D-23): archive idle transcripts, purge the trash after the
|
|
28
44
|
// grace period, scrub archives once a day. `env` supplies what only the host knows:
|
|
29
45
|
// paths() -> { projectsRoot, codexSessionsDir, walleSessionsDir, trashRoot, dataDir? }
|
|
30
46
|
// liveCtmSessionIds() -> ids of CTM sessions with a live PTY (a live session owns its transcript)
|
|
31
47
|
// hostShedding() -> true while the load-pressure controller is shedding work
|
|
32
|
-
|
|
48
|
+
// now() -> (optional, test seam) ms clock; defaults to Date.now
|
|
49
|
+
// log(line) -> (optional, test seam) one-line sink; defaults to console.log
|
|
50
|
+
// WP-6 observability: a skipped cycle and an empty cycle used to look identical in the logs. Every primary run
|
|
51
|
+
// now logs exactly one `ran` line, and a run that the `shouldRun` guard held back logs a `deferred` line at most
|
|
52
|
+
// once per ARCHIVE_INTERVAL_MS -- with how long it has been waiting and why. A successful run resets the
|
|
53
|
+
// deferral state. `deferReason` (5th argument, optional) is a function returning a short reason string ('' when
|
|
54
|
+
// none); without it the reason is 'guard'.
|
|
55
|
+
function registerArchiveJob(scheduler, guard, env, shouldRun, deferReason) {
|
|
33
56
|
let first = true;
|
|
34
57
|
let claudeAfter = '';
|
|
35
58
|
let codexAfter = '';
|
|
@@ -37,6 +60,26 @@ function registerArchiveJob(scheduler, guard, env, shouldRun) {
|
|
|
37
60
|
let claudeDigestAfter = '';
|
|
38
61
|
let digestAfter = '';
|
|
39
62
|
let lastScrubAt = 0;
|
|
63
|
+
const nowMs = () => (typeof env.now === 'function' ? Number(env.now()) : Date.now());
|
|
64
|
+
const log = typeof env.log === 'function' ? env.log : console.log;
|
|
65
|
+
// Deferral state (WP-6): startedAt = when the guard first held a cycle back, checks/reasons = what it saw,
|
|
66
|
+
// loggedAt = the last `deferred` line, so a long deferral is one line per interval, not one per tick.
|
|
67
|
+
const defer = { startedAt: 0, checks: 0, reasons: new Map(), loggedAt: 0 };
|
|
68
|
+
const resetDefer = () => { defer.startedAt = 0; defer.checks = 0; defer.reasons.clear(); defer.loggedAt = 0; };
|
|
69
|
+
const guardedShouldRun = typeof shouldRun === 'function' ? (...args) => {
|
|
70
|
+
if (shouldRun(...args)) return true;
|
|
71
|
+
const now = nowMs();
|
|
72
|
+
if (!defer.startedAt) defer.startedAt = now;
|
|
73
|
+
defer.checks += 1;
|
|
74
|
+
const reason = String((typeof deferReason === 'function' ? deferReason() : '') || 'guard');
|
|
75
|
+
defer.reasons.set(reason, (defer.reasons.get(reason) || 0) + 1);
|
|
76
|
+
if (!defer.loggedAt || now - defer.loggedAt >= ARCHIVE_INTERVAL_MS) {
|
|
77
|
+
defer.loggedAt = now;
|
|
78
|
+
const reasons = Array.from(defer.reasons, ([name, count]) => `${name}:${count}`).join(',');
|
|
79
|
+
log(`[transcript-archive] deferred ${Math.floor((now - defer.startedAt) / 60000)}m checks=${defer.checks} reasons=${reasons}`);
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
} : shouldRun;
|
|
40
83
|
scheduler.register({
|
|
41
84
|
name: 'transcript-archive',
|
|
42
85
|
heavy: true,
|
|
@@ -44,10 +87,10 @@ function registerArchiveJob(scheduler, guard, env, shouldRun) {
|
|
|
44
87
|
startDelayMs: 10 * 60 * 1000,
|
|
45
88
|
priority: 9,
|
|
46
89
|
pool: 'io',
|
|
47
|
-
shouldRun,
|
|
90
|
+
shouldRun: guardedShouldRun,
|
|
48
91
|
run: async () => {
|
|
49
92
|
if (typeof guard.isPrimary !== 'function' || !guard.isPrimary()) return { skipped: { not_primary: 1 } };
|
|
50
|
-
const now =
|
|
93
|
+
const now = nowMs();
|
|
51
94
|
const scrub = now - lastScrubAt >= SCRUB_INTERVAL_MS;
|
|
52
95
|
const result = await guard.run('archive', {
|
|
53
96
|
isPrimary: true,
|
|
@@ -77,9 +120,17 @@ function registerArchiveJob(scheduler, guard, env, shouldRun) {
|
|
|
77
120
|
digestAfter = digest.nextCodexDigestAfter || '';
|
|
78
121
|
}
|
|
79
122
|
const { archived, bytesIn, bytesOut, trashed, purged, verified, corrupt, skipped } = result;
|
|
123
|
+
const restored = _restoredCounts(result.restoredOriginals);
|
|
124
|
+
const errors = (result.errors || []).length + (digest ? digest.errors.length : 0);
|
|
125
|
+
const waitedMs = defer.startedAt ? Math.max(0, nowMs() - defer.startedAt) : 0;
|
|
126
|
+
log(`[transcript-archive] ran archived=${Number(archived) || 0} restoredUnits=${restored.units} `
|
|
127
|
+
+ `restoredFiles=${restored.files} linked=${restored.linked} decodedBytes=${restored.decodedBytes} `
|
|
128
|
+
+ `deferredLowDisk=${restored.deferredLowDisk} targetExists=${restored.targetExists} `
|
|
129
|
+
+ `restoreFailed=${restored.failed} errors=${errors} waitedMs=${waitedMs}`);
|
|
130
|
+
resetDefer();
|
|
80
131
|
return {
|
|
81
132
|
archived, bytesIn, bytesOut, trashed, purged, verified, corrupt, skipped, digested: digest ? digest.digested : 0,
|
|
82
|
-
errors
|
|
133
|
+
errors, restored,
|
|
83
134
|
};
|
|
84
135
|
},
|
|
85
136
|
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-2 (worker-transcript-link-derived-not-discovered-2026-10-25.html §5 D-4/D-7, §6, §8):
|
|
4
|
+
// the resume preflight predicts a Claude worker's transcript path from (cwd, agent_session_id)
|
|
5
|
+
// when agent_sessions.jsonl_path is empty, and must VERIFY the prediction before it links it --
|
|
6
|
+
// a predicted path is not evidence. This module is the one place that verification happens.
|
|
7
|
+
//
|
|
8
|
+
// It is the only module besides lib/transcript-source.js that names a transcript path, and it
|
|
9
|
+
// touches that path ONLY through transcript-source functions: `require('fs')` here would be a
|
|
10
|
+
// new site for bin/transcript-open-lint.js (the read has to stay on the one sanctioned reader,
|
|
11
|
+
// so archive/trash states, sibling `.zst` archives and the manifest all resolve identically).
|
|
12
|
+
|
|
13
|
+
const transcriptSource = require('./transcript-source');
|
|
14
|
+
|
|
15
|
+
// 64 KiB: enough to reach the session id in Claude Code's first record while never pulling a
|
|
16
|
+
// multi-megabyte transcript into memory. It is NOT a bound on the first LINE: a measured 34 of
|
|
17
|
+
// the 300 most recent real transcripts start with a single `queue-operation` record of 66 KB to
|
|
18
|
+
// 387 KB, so the head routinely ends mid-record. Only the `sessionId` key is read out of it
|
|
19
|
+
// (a bounded scan, never a JSON.parse of the whole record, which such a line would fail).
|
|
20
|
+
const HEAD_BYTES = 65536;
|
|
21
|
+
|
|
22
|
+
// Claude Code writes `"sessionId"` before any user content in the first record. Measured offset in
|
|
23
|
+
// real transcripts: byte 87. The value is a session uuid, so no escapes are possible.
|
|
24
|
+
const SESSION_ID_RE = /"sessionId"\s*:\s*"([^"]*)"/;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Verify that `derivedPath` really is the transcript of `agentSessionId`.
|
|
28
|
+
*
|
|
29
|
+
* States (the caller reports them verbatim in a refusal, so the lead can tell "the file is not
|
|
30
|
+
* where Claude writes it" from "the file is there but belongs to someone else"):
|
|
31
|
+
* plain -- a local file whose first JSON line names this session (ok: true)
|
|
32
|
+
* missing -- nothing at the path (or the resolver itself threw)
|
|
33
|
+
* archived -- resolved to an archived/trashed source; resume restores those by path, so a
|
|
34
|
+
* predicted path that is not plain is never linked
|
|
35
|
+
* unreadable -- the head could not be read, or its first line names no sessionId at all
|
|
36
|
+
* (empty file, non-JSON bytes, a record without the key)
|
|
37
|
+
* id-mismatch -- readable, but its sessionId is a different session
|
|
38
|
+
*
|
|
39
|
+
* @returns {Promise<{ok: boolean, state: string, headSessionId: string|null}>}
|
|
40
|
+
*/
|
|
41
|
+
async function verifyClaudeTranscriptAt(derivedPath, agentSessionId, {
|
|
42
|
+
resolveTranscriptSource = transcriptSource.resolveTranscriptSource,
|
|
43
|
+
readTranscriptHead = transcriptSource.readTranscriptHead,
|
|
44
|
+
} = {}) {
|
|
45
|
+
let source = null;
|
|
46
|
+
try {
|
|
47
|
+
source = await resolveTranscriptSource(derivedPath);
|
|
48
|
+
} catch {
|
|
49
|
+
// expected: an unreadable/absent path is exactly the "no transcript there" answer
|
|
50
|
+
return { ok: false, state: 'missing', headSessionId: null };
|
|
51
|
+
}
|
|
52
|
+
if (!source || source.state === 'missing') return { ok: false, state: 'missing', headSessionId: null };
|
|
53
|
+
// Anything not plain (archived, trashed, ...) is reported as archived: the resolve path below
|
|
54
|
+
// restores an archived transcript at the LINKED path, and a derived path has no link yet.
|
|
55
|
+
if (source.state !== 'plain') return { ok: false, state: 'archived', headSessionId: null };
|
|
56
|
+
|
|
57
|
+
let head = '';
|
|
58
|
+
try {
|
|
59
|
+
head = String(await readTranscriptHead(derivedPath, HEAD_BYTES));
|
|
60
|
+
} catch {
|
|
61
|
+
// expected: an unreadable/absent file -- unverifiable, never a link
|
|
62
|
+
return { ok: false, state: 'unreadable', headSessionId: null };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Scan the first line only (or the whole head when it is one unterminated line, i.e. the record
|
|
66
|
+
// is longer than HEAD_BYTES). Never parse the record: a first line over 64 KiB is normal and
|
|
67
|
+
// would throw. The key precedes user content, so the first match is the record's own session id.
|
|
68
|
+
const firstLine = head.split('\n')[0];
|
|
69
|
+
const match = SESSION_ID_RE.exec(firstLine);
|
|
70
|
+
if (!match) return { ok: false, state: 'unreadable', headSessionId: null };
|
|
71
|
+
|
|
72
|
+
const headSessionId = match[1];
|
|
73
|
+
if (headSessionId !== String(agentSessionId || '')) return { ok: false, state: 'id-mismatch', headSessionId };
|
|
74
|
+
return { ok: true, state: 'plain', headSessionId };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { verifyClaudeTranscriptAt };
|
|
@@ -42,14 +42,18 @@ function _row(input) {
|
|
|
42
42
|
const ALL_STATES = Object.freeze([...ARCHIVE_STATES, ...LEGACY_STATES]);
|
|
43
43
|
|
|
44
44
|
// WP-9. Rows in any of `states`, optionally changed before `olderThanMs` (state_changed_at_ms) or with a source
|
|
45
|
-
// mtime before `mtimeBeforeMs`. orderBy: 'mtime' (oldest source first) | '
|
|
46
|
-
// first) | 'changed' (oldest state change first, the
|
|
45
|
+
// mtime before `mtimeBeforeMs`. orderBy: 'mtime' (oldest source first) | 'mtime_desc' (newest source first, the
|
|
46
|
+
// WP-2 recovery order) | 'verified' (least recently verified first) | 'changed' (oldest state change first, the
|
|
47
|
+
// default). Bounded to 2000 rows.
|
|
47
48
|
function listByState(db, { states = [], olderThanMs = 0, mtimeBeforeMs = 0, orderBy = 'changed', limit = 500 } = {}) {
|
|
48
49
|
const wanted = (states || []).map(String).filter((s) => ALL_STATES.includes(s));
|
|
49
50
|
if (!wanted.length) return [];
|
|
50
51
|
const bounded = Math.max(1, Math.min(2000, Number(limit) || 500));
|
|
51
|
-
const order = {
|
|
52
|
-
|
|
52
|
+
const order = {
|
|
53
|
+
mtime: 'source_mtime_ms, source_path',
|
|
54
|
+
mtime_desc: 'source_mtime_ms DESC, source_path',
|
|
55
|
+
verified: 'last_verified_at_ms, source_path',
|
|
56
|
+
}[orderBy] || 'state_changed_at_ms, source_path';
|
|
53
57
|
const marks = wanted.map(() => '?').join(',');
|
|
54
58
|
const where = [`state IN (${marks})`];
|
|
55
59
|
const params = [...wanted];
|
|
@@ -22,7 +22,7 @@ const { finished } = require('node:stream/promises');
|
|
|
22
22
|
const zlib = require('node:zlib');
|
|
23
23
|
const { decodeFrame, waitForDecodedFrameClose, decompressParams } = require('./transcript-archive-codec');
|
|
24
24
|
const { getDataDir } = require('./data-dir');
|
|
25
|
-
const { createDecodedFrameCache } = require('./transcript-frame-cache');
|
|
25
|
+
const { createDecodedFrameCache, TOUCH_MIN_INTERVAL_MS } = require('./transcript-frame-cache');
|
|
26
26
|
|
|
27
27
|
const CACHE_DIR_NAME = 'transcript-archive-cache';
|
|
28
28
|
const DEFAULT_CACHE_MAX_BYTES = 4 * 1024 * 1024 * 1024;
|
|
@@ -1109,8 +1109,13 @@ function _decodeFrameSync(archive, index, opts, learnLength) {
|
|
|
1109
1109
|
// The cached bytes are this request's decoded bytes: charge them before returning, so a warm
|
|
1110
1110
|
// cache hit is accounted for exactly like a cold decode would be.
|
|
1111
1111
|
if (ledger && learnLength) _chargeLearnedDecoded(ledger, `${archive.cacheKey}:${index}`, hit.size);
|
|
1112
|
-
|
|
1113
|
-
|
|
1112
|
+
// Same rule as the async cache's touch (D-10b): the hit's own mtime is the last-touch record, so a
|
|
1113
|
+
// file touched in the last minute is left alone instead of paying a utimes write per read.
|
|
1114
|
+
const at = _now(opts);
|
|
1115
|
+
if (at - hit.mtimeMs >= TOUCH_MIN_INTERVAL_MS) {
|
|
1116
|
+
const when = new Date(at);
|
|
1117
|
+
fs.utimesSync(file, when, when);
|
|
1118
|
+
}
|
|
1114
1119
|
return file;
|
|
1115
1120
|
}
|
|
1116
1121
|
const max = _cacheMax(opts);
|
|
@@ -59,6 +59,11 @@ function setErrorSink(sink) {
|
|
|
59
59
|
}
|
|
60
60
|
const { shouldYieldAfterChunk } = require('./conversation-import/chunk-runner');
|
|
61
61
|
const { createImportMetrics } = require('./conversation-import/import-metrics');
|
|
62
|
+
// WP-2 (D-4): the ingest lane already parses every record, so the agent's own session name
|
|
63
|
+
// (`ai-title` / `custom-title`) is captured here and applied at the ingestJsonlFile choke point.
|
|
64
|
+
const { extractTitleSignalFromRecord } = require('./session-title-signals');
|
|
65
|
+
const { getAgentCapabilities } = require('./agent-capabilities');
|
|
66
|
+
const { applyAgentTitleSignals } = require('./agent-title-apply');
|
|
62
67
|
|
|
63
68
|
const PARSER_VERSION = 3;
|
|
64
69
|
const DEFAULT_INITIAL_TAIL_BYTES = 2 * 1024 * 1024;
|
|
@@ -1008,6 +1013,16 @@ function claudeMessagesFromEntry(entry, state = {}) {
|
|
|
1008
1013
|
if (queued) entry = queued;
|
|
1009
1014
|
if (shouldDropInheritedClaudeEntry(entry, state)) return [];
|
|
1010
1015
|
|
|
1016
|
+
// WP-2 (D-4): a `custom-title`/`ai-title` record produces no rows, but CTM is already reading
|
|
1017
|
+
// every record here — keep the last non-inherited one of each kind so ingestJsonlFile can apply
|
|
1018
|
+
// it (lib/agent-title-apply.js). Last wins; an ai-title never clears a custom-title.
|
|
1019
|
+
const titleSignal = extractTitleSignalFromRecord(entry);
|
|
1020
|
+
if (titleSignal.userTitle || titleSignal.providerTitle) {
|
|
1021
|
+
if (!state.agentTitleSignals) state.agentTitleSignals = {};
|
|
1022
|
+
if (titleSignal.userTitle) state.agentTitleSignals.customTitle = titleSignal.userTitle;
|
|
1023
|
+
else state.agentTitleSignals.aiTitle = titleSignal.providerTitle;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1011
1026
|
if (entry.type === 'system' && entry.subtype === 'compact_boundary'
|
|
1012
1027
|
&& structuredCapture.structuredCaptureEnabled()) {
|
|
1013
1028
|
const compactMeta = entry.compactMetadata || {};
|
|
@@ -1458,6 +1473,8 @@ function healSelfLinkedTranscriptRows(db, agentSessionId, ownerCtmSessionId) {
|
|
|
1458
1473
|
try {
|
|
1459
1474
|
const f = db.prepare('UPDATE transcript_files SET ctm_session_id=? WHERE agent_session_id=? AND ctm_session_id=?').run(owner, agentId, agentId);
|
|
1460
1475
|
const e = db.prepare('UPDATE transcript_events SET ctm_session_id=? WHERE agent_session_id=? AND ctm_session_id=?').run(owner, agentId, agentId);
|
|
1476
|
+
// Rows whose owner changed used to change silently. Ids only, truncated; never a path, never content.
|
|
1477
|
+
if (f.changes || e.changes) console.log(`[transcript-owner] re-keyed self-link agent=${agentId.slice(0, 8)} -> owner=${owner.slice(0, 8)} files=${f.changes || 0} events=${e.changes || 0}`);
|
|
1461
1478
|
return { files: f.changes || 0, events: e.changes || 0 };
|
|
1462
1479
|
} catch { return { files: 0, events: 0 }; }
|
|
1463
1480
|
}
|
|
@@ -1688,6 +1705,10 @@ function parseTranscriptTail(options = {}) {
|
|
|
1688
1705
|
return {
|
|
1689
1706
|
...base,
|
|
1690
1707
|
messages: projected.messages,
|
|
1708
|
+
// WP-2 (D-4): the agent-name signal rides with the parsed window so both the inline ingest
|
|
1709
|
+
// and the read-pool → preParsed apply path (workers/read-pool-worker.js parseTranscriptDelta)
|
|
1710
|
+
// carry it without a second read.
|
|
1711
|
+
agentTitleSignals: state.agentTitleSignals || null,
|
|
1691
1712
|
metrics,
|
|
1692
1713
|
read: { nextOffset: read.nextOffset, partialLine: read.partialLine || '', bytesRead: read.bytesRead },
|
|
1693
1714
|
};
|
|
@@ -2034,6 +2055,8 @@ function _ingestJsonlFileV2(db, options = {}) {
|
|
|
2034
2055
|
agentSessionId,
|
|
2035
2056
|
generation: 1,
|
|
2036
2057
|
fileSize: stat.size,
|
|
2058
|
+
// WP-2 (D-4): the parsed window's agent-name signal (title records produce no rows).
|
|
2059
|
+
agentTitleSignals: (preParsed || parsed).agentTitleSignals || null,
|
|
2037
2060
|
oversizedNonRetainedRecordsSkipped,
|
|
2038
2061
|
metrics: {
|
|
2039
2062
|
...(parsed.metrics || {}),
|
|
@@ -2466,6 +2489,8 @@ function _ingestJsonlFile(db, options = {}) {
|
|
|
2466
2489
|
|
|
2467
2490
|
let read;
|
|
2468
2491
|
let messages;
|
|
2492
|
+
// WP-2 (D-4): the agent-name signal (title records produce no rows) from whichever parse ran.
|
|
2493
|
+
let agentTitleSignals = null;
|
|
2469
2494
|
if (preParsed) {
|
|
2470
2495
|
// Off-thread parse already produced the delta (parseTranscriptTail) — apply is writes only.
|
|
2471
2496
|
read = {
|
|
@@ -2474,6 +2499,7 @@ function _ingestJsonlFile(db, options = {}) {
|
|
|
2474
2499
|
bytesRead: Number(preParsed.read?.bytesRead || 0),
|
|
2475
2500
|
};
|
|
2476
2501
|
messages = Array.isArray(preParsed.messages) ? preParsed.messages : [];
|
|
2502
|
+
agentTitleSignals = preParsed.agentTitleSignals || null;
|
|
2477
2503
|
} else {
|
|
2478
2504
|
const maxBytes = Math.min(
|
|
2479
2505
|
Math.max(0, readCeiling - cursor.offset),
|
|
@@ -2501,6 +2527,7 @@ function _ingestJsonlFile(db, options = {}) {
|
|
|
2501
2527
|
});
|
|
2502
2528
|
metrics.parseMs += Math.max(0, Date.now() - parseStartedAt);
|
|
2503
2529
|
messages = projected.messages;
|
|
2530
|
+
agentTitleSignals = state.agentTitleSignals || null;
|
|
2504
2531
|
metrics.recordsParsed += projected.recordsParsed;
|
|
2505
2532
|
}
|
|
2506
2533
|
metrics.sourceBytesRead += Number(read.bytesRead || 0);
|
|
@@ -3266,6 +3293,7 @@ function _ingestJsonlFile(db, options = {}) {
|
|
|
3266
3293
|
values: insertChunkStatementCounts,
|
|
3267
3294
|
},
|
|
3268
3295
|
overBudget: elapsedMs > IMPORT_SLICE_HARD_MAX_MS,
|
|
3296
|
+
agentTitleSignals,
|
|
3269
3297
|
messages: partial ? messages.slice(0, processedCount) : messages,
|
|
3270
3298
|
rowCursorDelta: {
|
|
3271
3299
|
ctmSessionId,
|
|
@@ -3367,6 +3395,18 @@ function ingestJsonlFile(db, options = {}) {
|
|
|
3367
3395
|
result,
|
|
3368
3396
|
coreDb: options.coreDb,
|
|
3369
3397
|
});
|
|
3398
|
+
// WP-2 (D-4/D-5/D-9, S-17): the single choke point for every ingest (event delta, targeted
|
|
3399
|
+
// import, v13 migration) — apply the agent's own name to its CTM row. Gated on the provider
|
|
3400
|
+
// declaring it reads ai-titles; a refused delta applied nothing, so it has no signal to apply.
|
|
3401
|
+
// D-11: the caller publishes the outcome (titleApply) on the main thread.
|
|
3402
|
+
if (result && !result.refused && result.agentTitleSignals && ownerCtmSessionId
|
|
3403
|
+
&& getAgentCapabilities(normalizeProvider(options.provider, filePath)).titleSpec?.readsAiTitle) {
|
|
3404
|
+
result.titleApply = applyAgentTitleSignals(options.coreDb || db, {
|
|
3405
|
+
ctmSessionId: ownerCtmSessionId,
|
|
3406
|
+
provider: normalizeProvider(options.provider, filePath),
|
|
3407
|
+
...result.agentTitleSignals,
|
|
3408
|
+
});
|
|
3409
|
+
}
|
|
3370
3410
|
return result;
|
|
3371
3411
|
}
|
|
3372
3412
|
function dryRunTranscriptByteBackfill(db, options = {}) {
|
|
@@ -65,10 +65,13 @@ function _socketBytesWritten(socket) {
|
|
|
65
65
|
// The traffic class of a message, from the channel the caller already resolved (`opts.channel`
|
|
66
66
|
// || the decoded `type`) plus the class `_wsSend` may have resolved explicitly (`opts.class` —
|
|
67
67
|
// the live-status heartbeat sends `session-activity` as `{ class: 'stream', channel: 'heartbeat' }`).
|
|
68
|
-
//
|
|
68
|
+
// T2 (docs/walle-turn-lifecycle-delivery-2026-10-26.html §3): the classification is delegated with
|
|
69
|
+
// the ACTUAL payload plus the caller's class and channel — a synthetic `{ type: channel }` object
|
|
70
|
+
// threw away the lifecycle fields (recordType/partType) that make a walle-transcript-append opener
|
|
71
|
+
// a control notification rather than a replaceable sample, which in economy let progress replace a
|
|
72
|
+
// queued opener. `classifyWsPayload` stays the one authority for the class split.
|
|
69
73
|
function _channelClass(channel, payload, cls) {
|
|
70
|
-
|
|
71
|
-
return classifyWsPayload(ch ? { type: ch } : payload, cls);
|
|
74
|
+
return classifyWsPayload(payload, cls, channel);
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
function createTunnelLaneGovernor({ now = Date.now, setIntervalFn = setInterval, clearIntervalFn = clearInterval, send } = {}) {
|
|
@@ -17,6 +17,12 @@ const STAGES = Object.freeze([
|
|
|
17
17
|
// echoed). Required (not in OPTIONAL_STAGES below), same as 'pty-written'.
|
|
18
18
|
'host-posted',
|
|
19
19
|
'pty-written',
|
|
20
|
+
// WP-4 (clock-free-tests-followups-r4-2026-10-24.html §5 R-9, §6.5): the host's OWN receipt
|
|
21
|
+
// of the PTY batch -- the instant its first byte entered the mirror-feed coalescer, carried on
|
|
22
|
+
// the 'feed' op as `hostReceivedAt`. `pty-output` below is the MAIN-thread arrival, which is a
|
|
23
|
+
// different clock hop; neither is causal for the other (every post-`pty-written` stage is
|
|
24
|
+
// "first X after the key"). Optional, like its neighbours.
|
|
25
|
+
'host-pty-data',
|
|
20
26
|
'pty-output',
|
|
21
27
|
// WP-3 D-C2: the two shadow, per-key echo matchers -- VS Code typeahead's byte rule and
|
|
22
28
|
// mosh's cell rule (see lib/typing-worker-stages.js, which runs them in the mirror). Both are
|
|
@@ -34,6 +40,19 @@ const STAGES = Object.freeze([
|
|
|
34
40
|
'mirror-written',
|
|
35
41
|
'emit-offered',
|
|
36
42
|
'emit-refused',
|
|
43
|
+
// WP-4 (clock-free-tests-followups-r3-2026-10-24.html §5 R-7, §6 WP-4): stamped by the worker
|
|
44
|
+
// (lib/typing-worker-stages.js's noteFramePostedForEchoed, called from emitSettled) for exactly
|
|
45
|
+
// the pending ids the frame that just SHIPPED already covers (their own inputSeq <= that
|
|
46
|
+
// frame's echoedThroughSeq, carried as `offeredEchoSeq`). Optional, like its neighbours above:
|
|
47
|
+
// a key whose frame is refused, or that is delivered without ever being pending, never gets it.
|
|
48
|
+
'frame-posted',
|
|
49
|
+
// WP-4 (clock-free-tests-followups-r4-2026-10-24.html §5 R-7/R-8, §6.5): the FIRST main-thread
|
|
50
|
+
// touch of the frame that carries this key's echo (`session-host-manager.js`'s
|
|
51
|
+
// `_stampMainArrival`), split out from `mirror-revision` (where main stamped the interaction)
|
|
52
|
+
// to price the receiver-loop leg alone. Carries `mainSpans` (R-8) -- the top recent main-thread
|
|
53
|
+
// spans overlapping [frame-posted.at, frame-arrived.at]. Optional, same reason as
|
|
54
|
+
// `frame-posted`: a key whose frame was refused, or that never had a pending row, never gets it.
|
|
55
|
+
'frame-arrived',
|
|
37
56
|
'mirror-revision',
|
|
38
57
|
'frame-sent',
|
|
39
58
|
'socket-received',
|
|
@@ -67,6 +86,14 @@ const STAGE_INDEX = new Map(STAGES.map((stage, index) => [stage, index]));
|
|
|
67
86
|
// WP-3 D-C2: 'echo-bytes'/'echo-cell' join them -- they are shadow stamps most keys never get.
|
|
68
87
|
const OPTIONAL_STAGES = new Set([
|
|
69
88
|
'pty-output', 'worker-received', 'mirror-written', 'emit-offered', 'emit-refused',
|
|
89
|
+
// WP-4 (R-7): 'frame-posted' joins them -- its absence must never move
|
|
90
|
+
// lastCompletedBoundary/missingBoundary for a row that shipped its frame before anything was
|
|
91
|
+
// pending (the common case).
|
|
92
|
+
'frame-posted',
|
|
93
|
+
// WP-4 r4 (R-7/R-8/R-9): 'frame-arrived' and 'host-pty-data' join for the same reason -- both
|
|
94
|
+
// are first-touch stamps on paths most interactions never take (a refused frame; a host whose
|
|
95
|
+
// coalescer delivered before any keystroke was pending), so neither may end the boundary walk.
|
|
96
|
+
'frame-arrived', 'host-pty-data',
|
|
70
97
|
'echo-bytes', 'echo-cell',
|
|
71
98
|
]);
|
|
72
99
|
const WORKLOAD_FIELDS = Object.freeze([
|
|
@@ -85,6 +112,27 @@ function boundedText(value, max = 80) {
|
|
|
85
112
|
return String(value || '').replace(/[^A-Za-z0-9_.:-]/g, '').slice(0, max);
|
|
86
113
|
}
|
|
87
114
|
|
|
115
|
+
// WP-4 r4 (R-8, §6.5): R-8's floor -- mainSpans are computed only for a frame-posted ->
|
|
116
|
+
// frame-arrived window at or above this, so the fast path allocates nothing.
|
|
117
|
+
const MAIN_SPANS_MIN_WINDOW_MS = 15;
|
|
118
|
+
|
|
119
|
+
// The `frame-arrived` record's mainSpans shape: a malformed entry is DROPPED rather than repaired
|
|
120
|
+
// into a plausible-looking 0 -- a reader may only ever see entries the sender actually produced.
|
|
121
|
+
// Name is bounded to 120 chars and overlapMs floored to a non-negative integer, and the record is
|
|
122
|
+
// the TOP 3 by overlapMs whatever order the sender used: the ranking is part of this record's
|
|
123
|
+
// contract (§6.5), not of the sender's good manners.
|
|
124
|
+
function sanitizeMainSpans(rawSpans) {
|
|
125
|
+
const mainSpans = [];
|
|
126
|
+
for (const entry of Array.isArray(rawSpans) ? rawSpans : []) {
|
|
127
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
128
|
+
const name = String(entry.name == null ? '' : entry.name).slice(0, 120);
|
|
129
|
+
if (!name) continue;
|
|
130
|
+
mainSpans.push({ name, overlapMs: Math.max(0, Math.floor(finite(entry.overlapMs))) });
|
|
131
|
+
}
|
|
132
|
+
mainSpans.sort((a, b) => b.overlapMs - a.overlapMs);
|
|
133
|
+
return mainSpans.slice(0, 3);
|
|
134
|
+
}
|
|
135
|
+
|
|
88
136
|
function percentile(values, pct) {
|
|
89
137
|
if (!values.length) return 0;
|
|
90
138
|
const sorted = values.slice().sort((a, b) => a - b);
|
|
@@ -172,6 +220,8 @@ function buildSlowInteractionRow(row, ktpMs, lastStage) {
|
|
|
172
220
|
echoBytesMs: stageMsFromKeydown(row, 'echo-bytes'),
|
|
173
221
|
echoCellMs: stageMsFromKeydown(row, 'echo-cell'),
|
|
174
222
|
mirrorRevisionMs: stageMsFromKeydown(row, 'mirror-revision'),
|
|
223
|
+
// WP-4 (R-7): the shipped-frame leg, same keydown rule as mirrorRevisionMs above.
|
|
224
|
+
framePostedMs: stageMsFromKeydown(row, 'frame-posted'),
|
|
175
225
|
frameSentMs: stageMsFromKeydown(row, 'frame-sent'),
|
|
176
226
|
socketReceivedMs: stageMsFromKeydown(row, 'socket-received'),
|
|
177
227
|
applyStartedMs: stageMsFromKeydown(row, 'apply-started'),
|
|
@@ -221,6 +271,9 @@ function createTypingInteractionTraceStore(options = {}) {
|
|
|
221
271
|
// emitting workerLoopMaxMs: 0 / host: {0,0,0} exactly as before this WP.
|
|
222
272
|
const getRollupExtras = typeof options.getRollupExtras === 'function' ? options.getRollupExtras : null;
|
|
223
273
|
const rollupIntervalMs = Math.max(1000, finite(options.rollupIntervalMs, 60_000));
|
|
274
|
+
// WP-4 r4 (R-8): `(startMs, endMs) => [{ name, overlapMs }]`, a pure read of the caller's
|
|
275
|
+
// main-thread span ring (server.js `_mainSpansForWindow`). Absent -> no spans are computed.
|
|
276
|
+
const mainSpansForWindow = typeof options.mainSpansForWindow === 'function' ? options.mainSpansForWindow : null;
|
|
224
277
|
const rows = new Map();
|
|
225
278
|
const publicRows = new Map();
|
|
226
279
|
const pendingClosed = new Map();
|
|
@@ -320,6 +373,8 @@ function createTypingInteractionTraceStore(options = {}) {
|
|
|
320
373
|
workerReceivedMs: stageDelta(row, 'worker-received', 'browser-keydown'),
|
|
321
374
|
mirrorWrittenMs: stageDelta(row, 'mirror-written', 'browser-keydown'),
|
|
322
375
|
emitOfferedMs: stageDelta(row, 'emit-offered', 'browser-keydown'),
|
|
376
|
+
// WP-4 (R-7): the shipped-frame leg for the closed interaction, read the same way.
|
|
377
|
+
framePostedMs: stageMsFromKeydown(row, 'frame-posted'),
|
|
323
378
|
gate,
|
|
324
379
|
gateHoldMs,
|
|
325
380
|
clientId: row.clientId,
|
|
@@ -453,10 +508,43 @@ function createTypingInteractionTraceStore(options = {}) {
|
|
|
453
508
|
rejectedSurfaceEpoch: Math.max(0, finite(event.rejectedSurfaceEpoch)),
|
|
454
509
|
sweepSkipReason: boundedText(event.sweepSkipReason, 24),
|
|
455
510
|
};
|
|
511
|
+
} else if (stage === 'emit-offered' || stage === 'frame-posted') {
|
|
512
|
+
// WP-4 (R-7): these two stages carry the OFFERED/SHIPPED frame's echoedThroughSeq, so a
|
|
513
|
+
// reader can compare it against this row's own `inputSeq` (set once by begin(), above) --
|
|
514
|
+
// that comparison is what separates "the offered frame did not yet contain this key's
|
|
515
|
+
// echo" from "worker->main delivery / main-thread queueing". A sender that carries no seq
|
|
516
|
+
// records `{ at }` alone: an absent seq must never read as a real 0.
|
|
517
|
+
// First-wins like every other stage: the `!previous || at < previous.at` guard above owns
|
|
518
|
+
// idempotency, so a later note never rewrites this record.
|
|
519
|
+
const offeredEchoSeq = event.offeredEchoSeq;
|
|
520
|
+
row.stages[stage] = offeredEchoSeq == null
|
|
521
|
+
? { at }
|
|
522
|
+
: { at, offeredEchoSeq: Math.max(0, Math.floor(finite(offeredEchoSeq))) };
|
|
523
|
+
} else if (stage === 'frame-arrived') {
|
|
524
|
+
// WP-4 r4 (R-8, §6.5): the arrival stamp may carry `mainSpans` -- the top ≤3 main-thread
|
|
525
|
+
// spans overlapping [frame-posted.at, frame-arrived.at], each `{ name, overlapMs }`.
|
|
526
|
+
// Same shape rule as `offeredEchoSeq` above: no array offered means `{ at }` alone (the
|
|
527
|
+
// store may still attach computed spans below, once both stamps are present).
|
|
528
|
+
row.stages[stage] = Array.isArray(event.mainSpans)
|
|
529
|
+
? { at, mainSpans: sanitizeMainSpans(event.mainSpans) }
|
|
530
|
+
: { at };
|
|
456
531
|
} else {
|
|
457
532
|
row.stages[stage] = { at };
|
|
458
533
|
}
|
|
459
534
|
}
|
|
535
|
+
// R-8, order-independent: the worker posts `frame-posted` as its own 'typing-stage' message
|
|
536
|
+
// AFTER the frame shipped (headless-term-service.js emitSettled -> noteFramePostedForEchoed),
|
|
537
|
+
// so main usually stamps `frame-arrived` BEFORE it knows `frame-posted`. Compute the spans when
|
|
538
|
+
// the SECOND of the two lands, once per row; the window is in the past either way, and the
|
|
539
|
+
// span ring (lib/perf-tracker.js, 3 s+) still holds it.
|
|
540
|
+
if (mainSpansForWindow && (stage === 'frame-posted' || stage === 'frame-arrived')) {
|
|
541
|
+
const arrival = row.stages['frame-arrived'];
|
|
542
|
+
const postedAt = row.stages['frame-posted']?.at;
|
|
543
|
+
if (arrival && !arrival.mainSpans && Number.isFinite(postedAt)
|
|
544
|
+
&& arrival.at - postedAt >= MAIN_SPANS_MIN_WINDOW_MS) {
|
|
545
|
+
arrival.mainSpans = sanitizeMainSpans(mainSpansForWindow(postedAt, arrival.at));
|
|
546
|
+
}
|
|
547
|
+
}
|
|
460
548
|
if (stage === 'painted' && !previous) captureClosedInteraction(row);
|
|
461
549
|
row.updatedAt = Math.max(row.updatedAt, at);
|
|
462
550
|
row.lastCompletedBoundary = '';
|
|
@@ -782,6 +870,9 @@ function noteWorkerStageMessage(store, msg = {}) {
|
|
|
782
870
|
sessionId: msg.sessionId,
|
|
783
871
|
stage: msg.stage,
|
|
784
872
|
at: msg.at,
|
|
873
|
+
// WP-4 (R-7): the offered/shipped frame's echoedThroughSeq rides the same worker message as
|
|
874
|
+
// the stage stamp, so emit-offered/frame-posted rows can be read against their inputSeq.
|
|
875
|
+
offeredEchoSeq: msg.offeredEchoSeq,
|
|
785
876
|
gate: msg.gate,
|
|
786
877
|
inFlightSeq: msg.inFlightSeq,
|
|
787
878
|
inFlightKind: msg.inFlightKind,
|
|
@@ -811,4 +902,5 @@ function pendingInteractionIdsFor(byInputSeqMap, limit = 24) {
|
|
|
811
902
|
|
|
812
903
|
module.exports = {
|
|
813
904
|
createTypingInteractionTraceStore, STAGES, GATES, noteWorkerStageMessage, pendingInteractionIdsFor,
|
|
905
|
+
MAIN_SPANS_MIN_WINDOW_MS,
|
|
814
906
|
};
|
|
@@ -25,6 +25,18 @@ function mergePendingInteractionIds(existing, incoming, limit = PENDING_IDS_LIMI
|
|
|
25
25
|
return merged.slice(-limit);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// WP-4 (R-7, clock-free-tests-followups-r3-2026-10-24.html §5 R-7, §6 WP-4): keep the per-id
|
|
29
|
+
// inputSeq map to exactly the ids `pending` still carries, oldest dropped first, capped at
|
|
30
|
+
// `limit` -- the same "keep the most recent N" policy mergePendingInteractionIds applies to the
|
|
31
|
+
// id list itself, so the two can never drift apart.
|
|
32
|
+
function prunePendingInputSeqs(byId, pending, limit = PENDING_IDS_LIMIT) {
|
|
33
|
+
for (const id of Array.from(byId.keys())) {
|
|
34
|
+
if (!pending.includes(id)) byId.delete(id);
|
|
35
|
+
}
|
|
36
|
+
while (byId.size > limit) byId.delete(byId.keys().next().value);
|
|
37
|
+
return byId;
|
|
38
|
+
}
|
|
39
|
+
|
|
28
40
|
// Post ONE 'typing-stage' message covering every id currently pending for this session -- the
|
|
29
41
|
// STAGES store's note() (lib/typing-interaction-trace.js) fans it out per-interactionId and is
|
|
30
42
|
// idempotent per (interactionId, stage), so calling this more than once for the same boundary is
|
|
@@ -126,6 +138,22 @@ function armEchoPrediction(entry, msg) {
|
|
|
126
138
|
entry.state.pendingTypingInteractionIds, msg.interactionIds,
|
|
127
139
|
);
|
|
128
140
|
entry.state.typingIdsAwaitingFeed = true;
|
|
141
|
+
// WP-4 (R-7): remember each pending id's OWN inputSeq here, so emitSettled can tell "the
|
|
142
|
+
// offered frame did not yet contain this key's echo" apart from "worker->main delivery or
|
|
143
|
+
// main-thread queueing" (the (i)/(ii) split design r3 §1.4 says today's stamps cannot make).
|
|
144
|
+
// Earliest seq wins for an id seen twice; the Math.max(0, ...) normalization is the same one
|
|
145
|
+
// headless-term-service.js's 'note-input' handler applies to msg.inputSeq.
|
|
146
|
+
const inputSeq = Math.max(0, Number(msg.inputSeq) || 0);
|
|
147
|
+
const seqById = entry.state.pendingTypingInputSeqById instanceof Map
|
|
148
|
+
? entry.state.pendingTypingInputSeqById
|
|
149
|
+
: new Map();
|
|
150
|
+
for (const rawId of msg.interactionIds) {
|
|
151
|
+
const id = String(rawId);
|
|
152
|
+
if (!seqById.has(id) || inputSeq < seqById.get(id)) seqById.set(id, inputSeq);
|
|
153
|
+
}
|
|
154
|
+
entry.state.pendingTypingInputSeqById = prunePendingInputSeqs(
|
|
155
|
+
seqById, entry.state.pendingTypingInteractionIds,
|
|
156
|
+
);
|
|
129
157
|
}
|
|
130
158
|
const echo = msg.echo;
|
|
131
159
|
if (!echo || !echo.interactionId || typeof echo.char !== 'string' || !echo.char) return;
|
|
@@ -177,9 +205,41 @@ function matchEchoPredictions(postTransport, sessionId, entry, data, now = Date.
|
|
|
177
205
|
entry.state.echoPredictions = kept;
|
|
178
206
|
}
|
|
179
207
|
|
|
208
|
+
// WP-4 (R-7), called from headless-term-service.js's emitSettled ONLY after
|
|
209
|
+
// _stateSyncEmitter.onMirrorUpdated returned `sent` -- the frame that just shipped is the first
|
|
210
|
+
// one that could carry these keys' echoes, so stamp `frame-posted` for exactly the pending ids
|
|
211
|
+
// the frame's `echoedThroughSeq` already covers (recorded inputSeq >= 1 AND <= offeredEchoSeq).
|
|
212
|
+
// An id with NO recorded seq is treated as echoed, which preserves the pre-WP-4 blanket clear.
|
|
213
|
+
// Returns the ids that remain un-echoed (recorded seq > offeredEchoSeq) so the caller can keep
|
|
214
|
+
// only those pending -- that is what makes `emit-offered -> frame-posted` readable as "the
|
|
215
|
+
// offered frame DID contain this key's echo" versus "the frame lacked it" instead of collapsing
|
|
216
|
+
// both into one delivery gap.
|
|
217
|
+
function noteFramePostedForEchoed(postTransport, sessionId, entry, offeredEchoSeq) {
|
|
218
|
+
const pending = Array.isArray(entry?.state?.pendingTypingInteractionIds)
|
|
219
|
+
? entry.state.pendingTypingInteractionIds
|
|
220
|
+
: [];
|
|
221
|
+
const seqById = entry?.state?.pendingTypingInputSeqById;
|
|
222
|
+
const covering = Math.max(0, Number(offeredEchoSeq) || 0);
|
|
223
|
+
const echoedIds = [];
|
|
224
|
+
const remaining = [];
|
|
225
|
+
for (const rawId of pending) {
|
|
226
|
+
const id = String(rawId);
|
|
227
|
+
const seq = seqById instanceof Map && seqById.has(id) ? Number(seqById.get(id)) : 0;
|
|
228
|
+
if (seq > covering) remaining.push(id);
|
|
229
|
+
else echoedIds.push(id);
|
|
230
|
+
}
|
|
231
|
+
noteWorkerTypingStage(postTransport, sessionId, echoedIds, 'frame-posted', { offeredEchoSeq: covering });
|
|
232
|
+
// The echoed ids are delivered; only the un-echoed ones stay pending (and keep their seq).
|
|
233
|
+
if (seqById instanceof Map) {
|
|
234
|
+
for (const id of echoedIds) seqById.delete(id);
|
|
235
|
+
}
|
|
236
|
+
return remaining;
|
|
237
|
+
}
|
|
238
|
+
|
|
180
239
|
module.exports = {
|
|
181
240
|
mergePendingInteractionIds,
|
|
182
241
|
noteWorkerTypingStage,
|
|
242
|
+
noteFramePostedForEchoed,
|
|
183
243
|
PENDING_IDS_LIMIT,
|
|
184
244
|
isSingleGraphemeEcho,
|
|
185
245
|
armEchoPrediction,
|