create-walle 0.10.0 → 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 +88 -41
- 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 +7 -5
- package/package.json +10 -4
- 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/capture-codex-rollout-records.js +417 -0
- package/template/coding-task-manager/bin/census-codex-lead-history.js +325 -0
- 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/seam-replay-codex.js +301 -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 +25 -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-history-terminal-renderer.js +33 -33
- 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-local-links.js +267 -0
- package/template/coding-task-manager/lib/codex-markdown-lists.js +160 -0
- package/template/coding-task-manager/lib/codex-native-history-prefix.js +4 -0
- package/template/coding-task-manager/lib/codex-paths.js +2 -1
- package/template/coding-task-manager/lib/codex-source-handoff.js +186 -0
- 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-owner-worker-client.js +22 -0
- package/template/coding-task-manager/lib/db-schema-source-files.js +1 -0
- package/template/coding-task-manager/lib/feature-manifest.js +1 -3
- package/template/coding-task-manager/lib/fs-watcher.js +3 -2
- package/template/coding-task-manager/lib/harness-history-transition.js +41 -14
- 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 +60 -23
- package/template/coding-task-manager/lib/history-transition-read-op.js +179 -23
- 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/kernel/diag-log.js +12 -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-observer.js +17 -2
- package/template/coding-task-manager/lib/lead-model-switch-runtime.js +57 -4
- package/template/coding-task-manager/lib/lead-model-switch.js +11 -2
- package/template/coding-task-manager/lib/lead-model-transition-runtime.js +42 -4
- 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 +18 -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 +56 -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-execution-recipe-metadata.js +16 -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/collaboration-codec.js +94 -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/responses-face.js +24 -2
- 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 +62 -14
- 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-admission-trace.js +69 -0
- 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/raw-history-block.js +3 -1
- package/template/coding-task-manager/lib/read-pool-client.js +36 -24
- package/template/coding-task-manager/lib/read-pool-op-registry.js +10 -1
- package/template/coding-task-manager/lib/read-pool-telemetry.js +14 -5
- 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 +18 -5
- 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 +43 -11
- package/template/coding-task-manager/lib/root-prompt-flow.js +175 -38
- package/template/coding-task-manager/lib/scrollback-archive-store.js +7 -4
- package/template/coding-task-manager/lib/server-memory-sampler-runtime.js +10 -0
- package/template/coding-task-manager/lib/server-memory-sampler.js +41 -2
- 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 +38 -39
- 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 +20 -50
- package/template/coding-task-manager/lib/session-model-action-preconditions.js +68 -3
- package/template/coding-task-manager/lib/session-resource-sampler.js +67 -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-status.js +2 -2
- 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 +136 -2
- package/template/coding-task-manager/lib/typing-worker-stages.js +208 -1
- package/template/coding-task-manager/lib/usage-ledger/claude-parse.js +53 -2
- package/template/coding-task-manager/lib/usage-ledger/ingest.js +13 -4
- package/template/coding-task-manager/lib/usage-ledger/read.js +14 -0
- package/template/coding-task-manager/lib/usage-ledger/schema.js +49 -1
- package/template/coding-task-manager/lib/usage-ledger/store.js +337 -6
- package/template/coding-task-manager/lib/usage-ledger/task-read.js +123 -5
- 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/input.js +1 -0
- 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/protocol/message-table.json +0 -10
- package/template/coding-task-manager/protocol/messages.js +0 -10
- package/template/coding-task-manager/protocol/ws-schema.json +0 -20
- package/template/coding-task-manager/protocol/ws-schema.md +0 -2
- 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 -2
- package/template/coding-task-manager/public/css/reviews.css +4 -0
- package/template/coding-task-manager/public/index.html +297 -240
- 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/client-health-core.js +81 -0
- package/template/coding-task-manager/public/js/client-health-main.js +61 -0
- 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 -4
- package/template/coding-task-manager/public/js/prompt-submission-tray.js +281 -0
- package/template/coding-task-manager/public/js/protocol.js +0 -10
- 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 +18 -3
- 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/session-usage-view.js +1 -0
- 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 -3
- 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 +964 -425
- 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/host-capabilities.js +2 -2
- 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/fly.toml +1 -1
- 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 +12 -5
- 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
- package/template/coding-task-manager/lib/giant-freeze-park.js +0 -31
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Repair "resume echo" titles (design doc session-name-agent-title-precedence §1e/§8 WP-6).
|
|
5
|
+
//
|
|
6
|
+
// A CTM session named after the harness's own resume boilerplate ("Resume: …", "Continue the
|
|
7
|
+
// conversation…") instead of the name the agent wrote into its own transcript is the bug WP-0..5
|
|
8
|
+
// fixes for NEW ingests. For rows that were already mis-titled, this script, per id:
|
|
9
|
+
//
|
|
10
|
+
// 1. demotes the row back to a CTM-generated name — user_renamed=0, title_source='ai_title',
|
|
11
|
+
// title_status='generated', title_status_reason='demoted_resume_echo' — so tier 2 may write
|
|
12
|
+
// again, then
|
|
13
|
+
// 2. re-runs WP-6's per-row backfill for that id: the SAME read-pool op the boot job uses
|
|
14
|
+
// ('backfillAgentTitles', one ≤64 KiB transcript tail read) and the SAME applier
|
|
15
|
+
// (lib/agent-title-apply.js applyAgentTitleSignals — the ONLY tier-2 apply).
|
|
16
|
+
//
|
|
17
|
+
// The initial id list this was written for (2026-10-23; NOT applied automatically — pass the ids
|
|
18
|
+
// explicitly). Kept here as documentation, never as code:
|
|
19
|
+
//
|
|
20
|
+
// 21b4bb0b-b889-4c7f-b8b8-fa333291fdd4
|
|
21
|
+
//
|
|
22
|
+
// Usage:
|
|
23
|
+
// node bin/repair-resume-echo-titles.js <id> [<id> …] [--db <path>] [--apply] [--force]
|
|
24
|
+
// node bin/repair-resume-echo-titles.js --ids <id,id,…> [--db <path>] [--apply]
|
|
25
|
+
//
|
|
26
|
+
// Dry run by default: each id's demote + apply runs in one IMMEDIATE transaction that is ROLLED
|
|
27
|
+
// BACK, so the printed before → after is exactly what --apply would write and nothing persists.
|
|
28
|
+
// --apply commits. --db picks the database file; with no --db the script uses CTM's own resolver
|
|
29
|
+
// (lib/data-dir.js + lib/domain-db-paths.js), i.e. <data dir>/task-manager.db and its core
|
|
30
|
+
// sibling if this install is flipped.
|
|
31
|
+
//
|
|
32
|
+
// ABSOLUTE RULE (same as bin/conversation-adopt-self-links.js): run this against a STOPPED CTM or
|
|
33
|
+
// a copy — NEVER against ports 3456/3457's live database, whose owning CTM keeps its own copy of
|
|
34
|
+
// these rows in memory and would overwrite the repair on its next write. --apply refuses a
|
|
35
|
+
// database whose -wal was written in the last 60 s unless --force is passed. The documented path
|
|
36
|
+
// is: stop CTM first, or work on a copy.
|
|
37
|
+
//
|
|
38
|
+
// Exit codes: 0 success, 2 refusal (bad args, missing file, looks live), 1 error.
|
|
39
|
+
|
|
40
|
+
// No CTM_PROCESS_ROLE here on purpose: this script never calls db.js initDb() — it opens the
|
|
41
|
+
// sqlite files itself — so the process identity CTM_PROCESS_ROLE exists for has no reader here,
|
|
42
|
+
// and setting it would mean a new raw flag read for nothing (bin/flag-lint.js).
|
|
43
|
+
|
|
44
|
+
const fs = require('node:fs');
|
|
45
|
+
const path = require('node:path');
|
|
46
|
+
|
|
47
|
+
const { loadSqliteDriver } = require('../lib/sqlite-driver');
|
|
48
|
+
const { getDataDir } = require('../lib/data-dir');
|
|
49
|
+
const { resolveDomainDbPath } = require('../lib/domain-db-paths');
|
|
50
|
+
const { applyAgentTitleSignals } = require('../lib/agent-title-apply');
|
|
51
|
+
const { createReadPoolClient } = require('../lib/read-pool-client');
|
|
52
|
+
|
|
53
|
+
const DEMOTE_REASON = 'demoted_resume_echo';
|
|
54
|
+
const REQUEST_TIMEOUT_MS = 30000;
|
|
55
|
+
const ROW_COLUMNS = 'title, title_source, title_status, title_status_reason, user_renamed, '
|
|
56
|
+
+ 'title_mirror_state, title_mirror_value';
|
|
57
|
+
|
|
58
|
+
function arg(name, fallback = '') {
|
|
59
|
+
const index = process.argv.indexOf(name);
|
|
60
|
+
if (index < 0) return fallback;
|
|
61
|
+
const value = process.argv[index + 1];
|
|
62
|
+
return value != null && !value.startsWith('--') ? value : fallback;
|
|
63
|
+
}
|
|
64
|
+
const has = (name) => process.argv.includes(name);
|
|
65
|
+
|
|
66
|
+
// A WAL sidecar that is being actively written is the cheap, honest signal that another
|
|
67
|
+
// process (the primary) owns this database. Copied from bin/conversation-adopt-self-links.js.
|
|
68
|
+
function looksLive(dbPath) {
|
|
69
|
+
try {
|
|
70
|
+
const wal = fs.statSync(`${dbPath}-wal`);
|
|
71
|
+
return wal.size > 0 && (Date.now() - wal.mtimeMs) < 60_000;
|
|
72
|
+
} catch {
|
|
73
|
+
/* expected: no -wal sidecar (or an unstattable one) means nobody is writing this
|
|
74
|
+
database right now, which is exactly the "not live" answer. */
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function refuse(message) {
|
|
80
|
+
console.error(`ERROR: ${message}`);
|
|
81
|
+
process.exit(2);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Positional ids, plus `--ids a,b,c`. Every other flag (and its value) is skipped, so
|
|
85
|
+
// `--db <path>` can never be mistaken for a session id.
|
|
86
|
+
function parseIds() {
|
|
87
|
+
const argv = process.argv.slice(2);
|
|
88
|
+
const out = [];
|
|
89
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
90
|
+
const item = String(argv[i]);
|
|
91
|
+
if (item === '--ids') { out.push(...String(argv[i + 1] || '').split(',')); i += 1; continue; }
|
|
92
|
+
if (item === '--db') { i += 1; continue; }
|
|
93
|
+
if (item.startsWith('--')) continue;
|
|
94
|
+
out.push(item);
|
|
95
|
+
}
|
|
96
|
+
return [...new Set(out.map((id) => id.trim()).filter(Boolean))];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function tableExists(handle, table) {
|
|
100
|
+
try {
|
|
101
|
+
return !!handle.prepare('SELECT 1 FROM sqlite_master WHERE type = ? AND name = ?').get('table', table);
|
|
102
|
+
} catch {
|
|
103
|
+
return false; // expected: an unreadable/foreign sqlite file is "no such table" here, never a crash
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// The file that actually holds ctm_sessions + agent_sessions: the given file when it already does
|
|
108
|
+
// (an explicit core.db, or a pre-split task-manager.db), otherwise its flipped core sibling.
|
|
109
|
+
function resolveCorePath(basePath) {
|
|
110
|
+
const Database = loadSqliteDriver({ source: 'repair-resume-echo-titles' });
|
|
111
|
+
const probe = new Database(basePath, { readonly: true, fileMustExist: true });
|
|
112
|
+
try {
|
|
113
|
+
if (tableExists(probe, 'ctm_sessions')) return basePath;
|
|
114
|
+
} finally {
|
|
115
|
+
probe.close();
|
|
116
|
+
}
|
|
117
|
+
return resolveDomainDbPath('core', basePath, { flipped: true });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Demote + re-title ONE id. The write half runs in its own IMMEDIATE transaction, committed by
|
|
122
|
+
* --apply and rolled back otherwise — that is what makes a dry run print the real after-state
|
|
123
|
+
* while writing nothing. The transaction is opened AFTER the await, never across it: the tail read
|
|
124
|
+
* belongs to the read pool, and a write lock must not be held while waiting on another process.
|
|
125
|
+
*/
|
|
126
|
+
async function repairOne({ core, pool, basePath, id, apply }) {
|
|
127
|
+
const readRow = () => core.prepare(`SELECT ${ROW_COLUMNS} FROM ctm_sessions WHERE id = ?`).get(id);
|
|
128
|
+
const before = readRow();
|
|
129
|
+
if (!before) return { id, action: 'skip', reason: 'no_row' };
|
|
130
|
+
// Presence check only — the read op resolves the file itself, by the same "latest agent_sessions
|
|
131
|
+
// row" rule db.js's display path uses (a stitched continuation adds a row).
|
|
132
|
+
const transcript = String(core.prepare(`
|
|
133
|
+
SELECT jsonl_path FROM agent_sessions WHERE ctm_session_id = ?
|
|
134
|
+
ORDER BY COALESCE(NULLIF(modified_at, ''), updated_at, created_at) DESC,
|
|
135
|
+
updated_at DESC, created_at DESC
|
|
136
|
+
LIMIT 1
|
|
137
|
+
`).get(id)?.jsonl_path || '');
|
|
138
|
+
// 1. re-run the per-row backfill: the boot job's own read op, scoped to this id.
|
|
139
|
+
let signal = null;
|
|
140
|
+
if (transcript) {
|
|
141
|
+
const page = await pool.request('backfillAgentTitles', {
|
|
142
|
+
dbPath: basePath, ids: [id], limit: 1,
|
|
143
|
+
}, { timeoutMs: REQUEST_TIMEOUT_MS });
|
|
144
|
+
const row = (Array.isArray(page?.rows) ? page.rows : []).find((r) => r.id === id);
|
|
145
|
+
if (row) {
|
|
146
|
+
signal = {
|
|
147
|
+
customTitle: row.customTitle || '',
|
|
148
|
+
aiTitle: row.aiTitle || '',
|
|
149
|
+
tailBytes: row.tailBytes,
|
|
150
|
+
jsonlPath: String(row.jsonlPath || transcript),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// 2. demote (D-1: tier 1 off, tier 3 restated, so the applier below is allowed to write) and
|
|
155
|
+
// apply — one synchronous burst, no awaits.
|
|
156
|
+
core.exec('BEGIN IMMEDIATE');
|
|
157
|
+
let outcome = null;
|
|
158
|
+
let labelSynced = false;
|
|
159
|
+
let after = null;
|
|
160
|
+
try {
|
|
161
|
+
core.prepare(`
|
|
162
|
+
UPDATE ctm_sessions
|
|
163
|
+
SET user_renamed = 0, title_source = 'ai_title', title_status = 'generated',
|
|
164
|
+
title_status_reason = ?
|
|
165
|
+
WHERE id = ?
|
|
166
|
+
`).run(DEMOTE_REASON, id);
|
|
167
|
+
outcome = signal
|
|
168
|
+
? applyAgentTitleSignals(core, { ctmSessionId: id, customTitle: signal.customTitle, aiTitle: signal.aiTitle })
|
|
169
|
+
: { applied: 'none', reason: transcript ? 'no_title_signal' : 'no_transcript', reassert: false };
|
|
170
|
+
if (outcome.applied === 'agent' && outcome.title) {
|
|
171
|
+
// D-13: the restore label and the durable title must never disagree. This is a local repair —
|
|
172
|
+
// it does not enqueue the multi-machine sync outbox row, which db.js's own
|
|
173
|
+
// updateStartupTaskLabel owns (see lib/agent-title-apply.js's header).
|
|
174
|
+
labelSynced = core.prepare('UPDATE startup_tasks SET label = ? WHERE ctm_session_id = ?')
|
|
175
|
+
.run(outcome.title, id).changes > 0;
|
|
176
|
+
}
|
|
177
|
+
// The dry run's whole point: this read happens INSIDE the transaction, so a rolled-back run
|
|
178
|
+
// still reports the state --apply would have left behind.
|
|
179
|
+
after = readRow();
|
|
180
|
+
core.exec(apply ? 'COMMIT' : 'ROLLBACK');
|
|
181
|
+
} catch (error) {
|
|
182
|
+
try { core.exec('ROLLBACK'); } catch { /* expected: a failed statement may already have aborted the transaction */ }
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
id,
|
|
187
|
+
action: 'demoted+retitled',
|
|
188
|
+
// The file the read op actually read, which is the row's latest transcript.
|
|
189
|
+
transcript: signal?.jsonlPath || transcript,
|
|
190
|
+
tailBytes: signal?.tailBytes ?? null,
|
|
191
|
+
signal: signal ? { customTitle: signal.customTitle || '', aiTitle: signal.aiTitle || '' } : null,
|
|
192
|
+
apply: outcome.applied,
|
|
193
|
+
reason: outcome.reason,
|
|
194
|
+
title: outcome.title || null,
|
|
195
|
+
labelSynced,
|
|
196
|
+
before,
|
|
197
|
+
after,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function main() {
|
|
202
|
+
const ids = parseIds();
|
|
203
|
+
if (ids.length === 0) refuse('at least one session id is required (see the header comment)');
|
|
204
|
+
const apply = has('--apply');
|
|
205
|
+
const dbArg = String(arg('--db') || '').trim();
|
|
206
|
+
const basePath = path.resolve(dbArg || path.join(getDataDir(), 'task-manager.db'));
|
|
207
|
+
if (!fs.existsSync(basePath)) refuse(`db not found: ${basePath}`);
|
|
208
|
+
const corePath = resolveCorePath(basePath);
|
|
209
|
+
if (!fs.existsSync(corePath)) refuse(`core db not found: ${corePath} (resolved from ${basePath})`);
|
|
210
|
+
if (apply && !has('--force')) {
|
|
211
|
+
for (const candidate of [basePath, corePath]) {
|
|
212
|
+
if (!looksLive(candidate)) continue;
|
|
213
|
+
console.error(`ERROR: ${candidate} looks like a LIVE database (recent -wal writes).`);
|
|
214
|
+
console.error(' Stop CTM and re-run, work on a copy, or pass --force.');
|
|
215
|
+
process.exit(2);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const Database = loadSqliteDriver({ source: 'repair-resume-echo-titles' });
|
|
220
|
+
const core = new Database(corePath, {});
|
|
221
|
+
core.pragma('busy_timeout = 30000');
|
|
222
|
+
const pool = createReadPoolClient({ size: 1, requestTimeoutMs: REQUEST_TIMEOUT_MS, closeTimeoutMs: 2000 });
|
|
223
|
+
const counts = { repaired: 0, skipped: 0 };
|
|
224
|
+
try {
|
|
225
|
+
await pool.start();
|
|
226
|
+
for (const id of ids) {
|
|
227
|
+
const line = await repairOne({ core, pool, basePath, id, apply });
|
|
228
|
+
if (line.action === 'skip') counts.skipped += 1;
|
|
229
|
+
else counts.repaired += 1;
|
|
230
|
+
console.log(JSON.stringify({ ...line, dryRun: !apply }));
|
|
231
|
+
}
|
|
232
|
+
console.log(JSON.stringify({ summary: true, dryRun: !apply, ids: ids.length, ...counts }));
|
|
233
|
+
} finally {
|
|
234
|
+
try { await pool.close(); } catch { /* expected: the pool may never have started */ }
|
|
235
|
+
try { core.close(); } catch { /* expected: closing an already-closed handle is not an error here */ }
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (require.main === module) {
|
|
240
|
+
main()
|
|
241
|
+
.catch((error) => {
|
|
242
|
+
// REPORT: print the failure and exit 1; nothing is retried.
|
|
243
|
+
console.error(`[repair-resume-echo-titles] ${error && error.stack ? error.stack : String(error)}`);
|
|
244
|
+
process.exitCode = 1;
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
module.exports = { looksLive, parseIds, resolveCorePath, DEMOTE_REASON };
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// bin/seam-replay-codex.js -- the Codex lane of the reader seam replay harness (design
|
|
5
|
+
// seam-proof-r3-codex-render-parity-2026-10-20, WP-0 / §6).
|
|
6
|
+
//
|
|
7
|
+
// Offline and read-only. For every Codex CTM session that still has a painted-row store, replay the
|
|
8
|
+
// RECORDED live screen (a store `seal` record's `ref.rows`) against CTM's own render of the session's
|
|
9
|
+
// rollouts AS THEY STOOD AT THE SEAL TIME, through the shipped join, and print COUNTS as JSON:
|
|
10
|
+
//
|
|
11
|
+
// sessions Codex sessions (core.db: ctm_sessions.provider = 'codex') with at least one rollout on
|
|
12
|
+
// disk AND a store directory, that contributed at least one taken seal.
|
|
13
|
+
// screens seals that reached the join: the rollout rendered rows at the seal's cut, and the screen
|
|
14
|
+
// held MIN_LIVE_ROWS non-blank rows.
|
|
15
|
+
// unreplayable seals whose render threw, produced no rows, or whose screen was too small to be one.
|
|
16
|
+
// byKind what findJoin decided: proven / overlap / none.
|
|
17
|
+
// why for a `none`, landingDiag's own refusal (`noknown` / `cover` / `loss` / `cut` / ...).
|
|
18
|
+
// dup3 `none` screens whose live rows repeat >= DUP3_MIN transcript-window rows (the visibly
|
|
19
|
+
// duplicated population the design measures).
|
|
20
|
+
// headerRows screens whose first live row is Codex's own `› ` prompt header (WP-3 changes what
|
|
21
|
+
// the join does with it; this counts how often it is there).
|
|
22
|
+
// hiddenTextRowsNotOnScreen proofs whose rows [cut, end) hold an evidence row with a cut word the
|
|
23
|
+
// live screen does not show. Must be 0: it is the join's own safety claim.
|
|
24
|
+
//
|
|
25
|
+
// The render is the SHIPPED lib/raw-codex-history-window.js `getRawCodexHistoryWindow`, fed every
|
|
26
|
+
// rollout of the session with a `fileOps` whose `fstatSync` caps `size` at the seal's cut, so the
|
|
27
|
+
// window is what the rollout held at that moment. Nothing is ever written: no file is opened for
|
|
28
|
+
// writing, no copy of a rollout is made, and the core.db handle is opened read-only and closed before
|
|
29
|
+
// the replay starts.
|
|
30
|
+
//
|
|
31
|
+
// PRIVACY (hard, the same rule as bin/seam-replay.js): never prints row text, transcript lines, file
|
|
32
|
+
// paths, file contents or a full session id. The report is JSON numbers, enums and the fixed key names
|
|
33
|
+
// below -- nothing else.
|
|
34
|
+
|
|
35
|
+
const fs = require('node:fs');
|
|
36
|
+
const path = require('node:path');
|
|
37
|
+
const os = require('node:os');
|
|
38
|
+
const Database = require('better-sqlite3');
|
|
39
|
+
const Raw = require('../lib/raw-codex-history-window.js');
|
|
40
|
+
const RowJoin = require('../public/js/row-join.js');
|
|
41
|
+
// The store's PURE reader only (`parseBuffer` verifies every line's CRC and stops at the first bad
|
|
42
|
+
// one). `createPaintedRowStore` is never called from here, so nothing is written, pruned or swept.
|
|
43
|
+
const { parseBuffer } = require('../lib/painted-row-store.js');
|
|
44
|
+
|
|
45
|
+
const DEFAULT_PER = 40; // newest N seals per session (the same default as the Claude lane)
|
|
46
|
+
const MAX_ROWS = 400; // rows the renderer may return (design §6: 400 / 1 MiB)
|
|
47
|
+
const MAX_BYTES = 1 << 20;
|
|
48
|
+
const SCAN_CHUNK_BYTES = 1 << 24; // 16 MiB: rollouts can be large, so the timestamp scan is chunked
|
|
49
|
+
const STAMP_SCAN_BYTES = 120; // the `"timestamp"` field must be inside a line's first 120 bytes
|
|
50
|
+
const MIN_LIVE_ROWS = 3; // a screen with fewer non-blank live rows is not a screen
|
|
51
|
+
const DUP3_MIN = 3; // `landingDiag().dup >= 3` is the visibly-duplicated population
|
|
52
|
+
const SID_PREFIX = 8; // never used for printing a full id; kept for callers that need one
|
|
53
|
+
// 46 bytes, well over the 32 the cursor signer demands. Local to this script: a cursor is never
|
|
54
|
+
// round-tripped here (each seal starts a fresh `older` read), so the value only has to be constant.
|
|
55
|
+
const CURSOR_SECRET = 'seam-replay-codex-offline-replay-cursor-secret';
|
|
56
|
+
const STRETCH_FILE_RE = /^stretch-\d+\.jsonl$/; // one stretch of a painted-row store session
|
|
57
|
+
const CODEX_PROMPT_HEADER = /^› /u; // Codex's own presentation-only prompt header row
|
|
58
|
+
// The Codex sessions and their rollouts. Read-only; `jsonl_path <> ''` skips a session with no file.
|
|
59
|
+
const CODEX_SESSIONS_SQL = "select a.ctm_session_id sid, a.jsonl_path p from agent_sessions a "
|
|
60
|
+
+ "join ctm_sessions c on c.id = a.ctm_session_id "
|
|
61
|
+
+ "where c.provider = 'codex' and a.provider = 'codex' and a.jsonl_path <> '' order by a.jsonl_path";
|
|
62
|
+
|
|
63
|
+
const defaultStore = () => path.join(os.homedir(), '.walle', 'data', 'painted-rows');
|
|
64
|
+
const defaultCoreDb = () => path.join(os.homedir(), '.walle', 'data', 'core.db');
|
|
65
|
+
|
|
66
|
+
// --- text handling ---------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
// The Claude lane's `strip` (bin/seam-replay.js), which is byte for byte RowJoin.stripForCompare:
|
|
69
|
+
// SGR/CSI removed, U+00A0 folded to a space, trailing blanks trimmed. Delegating to the join's own
|
|
70
|
+
// copy keeps one truth (and findJoin strips every row it is handed with it anyway).
|
|
71
|
+
function strip(text) {
|
|
72
|
+
return RowJoin.stripForCompare(text);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function inc(counts, key) {
|
|
76
|
+
counts[key] = (counts[key] || 0) + 1;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// --- core.db (read-only) ---------------------------------------------------
|
|
80
|
+
|
|
81
|
+
// Codex CTM sessions with at least one rollout still on disk: sid -> [rollout path], in path order.
|
|
82
|
+
// The DB handle is opened read-only (`fileMustExist`) and closed before anything else runs; a session
|
|
83
|
+
// whose rollout file has gone is dropped here rather than counted.
|
|
84
|
+
function codexSessions(coreDb) {
|
|
85
|
+
const db = new Database(coreDb, { readonly: true, fileMustExist: true });
|
|
86
|
+
let pairs = [];
|
|
87
|
+
try {
|
|
88
|
+
pairs = db.prepare(CODEX_SESSIONS_SQL).all();
|
|
89
|
+
} finally {
|
|
90
|
+
db.close();
|
|
91
|
+
}
|
|
92
|
+
const bySid = new Map();
|
|
93
|
+
for (const row of pairs) {
|
|
94
|
+
const file = String(row.p == null ? '' : row.p);
|
|
95
|
+
if (!file || !fs.existsSync(file)) continue;
|
|
96
|
+
const sid = String(row.sid == null ? '' : row.sid);
|
|
97
|
+
const list = bySid.get(sid);
|
|
98
|
+
if (list) list.push(file);
|
|
99
|
+
else bySid.set(sid, [file]);
|
|
100
|
+
}
|
|
101
|
+
return bySid;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// --- the recorded screens --------------------------------------------------
|
|
105
|
+
|
|
106
|
+
// Every `seal` record of one session directory, in file order (`stretch-*.jsonl` sorted by name, so
|
|
107
|
+
// the oldest stretch first). Each line's CRC is verified by the store's own reader; a file that is
|
|
108
|
+
// missing, unreadable or torn past its first bad line contributes only what it verified.
|
|
109
|
+
function readSeals(dir) {
|
|
110
|
+
let names;
|
|
111
|
+
try {
|
|
112
|
+
names = fs.readdirSync(dir);
|
|
113
|
+
} catch {
|
|
114
|
+
return []; /* expected: the session directory went away between the store scan and the read */
|
|
115
|
+
}
|
|
116
|
+
const seals = [];
|
|
117
|
+
for (const name of names.filter((n) => STRETCH_FILE_RE.test(n)).sort()) {
|
|
118
|
+
let buffer;
|
|
119
|
+
try {
|
|
120
|
+
buffer = fs.readFileSync(path.join(dir, name));
|
|
121
|
+
} catch {
|
|
122
|
+
continue; /* expected: a stretch file unreadable between the listing and the read */
|
|
123
|
+
}
|
|
124
|
+
for (const object of parseBuffer(buffer).objects) {
|
|
125
|
+
if (object.op !== 'seal' || !object.ref || !Array.isArray(object.ref.rows)) continue;
|
|
126
|
+
if (typeof object.at !== 'number') continue;
|
|
127
|
+
seals.push(object);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return seals;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// The timestamp marks of one rollout: `[parsedMs, byteOffset]` for every line whose first
|
|
134
|
+
// STAMP_SCAN_BYTES bytes hold a `"timestamp":"..."` field, in file order, plus the file size. Read in
|
|
135
|
+
// SCAN_CHUNK_BYTES chunks with a carry, because a rollout can hold more than one chunk and the scan is
|
|
136
|
+
// per line, not per chunk. Read-only: the file is opened 'r' and closed before returning.
|
|
137
|
+
function timestampMarks(file) {
|
|
138
|
+
const fd = fs.openSync(file, 'r');
|
|
139
|
+
try {
|
|
140
|
+
const size = fs.fstatSync(fd).size;
|
|
141
|
+
const marks = [];
|
|
142
|
+
let carry = Buffer.alloc(0);
|
|
143
|
+
let base = 0;
|
|
144
|
+
for (let offset = 0; offset < size; offset += SCAN_CHUNK_BYTES) {
|
|
145
|
+
const chunk = Buffer.alloc(Math.min(SCAN_CHUNK_BYTES, size - offset));
|
|
146
|
+
fs.readSync(fd, chunk, 0, chunk.length, offset);
|
|
147
|
+
const buffer = carry.length ? Buffer.concat([carry, chunk]) : chunk;
|
|
148
|
+
let position = 0;
|
|
149
|
+
for (;;) {
|
|
150
|
+
const newline = buffer.indexOf(10, position);
|
|
151
|
+
if (newline < 0) break;
|
|
152
|
+
const line = buffer.subarray(position, Math.min(newline, position + STAMP_SCAN_BYTES)).toString('utf8');
|
|
153
|
+
const match = line.match(/"timestamp":"([^"]+)"/);
|
|
154
|
+
if (match) marks.push([Date.parse(match[1]), base + position]);
|
|
155
|
+
position = newline + 1;
|
|
156
|
+
}
|
|
157
|
+
carry = buffer.subarray(position);
|
|
158
|
+
base += position;
|
|
159
|
+
}
|
|
160
|
+
return { size, marks };
|
|
161
|
+
} finally {
|
|
162
|
+
fs.closeSync(fd);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// The byte offset a seal's view of a rollout ends at: the offset of the FIRST line whose timestamp is
|
|
167
|
+
// after the seal, else the whole file (the rollout never went further than the seal).
|
|
168
|
+
function cutFor(marks, size, at) {
|
|
169
|
+
for (const [ms, offset] of marks) if (ms > at) return offset;
|
|
170
|
+
return size;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Is `sid` a session the store still has a directory for? A store without one is simply not replayed.
|
|
174
|
+
function hasStoreDir(store, sid) {
|
|
175
|
+
try {
|
|
176
|
+
return fs.statSync(path.join(store, sid)).isDirectory();
|
|
177
|
+
} catch {
|
|
178
|
+
return false; /* expected: no store directory for this session (or it is not a directory) */
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// --- the join --------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
// The safety count (design §7): does a proof's hidden tail hold an EVIDENCE row with a cut word the
|
|
185
|
+
// live screen does not show? Rows, not words; nothing is printed.
|
|
186
|
+
function hidesEvidenceRow(rows, kinds, live, cut) {
|
|
187
|
+
const liveWords = new Set();
|
|
188
|
+
for (const row of live) for (const word of RowJoin.cutWordsOf(row)) liveWords.add(word);
|
|
189
|
+
for (let i = Math.max(0, cut); i < rows.length; i += 1) {
|
|
190
|
+
if (kinds && kinds[i] !== 'text' && kinds[i] !== 'prompt') continue;
|
|
191
|
+
if (RowJoin.cutWordsOf(strip(rows[i])).some((word) => !liveWords.has(word))) return true;
|
|
192
|
+
}
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Replay ONE seal: render the session's rollouts as they stood at `seal.at`, join the recorded screen
|
|
197
|
+
// against them, and tally. Mutates `report`; returns nothing. Counts and enums only, ever.
|
|
198
|
+
function replaySeal(report, sid, seal, scans) {
|
|
199
|
+
const cutOf = new Map();
|
|
200
|
+
for (const scan of scans) cutOf.set(scan.file, cutFor(scan.marks, scan.size, seal.at));
|
|
201
|
+
const sources = [];
|
|
202
|
+
for (const scan of scans) {
|
|
203
|
+
const cut = cutOf.get(scan.file);
|
|
204
|
+
if (cut > 0) sources.push({ jsonl_path: scan.file, provider: 'codex', size: cut, offset: cut });
|
|
205
|
+
}
|
|
206
|
+
// No rollout held a byte at the seal time: there is no screen to replay and no join to make.
|
|
207
|
+
if (!sources.length) return;
|
|
208
|
+
// The cut is a `fstat` size cap, nothing more: the window reads the real files (read-only) and stops
|
|
209
|
+
// at the sealed size. `fd` -> path is remembered so a copied stat can be capped per source.
|
|
210
|
+
const fdPath = new Map();
|
|
211
|
+
const fileOps = {
|
|
212
|
+
...fs,
|
|
213
|
+
openSync(file, ...args) {
|
|
214
|
+
const fd = fs.openSync(file, ...args);
|
|
215
|
+
fdPath.set(fd, String(file));
|
|
216
|
+
return fd;
|
|
217
|
+
},
|
|
218
|
+
fstatSync(fd) {
|
|
219
|
+
const stat = fs.fstatSync(fd);
|
|
220
|
+
const cut = cutOf.get(fdPath.get(fd));
|
|
221
|
+
return Object.assign(Object.create(Object.getPrototypeOf(stat)), stat, {
|
|
222
|
+
size: cut === undefined ? stat.size : Math.min(stat.size, cut),
|
|
223
|
+
});
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
let win;
|
|
227
|
+
try {
|
|
228
|
+
win = Raw.getRawCodexHistoryWindow(sources, {
|
|
229
|
+
sessionId: sid, provider: 'codex', cols: seal.ref.cols, maxRows: MAX_ROWS, maxBytes: MAX_BYTES, cursorSecret: CURSOR_SECRET,
|
|
230
|
+
}, fileOps);
|
|
231
|
+
} catch {
|
|
232
|
+
report.unreplayable += 1; /* expected: a rollout shape the renderer refuses (counted, never printed) */
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const items = win.items || [];
|
|
236
|
+
const rows = win.rows || items.map((item) => item.content);
|
|
237
|
+
if (!rows.length) {
|
|
238
|
+
report.unreplayable += 1; /* expected: a render that produced no rows (counted, never printed) */
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const kinds = items.length === rows.length ? items.map((item) => item.kind) : undefined;
|
|
242
|
+
const live = seal.ref.rows.slice(seal.ref.pinned || 0).map(strip);
|
|
243
|
+
if (live.filter((row) => row.trim()).length < MIN_LIVE_ROWS) {
|
|
244
|
+
report.unreplayable += 1; /* expected: a screen too small to be one (counted, never printed) */
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
report.screens += 1;
|
|
248
|
+
const found = RowJoin.findJoin({ transcriptRows: rows, transcriptKinds: kinds, painted: { from: 0, rows: [] }, liveRows: live, appBelow: 0 });
|
|
249
|
+
inc(report.byKind, found.kind);
|
|
250
|
+
if (found.kind === 'none') {
|
|
251
|
+
const diag = RowJoin.landingDiag(rows, live, kinds);
|
|
252
|
+
inc(report.why, diag.why);
|
|
253
|
+
if (diag.dup >= DUP3_MIN) report.dup3 += 1;
|
|
254
|
+
}
|
|
255
|
+
if (CODEX_PROMPT_HEADER.test(live[0] || '')) report.headerRows += 1;
|
|
256
|
+
if (found.kind !== 'none' && hidesEvidenceRow(rows, kinds, live, found.cut)) report.hiddenTextRowsNotOnScreen += 1;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// --- the run ---------------------------------------------------------------
|
|
260
|
+
|
|
261
|
+
// The whole `--codex` mode (design §6). Returns the report object; the counts are the only thing an
|
|
262
|
+
// operator reads, and the caller prints it as JSON. `opts`:
|
|
263
|
+
// per newest N seals per session (default DEFAULT_PER)
|
|
264
|
+
// until an ISO time (or epoch ms) -- seals at or after it are not taken; null = no limit
|
|
265
|
+
// store painted-rows root (default ~/.walle/data/painted-rows)
|
|
266
|
+
// coreDb CTM core database (default ~/.walle/data/core.db), opened READ-ONLY
|
|
267
|
+
function runCodex(opts) {
|
|
268
|
+
const options = opts || {};
|
|
269
|
+
const per = Number(options.per) > 0 ? Math.floor(Number(options.per)) : DEFAULT_PER;
|
|
270
|
+
const until = options.until === null || options.until === undefined || options.until === ''
|
|
271
|
+
? null
|
|
272
|
+
: (typeof options.until === 'number' ? options.until : Date.parse(String(options.until)));
|
|
273
|
+
if (until !== null && !Number.isFinite(until)) throw new TypeError('--until must be an ISO time');
|
|
274
|
+
const report = {
|
|
275
|
+
mode: 'codex',
|
|
276
|
+
until: until === null ? null : new Date(until).toISOString(),
|
|
277
|
+
per,
|
|
278
|
+
sessions: 0,
|
|
279
|
+
screens: 0,
|
|
280
|
+
unreplayable: 0,
|
|
281
|
+
byKind: { proven: 0, overlap: 0, none: 0 },
|
|
282
|
+
why: {},
|
|
283
|
+
dup3: 0,
|
|
284
|
+
headerRows: 0,
|
|
285
|
+
hiddenTextRowsNotOnScreen: 0,
|
|
286
|
+
};
|
|
287
|
+
const store = options.store ? String(options.store) : defaultStore();
|
|
288
|
+
for (const [sid, paths] of codexSessions(options.coreDb ? String(options.coreDb) : defaultCoreDb())) {
|
|
289
|
+
if (!hasStoreDir(store, sid)) continue;
|
|
290
|
+
const seals = readSeals(path.join(store, sid))
|
|
291
|
+
.filter((seal) => until === null || seal.at <= until)
|
|
292
|
+
.slice(-per);
|
|
293
|
+
if (!seals.length) continue;
|
|
294
|
+
report.sessions += 1;
|
|
295
|
+
const scans = paths.map((file) => Object.assign({ file }, timestampMarks(file)));
|
|
296
|
+
for (const seal of seals) replaySeal(report, sid, seal, scans);
|
|
297
|
+
}
|
|
298
|
+
return report;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
module.exports = { runCodex, defaultStore, defaultCoreDb, DEFAULT_PER, MAX_ROWS, MAX_BYTES, SID_PREFIX };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// AC-1h (coding-task-manager/docs/fsevents-churn-kernel-write-lock-2026-10-23.html §8): measure
|
|
5
|
+
// the cost of one UNCONTENDED locked write through the installed module, on a throwaway temp
|
|
6
|
+
// database, and print the distribution. The pre-WP-1 link-claim mechanism measured 913–2,797 µs
|
|
7
|
+
// per cycle; the kernel-backed protocol's budget is p50 ≤ 100 µs.
|
|
8
|
+
//
|
|
9
|
+
// node bin/write-lock-bench.js --cycles 5000
|
|
10
|
+
//
|
|
11
|
+
// This is a measurement, not a gate: it always exits 0 and prints whether the budget was met.
|
|
12
|
+
// The cycle measured is a full `createSqliteWriteLock(...)(fn)` round trip — kernel BEGIN
|
|
13
|
+
// EXCLUSIVE, the fence, the 1,024-byte in-place identity write, and the COMMIT — i.e. everything
|
|
14
|
+
// a wrapped write pays before and after the caller's own statement.
|
|
15
|
+
//
|
|
16
|
+
// Adapted from the WP-0 exploration harness (scratchpad/lockexp/mech.js), which compared the
|
|
17
|
+
// candidate mechanisms; this file keeps only the shipped one, so the numbers stay comparable to
|
|
18
|
+
// the ones quoted in the design doc.
|
|
19
|
+
|
|
20
|
+
const fs = require('node:fs');
|
|
21
|
+
const os = require('node:os');
|
|
22
|
+
const path = require('node:path');
|
|
23
|
+
const Database = require('better-sqlite3');
|
|
24
|
+
|
|
25
|
+
const { createSqliteWriteLock, closeSqliteWriteLocksForTests } = require('../../shared/sqlite-write-lock');
|
|
26
|
+
|
|
27
|
+
const AC_1H_P50_BUDGET_US = 100;
|
|
28
|
+
const DEFAULT_CYCLES = 2000;
|
|
29
|
+
|
|
30
|
+
function parseCycles(argv) {
|
|
31
|
+
const index = argv.indexOf('--cycles');
|
|
32
|
+
if (index === -1) return DEFAULT_CYCLES;
|
|
33
|
+
const raw = argv[index + 1];
|
|
34
|
+
const value = Number(raw);
|
|
35
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
36
|
+
console.error(`write-lock-bench: --cycles needs a positive integer, got ${JSON.stringify(raw)}`);
|
|
37
|
+
process.exit(2);
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Nearest-rank percentile over an ascending array.
|
|
43
|
+
function percentile(sorted, p) {
|
|
44
|
+
if (sorted.length === 0) return 0;
|
|
45
|
+
const rank = Math.ceil((p / 100) * sorted.length);
|
|
46
|
+
return sorted[Math.min(sorted.length - 1, Math.max(0, rank - 1))];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function main() {
|
|
50
|
+
const cycles = parseCycles(process.argv.slice(2));
|
|
51
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'write-lock-bench-'));
|
|
52
|
+
const lockPath = path.join(dir, 'bench.db.write-lock');
|
|
53
|
+
const lock = createSqliteWriteLock(lockPath, { Database, label: 'bench' });
|
|
54
|
+
const warmup = Math.min(200, Math.max(1, Math.floor(cycles / 10)));
|
|
55
|
+
const samples = new Float64Array(cycles);
|
|
56
|
+
const hold = [];
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// Warmup absorbs the one-time costs (lock DB creation, the record's first write, the
|
|
60
|
+
// journal's first open) so the distribution describes steady state, which is what AC-1h
|
|
61
|
+
// compares against the baseline.
|
|
62
|
+
for (let i = 0; i < warmup; i += 1) lock(() => {}, 'bench');
|
|
63
|
+
for (let i = 0; i < cycles; i += 1) {
|
|
64
|
+
const started = process.hrtime.bigint();
|
|
65
|
+
lock(() => {}, 'bench');
|
|
66
|
+
samples[i] = Number(process.hrtime.bigint() - started) / 1000;
|
|
67
|
+
}
|
|
68
|
+
} finally {
|
|
69
|
+
closeSqliteWriteLocksForTests();
|
|
70
|
+
try {
|
|
71
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
72
|
+
} catch {
|
|
73
|
+
/* expected: best-effort removal of the throwaway bench directory; a leaked temp dir must
|
|
74
|
+
not turn a successful measurement into a failed run. */
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const sorted = Float64Array.prototype.slice.call(samples).sort((a, b) => a - b);
|
|
79
|
+
const total = sorted.reduce((sum, value) => sum + value, 0);
|
|
80
|
+
const p50 = percentile(sorted, 50);
|
|
81
|
+
const report = {
|
|
82
|
+
// The mechanism is named without its lock-path literal on purpose: that literal is confined to
|
|
83
|
+
// shared/sqlite-write-lock.js by bin/lint-shared-copy-parity.js (E-2), because an unrelated
|
|
84
|
+
// open()+close() on it drops a live holder's POSIX lock (D-6).
|
|
85
|
+
mechanism: 'kernel write lock (BEGIN EXCLUSIVE on the module\'s lock DB, protocol 2)',
|
|
86
|
+
cycles,
|
|
87
|
+
warmupCycles: warmup,
|
|
88
|
+
platform: `${process.platform}/${process.arch} node ${process.version}`,
|
|
89
|
+
p50us: Number(p50.toFixed(1)),
|
|
90
|
+
p95us: Number(percentile(sorted, 95).toFixed(1)),
|
|
91
|
+
p99us: Number(percentile(sorted, 99).toFixed(1)),
|
|
92
|
+
minUs: Number(sorted[0].toFixed(1)),
|
|
93
|
+
maxUs: Number(sorted[sorted.length - 1].toFixed(1)),
|
|
94
|
+
meanUs: Number((total / sorted.length).toFixed(1)),
|
|
95
|
+
budgetP50us: AC_1H_P50_BUDGET_US,
|
|
96
|
+
withinBudget: p50 <= AC_1H_P50_BUDGET_US,
|
|
97
|
+
};
|
|
98
|
+
console.log(`[write-lock-bench] ${report.platform}`);
|
|
99
|
+
console.log(
|
|
100
|
+
`[write-lock-bench] cycles=${report.cycles} (warmup ${report.warmupCycles}) `
|
|
101
|
+
+ `p50=${report.p50us}µs p95=${report.p95us}µs p99=${report.p99us}µs `
|
|
102
|
+
+ `min=${report.minUs}µs max=${report.maxUs}µs mean=${report.meanUs}µs`,
|
|
103
|
+
);
|
|
104
|
+
console.log(
|
|
105
|
+
`[write-lock-bench] AC-1h budget p50 <= ${report.budgetP50us}µs: `
|
|
106
|
+
+ `${report.withinBudget ? 'MET' : 'MISSED'} (baseline link-claim: 913–2,797µs)`,
|
|
107
|
+
);
|
|
108
|
+
console.log(JSON.stringify(report));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
main();
|