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
|
@@ -11,6 +11,10 @@ import { execFileSync } from 'node:child_process';
|
|
|
11
11
|
import { createHash, randomBytes } from 'node:crypto';
|
|
12
12
|
import { mkdirSync, readFileSync, statSync, writeFileSync, type Stats } from 'node:fs';
|
|
13
13
|
import { dirname, join, resolve } from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { parse as parseYaml } from 'yaml';
|
|
16
|
+
import { validateUnitCalls } from '../graph-runner/runner.js';
|
|
17
|
+
import { parseGraphTemplateYaml } from '../self-implement/graph-yaml.js';
|
|
14
18
|
import { resolveDaemonHarnessTarget } from '../intake-plane/harness-target.js';
|
|
15
19
|
import { HARNESS_RUN_ID_ENV, mintRunId, normalizeRunId } from '../harness/harness-space.js';
|
|
16
20
|
import { debug } from '../debug/log.js';
|
|
@@ -18,6 +22,7 @@ import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
|
18
22
|
import { withFileLockSync } from '../storage/file-lock.js';
|
|
19
23
|
import { checkCompletionEvidence, deliverableReceiptEvidence } from './completion-evidence.js';
|
|
20
24
|
import { judgeNextMove } from './judge.js';
|
|
25
|
+
import { loadRunLedger, runLedgerDir } from '../self-implement/run-ledger.js';
|
|
21
26
|
|
|
22
27
|
export interface ResolveHandTextDeps {
|
|
23
28
|
cwd: string;
|
|
@@ -88,12 +93,74 @@ export function taskCardLabelContext(card: Pick<TaskCard, 'id'>): TaskCardLabelC
|
|
|
88
93
|
export const COMPLETION_KINDS = ['code-pr', 'artifact', 'research-report', 'ops-action', 'content', 'watch-brief', 'decision-support', 'deliverable'] as const;
|
|
89
94
|
export type CompletionKind = typeof COMPLETION_KINDS[number];
|
|
90
95
|
|
|
96
|
+
export interface TaskComposition {
|
|
97
|
+
file: string;
|
|
98
|
+
/** null = 파일은 있으나 파싱 실패로 판을 확인하지 못했다. */
|
|
99
|
+
version: number | null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface CompositionSelection {
|
|
103
|
+
composition?: TaskComposition;
|
|
104
|
+
unitsResolved: boolean;
|
|
105
|
+
missingUnits: string[];
|
|
106
|
+
launcher?: 'harness-say' | 'graph-run';
|
|
107
|
+
error?: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** 1판은 조합을 고르고 단위 해석 결과를 기록한다. 단위 실행은 2판의 몫이다. */
|
|
111
|
+
export function selectTaskComposition(completion: CompletionKind, root = join(dirname(fileURLToPath(import.meta.url)), '..', '..')): CompositionSelection {
|
|
112
|
+
const file = `graphs/work/${completion}.yaml`;
|
|
113
|
+
const path = join(root, file);
|
|
114
|
+
let source: string;
|
|
115
|
+
try { source = readFileSync(path, 'utf8'); } catch (error) {
|
|
116
|
+
if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') return { unitsResolved: false, missingUnits: [] };
|
|
117
|
+
return { composition: { file, version: null }, unitsResolved: false, missingUnits: [],
|
|
118
|
+
error: `조합 읽기 오류: ${file}: ${error instanceof Error ? error.message : String(error)}` };
|
|
119
|
+
}
|
|
120
|
+
const parsed = parseGraphTemplateYaml(source, path);
|
|
121
|
+
if (!parsed.template || parsed.errors.length) {
|
|
122
|
+
let version: number | null = null;
|
|
123
|
+
try {
|
|
124
|
+
const raw: unknown = parseYaml(source);
|
|
125
|
+
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
126
|
+
const candidate = (raw as Record<string, unknown>).version;
|
|
127
|
+
if (typeof candidate === 'number' && Number.isInteger(candidate)) version = candidate;
|
|
128
|
+
}
|
|
129
|
+
} catch { /* 파싱 실패 자체를 아래 원장에 적는다. */ }
|
|
130
|
+
return { composition: { file, version }, unitsResolved: false, missingUnits: [],
|
|
131
|
+
error: `조합 오류: ${file}: ${parsed.errors.map(e => e.message).join('; ')}` };
|
|
132
|
+
}
|
|
133
|
+
const raw: unknown = parseYaml(source);
|
|
134
|
+
const launcher = raw && typeof raw === 'object' && !Array.isArray(raw) ? (raw as Record<string, unknown>).launcher : undefined;
|
|
135
|
+
if (launcher !== undefined && launcher !== 'harness-say' && launcher !== 'graph-run') {
|
|
136
|
+
return { composition: { file, version: parsed.template.version }, unitsResolved: false, missingUnits: [],
|
|
137
|
+
error: `조합 오류: ${file}: 모르는 launcher` };
|
|
138
|
+
}
|
|
139
|
+
if (completion === 'code-pr' && launcher === undefined) {
|
|
140
|
+
return { composition: { file, version: parsed.template.version }, unitsResolved: false, missingUnits: [],
|
|
141
|
+
error: `조합 오류: ${file}: launcher 없음` };
|
|
142
|
+
}
|
|
143
|
+
const missingUnits: string[] = [];
|
|
144
|
+
let unitError: string | undefined;
|
|
145
|
+
try { validateUnitCalls(parsed.template, path, [], call => missingUnits.push(call)); }
|
|
146
|
+
catch (error) { unitError = error instanceof Error ? error.message : String(error); }
|
|
147
|
+
return {
|
|
148
|
+
composition: { file, version: parsed.template.version },
|
|
149
|
+
unitsResolved: missingUnits.length === 0 && !unitError,
|
|
150
|
+
missingUnits,
|
|
151
|
+
...(launcher === 'harness-say' || launcher === 'graph-run' ? { launcher } : {}),
|
|
152
|
+
...(unitError ? { error: `조합 단위 해석 오류: ${file}: ${unitError}` } : {}),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
91
156
|
/**
|
|
92
157
|
* `run-bound`·`goal-bound`·`pr-bound` 은 runId(⊕ pr)를 싣는다 — 옛 카드의 칸은 at·event·detail 뿐이다.
|
|
93
158
|
* TA-LIVE-MOVE-CARD-HISTORY — `live-move` 줄만 kind·executed·ok·executorResult·head·effect 를 더 싣는다(`live-move-history.ts`).
|
|
94
159
|
*/
|
|
95
160
|
export interface TaskCardEvent {
|
|
96
161
|
at: string; event: string; detail?: string; runId?: string; pr?: number;
|
|
162
|
+
/** 원격 미결 수를 HQ 에 한 번만 회수하기 위한 원장 사건 id. */
|
|
163
|
+
sourceId?: string;
|
|
97
164
|
/** `closed-by-hand` 줄만 — 닫은 사유 · 닫은 자리(`closeTaskCard`). */
|
|
98
165
|
reason?: string; by?: string;
|
|
99
166
|
kind?: string; executed?: boolean; ok?: boolean; executorResult?: 'shadow' | 'live'; head?: string; effect?: string;
|
|
@@ -101,6 +168,8 @@ export interface TaskCardEvent {
|
|
|
101
168
|
resultPath?: string;
|
|
102
169
|
/** TA-REVIEW-RESULT-CAPTURE — `live-move-result` 줄만: 회수한 `self review --json` 의 칸(못 읽은 칸은 null). */
|
|
103
170
|
verdict?: string | null; reviewed?: boolean | null; mustFix?: number | null; reviewRoute?: string | null;
|
|
171
|
+
/** Exact bytes of the captured review stdout; repair must read the same resultPath and digest. */
|
|
172
|
+
reviewSha256?: string;
|
|
104
173
|
}
|
|
105
174
|
/** 카드 history 의 live-move 줄 이벤트 이름 — 상태 사유(«마지막 칸») 판정에서 뺀다. */
|
|
106
175
|
export const LIVE_MOVE_HISTORY_EVENT = 'live-move';
|
|
@@ -114,8 +183,15 @@ export interface TaskCard {
|
|
|
114
183
|
text: string;
|
|
115
184
|
seat?: TaskSeat;
|
|
116
185
|
checklistId?: string;
|
|
186
|
+
/** Explicit release version for an adopted PR; post-land still confirms the cell exists there. */
|
|
187
|
+
checklistVersion?: string;
|
|
117
188
|
/** 종결 종류 — 비면 code-pr. */
|
|
118
189
|
completion?: CompletionKind;
|
|
190
|
+
/** 선택된 일 조합 파일과 판(1판: 실행은 launcher 에 위임). */
|
|
191
|
+
composition?: TaskComposition;
|
|
192
|
+
graphSelectedBy?: 'completion';
|
|
193
|
+
unitsResolved?: boolean;
|
|
194
|
+
missingUnits?: string[];
|
|
119
195
|
/** 과제 대상 프로젝트(§A4b②) — target 은 절대 경로(git 이 아닐 수 있다). id 는 `task hand --project` 가 싣는다(옛 카드엔 없을 수 있다). */
|
|
120
196
|
project?: { id?: string; target: string };
|
|
121
197
|
/** 보드(§A4b① `board.ts`) 소속 — 목표 id · 이정표 id(`task hand --goal/--milestone`). 조각 카드의 `goalId`(골 sha 16자)와 다른 칸이다. */
|
|
@@ -127,7 +203,7 @@ export interface TaskCard {
|
|
|
127
203
|
* failed = 발사된 런이 PR·수확 가지 없이 죽었거나 끝내 안 떴다(TA-JUDGE-DEAD-RUN · `card-evidence.ts`) — 사유
|
|
128
204
|
* (`failed/needs-owner|needs-relaunch — …`)는 history 마지막 칸 · 자동 재발사하지 않는다(사람/판단부 몫).
|
|
129
205
|
*/
|
|
130
|
-
status: 'handed' | 'launched' | 'launch-failed' | 'failed';
|
|
206
|
+
status: 'handed' | 'launched' | 'launch-failed' | 'failed' | 'done';
|
|
131
207
|
history: TaskCardEvent[];
|
|
132
208
|
/** 미션 카드만 — 조각 카드 id(순서대로) · 쪼갠 출처(`mission.ts` splitMission). */
|
|
133
209
|
pieces?: string[];
|
|
@@ -142,6 +218,12 @@ export interface TaskCard {
|
|
|
142
218
|
* 번호는 `cardPrNumber` 로 읽는다(옛 카드의 number 도 그대로 읽힌다).
|
|
143
219
|
*/
|
|
144
220
|
pr?: number | TaskCardPr;
|
|
221
|
+
/** Host-verified existing OPEN PR adopted without launching a new implementation run. */
|
|
222
|
+
adoptedPr?: { pr: number; url: string; head: string; at: string };
|
|
223
|
+
/** Bound PR observed in a terminal GitHub state. Only applies while the card still binds the same PR and URL. */
|
|
224
|
+
prTerminal?: { pr: number; url: string; head: string; state: 'MERGED' | 'CLOSED'; at: string };
|
|
225
|
+
/** Post-land checklist judgement. A partial slice closes this card while leaving the checklist cell yellow. */
|
|
226
|
+
postLand?: { pr: number; head: string; at: string; outcome: 'complete' | 'partial'; evidence: string[]; remaining: string[] };
|
|
145
227
|
goalId?: string;
|
|
146
228
|
/** `manual` = pr/goalId 를 사람이 적었다(`recordPieceRef`) — 런 묶기가 건드리지 않는다. */
|
|
147
229
|
refSource?: 'manual';
|
|
@@ -167,7 +249,9 @@ export interface TaskCard {
|
|
|
167
249
|
*/
|
|
168
250
|
regates?: Array<{ pr: number; head: string; at: string; resultPath?: string; passed?: boolean; status?: string; baseCommit?: string; detail?: string;
|
|
169
251
|
/** REJUDGE base-stale — 같은 머리를 새 base 위에서 다시 잰 재게이트면, land 가 낡았다고 거절한 앞 재게이트의 base(멱등 키 = 머리 ⊕ 이 base). */
|
|
170
|
-
staleBase?: string
|
|
252
|
+
staleBase?: string;
|
|
253
|
+
/** TA-READY-BEFORE-REGATE — draft 때문에 실패한 같은 머리를 ready 뒤 딱 한 번 다시 잰다. */
|
|
254
|
+
readyRetry?: true }>;
|
|
171
255
|
/**
|
|
172
256
|
* Live land attempts, claimed before invocation, once per PR head even if the command fails.
|
|
173
257
|
* TA-LAND-RETRY-ON-INFRA — a failed attempt carries `class`/`reason`: `infra` (TA-side, allow-listed) leaves one more attempt on
|
|
@@ -180,6 +264,10 @@ export interface TaskCard {
|
|
|
180
264
|
taLand?: true;
|
|
181
265
|
/** TASKS-HAND-CHILD-LLM — `tasks hand --child-llm <provider>/<model> [--child-llm-effort <level>]` → 발사 인자 `--child-llm-provider/-model/-effort`. */
|
|
182
266
|
childLlm?: TaskChildLlm;
|
|
267
|
+
/** Same-PR repair launches; exact source head and worker result are retained for CAS and retry limits. */
|
|
268
|
+
repairs?: Array<{ pr: number; head: string; branch: string; at: string; move: string; runId: string;
|
|
269
|
+
resultPath: string; fromModel: string; toModel: string; status: 'requested' | 'unconfirmed' | 'failed' | 'applied';
|
|
270
|
+
detail?: string; commit?: string; bundleSha256?: string; bundlePath?: string }>;
|
|
183
271
|
}
|
|
184
272
|
|
|
185
273
|
/** 구현 자식 LLM 선택 — 하니스 `--child-llm-provider/--child-llm-model/--child-llm-effort` 로 그대로 넘긴다(검증 권위는 하니스). */
|
|
@@ -210,6 +298,12 @@ export function cardPrNumber(card: Pick<TaskCard, 'pr'>): number | undefined {
|
|
|
210
298
|
return typeof card.pr === 'number' ? card.pr : card.pr?.number;
|
|
211
299
|
}
|
|
212
300
|
|
|
301
|
+
/** A stale terminal marker must never close a card after its PR binding changes. */
|
|
302
|
+
export function boundPrTerminal(card: Pick<TaskCard, 'pr' | 'prTerminal'>): TaskCard['prTerminal'] | undefined {
|
|
303
|
+
const marker = card.prTerminal;
|
|
304
|
+
return marker && typeof card.pr === 'object' && card.pr.number === marker.pr && card.pr.url === marker.url ? marker : undefined;
|
|
305
|
+
}
|
|
306
|
+
|
|
213
307
|
/** A uniquely launched code-PR card may own a draft through its PR or its opening run. */
|
|
214
308
|
export function draftTaskOwner(cards: readonly TaskCard[], draft: { number: number; runId?: string | null }): TaskCard | undefined {
|
|
215
309
|
const owners = cards.filter((card) => card.status === 'launched' && (card.completion ?? 'code-pr') === 'code-pr'
|
|
@@ -316,7 +410,8 @@ export function launchArgs(card: Pick<TaskCard, 'text' | 'seat' | 'childLlm'>):
|
|
|
316
410
|
* 이유에 «착지 미확인»을 단다(착지했다/안 했다를 단정하지 않는다).
|
|
317
411
|
* 미션 카드는 스스로 발사하지 않는다 · 아직 안 뜬 조각의 선행이 덜 착지했으면 `wait after <id>`.
|
|
318
412
|
*/
|
|
319
|
-
export function nextMoveFor(card: TaskCard, landed?: ReadonlySet<string
|
|
413
|
+
export function nextMoveFor(card: TaskCard, landed?: ReadonlySet<string>, deps: { ledgerDir?: string } = {}): TaskCardMove {
|
|
414
|
+
// Completion callbacks record a shadow decision; do not suggest the live CLI here.
|
|
320
415
|
if (card.pieces) return { kind: 'wait', reason: `미션 — 조각 ${card.pieces.length}장이 선후대로 발사된다` };
|
|
321
416
|
const unmet = (card.after ?? []).filter((id) => !landed?.has(id));
|
|
322
417
|
if (card.status !== 'launched' && unmet.length > 0) return { kind: 'wait', reason: `after ${unmet.join(', ')}${landed ? '' : ' · 착지 미확인'}` };
|
|
@@ -324,6 +419,16 @@ export function nextMoveFor(card: TaskCard, landed?: ReadonlySet<string>): TaskC
|
|
|
324
419
|
if (card.status === 'launch-failed') return { kind: 'launch', command: launchArgs(card), reason: '직전 발사 실패 — 다시 발사' };
|
|
325
420
|
// live-move·hand-move 줄(관측 기록)은 실패 사유가 아니다 — 그 줄을 뺀 마지막 칸이 사유다(live-move 줄 없는 카드는 종전과 같다).
|
|
326
421
|
if (card.status === 'failed') return { kind: 'wait', reason: card.history.filter((item) => item.event !== LIVE_MOVE_HISTORY_EVENT && item.event !== LIVE_MOVE_RESULT_EVENT && item.event !== HAND_MOVE_HISTORY_EVENT).at(-1)?.detail ?? '런 실패 — PR·수확 가지 없음' };
|
|
422
|
+
if (card.status === 'launched' && card.runId) {
|
|
423
|
+
try {
|
|
424
|
+
const ledger = loadRunLedger(card.runId, deps.ledgerDir ?? runLedgerDir());
|
|
425
|
+
const terminal = ledger?.filter((entry) => entry.event === 'run-status').at(-1);
|
|
426
|
+
if (terminal && ['succeeded', 'failed', 'cancelled', 'abandoned'].includes(String(terminal.data.runStatus))) {
|
|
427
|
+
if (cardPrNumber(card) === undefined) return { kind: 'wait', reason: `런 원장 종료 — ${terminal.data.runStatus} · PR 없음` };
|
|
428
|
+
return { kind: 'wait', reason: `런 원장 종료 — ${terminal.data.runStatus} · 그림자 재판정은 완료 훅이 기록` };
|
|
429
|
+
}
|
|
430
|
+
} catch { /* 원장 결손·손상은 종료로 단정하지 않는다. */ }
|
|
431
|
+
}
|
|
327
432
|
return { kind: 'wait', reason: '발사됨 — 런 멈춤을 슈퍼바이저 그림자 판단(task-agent.shadow-move)이 받는다' };
|
|
328
433
|
}
|
|
329
434
|
|
|
@@ -358,6 +463,8 @@ export interface HandTaskOptions {
|
|
|
358
463
|
childLlm?: string;
|
|
359
464
|
childLlmEffort?: string;
|
|
360
465
|
statePath?: string;
|
|
466
|
+
/** 시험에서만 조합 저장소를 바꿔 해석·거부를 검증한다. */
|
|
467
|
+
compositionRoot?: string;
|
|
361
468
|
/** `--live` 일 때만 불린다. */
|
|
362
469
|
launcher?: TaskLauncher;
|
|
363
470
|
now?: () => Date;
|
|
@@ -427,6 +534,11 @@ export async function handTask(opts: HandTaskOptions): Promise<HandTaskResult> {
|
|
|
427
534
|
}
|
|
428
535
|
if (opts.childLlmEffort !== undefined && opts.childLlm === undefined) throw new Error('--child-llm-effort 는 --child-llm <provider>/<model> 과 함께 준다');
|
|
429
536
|
const childLlm = opts.childLlm !== undefined ? parseChildLlm(opts.childLlm, opts.childLlmEffort) : undefined;
|
|
537
|
+
const completion = opts.completion ?? 'code-pr';
|
|
538
|
+
const selected = selectTaskComposition(completion, opts.compositionRoot);
|
|
539
|
+
const selectionFailure = !selected.composition ? `조합 없음: graphs/work/${completion}.yaml`
|
|
540
|
+
: selected.error ?? (completion !== 'code-pr' || selected.launcher !== 'harness-say'
|
|
541
|
+
? `조합 실행기 없음: ${selected.composition.file} (1판은 code-pr harness-say만 발사)` : undefined);
|
|
430
542
|
let project: TaskCard['project'];
|
|
431
543
|
let targetIsGit: boolean | null = null;
|
|
432
544
|
if (opts.project) {
|
|
@@ -451,19 +563,25 @@ export async function handTask(opts: HandTaskOptions): Promise<HandTaskResult> {
|
|
|
451
563
|
...(opts.seat ? { seat: opts.seat } : {}),
|
|
452
564
|
...(opts.checklistId ? { checklistId: opts.checklistId } : {}),
|
|
453
565
|
...(opts.completion ? { completion: opts.completion } : {}),
|
|
566
|
+
...(selected.composition ? { composition: selected.composition, graphSelectedBy: 'completion' as const } : {}),
|
|
567
|
+
unitsResolved: selected.unitsResolved,
|
|
568
|
+
missingUnits: selected.missingUnits,
|
|
454
569
|
...(project ? { project } : {}),
|
|
455
570
|
...(opts.goal ? { goal: opts.goal } : {}),
|
|
456
571
|
...(opts.milestone ? { milestone: opts.milestone } : {}),
|
|
457
572
|
...(opts.taLand === true ? { taLand: true as const } : {}),
|
|
458
573
|
...(childLlm ? { childLlm } : {}),
|
|
459
574
|
createdAt: now.toISOString(),
|
|
460
|
-
status: 'handed',
|
|
461
|
-
history: [
|
|
575
|
+
status: selectionFailure ? 'failed' : 'handed',
|
|
576
|
+
history: selectionFailure ? [{ at: now.toISOString(), event: !selected.composition ? 'composition-missing'
|
|
577
|
+
: selected.error ? 'composition-invalid' : 'composition-launcher-unavailable', detail: selectionFailure }] : [],
|
|
462
578
|
...(opts.mission ? { mission: opts.mission, after: [...(opts.after ?? [])] } : {}),
|
|
463
|
-
...(opts.cardFields
|
|
579
|
+
...(opts.cardFields?.pr !== undefined ? { pr: opts.cardFields.pr } : {}),
|
|
580
|
+
...(opts.cardFields?.goalId !== undefined ? { goalId: opts.cardFields.goalId } : {}),
|
|
581
|
+
...(opts.cardFields?.handed !== undefined ? { handed: opts.cardFields.handed } : {}),
|
|
464
582
|
};
|
|
465
|
-
const move = nextMoveFor(card, opts.landed);
|
|
466
|
-
if (move.kind !== 'launch') throw new Error(`발사할 수 없는 과제다 — ${move.reason}`);
|
|
583
|
+
const move = selectionFailure ? undefined : nextMoveFor(card, opts.landed);
|
|
584
|
+
if (move && move.kind !== 'launch') throw new Error(`발사할 수 없는 과제다 — ${move.reason}`);
|
|
467
585
|
const guard = opts.writeGuard;
|
|
468
586
|
const writeCard = (target: string, next: TaskCard): void => {
|
|
469
587
|
if (!guard) { writeTaskCards(target, [next]); return; }
|
|
@@ -479,6 +597,13 @@ export async function handTask(opts: HandTaskOptions): Promise<HandTaskResult> {
|
|
|
479
597
|
if (!owned.ok) throw new TaskCardSupersededError(next.id);
|
|
480
598
|
};
|
|
481
599
|
writeCard(path, card);
|
|
600
|
+
try { debug.log('task-agent.graph-select', !selected.composition ? 'composition-missing' : selected.error ? 'composition-invalid' : 'composition-selected', {
|
|
601
|
+
completion, file: selected.composition?.file ?? `graphs/work/${completion}.yaml`,
|
|
602
|
+
unitsResolved: selected.unitsResolved, missingUnits: selected.missingUnits,
|
|
603
|
+
...(selected.error ? { error: selected.error } : {}),
|
|
604
|
+
}); } catch { /* fail-soft */ }
|
|
605
|
+
if (selectionFailure) throw new Error(`${selectionFailure} (카드 ${id})`);
|
|
606
|
+
if (!move || move.kind !== 'launch') throw new Error('조합 선택 뒤 발사 수가 없다');
|
|
482
607
|
try { debug.log('task-agent', 'handed', { taskId: id, seat: card.seat ?? null, checklistId: card.checklistId ?? null, mode, projectId: project?.id ?? null, targetIsGit, taLand: card.taLand === true }); } catch { /* fail-soft */ }
|
|
483
608
|
if (childLlm) {
|
|
484
609
|
try { debug.log('task-agent', 'child-llm', { taskId: id, input: opts.childLlm, provider: childLlm.provider, model: childLlm.model, effort: childLlm.effort ?? null, mode }); } catch { /* fail-soft */ }
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** Pod repair result: a commit based on the exact PR head, returned without opening a PR. */
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
6
|
+
|
|
7
|
+
export const REPAIR_REF = 'refs/elanous/repair/result';
|
|
8
|
+
export const REPAIR_BUNDLE_MAX_BYTES = 5 * 1024 * 1024;
|
|
9
|
+
const SHA40 = /^[0-9a-f]{40}$/;
|
|
10
|
+
|
|
11
|
+
export interface PodRepairResult {
|
|
12
|
+
pr: number;
|
|
13
|
+
baseHead: string;
|
|
14
|
+
runId: string;
|
|
15
|
+
commit: string;
|
|
16
|
+
bundleSha256: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Host-side receipt gate. A missing, mismatched, or truncated return is never a repair result. */
|
|
20
|
+
export function readPodRepairArtifact(artifactDir: string, expected: { pr: number; baseHead: string; runId?: string }): PodRepairResult {
|
|
21
|
+
const result = JSON.parse(readFileSync(join(artifactDir, 'result.json'), 'utf8')) as Partial<PodRepairResult>;
|
|
22
|
+
if (result.pr !== expected.pr || result.baseHead !== expected.baseHead || (expected.runId && result.runId !== expected.runId)
|
|
23
|
+
|| !result.runId || !SHA40.test(result.commit ?? '') || !/^[0-9a-f]{64}$/.test(result.bundleSha256 ?? '')) {
|
|
24
|
+
throw new Error('repair result mismatch');
|
|
25
|
+
}
|
|
26
|
+
const bundle = readFileSync(join(artifactDir, 'commit.bundle'));
|
|
27
|
+
if (bundle.length > REPAIR_BUNDLE_MAX_BYTES) throw new Error('repair bundle too large');
|
|
28
|
+
if (createHash('sha256').update(bundle).digest('hex') !== result.bundleSha256) throw new Error('repair bundle checksum mismatch');
|
|
29
|
+
return result as PodRepairResult;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function git(cwd: string, ...args: string[]): string {
|
|
33
|
+
return execFileSync('git', ['-C', cwd, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function completedWorktree(output: string): { path: string; runId: string } {
|
|
37
|
+
for (const line of output.trimEnd().split(/\r?\n/).reverse()) {
|
|
38
|
+
if (!line.startsWith('{')) continue;
|
|
39
|
+
let value: Record<string, unknown>;
|
|
40
|
+
try { value = JSON.parse(line) as Record<string, unknown>; } catch { continue; }
|
|
41
|
+
if (value.kind === 'self' && value.result && typeof value.result === 'object') value = value.result as Record<string, unknown>;
|
|
42
|
+
if (value.stage !== 'worktree-completed' || value.ok !== true || typeof value.worktreePath !== 'string'
|
|
43
|
+
|| typeof value.runId !== 'string' || !value.runId)
|
|
44
|
+
throw new Error('repair child final result is not a successful worktree-only result');
|
|
45
|
+
return { path: value.worktreePath, runId: value.runId };
|
|
46
|
+
}
|
|
47
|
+
throw new Error('repair child did not return a successful worktree-only result');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Called in the Pod after the child exits successfully and before artifact export. */
|
|
51
|
+
export function writePodRepairCommit(input: { pr: number; baseHead: string; runId: string; output: string; repo: string; outbox: string }): PodRepairResult {
|
|
52
|
+
if (!Number.isSafeInteger(input.pr) || input.pr < 1 || !SHA40.test(input.baseHead)) throw new Error('invalid repair PR or base head');
|
|
53
|
+
if (!input.runId.trim()) throw new Error('repair parent run id missing');
|
|
54
|
+
const { path, runId: childRunId } = completedWorktree(input.output);
|
|
55
|
+
if (!isAbsolute(path)) throw new Error('repair worktree path is not absolute');
|
|
56
|
+
const worktree = realpathSync(path);
|
|
57
|
+
const repo = realpathSync(input.repo);
|
|
58
|
+
if (worktree === repo) throw new Error('repair result must be a separate worktree');
|
|
59
|
+
if (git(worktree, 'rev-parse', '--is-inside-work-tree') !== 'true') throw new Error('repair result is not a git worktree');
|
|
60
|
+
const commonDir = (cwd: string) => realpathSync(resolve(cwd, git(cwd, 'rev-parse', '--git-common-dir')));
|
|
61
|
+
if (commonDir(worktree) !== commonDir(repo)) throw new Error('repair worktree belongs to another repository');
|
|
62
|
+
if (git(worktree, 'config', '--worktree', '--get', 'elanous.harness.owner') !== `dev:${childRunId}`
|
|
63
|
+
|| git(worktree, 'config', '--worktree', '--get', 'elanous.harness.command') !== 'elanous dev')
|
|
64
|
+
throw new Error('repair worktree is not owned by the completed child run');
|
|
65
|
+
if (git(repo, 'rev-parse', 'HEAD') !== input.baseHead) throw new Error('Pod source head changed before repair export');
|
|
66
|
+
git(worktree, 'cat-file', '-e', `${input.baseHead}^{commit}`);
|
|
67
|
+
execFileSync('git', ['-C', worktree, 'add', '-A', '--', '.'], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
68
|
+
const staged = git(worktree, 'diff', '--cached', '--name-only');
|
|
69
|
+
if (staged) git(worktree, 'commit', '-m', `repair: PR #${input.pr} from ${input.baseHead.slice(0, 12)}`);
|
|
70
|
+
const commit = git(worktree, 'rev-parse', 'HEAD');
|
|
71
|
+
if (!SHA40.test(commit) || commit === input.baseHead) throw new Error('repair made no new commit');
|
|
72
|
+
execFileSync('git', ['-C', worktree, 'merge-base', '--is-ancestor', input.baseHead, commit], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
73
|
+
git(worktree, 'update-ref', REPAIR_REF, commit);
|
|
74
|
+
const dir = join(input.outbox, 'repair');
|
|
75
|
+
mkdirSync(dir, { recursive: true });
|
|
76
|
+
const bundle = join(dir, 'commit.bundle');
|
|
77
|
+
execFileSync('git', ['-C', worktree, 'bundle', 'create', bundle, REPAIR_REF, `^${input.baseHead}`], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
78
|
+
const size = statSync(bundle).size;
|
|
79
|
+
if (size > REPAIR_BUNDLE_MAX_BYTES) throw new Error(`repair bundle too large: ${size} > ${REPAIR_BUNDLE_MAX_BYTES}`);
|
|
80
|
+
const bundleSha256 = createHash('sha256').update(readFileSync(bundle)).digest('hex');
|
|
81
|
+
const result = { pr: input.pr, baseHead: input.baseHead, runId: input.runId, commit, bundleSha256 };
|
|
82
|
+
writeFileSync(join(dir, 'result.json'), JSON.stringify(result) + '\n', { flag: 'wx', mode: 0o600 });
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (import.meta.main) {
|
|
87
|
+
try {
|
|
88
|
+
const [pr, baseHead, outputPath, repo, outbox] = process.argv.slice(2);
|
|
89
|
+
if (!pr || !baseHead || !outputPath || !repo || !outbox) throw new Error('usage: pod-repair-commit <pr> <base-head> <child-output> <repo> <outbox>');
|
|
90
|
+
const result = writePodRepairCommit({ pr: Number(pr), baseHead, runId: process.env.ELANOUS_PARENT_RUN_ID ?? '', output: readFileSync(outputPath, 'utf8'), repo, outbox });
|
|
91
|
+
console.log(`ELANOUS_POD_REPAIR_COMMIT ${result.pr} ${result.baseHead} ${result.commit}`);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error(`ELANOUS_POD_REPAIR_FAILED ${error instanceof Error ? error.message : String(error)}`);
|
|
94
|
+
process.exitCode = 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -19,6 +19,7 @@ import { coalescedInstallationCredential } from '../../auth/github-app-token.js'
|
|
|
19
19
|
import { POD_CREDENTIAL_TOKEN_ENV, POD_CREDENTIAL_URL_ENV } from '../../grok/credential.js';
|
|
20
20
|
import { collectPodLedgers, createPodLedgerFollower } from './pod-ledger-collect.js';
|
|
21
21
|
import { collectPodArtifacts, podNoResultDiagnostic } from './pod-artifact-return.js';
|
|
22
|
+
import { readPodRepairArtifact } from './pod-repair-commit.js';
|
|
22
23
|
import { isValidAccountName, resolveCodexAccount } from '../../oauth/codex-account.js';
|
|
23
24
|
import { effectiveInstanceRoot } from '../../instance/resolve.js';
|
|
24
25
|
import { controlInboxEnv } from '../../harness/control-inbox.js';
|
|
@@ -27,7 +28,7 @@ import { mintRunId, normalizeRunId } from '../../harness/harness-space.js';
|
|
|
27
28
|
import { TA_LAND_ENV } from '../../task-agent/task-hand.js';
|
|
28
29
|
import { appendRunLedgerEntry, loadRunLedger, runLedgerDir, runLedgerPath } from '../../self-implement/run-ledger.js';
|
|
29
30
|
import type { GoalExecutionRecord } from '../../self-implement/orchestrator.js';
|
|
30
|
-
import { runHostRegate, type HostRegateResult } from '../../self-implement/host-regate.js';
|
|
31
|
+
import { hostRepositoryUnavailable, runHostRegate, type HostRegateResult } from '../../self-implement/host-regate.js';
|
|
31
32
|
import { resolvePodHostAcpReview, runPodHostAcpReview, type PodHostAcpReviewOutcome } from './pod-host-acp-review.js';
|
|
32
33
|
import { REVIEW_DEFERRED_TO_HOST_ENV, REVIEW_DEFERRED_TO_HOST_MERGE_REASON } from '../../self-implement/review-deferred-to-host.js';
|
|
33
34
|
import { classifyPodTerminal, applyPodTerminal, podTerminalPrBody, type PrTerminalRecord, type DraftResidueContext } from '../../self-dev/draft-residue.js';
|
|
@@ -137,6 +138,8 @@ export interface PodSpawnOptions {
|
|
|
137
138
|
authorConfig?: () => UserConfig;
|
|
138
139
|
/** 원천 — 없으면 종전 `git clone --depth 50`. bundle 이면 apply 뒤 kubectl cp 로 싣는다. */
|
|
139
140
|
source?: PodSource;
|
|
141
|
+
/** Existing PR repair: pin the source head and export a commit bundle instead of opening a PR. */
|
|
142
|
+
repairCommitOnly?: { pr: number; baseHead: string };
|
|
140
143
|
/** 호스트 환경에서 읽어 Pod env 로 넣을 키 이름(예: OPENROUTER_API_KEY · ANTHROPIC_API_KEY) — 벤치마크 과금 경로. */
|
|
141
144
|
passEnv?: readonly string[];
|
|
142
145
|
/** 호스트가 재발사를 소유하면 Pod 자식의 자체 감독을 끈다. 독립 호출 기본 true; 실제 CLI 호스트는 자기 감독 상태를 명시한다. */
|
|
@@ -953,7 +956,10 @@ export function podSigtermTrapScript(seqFile: string = POD_USAGE_PARTIAL_SEQ_FIL
|
|
|
953
956
|
return `trap '{ [ -n "$si_pid" ] && kill "$si_pid" 2>/dev/null || true; [ -n "\${usage_partial_pid:-}" ] && kill "$usage_partial_pid" 2>/dev/null || true; si_seq=$(cat ${seqFile} 2>/dev/null || true); case "$si_seq" in ""|*[!0-9]*) si_seq=0;; esac; si_seq=$((10#$si_seq + 1)); { [ -f scripts/usage-rollup.ts ] && bun scripts/usage-rollup.ts --since 12h --partial --seq "$si_seq"; } & si_rollup_pid=$!; for si_i in 1 2 3 4 5 6 7 8; do kill -0 "$si_rollup_pid" 2>/dev/null || break; sleep 1; done; kill "$si_rollup_pid" 2>/dev/null || true; wait "$si_rollup_pid" 2>/dev/null || true; exit 143; }' TERM`;
|
|
954
957
|
}
|
|
955
958
|
|
|
956
|
-
export function podJobManifest(o: { name: string; namespace: string; image: string; workIdentity?: PodWorkIdentity; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; hostMirror?: string; args: readonly string[]; passEnv: readonly string[]; deadlineSeconds: number; runId?: string; parentRunId?: string; armEnv?: Readonly<Record<string, string>>; hostId?: string; imageCommit?: string | null; skillEnvs?: readonly string[]; memoryLimit?: string; memoryRequest?: string; goalDoc?: string; /** AUTHOR-POD2 — run `harness say` on /creds/feature inside the Pod (authoring happens off the host). */ authorSentence?: boolean; authorGrade?: 'full' | 'lite'; grokCredential?: 'subscription' | 'api_key'; codexAccounts?: readonly string[]; appCredential?: boolean; /** Test seam — default POD_GH_STALE_SECONDS. */ githubStaleSeconds?: number; /** Which goal execution and attempt this Job is — a resumed host verifies it before following the Job (POD9). */ execution?: { key: string; attempt: number }; hostLeaseAdmitted?: boolean; /** POD-ROLE-LLM — OpenRouter 자식 Pod 에 리뷰어용 codex 자격을 깐다. */ reviewerCodexAuth?: boolean; /** BEDROCK-PROVIDER ② — bedrock 자식만: 이름 있는 AWS Secret 의 키(secretKeyRef) ⊕ 리전 ⊕ 이그레스 라벨. */ bedrock?: BedrockPodSpec; /** ZAI-POD-CHILD — glm(ZAI) 자식만: 이름 있는 ZAI Secret 의 키(secretKeyRef) ⊕ 이그레스·동시 상한 라벨(Pod ⊕ Job). */ zai?: ZaiPodSpec }): Record<string, unknown> {
|
|
959
|
+
export function podJobManifest(o: { name: string; namespace: string; image: string; workIdentity?: PodWorkIdentity; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; repairCommitOnly?: { pr: number; baseHead: string }; hostMirror?: string; args: readonly string[]; passEnv: readonly string[]; deadlineSeconds: number; runId?: string; parentRunId?: string; armEnv?: Readonly<Record<string, string>>; hostId?: string; imageCommit?: string | null; skillEnvs?: readonly string[]; memoryLimit?: string; memoryRequest?: string; goalDoc?: string; /** AUTHOR-POD2 — run `harness say` on /creds/feature inside the Pod (authoring happens off the host). */ authorSentence?: boolean; authorGrade?: 'full' | 'lite'; grokCredential?: 'subscription' | 'api_key'; codexAccounts?: readonly string[]; appCredential?: boolean; /** Test seam — default POD_GH_STALE_SECONDS. */ githubStaleSeconds?: number; /** Which goal execution and attempt this Job is — a resumed host verifies it before following the Job (POD9). */ execution?: { key: string; attempt: number }; hostLeaseAdmitted?: boolean; /** POD-ROLE-LLM — OpenRouter 자식 Pod 에 리뷰어용 codex 자격을 깐다. */ reviewerCodexAuth?: boolean; /** BEDROCK-PROVIDER ② — bedrock 자식만: 이름 있는 AWS Secret 의 키(secretKeyRef) ⊕ 리전 ⊕ 이그레스 라벨. */ bedrock?: BedrockPodSpec; /** ZAI-POD-CHILD — glm(ZAI) 자식만: 이름 있는 ZAI Secret 의 키(secretKeyRef) ⊕ 이그레스·동시 상한 라벨(Pod ⊕ Job). */ zai?: ZaiPodSpec }): Record<string, unknown> {
|
|
960
|
+
if (o.repairCommitOnly && (o.source?.kind !== 'pr' || o.source.number !== o.repairCommitOnly.pr || !/^[0-9a-f]{40}$/.test(o.repairCommitOnly.baseHead) || o.args.includes('--open-pr') || o.args.includes('--merge-by-host') || o.args.includes('--auto-merge'))) {
|
|
961
|
+
throw new Error('repair commit-only requires matching PR source and no PR/merge flags');
|
|
962
|
+
}
|
|
957
963
|
const quoted = o.args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(' ');
|
|
958
964
|
const childProviderIndex = o.args.indexOf('--child-llm-provider');
|
|
959
965
|
const childModelIndex = o.args.indexOf('--child-llm-model');
|
|
@@ -962,7 +968,7 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
|
|
|
962
968
|
// POD-ANTHROPIC-PROVIDER — anthropic 자식은 키(env · secretKeyRef)만 쓴다. codex·grok 자격 파일을 깔지 않는다.
|
|
963
969
|
const anthropicChild = childProviderIndex >= 0 && o.args[childProviderIndex + 1] === 'anthropic';
|
|
964
970
|
const goalPath = o.goalDoc ? `'${(o.goalDoc.startsWith('-') ? `./${o.goalDoc}` : o.goalDoc).replace(/'/g, `'\\''`)}'` : undefined;
|
|
965
|
-
const delegated = Boolean(o.goalDoc) || o.authorSentence === true;
|
|
971
|
+
const delegated = !o.repairCommitOnly && (Boolean(o.goalDoc) || o.authorSentence === true);
|
|
966
972
|
const askBaseIndex = delegated ? o.args.indexOf('--base') : -1;
|
|
967
973
|
const quotedAsk = (askBaseIndex >= 0 && o.args[askBaseIndex + 1]
|
|
968
974
|
? ` '--base' '${o.args[askBaseIndex + 1]!.replace(/'/g, `'\\''`)}'`
|
|
@@ -997,12 +1003,13 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
|
|
|
997
1003
|
podGitPushGuardScript(),
|
|
998
1004
|
'curl -s -m 3 -o /dev/null http://host.orb.internal:31415/health && { echo "[pod] ISOLATION FAIL"; exit 3; }',
|
|
999
1005
|
podSourceScript(o.source ?? { kind: 'default' }, o.repoUrl),
|
|
1006
|
+
...(o.repairCommitOnly ? [`[ "$(git rev-parse HEAD)" = '${o.repairCommitOnly.baseHead}' ] || { echo 'ELANOUS_POD_REPAIR_FAILED source-head-mismatch'; exit 8; }`] : []),
|
|
1000
1007
|
...(goalPath ? [
|
|
1001
1008
|
`mkdir -p -- "$(dirname -- ${goalPath})" && cp -- /creds/goal-doc ${goalPath} || exit 6`,
|
|
1002
1009
|
] : []),
|
|
1003
1010
|
// Only fixed command shapes leave the Pod; arbitrary argv (including shell -c text) is never emitted.
|
|
1004
1011
|
`(while :; do { mem=$(if [ -r /sys/fs/cgroup/memory.current ]; then cat /sys/fs/cgroup/memory.current; elif [ -r /sys/fs/cgroup/memory/memory.usage_in_bytes ]; then cat /sys/fs/cgroup/memory/memory.usage_in_bytes; else printf -- -; fi); top=$(ps -eo rss=,comm=,args= --sort=-rss | head -5 | awk 'function encode(s) { gsub(/%/, "%25", s); gsub(/:/, "%3A", s); gsub(/ /, "%20", s); gsub(/\\t/, "%09", s); return s } { rss=$1; name=$2; sub(/^[[:space:]]*[0-9]+[[:space:]]+[^[:space:]]+[[:space:]]*/, ""); n=split($0, a, /[[:space:]]+/); cmd="<redacted>"; if (name=="sleep" && a[2]=="30") cmd="sleep 30" (n>2 ? " <redacted>" : ""); else if (name=="bun") { cmd="bun <redacted>"; if (a[2]=="test") cmd="bun test <redacted>"; else if (a[2]=="run") cmd="bun run <redacted>"; else if ((a[2]=="x" || a[2]=="exec") && a[3]=="tsc") cmd="bun x tsc <redacted>" } else if (name=="tsc") cmd="tsc <redacted>"; else if (name=="elanous") { cmd="elanous <redacted>"; if (a[2]=="self") cmd="elanous self <redacted>"; else if (a[2]=="harness") cmd="elanous harness <redacted>" } else if (name=="node") cmd="node <redacted>"; if (name=="sleep" && cmd=="<redacted>") cmd="sleep <redacted>"; else if (cmd=="<redacted>" && name!="bash" && name!="sh") name="other"; printf " %s:%s:%s", rss, encode(name), encode(substr(cmd,1,120)) }'); printf "ELANOUS_MEM %s %s%s\\n" "$(date +%s)" "$mem" "$top"; } || true; sleep 15 || break; done) & mem_sampler_pid=$!`,
|
|
1005
|
-
...(o.appCredential ? [podGithubWatchdogScript(o.githubStaleSeconds ?? POD_GH_STALE_SECONDS)] : []),
|
|
1012
|
+
...(o.appCredential && !o.repairCommitOnly ? [podGithubWatchdogScript(o.githubStaleSeconds ?? POD_GH_STALE_SECONDS)] : []),
|
|
1006
1013
|
// METER-POD-ROLLUP-GAP — 부분 롤업 표본기: 최종 줄까지 못 사는 Pod 도 N 분마다(기본 5 · ELANOUS_USAGE_PARTIAL_MINUTES) PARTIAL 줄을 하나씩 남긴다.
|
|
1007
1014
|
`(usage_partial_seq=0; while :; do { usage_partial_seq=$((usage_partial_seq+1)); printf '%s\\n' "$usage_partial_seq" > ${POD_USAGE_PARTIAL_SEQ_FILE} 2>/dev/null || true; [ -f scripts/usage-rollup.ts ] && bun scripts/usage-rollup.ts --since 12h --partial --seq "$usage_partial_seq" || true; } || true; usage_partial_sleep=$(( \${ELANOUS_USAGE_PARTIAL_MINUTES:-5} * 60 )); sleep "$usage_partial_sleep" || break; done) & usage_partial_pid=$!`,
|
|
1008
1015
|
// ⭐ MF-d3b5a70f — si_pid 초기화 + trap: 메인 명령을 아래에서 `& si_pid=$!` 로 띄우고 `wait "$si_pid"` 로
|
|
@@ -1013,7 +1020,9 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
|
|
|
1013
1020
|
// 마지막 줄 JSON 이 «맨 끝»이어야 한다(parseSelfImplementJson) — rollup 은 그 앞에.
|
|
1014
1021
|
// ⛔ `--author-grade` 는 Pod 안 `elanous`(이미지에 깔린 판)가 모를 수 있어 넘기지 않는다 — 10-06 «unknown option» 즉사(#24445 되돌림).
|
|
1015
1022
|
// AUTHOR-LITE2-POD — 등급은 환경변수 `ELANOUS_AUTHOR_GRADE` 로 싣는다(옛 Pod elanous 는 모르는 환경변수를 무시하고 full 로 돈다).
|
|
1016
|
-
o.
|
|
1023
|
+
o.repairCommitOnly
|
|
1024
|
+
? `export ${OLD_DOOR_STAMP_ENV}=self-implement; elanous self implement "$(cat /creds/feature)" --json --no-open-pr ${quoted} > /tmp/si.out 2>&1 & si_pid=$!; wait "$si_pid"; rc=$?; if [ "$rc" -eq 0 ]; then elanous self repair-commit-export '${o.repairCommitOnly.pr}' '${o.repairCommitOnly.baseHead}' /tmp/si.out "$PWD" "$HOME/outbox" || rc=8; fi`
|
|
1025
|
+
: o.authorSentence
|
|
1017
1026
|
? `echo "ELANOUS_AUTHOR_ON_POD started host=$(hostname) at=$(date -u +%Y-%m-%dT%H:%M:%SZ)"; author_t0=$(date +%s); ${o.authorGrade === 'lite' || o.authorGrade === 'full' ? `ELANOUS_AUTHOR_GRADE=${o.authorGrade} ` : ''}elanous harness say --substrate local --json${quotedAsk}${quotedChildLlm} -- "$(cat /creds/feature)" > /tmp/si.out 2>&1 & si_pid=$!; wait "$si_pid"; rc=$?; echo "ELANOUS_AUTHOR_ON_POD finished host=$(hostname) rc=$rc seconds=$(( $(date +%s) - author_t0 ))"; ${POD_RESULT_FLATTEN}`
|
|
1018
1027
|
: goalPath
|
|
1019
1028
|
? `elanous harness ask ${goalPath} --json${quotedAsk}${quotedChildLlm} > /tmp/si.out 2>&1 & si_pid=$!; wait "$si_pid"; rc=$?; ${POD_RESULT_FLATTEN}`
|
|
@@ -1074,12 +1083,12 @@ for ledger in "\${ELANOUS_STATE_DIR:-$HOME/.elanous}"/run-ledger/*.jsonl; do
|
|
|
1074
1083
|
done
|
|
1075
1084
|
if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
|
|
1076
1085
|
'kill "$mem_sampler_pid" 2>/dev/null || true',
|
|
1077
|
-
...(o.appCredential ? ['kill "$gh_watch_pid" 2>/dev/null || true'] : []),
|
|
1086
|
+
...(o.appCredential && !o.repairCommitOnly ? ['kill "$gh_watch_pid" 2>/dev/null || true'] : []),
|
|
1078
1087
|
'[ -n "${usage_partial_pid:-}" ] && kill "$usage_partial_pid" 2>/dev/null || true',
|
|
1079
1088
|
'wait "$mem_sampler_pid" 2>/dev/null || true',
|
|
1080
1089
|
'[ -n "${usage_partial_pid:-}" ] && wait "$usage_partial_pid" 2>/dev/null || true',
|
|
1081
1090
|
'tail -n 1 /tmp/si.out',
|
|
1082
|
-
podSalvageScript(),
|
|
1091
|
+
...(o.repairCommitOnly ? [] : [podSalvageScript()]),
|
|
1083
1092
|
'exit $rc',
|
|
1084
1093
|
].join('\n');
|
|
1085
1094
|
return {
|
|
@@ -1108,6 +1117,7 @@ if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
|
|
|
1108
1117
|
env: [
|
|
1109
1118
|
...(o.runId ? [{ name: 'ELANOUS_RUN_ID', value: o.runId }] : []),
|
|
1110
1119
|
...(o.parentRunId ? [{ name: 'ELANOUS_PARENT_RUN_ID', value: o.parentRunId }] : []),
|
|
1120
|
+
...(o.workIdentity?.taskCard ? [{ name: 'ELANOUS_TASK_CARD_ID', value: o.workIdentity.taskCard }] : []),
|
|
1111
1121
|
{ name: 'ELANOUS_SUBSTRATE', value: 'pod' },
|
|
1112
1122
|
...Object.entries(controlInboxEnv(POD_CONTROL_INBOX_DIR)).map(([name, value]) => ({ name, value })),
|
|
1113
1123
|
// ⭐ 런 출처(🅣 RFC run-origin §A3 · #20457/#20468 칸 이름) — Pod 는 자기 elanous_id 를 쓰지 않고 «띄운 감독기»의 hostId 를 물려받는다.
|
|
@@ -1148,6 +1158,9 @@ function defaultGhComment(prNumber: number, body: string): void {
|
|
|
1148
1158
|
}
|
|
1149
1159
|
|
|
1150
1160
|
export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplementJobSpawn {
|
|
1161
|
+
if (options.repairCommitOnly && (options.source?.kind !== 'pr' || options.source.number !== options.repairCommitOnly.pr || !/^[0-9a-f]{40}$/.test(options.repairCommitOnly.baseHead))) {
|
|
1162
|
+
throw new Error('repair commit-only requires a matching PR source and exact head');
|
|
1163
|
+
}
|
|
1151
1164
|
const baseKubectl = options.kubectl ?? defaultKubectl;
|
|
1152
1165
|
const sleep = options.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)));
|
|
1153
1166
|
const namespace = options.namespace ?? 'elanous-test';
|
|
@@ -1159,6 +1172,9 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1159
1172
|
if (hostMirror && !isAbsolute(hostMirror)) throw new Error('pod.hostMirror must be an absolute directory path');
|
|
1160
1173
|
const hostSupervised = options.hostSupervised ?? true;
|
|
1161
1174
|
return (input) => {
|
|
1175
|
+
if (options.repairCommitOnly && (input.autoMerge || input.openPr || input.base !== `pr-${options.repairCommitOnly.pr}`)) {
|
|
1176
|
+
throw new Error('repair commit-only refuses auto merge, new PR, or a different base');
|
|
1177
|
+
}
|
|
1162
1178
|
const name = podJobName(input.spaceId);
|
|
1163
1179
|
let parentGoal: string | undefined;
|
|
1164
1180
|
let parentGoalUnreadable = false;
|
|
@@ -1211,6 +1227,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1211
1227
|
const childRunId = mintRunId();
|
|
1212
1228
|
const executionKey = createHash('sha256').update(JSON.stringify({ spaceId: input.spaceId, feature: input.feature, base: input.base, autoMerge: input.autoMerge, autoReview: input.autoReview, openPr: input.openPr, draft: input.draft })).digest('hex');
|
|
1213
1229
|
const parentRunId = env.ELANOUS_RUN_ID?.trim();
|
|
1230
|
+
if (options.repairCommitOnly && !parentRunId) throw new Error('repair commit-only requires a parent run id');
|
|
1214
1231
|
// Standalone callers journal retries without altering the existing Pod manifest.
|
|
1215
1232
|
const ledgerRunId = parentRunId || `run-${executionKey.slice(0, 8)}-${executionKey.slice(8, 12)}-${executionKey.slice(12, 16)}-${executionKey.slice(16, 20)}-${executionKey.slice(20, 32)}`;
|
|
1216
1233
|
const address = `self-impl:${input.spaceId}`;
|
|
@@ -1236,7 +1253,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1236
1253
|
...(input.autoReview ? ['--auto-review'] : []),
|
|
1237
1254
|
// ⭐ 계약이 미래 노드를 바꾼다(첫 규칙): Pod 는 끝나면 사라지므로 완료 하한 = PR. 작업 트리로만 끝나면 결과가 Pod 와 함께 없어진다.
|
|
1238
1255
|
// ⚠️ 임시 안전망(🅢 2026-09-26) — 🅣 의 «종결 하한 오버레이»(그래프 쪽)가 서면 이 줄을 지우고 그것으로 바꾼다(채널 합의).
|
|
1239
|
-
...(input.openPr || (!input.autoMerge && completionFloorFor({ substrate: 'pod' }) === 'pr') ? ['--open-pr'] : []),
|
|
1256
|
+
...(!options.repairCommitOnly && (input.openPr || (!input.autoMerge && completionFloorFor({ substrate: 'pod' }) === 'pr')) ? ['--open-pr'] : []),
|
|
1240
1257
|
...(input.draft === false ? ['--no-draft'] : []),
|
|
1241
1258
|
...(hostSupervised ? ['--no-supervise'] : []),
|
|
1242
1259
|
...(options.extraArgs ?? []),
|
|
@@ -1326,7 +1343,8 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1326
1343
|
if (unfitDone) return unfitDone;
|
|
1327
1344
|
// HARNESS-FULL-GRAPH — Pod 입장(입장 관문 ⊕ 멤버 슬롯)은 그래프 노드 pool-admit 이다.
|
|
1328
1345
|
// 2판: decompose →(substrate=pod) pool-admit 간선은 «여기»(Pod 입장 직전 · 실제 단계 경계)에서 남긴다.
|
|
1329
|
-
routeJourneyEdge('
|
|
1346
|
+
if (options.repairCommitOnly) routeJourneyEdge('dispatch', 'pr-source', { source: 'pr', substrate: 'pod' });
|
|
1347
|
+
else routeJourneyEdge('decompose', 'pod', { substrate: 'pod' });
|
|
1330
1348
|
enterJourneyNode('pool-admit', { provenance: 'self-implement-pod', data: { spaceId: input.spaceId } });
|
|
1331
1349
|
// ⛔ 감싸는 함수(async 래퍼)를 쓰지 않는다 — 마이크로태스크가 늘면 Job apply 순서가 밀린다(시험이 잡았다). 같은 자리 try/catch 로.
|
|
1332
1350
|
const podAdmitError = (error: unknown): void => {
|
|
@@ -1353,6 +1371,8 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1353
1371
|
try {
|
|
1354
1372
|
if (!context) return { exitCode: 1, output: '', error: { code: 'pod-context', message: 'Pod Job context를 확인할 수 없다' } };
|
|
1355
1373
|
const cleanupSecret = () => { kubectl(['-n', namespace, 'delete', 'secret', `${name}-creds`, '--ignore-not-found']); };
|
|
1374
|
+
let repairResultAnnounced = false;
|
|
1375
|
+
let repairArtifactReady = false;
|
|
1356
1376
|
const collectFullLogs = (unavailableEvent: string, replace?: ReadonlySet<string>, onEvent?: (event: string, data: Record<string, unknown>) => void, childId?: string): { status: 'ok' | 'unavailable' | 'incomplete'; childLedgerIncomplete: boolean; samples: ReturnType<typeof parseMemSamples> } => {
|
|
1357
1377
|
let full: ReturnType<Kubectl>;
|
|
1358
1378
|
try {
|
|
@@ -1383,6 +1403,20 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1383
1403
|
collectPodArtifacts(full.stdout, { dir: join(effectiveInstanceRoot(), 'pod-artifacts'), job: name, log: record });
|
|
1384
1404
|
}
|
|
1385
1405
|
catch (error) { record('self-implement.pod', 'artifact-collect-incomplete', { job: name, reason: error instanceof Error ? error.message : String(error) }); }
|
|
1406
|
+
if (options.repairCommitOnly) {
|
|
1407
|
+
const artifactDir = join(effectiveInstanceRoot(), 'pod-artifacts', name, 'repair');
|
|
1408
|
+
try {
|
|
1409
|
+
const result = readPodRepairArtifact(artifactDir, { ...options.repairCommitOnly, runId: parentRunId });
|
|
1410
|
+
repairArtifactReady = true;
|
|
1411
|
+
if (!repairResultAnnounced) {
|
|
1412
|
+
console.log(`ELANOUS_POD_REPAIR_RESULT ${JSON.stringify({ job: name, artifactDir, pr: result.pr, baseHead: result.baseHead, runId: result.runId, commit: result.commit, bundleSha256: result.bundleSha256 })}`);
|
|
1413
|
+
repairResultAnnounced = true;
|
|
1414
|
+
}
|
|
1415
|
+
} catch (error) {
|
|
1416
|
+
repairArtifactReady = false;
|
|
1417
|
+
record('self-implement.pod', 'repair-artifact-incomplete', { job: name, reason: error instanceof Error ? error.message : String(error) });
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1386
1420
|
return { status: ledgerIncomplete || artifactIncomplete ? 'incomplete' : 'ok', childLedgerIncomplete, samples: parseMemSamples(full.stdout) };
|
|
1387
1421
|
};
|
|
1388
1422
|
let retryAccount: string | undefined;
|
|
@@ -1838,7 +1872,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1838
1872
|
slotId: env.ELANOUS_POD_SLOT_ID,
|
|
1839
1873
|
work: !goalDoc && env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? 'authoring' : (oomRetried ? retryTier : memoryTier) === 'lite' ? 'lite' : 'harness',
|
|
1840
1874
|
};
|
|
1841
|
-
const job = podJobManifest({ name, namespace, image: jobImage, workIdentity, ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}), repoUrl, ...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), args: jobArgs, passEnv: jobPassEnv, deadlineSeconds: options.deadlineSeconds ?? POD_JOB_DEADLINE_SECONDS, ...(goalDoc ? { goalDoc } : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? { authorSentence: true, ...(env.ELANOUS_POD_AUTHOR_GRADE === 'lite' || env.ELANOUS_POD_AUTHOR_GRADE === 'full' ? { authorGrade: env.ELANOUS_POD_AUTHOR_GRADE } : {}) } : {}), runId: launchRunId, ...(parentRunId ? { parentRunId } : {}), ...(jobArmEnv ? { armEnv: jobArmEnv } : {}), hostId: resolveHostId(env), skillEnvs: Object.keys(skillEnvs), memoryLimit, memoryRequest, imageCommit: options.imageCommit !== undefined ? options.imageCommit : options.kubectl ? null : podImageFreshness({ image }).imageCommit, ...(grok ? { grokCredential: 'grokAuth' in creds && creds.grokAuth ? 'subscription' as const : 'api_key' as const } : {}), ...(codexAccounts ? { codexAccounts } : {}), ...(hostRefresh ? { appCredential: true } : {}), execution: { key: executionKey, attempt: oomRetried ? 2 : 1 }, hostLeaseAdmitted: !!releaseAdmission, ...(reviewerCredential ? { reviewerCodexAuth: true } : {}), ...(bedrockSpec ? { bedrock: bedrockSpec } : {}), ...(zaiSpec ? { zai: zaiSpec } : {}) }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
|
|
1875
|
+
const job = podJobManifest({ name, namespace, image: jobImage, workIdentity, ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}), repoUrl, ...(options.source ? { source: options.source } : {}), ...(options.repairCommitOnly ? { repairCommitOnly: options.repairCommitOnly } : {}), ...(hostMirror ? { hostMirror } : {}), args: jobArgs, passEnv: jobPassEnv, deadlineSeconds: options.deadlineSeconds ?? POD_JOB_DEADLINE_SECONDS, ...(goalDoc ? { goalDoc } : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? { authorSentence: true, ...(env.ELANOUS_POD_AUTHOR_GRADE === 'lite' || env.ELANOUS_POD_AUTHOR_GRADE === 'full' ? { authorGrade: env.ELANOUS_POD_AUTHOR_GRADE } : {}) } : {}), runId: launchRunId, ...(parentRunId ? { parentRunId } : {}), ...(jobArmEnv ? { armEnv: jobArmEnv } : {}), hostId: resolveHostId(env), skillEnvs: Object.keys(skillEnvs), memoryLimit, memoryRequest, imageCommit: options.imageCommit !== undefined ? options.imageCommit : options.kubectl ? null : podImageFreshness({ image }).imageCommit, ...(grok ? { grokCredential: 'grokAuth' in creds && creds.grokAuth ? 'subscription' as const : 'api_key' as const } : {}), ...(codexAccounts ? { codexAccounts } : {}), ...(hostRefresh ? { appCredential: true } : {}), execution: { key: executionKey, attempt: oomRetried ? 2 : 1 }, hostLeaseAdmitted: !!releaseAdmission, ...(reviewerCredential ? { reviewerCodexAuth: true } : {}), ...(bedrockSpec ? { bedrock: bedrockSpec } : {}), ...(zaiSpec ? { zai: zaiSpec } : {}) }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
|
|
1842
1876
|
const carriedBranch = goalDoc ? 'goal-doc' as const : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? 'author' as const : undefined;
|
|
1843
1877
|
const carriedChildLlm = carriedBranch ? podDelegatedChildLlm(jobArgs) : undefined;
|
|
1844
1878
|
const a = kubectl(['apply', '-f', '-'], JSON.stringify(job));
|
|
@@ -2046,6 +2080,9 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2046
2080
|
}
|
|
2047
2081
|
// Pod 안 경로는 호스트에서 쓸 수 없다.
|
|
2048
2082
|
let disposition = parsed ? { ...parsed, worktreePath: undefined } : undefined;
|
|
2083
|
+
if (options.repairCommitOnly && state === 'complete' && !repairArtifactReady) {
|
|
2084
|
+
disposition = { ...disposition, worktreePath: undefined, ok: false, stage: 'repair-artifact-missing', detail: 'Pod repair result or bundle was not collected' };
|
|
2085
|
+
}
|
|
2049
2086
|
if (input.autoMerge && state === 'complete' && disposition?.stage === 'merge-ready') {
|
|
2050
2087
|
const prNumber = disposition.prNumber;
|
|
2051
2088
|
const headCommit = disposition.checkedHeadCommit;
|
|
@@ -2096,13 +2133,35 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2096
2133
|
debug.log('harness.host-regate', 'unmeasured', { pr: prNumber ?? null, files: [], os: process.platform, failedStep: 'pod-result' });
|
|
2097
2134
|
regate = { passed: false, failures: [{ step: 'pod-result', detail: 'missing PR number or checked head commit' }], os: process.platform };
|
|
2098
2135
|
} else {
|
|
2136
|
+
let hostRoot: string | undefined;
|
|
2137
|
+
let hostCwd: string | undefined;
|
|
2099
2138
|
try {
|
|
2100
|
-
|
|
2139
|
+
hostCwd = process.cwd();
|
|
2140
|
+
hostRoot = findGitDir(hostCwd)?.root;
|
|
2101
2141
|
if (!hostRoot) throw new Error('host repository unavailable');
|
|
2102
2142
|
regate = await (options.hostRegate ?? runHostRegate)({ prNumber, headCommit, repoRoot: hostRoot, ...(goalFile ? { goalFile } : {}), ...(taLandOwner ? { noMerge: true as const } : {}) });
|
|
2103
2143
|
} catch (error) {
|
|
2104
2144
|
debug.log('harness.host-regate', 'unmeasured', { pr: prNumber, files: [], os: process.platform, failedStep: 'host-regate' });
|
|
2105
|
-
regate = { passed: false, failures: [{ step: 'host-regate', detail: error instanceof Error ? error.message : String(error) }], os: process.platform };
|
|
2145
|
+
regate = { passed: false, ...(taLandOwner ? { status: 'unmeasured' as const } : {}), failures: [{ step: 'host-regate', detail: error instanceof Error ? error.message : String(error) }], os: process.platform };
|
|
2146
|
+
}
|
|
2147
|
+
if (taLandOwner && !hostRoot && hostRepositoryUnavailable(regate)) {
|
|
2148
|
+
let recordedRoot: string | undefined;
|
|
2149
|
+
try {
|
|
2150
|
+
const recorded = loadRunLedger(ledgerRunId, ledgerDir)?.find((entry) => entry.event === 'start')?.data.targetRoot;
|
|
2151
|
+
if (typeof recorded === 'string' && isAbsolute(recorded)) {
|
|
2152
|
+
const foundRoot = findGitDir(recorded)?.root;
|
|
2153
|
+
// The recorded path must itself name the repository root, including symlink aliases.
|
|
2154
|
+
if (foundRoot && realpathSync(foundRoot) === realpathSync(recorded)) recordedRoot = foundRoot;
|
|
2155
|
+
}
|
|
2156
|
+
} catch { /* unreadable ledger or path leaves the original unmeasured result intact */ }
|
|
2157
|
+
if (recordedRoot) {
|
|
2158
|
+
try {
|
|
2159
|
+
regate = await (options.hostRegate ?? runHostRegate)({ prNumber, headCommit, repoRoot: recordedRoot, ...(goalFile ? { goalFile } : {}), noMerge: true });
|
|
2160
|
+
} catch (error) {
|
|
2161
|
+
regate = { passed: false, status: 'unmeasured', failures: [{ step: 'host-regate', detail: error instanceof Error ? error.message : String(error) }], os: process.platform };
|
|
2162
|
+
}
|
|
2163
|
+
debug.log('harness.host-regate', 'regate-retry', { pr: prNumber, from: hostCwd, to: recordedRoot, result: regate.status ?? (regate.passed ? 'passed' : 'failed') });
|
|
2164
|
+
}
|
|
2106
2165
|
}
|
|
2107
2166
|
}
|
|
2108
2167
|
// runHostRegate comments on its own failures; these two paths never reached it.
|