elanous 0.2.25 → 0.2.28
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/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/{1454-37fd16f479f5554a.js → 1454-29d1d773d5996a49.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{599-a914cc0468f56143.js → 599-56f2c0a5cc4a7295.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-5ab1e47978577c5e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-24453f6a302816b4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-6c131d30bb1cf871.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-b12149c51f7d04b1.js → page-fa46d4588e8442c2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-dc0772b11861af51.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-66ad46ed0279feb9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-5159dc8752c6cd29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-c5a2478861c08b87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-f722a9dec084e08b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/{page-305cd294e754906e.js → page-256ffd365d32e100.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-dea4b795741126fd.js → page-e0de78ae7b99f01c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-cfbdc44b708d9801.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-45889e7070d11126.js → page-4c741eb760f2f77c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-16a2e893f83ae4d0.js → page-e9ba98f5f1440a21.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-dafc901bb1b72b5f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-56888bc744e08dac.js → page-2f6f303fa114ec24.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-1634081c5272006c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/{page-952c6ceec6d9de38.js → page-ced0bcfc6542b3fc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/{page-6d0202af568f4c63.js → page-bb1f05cd51c38944.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-646b50f63b6fe732.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-db069b48a59c8c00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-4fb6fc34e20c07b7.js → page-f736324afa2117c1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-e9502921f4f01b1f.js → page-6f131b51193a36f9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-b6169ea2f09cf287.js → not-found-435f5e2faf02bc01.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/{page-6a14333d1b1b3967.js → page-497e0eedf20e7890.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-8c91a9a66c9ec90a.js → page-a8d28fdf995e16ea.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8966452228753c5e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fd5d18656cb432b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-5663779cb1a37609.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/{page-3d3fc73dd187ed52.js → page-66a67eb19ebd976b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-3b492aa78cf3c4a6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-9c04dac0831c2bbb.js → page-c71267e7681da982.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-a21d1e1797172692.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-04de9ca6d52a3893.js → page-cb249e81ed339318.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-62527677bbc2fdc4.js → page-781cfc5114e097da.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-9a2e09c4a311764c.js → page-478157214db6a023.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-bfeb35d69e8b24bb.js → page-86141f12cf090b7f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/{page-89cb33bced824e4a.js → page-7256c73bb1c36751.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-5c738ca7adef3de5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-0138c8091ec17224.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-cc2f24f16d85e735.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/{page-6445ac4cbb8dd869.js → page-979d318a534e92da.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-182d394f7e6e096b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-21050928a697ed7a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-44b9d807005a9030.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-20162174b77f96cf.js → page-601fb77340e63c9a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-75d3fb5d47af0edf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-05a4b2c9b91034aa.js → page-11bf9e5725a72c22.js} +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +13 -13
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +13 -13
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +13 -13
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +13 -13
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +13 -13
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +13 -13
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +13 -13
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +13 -13
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +13 -13
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +13 -13
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +13 -13
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +13 -13
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +13 -13
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +13 -13
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +13 -13
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +13 -13
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +13 -13
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +13 -13
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +13 -13
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +13 -13
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +13 -13
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +13 -13
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +13 -13
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +13 -13
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +13 -13
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +13 -13
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +13 -13
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +13 -13
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +14 -14
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +13 -13
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +13 -13
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +13 -13
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +13 -13
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +13 -13
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +13 -13
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +13 -13
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +13 -13
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +13 -13
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +13 -13
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +13 -13
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +13 -13
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +13 -13
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +13 -13
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +13 -13
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +13 -13
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +13 -13
- package/graphs/implement-loop.yaml +38 -1
- package/graphs/steward/recipes.yaml +6 -0
- package/graphs/steward/release-day.yaml +23 -0
- package/graphs/steward/steward.yaml +9 -1
- package/graphs/work/code-pr.yaml +23 -0
- package/package.json +1 -1
- package/scripts/browser-observe-cycle.ts +76 -2
- package/scripts/ci-typecheck-changed.ts +119 -7
- package/scripts/coord-msg-bridge.ts +209 -0
- package/scripts/coord-post.sh +40 -7
- package/scripts/coord-requests-bridge.ts +10 -5
- package/scripts/detect-test-interference.ts +31 -1
- package/scripts/expose-rubric-suggest.ts +1 -1
- package/scripts/measure-tsc-affected-roots.ts +10 -1
- package/scripts/release-loop/docs-node.ts +51 -22
- package/scripts/release-loop/export-check-node.ts +8 -5
- package/scripts/release-loop/gate-node.ts +37 -4
- package/scripts/release-loop/node-verdict.ts +10 -3
- package/scripts/release-loop/publish-node.ts +3 -0
- package/scripts/release-loop/tui-node-checks.ts +5 -2
- package/scripts/skills/public-check.ts +5 -2
- package/scripts/test-deterministic.ts +520 -37
- package/scripts/ux-sim.ts +7 -1
- package/scripts/verify-kpack6-live.ts +230 -0
- package/src/acp/codex-plugins.ts +17 -0
- package/src/agent/task-notification.ts +32 -2
- package/src/agent-mission/in-agent-plugin.ts +8 -3
- package/src/agent-substrate/review-observation.ts +9 -2
- package/src/agent-substrate/self-review-cli.ts +62 -16
- package/src/boot/daemon-tools/self-implement.ts +72 -4
- package/src/briefing/brief-items.ts +151 -35
- package/src/budget/llm-cost.ts +30 -3
- package/src/budget/usage-outcomes.ts +32 -0
- package/src/claims/claims-ledger.ts +23 -0
- package/src/claims/claims-render.ts +22 -2
- package/src/cli/brief-cli.ts +131 -10
- package/src/cli/claims-cli.ts +24 -4
- package/src/cli/control-cli.ts +29 -0
- package/src/cli/doctor-cli.ts +90 -8
- package/src/cli/msg-cli.ts +177 -11
- package/src/cli/plugin-cli.ts +11 -3
- package/src/cli/pr-cli.ts +133 -18
- package/src/cli/pr-land-overlap-evidence.ts +18 -10
- package/src/cli/pr-land-refusal.ts +20 -0
- package/src/cli/release-cli.ts +34 -5
- package/src/cli/rfc-cli.ts +24 -0
- package/src/cli/seat-cli.ts +121 -0
- package/src/cli/self-utterances-cli.ts +140 -0
- package/src/cli/tailscale-serve.ts +5 -8
- package/src/cli/tasks-cli.ts +528 -31
- package/src/cli/usage-cli.ts +9 -4
- package/src/context-card/cell.ts +40 -0
- package/src/dashboard/boot-timing.ts +35 -0
- package/src/dashboard/first-screen-band.ts +63 -13
- package/src/dashboard/index.ts +94 -27
- package/src/dashboard/input/chat-main-plain-turn-runtime.ts +26 -0
- package/src/dashboard/skill-detection-runtime.ts +24 -4
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +6 -0
- package/src/debug/log.ts +1 -1
- package/src/domains/morning-report.ts +3 -1
- package/src/domains/release-tool.ts +29 -12
- package/src/domains/surface-events.ts +16 -0
- package/src/elanous-config-dir.ts +2 -2
- package/src/fleet/host-doctor.ts +73 -2
- package/src/github/pr-labels.ts +10 -4
- package/src/graph-runner/runner.ts +104 -15
- package/src/harness/harness-clean.ts +21 -3
- package/src/harness/harness-cli-command.ts +68 -10
- package/src/harness/harness-pod-dispatch.ts +11 -4
- package/src/harness/harness-stop.ts +11 -7
- package/src/hq/seats.ts +17 -3
- package/src/index.ts +69 -9
- package/src/instance/current.ts +2 -6
- package/src/instance/resolve.ts +5 -6
- package/src/intelligence-map/model-bench-scores.ts +102 -0
- package/src/llm/openai-sampling-fields.ts +89 -0
- package/src/llm/runtime-model-family.ts +61 -0
- package/src/llm/zai-coding-plan.ts +27 -1
- package/src/llm.ts +13 -89
- package/src/loops/orchestrator/inbox.ts +73 -0
- package/src/loops/orchestrator/resync-shadow.ts +100 -12
- package/src/loops/orchestrator/tick.ts +73 -2
- package/src/machines/machine-ledger.ts +31 -3
- package/src/maturity/cli-maturity.ts +13 -3
- package/src/maturity/feature-maturity.ts +1 -1
- package/src/msg/msg-sse-server.ts +141 -0
- package/src/msg/msg-store.ts +149 -17
- package/src/ops/overview-collect.ts +3 -2
- package/src/ops/overview-owner.ts +29 -0
- package/src/patents/ledger.ts +128 -0
- package/src/plugin-hooks/shell-hook.ts +17 -2
- package/src/plugin-missions/evaluator-runner.ts +18 -2
- package/src/plugins/install/market-fetch.ts +32 -1
- package/src/plugins/install/plugin-install.ts +11 -1
- package/src/pod-lease/admission.ts +116 -41
- package/src/prompt-enhance/enhance.ts +11 -0
- package/src/release-loop/checklist.ts +52 -4
- package/src/release-loop/feature-store.ts +10 -9
- package/src/release-loop/merged-pr-checklist.ts +24 -5
- package/src/release-loop/rubric.ts +70 -0
- package/src/rfc-registry/registry.ts +88 -0
- package/src/seat-dispatch/seat-request-ledger.ts +4 -0
- package/src/seat-loop/knowhow-lint.ts +233 -0
- package/src/seat-loop/seat-loop.ts +5 -1
- package/src/seat-loop/seat-pack.ts +16 -4
- package/src/seat-loop/traffic-signal.ts +7 -0
- package/src/self-dev/ask-launch-flow.ts +48 -8
- package/src/self-dev/ask-path-candidates.ts +69 -14
- package/src/self-dev/decompose-proposal.ts +43 -1
- package/src/self-dev/draft-sweep.ts +23 -14
- package/src/self-dev/graph-journey-nodes.ts +2 -0
- package/src/self-dev/launch-preflight-text.ts +5 -1
- package/src/self-dev/launch-preflight.ts +143 -10
- package/src/self-dev/orchestrate.ts +6 -2
- package/src/self-dev/run-supervisor.ts +35 -1
- package/src/self-implement/gate-cli.ts +6 -2
- package/src/self-implement/goal-author.ts +5 -28
- package/src/self-implement/goal-command-execution.ts +26 -0
- package/src/self-implement/goal-new-paths.ts +4 -5
- package/src/self-implement/graph-authority.ts +17 -3
- package/src/self-implement/graph-yaml.ts +15 -7
- package/src/self-implement/headless-elanous-driver.ts +16 -1
- package/src/self-implement/host-regate.ts +44 -9
- package/src/self-implement/orchestrator.ts +5 -11
- package/src/self-implement/pwa-import-graph.ts +194 -7
- package/src/self-implement/rework-policy.ts +80 -2
- package/src/self-implement/self-implement-runtime.ts +126 -21
- package/src/self-implement/speed-claims.ts +145 -6
- package/src/session/index.ts +14 -4
- package/src/session-runtime/index.ts +55 -11
- package/src/skills/tools/persistent-grounding.ts +59 -4
- package/src/skills/tools/shell.ts +79 -1
- package/src/steward/rejudge-tick.ts +12 -1
- package/src/steward/release-day.ts +158 -0
- package/src/steward/resync-tick.ts +123 -0
- package/src/task-agent/actions.ts +1 -0
- package/src/task-agent/adopt-pr.ts +140 -0
- package/src/task-agent/board.ts +11 -1
- package/src/task-agent/card-evidence.ts +37 -5
- package/src/task-agent/cover.ts +105 -19
- package/src/task-agent/failure-notify.ts +61 -0
- package/src/task-agent/green-sweep.ts +170 -0
- package/src/task-agent/land-worktree.ts +52 -5
- package/src/task-agent/live-move-history.ts +1 -0
- package/src/task-agent/live-moves.ts +56 -17
- package/src/task-agent/parent-host.ts +1 -1
- package/src/task-agent/postland-card.ts +139 -0
- package/src/task-agent/rejudge-on-head.ts +321 -25
- package/src/task-agent/remote-rejudge-bind.ts +62 -0
- package/src/task-agent/repair-branch-cas.ts +182 -0
- package/src/task-agent/repair-cycle.ts +275 -0
- package/src/task-agent/repair-next-move.ts +31 -0
- package/src/task-agent/repair-review.ts +24 -0
- package/src/task-agent/repair-worker.ts +87 -0
- package/src/task-agent/review-result-capture.ts +66 -2
- package/src/task-agent/shadow.ts +150 -29
- package/src/task-agent/task-hand.ts +133 -8
- package/src/task-orchestrator/surfaces/pod-repair-commit.ts +96 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +71 -12
- package/src/telegram.ts +30 -1
- package/src/tool-runtime/tool-search-runtime.ts +25 -0
- package/src/tui.ts +20 -1
- package/src/user-config.ts +11 -60
- package/src/ux-sim/fault-sqlite-busy.ts +50 -0
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-9360b1285b2a346e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f08f6529d09ffae1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-f000d54d258f9ef6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-22f674e7e4c2c9d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-8888733e40cea6d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-9717ec6964b92276.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-44eaad486bc98a45.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-97fa6cf3d34c7eb9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-35e05d7450461a67.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-b2638013c0bf7dfd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-c44f93a41167536f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-1b810474507f3702.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-74366ae2693a7d95.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4e634c5b98fd4e93.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ed3070d637850548.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-b9cc8a10bc6078b4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-128458c8d86fbb5f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-cf0c2b0183991d09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-b008aff96be56654.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-814394dcbae789b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-6a87325c87f09dd2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-852170303c6cd755.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-087ceeb975ef272b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-97c7361cc2a19319.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-8c6901833f897496.js +0 -1
- /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → y1ltt7oCZxVpC6zVPhZ2O}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → y1ltt7oCZxVpC6zVPhZ2O}/_ssgManifest.js +0 -0
|
@@ -4,6 +4,7 @@ import { getUserConfig } from '../user-config.js';
|
|
|
4
4
|
import { resolve } from 'node:path';
|
|
5
5
|
import type { ModelFamily } from '../models/prompts.js';
|
|
6
6
|
import type { InputSourceKind, InputSourceRef } from '../input/input-source-kind.js';
|
|
7
|
+
import type { ControlTurnIntent, SubmitTurnIntent } from '../input/input-intent.js';
|
|
7
8
|
import { getSessionCwd } from '../session/working-dir.js';
|
|
8
9
|
import {
|
|
9
10
|
armQuickControlOnce,
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
import type { LLMMessage, LLMToolSpec } from '../llm.js';
|
|
23
24
|
import type { LLMToolDef } from '../plugins/core/types.js';
|
|
24
25
|
import type { ToolRunResult, ToolRuntime } from '../tool-runtime/types.js';
|
|
26
|
+
import { blockWriteToolSearchHydration, type ToolSearchRunResult } from '../tool-runtime/tool-search-runtime.js';
|
|
25
27
|
import { findNativeTool, isNativeToolModelExposed } from '../native-tool-catalog.js';
|
|
26
28
|
import { hasAstGrep } from '../skills/tools/ast-grep.js';
|
|
27
29
|
import { buildOmniSearchTool, omniSearchAvailable } from '../skills/tools/omni-search.js';
|
|
@@ -1731,6 +1733,8 @@ const ESSENTIAL_NATIVE_RULE_IDS = [
|
|
|
1731
1733
|
'agent', 'agent-output', 'agent-reply', 'agent-stop', 'agent-list', 'self-implement', 'self-orchestrate', 'dev-harness',
|
|
1732
1734
|
] as const;
|
|
1733
1735
|
const ESSENTIAL_HOST_FAMILY_IDS = ['self-ops', 'release-read'] as const;
|
|
1736
|
+
// Keep this TUI essential addition out of the shared control/agent self-ops family.
|
|
1737
|
+
const ESSENTIAL_HOST_EXTRA_NAMES = ['logs_query'] as const;
|
|
1734
1738
|
const ESSENTIAL_RUNTIME_FAMILY_IDS = ['bash', 'run-shell', 'pty-shell', 'run-tests', 'elanous_skills_list', 'skill_exec', 'code-grounding'] as const;
|
|
1735
1739
|
const ESSENTIAL_OPTIONAL_TOOL_NAMES = [
|
|
1736
1740
|
'GetDashboardState',
|
|
@@ -1764,7 +1768,11 @@ function buildEssentialSessionRuntimeToolSpecs(opts: {
|
|
|
1764
1768
|
.filter((tool): tool is LLMToolDef => !!tool)
|
|
1765
1769
|
.map(toSpec)
|
|
1766
1770
|
: [];
|
|
1767
|
-
})
|
|
1771
|
+
}).concat(ESSENTIAL_HOST_EXTRA_NAMES
|
|
1772
|
+
.filter(name => isToolAvailable(name, opts.toolAvailability))
|
|
1773
|
+
.map(name => hostByName.get(name))
|
|
1774
|
+
.filter((tool): tool is LLMToolDef => !!tool)
|
|
1775
|
+
.map(toSpec));
|
|
1768
1776
|
const runtimeById = new Map(opts.runtimeTools.map(tool => [tool.id, tool] as const));
|
|
1769
1777
|
const runtimeSpecs = ESSENTIAL_RUNTIME_FAMILY_IDS.flatMap(id => {
|
|
1770
1778
|
const rule = RUNTIME_TOOL_FAMILY_RULE_MAP.get(id);
|
|
@@ -1833,12 +1841,23 @@ export function buildSessionRuntimeToolSpecs(opts: {
|
|
|
1833
1841
|
// toolset (incident 2026-05-04 — Opus replied "no file tools in this
|
|
1834
1842
|
// session" while Bash was actually exposed at position 7+).
|
|
1835
1843
|
const finalSpecs: LLMToolSpec[] = opts.rich === false
|
|
1836
|
-
?
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1844
|
+
? (() => {
|
|
1845
|
+
const essentialSpecs = buildEssentialSessionRuntimeToolSpecs({
|
|
1846
|
+
hostTools: opts.hostTools,
|
|
1847
|
+
runtimeTools: opts.runtimeTools ?? [],
|
|
1848
|
+
optionalTools: opts.optionalTools ?? [],
|
|
1849
|
+
toolAvailability: opts.toolAvailability,
|
|
1850
|
+
});
|
|
1851
|
+
if (!hasDevHarnessIntent(opts.userText)) return essentialSpecs;
|
|
1852
|
+
const droppedNames = new Set(['Edit', 'Write']);
|
|
1853
|
+
const filteredSpecs = essentialSpecs.filter(spec => !droppedNames.has(spec.name));
|
|
1854
|
+
if (filteredSpecs.length !== essentialSpecs.length) {
|
|
1855
|
+
debug.log('session-runtime.tools', 'harness-phrase-write-tools-dropped', {
|
|
1856
|
+
dropped: ['Edit', 'Write'],
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
return filteredSpecs;
|
|
1860
|
+
})()
|
|
1842
1861
|
: [
|
|
1843
1862
|
...dynamicNativeTools,
|
|
1844
1863
|
...hostSpecs,
|
|
@@ -2084,6 +2103,10 @@ export async function dispatchSessionRuntimeTool(
|
|
|
2084
2103
|
args: Record<string, unknown>,
|
|
2085
2104
|
deps: SessionRuntimeDispatchDeps,
|
|
2086
2105
|
): Promise<unknown> {
|
|
2106
|
+
const finishRuntimeResult = (result: unknown): unknown =>
|
|
2107
|
+
name === 'ToolSearch' && isDevHarnessIntent(deps.userText ?? '')
|
|
2108
|
+
? blockWriteToolSearchHydration(result as ToolSearchRunResult)
|
|
2109
|
+
: result;
|
|
2087
2110
|
// Fix W-bis (2026-04-25): codex family always enables scoped-analysis
|
|
2088
2111
|
// mode regardless of userText vocabulary. Rationale (see docs/RESEARCH-
|
|
2089
2112
|
// codex-reread-pathology-2refs-2026-04-25.md): codex's search-heavy
|
|
@@ -2178,8 +2201,9 @@ export async function dispatchSessionRuntimeTool(
|
|
|
2178
2201
|
const nativeRuntime = nativeRule?.preferRuntime ? deps.getToolRuntime(name) : undefined;
|
|
2179
2202
|
if (nativeRuntime) {
|
|
2180
2203
|
const result = await deps.dispatchToolRuntime(name, normalizedArgs, deps.toolCallId);
|
|
2181
|
-
|
|
2182
|
-
|
|
2204
|
+
const safeResult = finishRuntimeResult(result);
|
|
2205
|
+
rememberCandidateListingResult(name, normalizedArgs, safeResult, scopedAnalysis, deps.searchPlannerState);
|
|
2206
|
+
return safeResult;
|
|
2183
2207
|
}
|
|
2184
2208
|
if (nativeRule?.dispatch) {
|
|
2185
2209
|
let result: unknown;
|
|
@@ -2213,8 +2237,9 @@ export async function dispatchSessionRuntimeTool(
|
|
|
2213
2237
|
const isPtyTool = name.startsWith('PtyShell') || name.startsWith('pty_shell');
|
|
2214
2238
|
if (!isPtyTool || deps.ptyDashboardOn) {
|
|
2215
2239
|
const result = await deps.dispatchToolRuntime(name, normalizedArgs, deps.toolCallId);
|
|
2216
|
-
|
|
2217
|
-
|
|
2240
|
+
const safeResult = finishRuntimeResult(result);
|
|
2241
|
+
rememberCandidateListingResult(name, normalizedArgs, safeResult, scopedAnalysis, deps.searchPlannerState);
|
|
2242
|
+
return safeResult;
|
|
2218
2243
|
}
|
|
2219
2244
|
}
|
|
2220
2245
|
|
|
@@ -2699,6 +2724,25 @@ function hasDevHarnessIntent(text: string): boolean {
|
|
|
2699
2724
|
return hasAny(text, DEV_HARNESS_INTENT_TERMS);
|
|
2700
2725
|
}
|
|
2701
2726
|
|
|
2727
|
+
export function isDevHarnessIntent(text: string): boolean {
|
|
2728
|
+
return hasDevHarnessIntent(text);
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
/** The dashboard calls this immediately after submitIntent resolution. A
|
|
2732
|
+
* fixed plain turn becomes the same control turn used by `/harness ask`, so
|
|
2733
|
+
* the model dispatch branch is never reached for that turn. */
|
|
2734
|
+
export function routeDevHarnessSubmitIntent(
|
|
2735
|
+
intent: SubmitTurnIntent | ControlTurnIntent,
|
|
2736
|
+
): SubmitTurnIntent | ControlTurnIntent {
|
|
2737
|
+
if (intent.kind !== 'submit-turn' || intent.route !== 'plain' || !isDevHarnessIntent(intent.text)) return intent;
|
|
2738
|
+
return {
|
|
2739
|
+
kind: 'control-turn',
|
|
2740
|
+
source: intent.source,
|
|
2741
|
+
command: 'slash-command',
|
|
2742
|
+
commandText: `/harness ask ${intent.text}`,
|
|
2743
|
+
};
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2702
2746
|
export function shouldExposeDevHarnessSessionTool(text: string): boolean {
|
|
2703
2747
|
return isDevHarnessModelSurfaceEnabled() && hasDevHarnessIntent(text);
|
|
2704
2748
|
}
|
|
@@ -212,6 +212,26 @@ function groundingMessages(goal: string, terms: readonly string[]): LLMMessage[]
|
|
|
212
212
|
}];
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
function unreadEvidencePaths(evidence: string, readFiles: ReadonlySet<string>): string[] {
|
|
216
|
+
return Array.from(new Set(evidence.split(/\r?\n/)
|
|
217
|
+
.flatMap(repositoryRelativeCodePaths)
|
|
218
|
+
.filter((path) => isCodeRootPath(path) && !readFiles.has(path)))).slice(0, 8);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function rejectedEvidenceFeedback(evidence: string, readFiles: ReadonlySet<string>): LLMMessage {
|
|
222
|
+
const unread = unreadEvidencePaths(evidence, readFiles);
|
|
223
|
+
const read = Array.from(readFiles).slice(0, 8);
|
|
224
|
+
return {
|
|
225
|
+
role: 'user',
|
|
226
|
+
content: [
|
|
227
|
+
'Your completion evidence was rejected because it cited a repository path that Read did not verify.',
|
|
228
|
+
`Unverified paths: ${unread.length ? unread.join(', ') : '(none in allowed code roots)'}`,
|
|
229
|
+
`Paths actually Read: ${read.length ? read.join(', ') : '(none)'}`,
|
|
230
|
+
'Try once more. Read each relevant unverified path before citing it, or remove it from the evidence. Cite only repository-relative code paths that this grounding run actually Read, each with a concrete reason. If no path is relevant, do not invent evidence.',
|
|
231
|
+
].join('\n'),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
215
235
|
/** Bounded, shared, read-only grounding loop. Failure or no candidates returns null. */
|
|
216
236
|
export async function groundPersistently(goal: string, cwd: string, deps: PersistentGroundingDeps = {}): Promise<PersistentGroundingResult | null> {
|
|
217
237
|
const startedAt = performance.now();
|
|
@@ -250,20 +270,53 @@ export async function groundPersistently(goal: string, cwd: string, deps: Persis
|
|
|
250
270
|
},
|
|
251
271
|
};
|
|
252
272
|
const options: GoalLoopOptions = { maxIterations, objective: `Persistent repository grounding: ${goal.slice(0, 200)}` };
|
|
253
|
-
const
|
|
254
|
-
const
|
|
273
|
+
const first = await loop(ctx, options);
|
|
274
|
+
const firstVerified = first.goalComplete && first.stopReason === 'goal_complete'
|
|
255
275
|
? verifiedEvidenceFromCompletion(completionEvidence, readFiles)
|
|
256
276
|
: {
|
|
257
277
|
files: [],
|
|
258
278
|
evidence: [],
|
|
259
279
|
rejection: { noPath: 0, outsideRoot: 0, notRead: 0, noMeaningfulText: 0, samples: [] },
|
|
260
280
|
};
|
|
281
|
+
let result = first;
|
|
282
|
+
let verified = firstVerified;
|
|
283
|
+
let feedbackRetry = false;
|
|
284
|
+
let retryIterations = 0;
|
|
285
|
+
if (firstVerified.rejection.notRead > 0) {
|
|
286
|
+
feedbackRetry = true;
|
|
287
|
+
const feedback = rejectedEvidenceFeedback(completionEvidence, readFiles);
|
|
288
|
+
completionEvidence = '';
|
|
289
|
+
try {
|
|
290
|
+
const retry = await loop({ ...ctx, sessionId: `${sessionId}-evidence-retry`, messages: [...ctx.messages, feedback] },
|
|
291
|
+
{ ...options, maxIterations: 2 });
|
|
292
|
+
retryIterations = retry.iterations;
|
|
293
|
+
const retryVerified = retry.goalComplete && retry.stopReason === 'goal_complete'
|
|
294
|
+
? verifiedEvidenceFromCompletion(completionEvidence, readFiles)
|
|
295
|
+
: null;
|
|
296
|
+
if (retryVerified) {
|
|
297
|
+
verified = {
|
|
298
|
+
files: Array.from(new Set([...firstVerified.files, ...retryVerified.files])),
|
|
299
|
+
evidence: Array.from(new Set([...firstVerified.evidence, ...retryVerified.evidence])),
|
|
300
|
+
rejection: retryVerified.rejection,
|
|
301
|
+
};
|
|
302
|
+
result = retry;
|
|
303
|
+
}
|
|
304
|
+
} catch (error) {
|
|
305
|
+
try {
|
|
306
|
+
debug.log('grounding.persistent', 'evidence-retry-failed', { sessionId,
|
|
307
|
+
reason: error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200) }, { level: 'warn' });
|
|
308
|
+
} catch {
|
|
309
|
+
// A failed retry and its telemetry cannot erase evidence already verified by Read.
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
const totalIterations = first.iterations + retryIterations;
|
|
261
314
|
try {
|
|
262
315
|
debug.log('grounding.persistent', 'finished', {
|
|
263
316
|
sessionId,
|
|
264
317
|
maxIterations,
|
|
265
318
|
durationMs: durationMs(),
|
|
266
|
-
iterations:
|
|
319
|
+
iterations: totalIterations,
|
|
267
320
|
stopReason: result.stopReason,
|
|
268
321
|
// ⛔ 「끝났다」가 두 뜻이다 — ⓐ 스스로 목표를 끝냈다 ⓑ 턴이 «그냥» 끝났다.
|
|
269
322
|
// 📏 2026-08-11 72차 전수(400건): 후보 0인 finished 가 5건이고 ***다섯 다 end_turn*** 이었다.
|
|
@@ -273,6 +326,8 @@ export async function groundPersistently(goal: string, cwd: string, deps: Persis
|
|
|
273
326
|
candidateCount: verified.files.length,
|
|
274
327
|
evidenceCount: verified.evidence.length,
|
|
275
328
|
evidenceRejection: verified.rejection,
|
|
329
|
+
feedbackRetry,
|
|
330
|
+
...(feedbackRetry ? { firstEvidenceRejection: firstVerified.rejection } : {}),
|
|
276
331
|
toolCalls,
|
|
277
332
|
});
|
|
278
333
|
} catch {
|
|
@@ -281,7 +336,7 @@ export async function groundPersistently(goal: string, cwd: string, deps: Persis
|
|
|
281
336
|
return {
|
|
282
337
|
files: verified.files,
|
|
283
338
|
evidence: verified.evidence,
|
|
284
|
-
iterations:
|
|
339
|
+
iterations: totalIterations,
|
|
285
340
|
stopReason: result.stopReason,
|
|
286
341
|
};
|
|
287
342
|
} catch (error) {
|
|
@@ -31,9 +31,13 @@ import {
|
|
|
31
31
|
runShell as runnerDispatch,
|
|
32
32
|
getShellRunnerDeps,
|
|
33
33
|
} from '../../shell-runner/dispatch.js';
|
|
34
|
-
import type { ShellResult as RunnerResult } from '../../shell-runner/types.js';
|
|
34
|
+
import type { ShellHandle, ShellResult as RunnerResult } from '../../shell-runner/types.js';
|
|
35
35
|
import { debug } from '../../debug/log.js';
|
|
36
36
|
import { getSessionCwd } from '../../session/working-dir.js';
|
|
37
|
+
import {
|
|
38
|
+
globalTaskNotificationQueue,
|
|
39
|
+
type TaskNotificationQueue,
|
|
40
|
+
} from '../../agent/task-notification.js';
|
|
37
41
|
|
|
38
42
|
/** LLMToolSpec — the shape surfaced to the model. Kept narrow so
|
|
39
43
|
* accidental misuse is hard: no env override, no cwd outside the
|
|
@@ -178,6 +182,62 @@ function logShellDispatch(event: string, data: Record<string, unknown>): void {
|
|
|
178
182
|
debug.log('shell.dispatch', event, data);
|
|
179
183
|
}
|
|
180
184
|
|
|
185
|
+
// ── bg completion → turn notification queue (TUI-BG-SHELL) ───────────
|
|
186
|
+
// When a bg-mode shell handle settles, its result is no longer awaited
|
|
187
|
+
// by anyone — the spawn already returned the `outcome=started` stub.
|
|
188
|
+
// To surface the eventual result we enqueue one task-notification into
|
|
189
|
+
// the existing turn notification queue (injected at turn-start by
|
|
190
|
+
// chat/skill-runner). One notice per handle; opening an idle turn from
|
|
191
|
+
// the wake signal is deliberately NOT wired here (TUI-EVENT-TURN-QUEUE).
|
|
192
|
+
|
|
193
|
+
export interface ShellCompletionNoticeInput {
|
|
194
|
+
handle: ShellHandle;
|
|
195
|
+
ok: boolean;
|
|
196
|
+
exitCode: number | null;
|
|
197
|
+
durationMs: number;
|
|
198
|
+
/** Set when ok === false — the runner's failure reason. */
|
|
199
|
+
errorMessage?: string;
|
|
200
|
+
/** Caller-supplied free-form label (RunShell's `description` arg). */
|
|
201
|
+
label?: string;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Per-handle dedup guard — module-local, keyed by the handle OBJECT.
|
|
205
|
+
* A registry replacement may mint a NEW handle that reuses an id; the
|
|
206
|
+
* shell layer must not swallow that fresh completion as a duplicate
|
|
207
|
+
* settle (an id-keyed record here would). */
|
|
208
|
+
const notifiedHandles = new WeakSet<ShellHandle>();
|
|
209
|
+
let nextShellCompletionNoticeId = 0;
|
|
210
|
+
|
|
211
|
+
/** Enqueue one bg shell-handle completion notice. Returns true when this
|
|
212
|
+
* handle had not notified before (the notice was handed to `queue`),
|
|
213
|
+
* false on a duplicate settle of the same handle. The queue's own
|
|
214
|
+
* The shared queue deduplicates by taskId even after drain, so each
|
|
215
|
+
* completed handle gets its own notification id. The original handle id
|
|
216
|
+
* remains in the output for ShellPoll and human follow-up.
|
|
217
|
+
* `queue` defaults to the global turn notification queue; injectable
|
|
218
|
+
* so tests can isolate. */
|
|
219
|
+
export function enqueueShellCompletionNotice(
|
|
220
|
+
input: ShellCompletionNoticeInput,
|
|
221
|
+
queue: TaskNotificationQueue = globalTaskNotificationQueue,
|
|
222
|
+
): boolean {
|
|
223
|
+
if (notifiedHandles.has(input.handle)) return false;
|
|
224
|
+
notifiedHandles.add(input.handle);
|
|
225
|
+
const header = `RunShell bg handle=${input.handle.id} ${input.ok ? 'completed' : 'failed'}`;
|
|
226
|
+
const output = `${header}${input.ok || input.exitCode !== null ? ` exitCode=${input.exitCode ?? 'null'}` : ''} elapsed=${input.durationMs}ms${input.errorMessage ? ` error=${input.errorMessage}` : ''}`;
|
|
227
|
+
queue.enqueue({
|
|
228
|
+
taskId: `shell:${input.handle.id}:${++nextShellCompletionNoticeId}`,
|
|
229
|
+
agentName: 'RunShell',
|
|
230
|
+
...(input.label ? { label: input.label } : {}),
|
|
231
|
+
state: input.ok ? 'done' : 'error',
|
|
232
|
+
output,
|
|
233
|
+
truncated: false,
|
|
234
|
+
...(input.errorMessage ? { errorMessage: input.errorMessage } : {}),
|
|
235
|
+
durationMs: input.durationMs,
|
|
236
|
+
finishedAt: Date.now(),
|
|
237
|
+
});
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
|
|
181
241
|
export async function dispatchRunShell(
|
|
182
242
|
rawArgs: Record<string, unknown>,
|
|
183
243
|
opts: { signal?: AbortSignal } = {},
|
|
@@ -269,6 +329,16 @@ async function dispatchViaRunner(args: {
|
|
|
269
329
|
exitCode: result.exitCode ?? null,
|
|
270
330
|
durationMs: result.durationMs,
|
|
271
331
|
});
|
|
332
|
+
const ok = result.outcome === 'exit' && result.exitCode === 0;
|
|
333
|
+
enqueueShellCompletionNotice({
|
|
334
|
+
handle,
|
|
335
|
+
ok,
|
|
336
|
+
exitCode: result.exitCode ?? null,
|
|
337
|
+
durationMs: result.durationMs,
|
|
338
|
+
...(!ok ? { errorMessage: result.exitCode !== undefined
|
|
339
|
+
? `exitCode=${result.exitCode}` : `outcome=${result.outcome}` } : {}),
|
|
340
|
+
...(args.description !== undefined ? { label: args.description } : {}),
|
|
341
|
+
});
|
|
272
342
|
},
|
|
273
343
|
(err: unknown) => {
|
|
274
344
|
debug.log('skill-tool.shell', 'bg-finished', {
|
|
@@ -277,6 +347,14 @@ async function dispatchViaRunner(args: {
|
|
|
277
347
|
durationMs: Date.now() - bgStartedAt,
|
|
278
348
|
error: err instanceof Error ? err.message : String(err),
|
|
279
349
|
});
|
|
350
|
+
enqueueShellCompletionNotice({
|
|
351
|
+
handle,
|
|
352
|
+
ok: false,
|
|
353
|
+
exitCode: null,
|
|
354
|
+
durationMs: Date.now() - bgStartedAt,
|
|
355
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
356
|
+
...(args.description !== undefined ? { label: args.description } : {}),
|
|
357
|
+
});
|
|
280
358
|
},
|
|
281
359
|
);
|
|
282
360
|
return {
|
|
@@ -19,6 +19,7 @@ import { debug } from '../debug/log.js';
|
|
|
19
19
|
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
20
20
|
import { spawnElanous } from '../instance/spawn-elanous.js';
|
|
21
21
|
import { getUserConfig } from '../user-config.js';
|
|
22
|
+
import { resyncPendingPath } from './resync-tick.js';
|
|
22
23
|
|
|
23
24
|
export const REJUDGE_TICK_EVENT = 'rejudge-tick';
|
|
24
25
|
export const REJUDGE_DEFAULT_MAX_LOAD = 60;
|
|
@@ -49,6 +50,7 @@ export interface RejudgeTickDeps {
|
|
|
49
50
|
config?: () => { rejudgeEveryMinutes?: number; rejudgeMaxLoad?: number } | undefined;
|
|
50
51
|
load?: () => number;
|
|
51
52
|
stampPath?: string;
|
|
53
|
+
pendingPath?: string;
|
|
52
54
|
run?: () => Promise<RejudgeRun>;
|
|
53
55
|
log?: (event: string, data: Record<string, unknown>) => void;
|
|
54
56
|
}
|
|
@@ -110,7 +112,9 @@ export async function rejudgeTick(deps: RejudgeTickDeps = {}): Promise<RejudgeTi
|
|
|
110
112
|
const now = (deps.now ?? (() => new Date()))();
|
|
111
113
|
const stampPath = deps.stampPath ?? defaultStampPath();
|
|
112
114
|
const lastStartedAt = readStamp(stampPath);
|
|
113
|
-
|
|
115
|
+
const pendingAt = readPending(deps.pendingPath ?? resyncPendingPath());
|
|
116
|
+
const savedSinceLastRun = pendingAt !== null && (!lastStartedAt || Date.parse(pendingAt) > Date.parse(lastStartedAt));
|
|
117
|
+
if (!savedSinceLastRun && lastStartedAt && now.getTime() - Date.parse(lastStartedAt) < everyMinutes * 60_000 - REJUDGE_JITTER_MS) {
|
|
114
118
|
return emit({ decision: 'not-due', everyMinutes, lastStartedAt });
|
|
115
119
|
}
|
|
116
120
|
const maxLoad = positive(cfg?.rejudgeMaxLoad) ?? REJUDGE_DEFAULT_MAX_LOAD;
|
|
@@ -133,6 +137,13 @@ export async function rejudgeTick(deps: RejudgeTickDeps = {}): Promise<RejudgeTi
|
|
|
133
137
|
}
|
|
134
138
|
}
|
|
135
139
|
|
|
140
|
+
function readPending(path: string): string | null {
|
|
141
|
+
try {
|
|
142
|
+
const value = (JSON.parse(readFileSync(path, 'utf8')) as { savedAt?: unknown }).savedAt;
|
|
143
|
+
return typeof value === 'string' && Number.isFinite(Date.parse(value)) ? value : null;
|
|
144
|
+
} catch { return null; }
|
|
145
|
+
}
|
|
146
|
+
|
|
136
147
|
if (import.meta.main) {
|
|
137
148
|
try { await (await import('../domains/standalone-log-sink.js')).registerStandaloneLogSink('steward'); }
|
|
138
149
|
catch (error) { console.error(`steward rejudge: log sink registration failed — ${error instanceof Error ? error.message : String(error)}`); }
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/** Release-day shadow: observe the scheduled phase without performing its action. */
|
|
2
|
+
import { chmodSync, mkdirSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { Database } from 'bun:sqlite';
|
|
5
|
+
import { debug } from '../debug/log.js';
|
|
6
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
7
|
+
import { listSchedules, type ReleaseSchedule } from '../release-loop/release-schedule.js';
|
|
8
|
+
|
|
9
|
+
export type ReleaseDayPhase = 'gate-mode' | 'carry-p0' | 'cut-run' | 'green-back' | 'none' | 'unmeasured';
|
|
10
|
+
export interface ReleaseDayDecision {
|
|
11
|
+
version: string;
|
|
12
|
+
phase: ReleaseDayPhase;
|
|
13
|
+
dueAt: string | null;
|
|
14
|
+
scheduleUpdatedAt: string;
|
|
15
|
+
reason?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The release record, rather than publishAt (a planned time), opens green-back. */
|
|
19
|
+
export function decideReleaseDayPhase(schedule: ReleaseSchedule, now: Date, publishedAt: string | null): ReleaseDayDecision {
|
|
20
|
+
const cut = Date.parse(schedule.cutAt);
|
|
21
|
+
const land = schedule.landBy ? Date.parse(schedule.landBy) : cut;
|
|
22
|
+
const gate = cut - 30 * 60_000;
|
|
23
|
+
const time = now.getTime();
|
|
24
|
+
let phase: ReleaseDayPhase = 'none';
|
|
25
|
+
let dueAt: string | null = null;
|
|
26
|
+
if (publishedAt && time >= Date.parse(publishedAt)) {
|
|
27
|
+
phase = 'green-back'; dueAt = publishedAt;
|
|
28
|
+
} else if (time >= cut) {
|
|
29
|
+
phase = 'cut-run'; dueAt = schedule.cutAt;
|
|
30
|
+
} else if (time >= land) {
|
|
31
|
+
phase = 'carry-p0'; dueAt = schedule.landBy;
|
|
32
|
+
} else if (time >= gate) {
|
|
33
|
+
phase = 'gate-mode'; dueAt = new Date(gate).toISOString();
|
|
34
|
+
}
|
|
35
|
+
return { version: schedule.version, phase, dueAt, scheduleUpdatedAt: schedule.updatedAt };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type PublicationProbe = { kind: 'published'; at: string } | { kind: 'unpublished' } | { kind: 'unmeasured'; reason: string };
|
|
39
|
+
|
|
40
|
+
function actualPublicationProbe(version: string, root: string): PublicationProbe {
|
|
41
|
+
const path = join(root, 'release', version, 'release.json');
|
|
42
|
+
let raw: string;
|
|
43
|
+
try { raw = readFileSync(path, 'utf8'); }
|
|
44
|
+
catch (error) {
|
|
45
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { kind: 'unpublished' };
|
|
46
|
+
return { kind: 'unmeasured', reason: 'release-record-read-failed' };
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const record = JSON.parse(raw) as { version?: unknown; publishedAt?: unknown };
|
|
50
|
+
if (record.version !== version) return { kind: 'unmeasured', reason: 'release-record-version-mismatch' };
|
|
51
|
+
if (record.publishedAt == null) return { kind: 'unmeasured', reason: 'release-record-published-at-missing' };
|
|
52
|
+
if (typeof record.publishedAt !== 'string' || !Number.isFinite(Date.parse(record.publishedAt)))
|
|
53
|
+
return { kind: 'unmeasured', reason: 'release-record-published-at-invalid' };
|
|
54
|
+
return { kind: 'published', at: record.publishedAt };
|
|
55
|
+
} catch { return { kind: 'unmeasured', reason: 'release-record-json-failed' }; }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ReleaseDayScheduleState { publication: PublicationProbe; greenObserved: boolean }
|
|
59
|
+
|
|
60
|
+
/** A late release and the next gate can both be due; return both instead of starving either. */
|
|
61
|
+
export function selectReleaseDaySchedules(
|
|
62
|
+
schedules: ReleaseSchedule[], now: Date, state: (version: string) => ReleaseDayScheduleState,
|
|
63
|
+
): ReleaseSchedule[] {
|
|
64
|
+
const ordered = schedules.filter(s => Number.isFinite(Date.parse(s.cutAt))).sort((a, b) => Date.parse(a.cutAt) - Date.parse(b.cutAt));
|
|
65
|
+
const time = now.getTime();
|
|
66
|
+
const dueFuture = ordered.filter(s => {
|
|
67
|
+
const cut = Date.parse(s.cutAt);
|
|
68
|
+
const firstDue = Math.min(cut - 30 * 60_000, s.landBy ? Date.parse(s.landBy) : cut);
|
|
69
|
+
return time < cut && time >= firstDue;
|
|
70
|
+
});
|
|
71
|
+
const past = ordered.filter(s => Date.parse(s.cutAt) <= time);
|
|
72
|
+
const unpublished = past.filter(s => state(s.version).publication.kind === 'unpublished');
|
|
73
|
+
const unmeasured = past.filter(s => state(s.version).publication.kind === 'unmeasured');
|
|
74
|
+
const pendingGreen = past.filter(s => {
|
|
75
|
+
const status = state(s.version);
|
|
76
|
+
return status.publication.kind === 'published' && !status.greenObserved;
|
|
77
|
+
});
|
|
78
|
+
const due = [...dueFuture, ...unpublished, ...unmeasured, ...pendingGreen];
|
|
79
|
+
if (due.length) return [...new Map(due.map(s => [s.version, s])).values()];
|
|
80
|
+
const upcoming = ordered.find(s => Date.parse(s.cutAt) > time);
|
|
81
|
+
return upcoming && Date.parse(upcoming.cutAt) - time <= 24 * 60 * 60_000 ? [upcoming] : [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface ReleaseDayShadowDeps {
|
|
85
|
+
now?: () => Date;
|
|
86
|
+
releaseRoot?: string;
|
|
87
|
+
stateRoot?: string;
|
|
88
|
+
schedules?: () => ReleaseSchedule[];
|
|
89
|
+
publishedAt?: (version: string, root: string) => string | null;
|
|
90
|
+
log?: (event: string, data: Record<string, unknown>) => void;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ReleaseDayShadowResult extends ReleaseDayDecision { recorded: boolean }
|
|
94
|
+
|
|
95
|
+
export function runReleaseDayShadow(deps: ReleaseDayShadowDeps = {}): ReleaseDayShadowResult[] {
|
|
96
|
+
const now = (deps.now ?? (() => new Date()))();
|
|
97
|
+
// The graph child's stamped universe also owns its release schedule under --test.
|
|
98
|
+
const releaseRoot = deps.releaseRoot ?? effectiveInstanceRoot();
|
|
99
|
+
const schedules = (deps.schedules ?? (() => listSchedules(releaseRoot)))();
|
|
100
|
+
const log = deps.log ?? ((event: string, data: Record<string, unknown>) => debug.log('steward.release-day', event, data));
|
|
101
|
+
if (!schedules.length) { log('decision', { phase: 'none', reason: 'no-release-schedule', at: now.toISOString(), recorded: false, mode: 'shadow' }); return []; }
|
|
102
|
+
const dir = join(deps.stateRoot ?? effectiveInstanceRoot(), 'steward');
|
|
103
|
+
mkdirSync(dir, { recursive: true });
|
|
104
|
+
const path = join(dir, 'release-day-shadow.sqlite');
|
|
105
|
+
const db = new Database(path, { create: true, strict: true });
|
|
106
|
+
try {
|
|
107
|
+
chmodSync(path, 0o600);
|
|
108
|
+
db.exec('PRAGMA busy_timeout = 10000');
|
|
109
|
+
db.exec('CREATE TABLE IF NOT EXISTS phase_observations (version TEXT NOT NULL, phase TEXT NOT NULL, due_at TEXT NOT NULL, observed_at TEXT NOT NULL, schedule_updated_at TEXT NOT NULL, PRIMARY KEY (version, phase))');
|
|
110
|
+
const states = new Map<string, ReleaseDayScheduleState>();
|
|
111
|
+
for (const schedule of schedules) {
|
|
112
|
+
const observed = db.query('SELECT phase FROM phase_observations WHERE version = ?').all(schedule.version) as Array<{ phase: string }>;
|
|
113
|
+
const injected = deps.publishedAt?.(schedule.version, releaseRoot);
|
|
114
|
+
states.set(schedule.version, {
|
|
115
|
+
publication: deps.publishedAt
|
|
116
|
+
? injected == null ? { kind: 'unpublished' }
|
|
117
|
+
: Number.isFinite(Date.parse(injected)) ? { kind: 'published', at: injected }
|
|
118
|
+
: { kind: 'unmeasured', reason: 'injected-published-at-invalid' }
|
|
119
|
+
: actualPublicationProbe(schedule.version, releaseRoot),
|
|
120
|
+
greenObserved: observed.some(row => row.phase === 'green-back'),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
const selected = selectReleaseDaySchedules(schedules, now, version => states.get(version)!);
|
|
124
|
+
if (!selected.length) { log('decision', { phase: 'none', reason: 'outside-release-window', at: now.toISOString(), recorded: false, mode: 'shadow' }); return []; }
|
|
125
|
+
const results: ReleaseDayShadowResult[] = [];
|
|
126
|
+
for (const schedule of selected) {
|
|
127
|
+
const publication = states.get(schedule.version)!.publication;
|
|
128
|
+
if (publication.kind === 'unmeasured') {
|
|
129
|
+
const result: ReleaseDayShadowResult = { version: schedule.version, phase: 'unmeasured', dueAt: null,
|
|
130
|
+
scheduleUpdatedAt: schedule.updatedAt, reason: publication.reason, recorded: false };
|
|
131
|
+
log('unmeasured', { ...result, at: now.toISOString(), mode: 'shadow' });
|
|
132
|
+
results.push(result);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const publishedAt = publication.kind === 'published' ? publication.at : null;
|
|
136
|
+
const decision = decideReleaseDayPhase(schedule, now, publishedAt);
|
|
137
|
+
let recorded = false;
|
|
138
|
+
if (decision.phase !== 'none') {
|
|
139
|
+
const row = db.query('INSERT OR IGNORE INTO phase_observations (version, phase, due_at, observed_at, schedule_updated_at) VALUES (?, ?, ?, ?, ?)')
|
|
140
|
+
.run(decision.version, decision.phase, decision.dueAt!, now.toISOString(), decision.scheduleUpdatedAt);
|
|
141
|
+
recorded = row.changes === 1;
|
|
142
|
+
}
|
|
143
|
+
log('decision', { ...decision, at: now.toISOString(), recorded, mode: 'shadow', publishedAt });
|
|
144
|
+
results.push({ ...decision, recorded });
|
|
145
|
+
}
|
|
146
|
+
return results;
|
|
147
|
+
} finally { db.close(); }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (import.meta.main) {
|
|
151
|
+
try {
|
|
152
|
+
const sinkReady = await (await import('../domains/standalone-log-sink.js')).registerStandaloneLogSink('steward');
|
|
153
|
+
if (!sinkReady) throw new Error('durable log sink unavailable');
|
|
154
|
+
const results = runReleaseDayShadow();
|
|
155
|
+
console.log(`steward release-day shadow: ${results.map(result => `${result.version}:${result.phase}${result.recorded ? ' · recorded' : ''}`).join(', ') || 'none'}`);
|
|
156
|
+
}
|
|
157
|
+
catch (error) { console.error(`steward release-day shadow failed: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
158
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/** Resync labeled PRs before the steward's TA rejudge node sees their heads. */
|
|
2
|
+
import { debug } from '../debug/log.js';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { getElanousConfigDir } from '../elanous-config-dir.js';
|
|
7
|
+
import { resolveHqLaunchSeat } from '../hq/seats.js';
|
|
8
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
9
|
+
import { runResyncShadow, type ResyncShadowSummary } from '../loops/orchestrator/resync-shadow.js';
|
|
10
|
+
import { listSchedules, type ReleaseSchedule } from '../release-loop/release-schedule.js';
|
|
11
|
+
|
|
12
|
+
type ReleaseRunState = 'active' | 'idle' | 'unknown';
|
|
13
|
+
type ReleaseWindowState = 'inside' | 'outside' | 'unknown';
|
|
14
|
+
type ResyncTickResult = { decision: 'ran' | 'failed' | 'skipped-release' | 'skipped-release-window'; summary?: ResyncShadowSummary; reason?: string };
|
|
15
|
+
|
|
16
|
+
export function resyncPendingPath(): string { return join(effectiveInstanceRoot(), 'steward', 'resync-pending.json'); }
|
|
17
|
+
|
|
18
|
+
/** The same registered, detached OP checkout used by steward launch; the installed package is not a git repository. */
|
|
19
|
+
export function stewardResyncRepoRoot(
|
|
20
|
+
resolveSeat: typeof resolveHqLaunchSeat = resolveHqLaunchSeat,
|
|
21
|
+
configDir: () => string = getElanousConfigDir,
|
|
22
|
+
): string { return resolveSeat('OP', configDir()); }
|
|
23
|
+
|
|
24
|
+
/** A release may detach the OP checkout. Unknown process state also skips remote branch writes. */
|
|
25
|
+
export function probeReleaseRun(
|
|
26
|
+
list: () => { status: number | null; stdout?: string; error?: Error } = () => spawnSync('pgrep', ['-f', 'release[[:space:]]+(run|resume)'], { encoding: 'utf8', timeout: 5_000 }),
|
|
27
|
+
command: (pid: string) => string | null = pid => {
|
|
28
|
+
const result = spawnSync('ps', ['-p', pid, '-o', 'command='], { encoding: 'utf8', timeout: 5_000 });
|
|
29
|
+
return result.status === 0 ? result.stdout.trim() : null;
|
|
30
|
+
},
|
|
31
|
+
): ReleaseRunState {
|
|
32
|
+
try {
|
|
33
|
+
const result = list();
|
|
34
|
+
if (result.error || result.status === null) return 'unknown';
|
|
35
|
+
if (result.status === 1) return 'idle';
|
|
36
|
+
if (result.status !== 0) return 'unknown';
|
|
37
|
+
const pids = (result.stdout ?? '').trim().split(/\s+/).filter(pid => /^\d+$/.test(pid));
|
|
38
|
+
if (!pids.length) return 'unknown';
|
|
39
|
+
for (const pid of pids) {
|
|
40
|
+
const argv = command(pid);
|
|
41
|
+
if (argv === null) continue;
|
|
42
|
+
const executable = argv.trim().split(/\s+/)[0]?.split('/').at(-1);
|
|
43
|
+
if (['bun', 'node', 'eln', 'elanous'].includes(executable ?? '') && /(?:^|\s)release\s+(?:run|resume)(?:\s|$)/.test(argv)) return 'active';
|
|
44
|
+
// pgrep saw a release-shaped command, but a shell/wrapper cannot prove whether it will launch the CLI.
|
|
45
|
+
}
|
|
46
|
+
return 'unknown';
|
|
47
|
+
} catch { return 'unknown'; }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A release cut may start after the process probe; the scheduled cut/publication window is a fixed safety boundary. */
|
|
51
|
+
export function releaseWindowState(
|
|
52
|
+
nowMs = Date.now(),
|
|
53
|
+
read: () => ReleaseSchedule[] = listSchedules,
|
|
54
|
+
): ReleaseWindowState {
|
|
55
|
+
try {
|
|
56
|
+
const schedules = read();
|
|
57
|
+
if (!schedules.length) return 'unknown';
|
|
58
|
+
const rows = schedules.map(row => ({ cut: Date.parse(row.cutAt), publish: Date.parse(row.publishAt ?? '') }));
|
|
59
|
+
if (rows.some(row => !Number.isFinite(row.cut))) return 'unknown';
|
|
60
|
+
const latestPast = rows.filter(row => row.cut <= nowMs).sort((a, b) => b.cut - a.cut)[0];
|
|
61
|
+
if (latestPast && !Number.isFinite(latestPast.publish)) return 'unknown';
|
|
62
|
+
if (latestPast && nowMs <= latestPast.publish + 60 * 60_000) return 'inside';
|
|
63
|
+
const next = rows.filter(row => row.cut > nowMs).sort((a, b) => a.cut - b.cut)[0];
|
|
64
|
+
if (!next || !Number.isFinite(next.publish) || next.publish < next.cut) return 'unknown';
|
|
65
|
+
return nowMs >= next.cut - 40 * 60_000 ? 'inside' : 'outside';
|
|
66
|
+
} catch { return 'unknown'; }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function defaultResyncRun(
|
|
70
|
+
repoRoot = stewardResyncRepoRoot(),
|
|
71
|
+
execute: typeof runResyncShadow = runResyncShadow,
|
|
72
|
+
): Promise<ResyncShadowSummary> { return execute(3, { repoRoot }, 'live'); }
|
|
73
|
+
|
|
74
|
+
export async function resyncTick(
|
|
75
|
+
run: () => Promise<ResyncShadowSummary> = () => defaultResyncRun(),
|
|
76
|
+
log: (event: string, data: Record<string, unknown>) => void = (event, data) => debug.log('loop.steward', event, data),
|
|
77
|
+
pendingPath: string = resyncPendingPath(),
|
|
78
|
+
): Promise<ResyncTickResult> { return resyncTickWithReleaseProbe(run, log, pendingPath, probeReleaseRun); }
|
|
79
|
+
|
|
80
|
+
export async function resyncTickWithReleaseProbe(
|
|
81
|
+
run: () => Promise<ResyncShadowSummary>,
|
|
82
|
+
log: (event: string, data: Record<string, unknown>) => void,
|
|
83
|
+
pendingPath: string,
|
|
84
|
+
releaseProbe: () => ReleaseRunState,
|
|
85
|
+
windowProbe: () => ReleaseWindowState = releaseWindowState,
|
|
86
|
+
): Promise<ResyncTickResult> {
|
|
87
|
+
let result: ResyncTickResult;
|
|
88
|
+
try {
|
|
89
|
+
const release = releaseProbe();
|
|
90
|
+
if (release !== 'idle') {
|
|
91
|
+
result = { decision: 'skipped-release', reason: release === 'active' ? 'release run active' : 'release process probe unavailable' };
|
|
92
|
+
} else {
|
|
93
|
+
const window = windowProbe();
|
|
94
|
+
if (window !== 'outside') {
|
|
95
|
+
result = { decision: 'skipped-release-window', reason: window === 'inside' ? 'scheduled release window' : 'release schedule unavailable' };
|
|
96
|
+
} else {
|
|
97
|
+
const summary = await run();
|
|
98
|
+
if (summary.saved > 0) {
|
|
99
|
+
mkdirSync(dirname(pendingPath), { recursive: true });
|
|
100
|
+
writeFileSync(pendingPath, JSON.stringify({ savedAt: new Date().toISOString() }));
|
|
101
|
+
}
|
|
102
|
+
result = { decision: 'ran', summary };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (error) { result = { decision: 'failed', reason: (error instanceof Error ? error.message : String(error)).slice(-300) }; }
|
|
107
|
+
try { log('resync-tick', { ...result }); }
|
|
108
|
+
catch (error) {
|
|
109
|
+
result = { decision: 'failed', reason: `log failed: ${error instanceof Error ? error.message : String(error)}`.slice(-300) };
|
|
110
|
+
console.error(`steward resync: ${result.reason}`);
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (import.meta.main) {
|
|
116
|
+
let sinkReady = false;
|
|
117
|
+
try { sinkReady = await (await import('../domains/standalone-log-sink.js')).registerStandaloneLogSink('steward'); }
|
|
118
|
+
catch (error) { console.error(`steward resync: log sink registration failed — ${error instanceof Error ? error.message : String(error)}`); }
|
|
119
|
+
// A missing durable sink must not precede a remote branch update.
|
|
120
|
+
const result = sinkReady ? await resyncTick() : { decision: 'failed' as const, reason: 'log sink unavailable' };
|
|
121
|
+
console.log(`steward resync: ${result.decision}${result.reason ? ` — ${result.reason}` : ''}`);
|
|
122
|
+
process.exitCode = 0;
|
|
123
|
+
}
|