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
|
@@ -166,6 +166,7 @@ export async function executeNextAction(action: NextAction, deps: ActionDeps): P
|
|
|
166
166
|
catch { return card('PR labels unavailable'); }
|
|
167
167
|
if (!Array.isArray(labels) || !labels.every(label => label && typeof label === 'object' && typeof label.name === 'string')) return card('PR labels unavailable');
|
|
168
168
|
if (labels.some(label => label.name === 'elanous:release-path')) return card('release-path');
|
|
169
|
+
if (labels.some(label => label.name === 'elanous:keep')) return card('elanous:keep');
|
|
169
170
|
const day = new Date().toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' });
|
|
170
171
|
if (deps.landingDay && deps.landingDay !== day) deps.landingsToday = 0;
|
|
171
172
|
deps.landingDay = day;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/** Attach an existing open PR to a TA card without starting another implementation run. */
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { loadRunLedger, runLedgerDir, type RunLedgerEntry } from '../self-implement/run-ledger.js';
|
|
4
|
+
import { remoteLedgerMirrorDir } from './parent-host.js';
|
|
5
|
+
import { prodInstanceRoot } from '../instance/resolve.js';
|
|
6
|
+
import { cardPrNumber, updateTaskCard, type TaskCard } from './task-hand.js';
|
|
7
|
+
|
|
8
|
+
export interface AdoptPrView {
|
|
9
|
+
number: number; url: string; state: string; baseRefName: string; headRefOid: string;
|
|
10
|
+
}
|
|
11
|
+
export interface AdoptPrInput {
|
|
12
|
+
pr: AdoptPrView;
|
|
13
|
+
repo: string;
|
|
14
|
+
statePath: string;
|
|
15
|
+
cardId?: string;
|
|
16
|
+
version?: string;
|
|
17
|
+
checklist?: { version: string; id: string; title: string; status: string };
|
|
18
|
+
now?: Date;
|
|
19
|
+
terminalRunEvidence?: boolean;
|
|
20
|
+
unlinkedTerminal?: boolean;
|
|
21
|
+
observedRun?: { runId?: string; runChildId?: string; parentHost?: string; launchId?: string };
|
|
22
|
+
observedChecklist?: { id: string; version: string; status: string; cardVersion?: string };
|
|
23
|
+
checklistAtWrite?: () => { id: string; status: string } | undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** A bound implementation may still push to its PR. Require the child to have stopped on this exact PR first. */
|
|
27
|
+
export function adoptedRunTerminal(card: TaskCard, pr: AdoptPrView,
|
|
28
|
+
load: (runId: string, dir: string) => RunLedgerEntry[] | null = loadRunLedger, allowUnlinked = false): boolean {
|
|
29
|
+
if (!card.runId) return false;
|
|
30
|
+
const dir = card.parentHost ? remoteLedgerMirrorDir(prodInstanceRoot(), card.parentHost.host) : runLedgerDir();
|
|
31
|
+
try {
|
|
32
|
+
const parent = load(card.runId, dir);
|
|
33
|
+
if (!parent) return false;
|
|
34
|
+
const childIds = parent.filter((entry) => entry.event === 'pod-child-run' && typeof entry.data?.childRunId === 'string')
|
|
35
|
+
.map((entry) => String(entry.data.childRunId));
|
|
36
|
+
const candidates = childIds.length ? childIds : [card.runId];
|
|
37
|
+
let allStopped = candidates.length > 0;
|
|
38
|
+
let noPrEvents = true;
|
|
39
|
+
let exactMatches = 0;
|
|
40
|
+
for (const childId of candidates) {
|
|
41
|
+
const child = childId === card.runId ? parent : load(childId, dir);
|
|
42
|
+
if (!child) { allStopped = false; continue; }
|
|
43
|
+
let prAt = -1;
|
|
44
|
+
let statusAt = -1;
|
|
45
|
+
for (let at = child.length - 1; at >= 0 && (prAt < 0 || statusAt < 0); at--) {
|
|
46
|
+
if (prAt < 0 && child[at]?.event === 'pr-opened') prAt = at;
|
|
47
|
+
if (statusAt < 0 && child[at]?.event === 'run-status') statusAt = at;
|
|
48
|
+
}
|
|
49
|
+
const opened = child[prAt];
|
|
50
|
+
const status = child[statusAt];
|
|
51
|
+
if (prAt >= 0) noPrEvents = false;
|
|
52
|
+
if (!status || !['completed', 'failed', 'cancelled'].includes(String(status.data?.runStatus))) allStopped = false;
|
|
53
|
+
if ((!card.runChildId || card.runChildId === childId) && opened?.data?.number === pr.number && opened.data?.url === pr.url && statusAt > prAt) exactMatches++;
|
|
54
|
+
}
|
|
55
|
+
if (!allStopped) return false;
|
|
56
|
+
if (exactMatches === 1) return true;
|
|
57
|
+
if (allowUnlinked && exactMatches === 0 && noPrEvents && (!card.runChildId || candidates.includes(card.runChildId))) return true;
|
|
58
|
+
} catch { /* unreadable is not terminal evidence */ }
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The PR is read from the selected repository before this call; rejudge checks it again before every live move. */
|
|
63
|
+
export function adoptExistingPr(input: AdoptPrInput): { card: TaskCard; created: boolean; changed: boolean } {
|
|
64
|
+
const { pr } = input;
|
|
65
|
+
const slug = pr.url.match(/^https:\/\/github\.com\/([^/]+\/[^/]+)\/pull\/\d+\/?$/)?.[1];
|
|
66
|
+
if (!Number.isSafeInteger(pr.number) || pr.number < 1 || pr.state !== 'OPEN' || pr.baseRefName !== 'main'
|
|
67
|
+
|| !slug || slug.toLowerCase() !== input.repo.toLowerCase()
|
|
68
|
+
|| Number(pr.url.match(/\/pull\/(\d+)/)?.[1]) !== pr.number
|
|
69
|
+
|| !/^[a-f0-9]{40}$/i.test(pr.headRefOid)) throw new Error('입양할 PR은 현재 저장소의 OPEN/main PR 번호·URL·head SHA40이어야 한다');
|
|
70
|
+
if ((input.cardId === undefined) === (input.checklist === undefined)) throw new Error('--card 또는 --checklist/--version 중 하나만 준다');
|
|
71
|
+
if (input.cardId && !input.version) throw new Error('기존 카드는 체크리스트 판을 확정해야 한다');
|
|
72
|
+
if (input.checklist && (!input.checklist.id || !input.checklist.title || !input.checklist.version
|
|
73
|
+
|| ['green', 'done'].includes(input.checklist.status))) throw new Error('입양할 체크리스트 칸은 미완 판·id·제목이어야 한다');
|
|
74
|
+
const id = input.cardId ?? `ta-${(input.now ?? new Date()).toISOString().slice(0, 10).replaceAll('-', '')}-${randomBytes(3).toString('hex')}`;
|
|
75
|
+
const at = (input.now ?? new Date()).toISOString();
|
|
76
|
+
let created = false;
|
|
77
|
+
let changed = false;
|
|
78
|
+
const result = updateTaskCard(input.statePath, id, (current, tasks) => {
|
|
79
|
+
const duplicate = Object.values(tasks).find((card) => card.id !== id && cardPrNumber(card) === pr.number);
|
|
80
|
+
if (duplicate) throw new Error(`PR #${pr.number}는 이미 카드 ${duplicate.id}에 묶였다`);
|
|
81
|
+
const checklistId = current?.checklistId ?? input.checklist?.id;
|
|
82
|
+
const checklistVersion = input.version ?? input.checklist?.version;
|
|
83
|
+
const sameCell = Object.values(tasks).find((card) => card.id !== id && card.status === 'launched' && card.taLand === true
|
|
84
|
+
&& cardPrNumber(card) !== undefined && card.checklistId === checklistId
|
|
85
|
+
&& (!card.checklistVersion || !checklistVersion || card.checklistVersion === checklistVersion));
|
|
86
|
+
if (sameCell) throw new Error(`칸 ${checklistId}에 이미 TA 착지 후보 카드 ${sameCell.id}가 있다`);
|
|
87
|
+
if (!input.cardId) {
|
|
88
|
+
const owner = Object.values(tasks).find((card) => card.status === 'launched' && card.checklistId === checklistId
|
|
89
|
+
&& (!card.checklistVersion || !checklistVersion || card.checklistVersion === checklistVersion));
|
|
90
|
+
if (owner) throw new Error(`칸 ${checklistId}에 기존 카드 ${owner.id}가 있다 — --card로 그 카드를 선택한다`);
|
|
91
|
+
}
|
|
92
|
+
if (input.cardId && (!input.observedRun || current?.runId !== input.observedRun.runId
|
|
93
|
+
|| current?.runChildId !== input.observedRun.runChildId
|
|
94
|
+
|| current?.parentHost?.host !== input.observedRun.parentHost
|
|
95
|
+
|| current?.launchId !== input.observedRun.launchId))
|
|
96
|
+
throw new Error(`입양 중 카드 런 식별자가 바뀌었다: ${id}`);
|
|
97
|
+
if (input.cardId && (!input.observedChecklist || current?.checklistId !== input.observedChecklist.id
|
|
98
|
+
|| current?.checklistVersion !== input.observedChecklist.cardVersion
|
|
99
|
+
|| input.version !== input.observedChecklist.version
|
|
100
|
+
|| ['green', 'done'].includes(input.observedChecklist.status)))
|
|
101
|
+
throw new Error(`입양 중 카드 칸·판이 바뀌었다: ${id}`);
|
|
102
|
+
const liveCell = input.checklistAtWrite?.();
|
|
103
|
+
if (input.checklistAtWrite && (!liveCell || liveCell.id !== checklistId || ['green', 'done'].includes(liveCell.status)))
|
|
104
|
+
throw new Error(`입양 중 체크리스트 칸이 바뀌거나 완료됐다: ${checklistId}`);
|
|
105
|
+
if (current?.adoptedPr?.pr === pr.number && cardPrNumber(current) === pr.number && current.taLand === true
|
|
106
|
+
&& typeof current.pr === 'object' && current.pr.url === pr.url) {
|
|
107
|
+
if (current.status !== 'launched') throw new Error(`이미 닫힌 입양 카드다: ${id} (${current.status})`);
|
|
108
|
+
if (current.checklistVersion !== (input.version ?? input.checklist?.version)) throw new Error(`이미 입양한 카드의 판이 다르다: ${current.checklistVersion ?? '미기록'} ≠ ${input.version ?? input.checklist?.version}`);
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
if (input.cardId) {
|
|
112
|
+
const boundPr = current && cardPrNumber(current);
|
|
113
|
+
const sameBoundPr = boundPr === pr.number && typeof current?.pr === 'object' && current.pr.url === pr.url;
|
|
114
|
+
if (!current || current.status !== 'launched' || !current.checklistId
|
|
115
|
+
|| (boundPr !== undefined && !sameBoundPr)
|
|
116
|
+
|| (input.checklist && current.checklistId !== input.checklist.id)) throw new Error(`입양할 launched·미바인딩/동일 PR 바인딩 카드가 아니다: ${id}`);
|
|
117
|
+
if (boundPr !== undefined && input.unlinkedTerminal)
|
|
118
|
+
throw new Error(`이미 PR이 바인딩된 카드는 --unlinked-terminal로 입양할 수 없다: ${id}`);
|
|
119
|
+
if (input.terminalRunEvidence !== true) throw new Error(`구현 런 종결·PR 근거가 없다: ${current.runId ?? current.launchId ?? 'runId 없음'} — runId 없는 카드는 tasks close 후 --checklist 새 카드로 입양`);
|
|
120
|
+
if (input.version && current.checklistVersion && current.checklistVersion !== input.version)
|
|
121
|
+
throw new Error(`카드 판이 다르다: ${current.checklistVersion} ≠ ${input.version}`);
|
|
122
|
+
} else {
|
|
123
|
+
if (current) throw new Error(`새 카드 id 충돌: ${id}`);
|
|
124
|
+
}
|
|
125
|
+
const base: TaskCard = current ?? {
|
|
126
|
+
id, text: input.checklist!.title, checklistId: input.checklist!.id,
|
|
127
|
+
createdAt: at, status: 'launched', history: [],
|
|
128
|
+
};
|
|
129
|
+
created = !current;
|
|
130
|
+
changed = true;
|
|
131
|
+
return { ...base, ...(input.version || input.checklist ? { checklistVersion: input.version ?? input.checklist!.version } : {}),
|
|
132
|
+
taLand: true, pr: { number: pr.number, url: pr.url },
|
|
133
|
+
adoptedPr: { pr: pr.number, url: pr.url, head: pr.headRefOid, at },
|
|
134
|
+
history: [...base.history, { at, event: 'pr-adopted', pr: pr.number, head: pr.headRefOid,
|
|
135
|
+
detail: `기존 OPEN PR 입양 · ${pr.url} · 새 구현 발사 0${input.unlinkedTerminal ? ' · 명시적 비연결 종결 원장' : ''}` }],
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
if (!result) throw new Error(`카드 쓰기 실패: ${id}`);
|
|
139
|
+
return { card: result, created, changed };
|
|
140
|
+
}
|
package/src/task-agent/board.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { releaseLedgerRoot } from '../instance/resolve.js';
|
|
|
12
12
|
import { listChecklist, type ChecklistStatus } from '../release-loop/checklist.js';
|
|
13
13
|
import { buildRunProgressRows, queryRunningRuns, type RunningRunsResult, type RunProgressRow } from '../self-implement/running-runs.js';
|
|
14
14
|
import {
|
|
15
|
-
cardPrNumber, CLOSED_BY_HAND_EVENT, HAND_MOVE_HISTORY_EVENT, LIVE_MOVE_HISTORY_EVENT, LIVE_MOVE_RESULT_EVENT, readTaskAgentState,
|
|
15
|
+
boundPrTerminal, cardPrNumber, CLOSED_BY_HAND_EVENT, HAND_MOVE_HISTORY_EVENT, LIVE_MOVE_HISTORY_EVENT, LIVE_MOVE_RESULT_EVENT, readTaskAgentState,
|
|
16
16
|
type CompletionKind, type TaskCard,
|
|
17
17
|
} from './task-hand.js';
|
|
18
18
|
|
|
@@ -29,6 +29,10 @@ export interface BoardTaskLedger {
|
|
|
29
29
|
lastEvent: { at: string; event: string; detail: string | null } | null;
|
|
30
30
|
/** 카드가 failed · launch-failed 일 때 마지막 줄의 사유(예: `failed/needs-owner — exit 1 · pod-failure`). 그 밖에는 null. */
|
|
31
31
|
exitReason: string | null;
|
|
32
|
+
/** Terminal PR observation, only when it still matches this card's binding. */
|
|
33
|
+
prTerminal?: NonNullable<TaskCard['prTerminal']>;
|
|
34
|
+
/** Checklist judgement after merge; partial means this slice ended while the cell remains open. */
|
|
35
|
+
postLand?: NonNullable<TaskCard['postLand']>;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
/** `GET /api/task-agent/board` 응답 — progress 는 진전 관측(`queryRunningRuns`)을 싣는다; 관측을 못 얻으면 null(0을 «없다»로 쓰지 않는다). */
|
|
@@ -87,11 +91,14 @@ function aggregate(node: BoardNode): void {
|
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
function cardStatus(card: TaskCard): BoardStatus {
|
|
94
|
+
const terminal = boundPrTerminal(card);
|
|
95
|
+
if (terminal) return terminal.state === 'MERGED' ? 'done' : 'blocked';
|
|
90
96
|
if (card.status === 'launch-failed' || card.status === 'failed') return 'blocked';
|
|
91
97
|
return card.greenProposal ? 'done' : 'open';
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
function cardLedger(card: TaskCard): BoardTaskLedger {
|
|
101
|
+
const terminal = boundPrTerminal(card);
|
|
95
102
|
const last = (card.history ?? [])
|
|
96
103
|
.filter((item) => item.event !== LIVE_MOVE_HISTORY_EVENT && item.event !== LIVE_MOVE_RESULT_EVENT && item.event !== HAND_MOVE_HISTORY_EVENT)
|
|
97
104
|
.at(-1);
|
|
@@ -105,6 +112,9 @@ function cardLedger(card: TaskCard): BoardTaskLedger {
|
|
|
105
112
|
lastStatus: card.status,
|
|
106
113
|
lastEvent: last ? { at: last.at, event: last.event, detail: last.detail ?? null } : null,
|
|
107
114
|
exitReason: ended ? reason ?? null : null,
|
|
115
|
+
...(terminal ? { prTerminal: terminal } : {}),
|
|
116
|
+
...(terminal && terminal.state === 'MERGED' && card.postLand?.pr === terminal.pr && card.postLand.head === terminal.head
|
|
117
|
+
? { postLand: card.postLand } : {}),
|
|
108
118
|
};
|
|
109
119
|
}
|
|
110
120
|
|
|
@@ -17,6 +17,7 @@ import { appendRunLedgerEntry, type RunLedgerEntry } from '../self-implement/run
|
|
|
17
17
|
import { cardPrNumber, taskAgentStatePath, updateTaskCard, readTaskCard, type TaskCard } from './task-hand.js';
|
|
18
18
|
import { judgeNextMove, type TaskJudgeInput } from './judge.js';
|
|
19
19
|
import { remoteParentAlive, type SshRunner } from './parent-host.js';
|
|
20
|
+
import { notifyRemoteFailure, remoteFailureReason } from './failure-notify.js';
|
|
20
21
|
|
|
21
22
|
const GOAL_ID = /^[a-f0-9]{16}$/;
|
|
22
23
|
const PR_URL = /^https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/pull\/(\d+)$/;
|
|
@@ -134,6 +135,8 @@ export interface CardRunEvidenceDeps {
|
|
|
134
135
|
dispatchPod?: (runId: string, sinceIso: string) => boolean;
|
|
135
136
|
runExit?: (runId: string) => RunExit | undefined;
|
|
136
137
|
ssh?: SshRunner;
|
|
138
|
+
/** Isolated tests can put incident messages in their own mailbox; production uses the resolved default store. */
|
|
139
|
+
incidentStorePath?: string;
|
|
137
140
|
}
|
|
138
141
|
|
|
139
142
|
async function defaultLoadLedger(): Promise<(runId: string) => RunLedgerEntry[] | null> {
|
|
@@ -145,8 +148,7 @@ async function defaultLoadLedger(): Promise<(runId: string) => RunLedgerEntry[]
|
|
|
145
148
|
// HARNESS-PARENT-ON-MSB1: 원격 부모 런의 원장 거울(`tasks show` 가 ssh 로 당긴다) — 이 우주 ⊕ 연합 «뒤»에만 붙는다.
|
|
146
149
|
try {
|
|
147
150
|
const { remoteLedgerMirrorDirs } = await import('./parent-host.js');
|
|
148
|
-
|
|
149
|
-
dirs = [...new Set([...dirs, ...remoteLedgerMirrorDirs(prodInstanceRoot())])];
|
|
151
|
+
dirs = [...new Set([...dirs, ...remoteLedgerMirrorDirs(effectiveInstanceRoot())])];
|
|
150
152
|
} catch { /* 거울이 없으면 종전 그대로 */ }
|
|
151
153
|
return ledgerLoader(dirs, (runId, dir) => loadRunLedger(runId, dir), federationFailure);
|
|
152
154
|
}
|
|
@@ -216,16 +218,46 @@ export async function readCardRunSnapshot(card: Pick<TaskCard, 'runId' | 'create
|
|
|
216
218
|
*/
|
|
217
219
|
export async function refreshCardRunBinding(cardId: string, statePath = taskAgentStatePath(), deps: CardRunEvidenceDeps = {}): Promise<{ card: TaskCard | undefined; evidence?: CardRunEvidence }> {
|
|
218
220
|
let card = readTaskCard(cardId, statePath);
|
|
219
|
-
// 수동 근거(refSource manual)는
|
|
220
|
-
//
|
|
221
|
+
// 수동 근거(refSource manual)는 아래 바인딩에서 덮지 않는다. 원격 부모의 확정 실패는
|
|
222
|
+
// 수동 근거가 있어도 읽어야 하므로 여기서 반환하지 않는다.
|
|
223
|
+
// 둘 다 차 있으면 다시 읽지 않는다(한 번만) — 단 «10분 발사 근거 없음»으로 failed 된 카드는
|
|
224
|
+
// PR·골이 먼저 묶였어도 되살림 판정을 계속 받는다.
|
|
221
225
|
const awaitingRevival = card?.status === 'failed' && [...card.history].reverse().find((item) => item.event === 'failed')?.detail === LATE_LAUNCH_FAILURE;
|
|
222
|
-
if (!card?.runId ||
|
|
226
|
+
if (!card?.runId || (card.pr !== undefined && card.goalId !== undefined && !awaitingRevival)) return { card };
|
|
223
227
|
const runId = card.runId;
|
|
228
|
+
// A stale HQ mirror cannot prove a terminal remote failure. Refresh it here as well as in `tasks show`,
|
|
229
|
+
// since mission advance and direct callers also reach this function.
|
|
230
|
+
if (card.parentHost && !deps.loadLedger) {
|
|
231
|
+
try {
|
|
232
|
+
const { mirrorRemoteRunLedgers } = await import('./parent-host.js');
|
|
233
|
+
mirrorRemoteRunLedgers(card.parentHost, runId, effectiveInstanceRoot(), deps.ssh);
|
|
234
|
+
} catch (error) {
|
|
235
|
+
try { debug.log('task-agent', 'card-bind-failed', { card: cardId, runId, reason: `remote ledger refresh failed: ${error instanceof Error ? error.message : String(error)}` }); } catch { /* fail-soft */ }
|
|
236
|
+
return { card };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
224
239
|
const snapshot = await readCardRunSnapshot(card, deps);
|
|
225
240
|
if (snapshot.readErrors?.length) {
|
|
226
241
|
try { debug.log('task-agent', 'card-bind-failed', { card: cardId, runId: card.runId, errors: snapshot.readErrors }); } catch { /* fail-soft */ }
|
|
227
242
|
}
|
|
228
243
|
const evidence = bindCardEvidence(card, snapshot);
|
|
244
|
+
const remoteReason = remoteFailureReason(card, snapshot, evidence, deps.ssh);
|
|
245
|
+
if (remoteReason && (card.status === 'launched' || card.status === 'failed')) {
|
|
246
|
+
if (card.status === 'launched') {
|
|
247
|
+
const at = (deps.now ?? (() => new Date()))().toISOString();
|
|
248
|
+
card = updateTaskCard(statePath, cardId, (current) => current?.status === 'launched' && current.runId === runId && !current.pr
|
|
249
|
+
? { ...current, status: 'failed', history: [...current.history, { at, event: 'failed', detail: `failed/needs-owner — ${remoteReason}`, runId }] }
|
|
250
|
+
: undefined) ?? card;
|
|
251
|
+
}
|
|
252
|
+
// An older generic Pod/no-launch failure may have closed this same run before its supervisor tail arrived.
|
|
253
|
+
// The terminal evidence still warrants an incident; message provenance keeps retries at one row.
|
|
254
|
+
const lastFailure = [...card.history].reverse().find((row) => row.event === 'failed');
|
|
255
|
+
if (card.status === 'failed' && lastFailure?.runId === runId && !card.history.some((row) => row.event === 'closed-by-hand')) {
|
|
256
|
+
try { notifyRemoteFailure(card, remoteReason, deps.incidentStorePath); }
|
|
257
|
+
catch (error) { try { debug.log('task-agent', 'failure-notify-failed', { card: cardId, runId, reason: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ } }
|
|
258
|
+
return { card, evidence };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
229
261
|
let revived = false;
|
|
230
262
|
const lastFailure = [...card.history].reverse().find((item) => item.event === 'failed');
|
|
231
263
|
if (card.status === 'failed' && lastFailure?.event === 'failed' && lastFailure.detail === LATE_LAUNCH_FAILURE
|
package/src/task-agent/cover.ts
CHANGED
|
@@ -8,7 +8,7 @@ export interface CoverRow {
|
|
|
8
8
|
byTaskAgent: number | null;
|
|
9
9
|
/** Total work by all seats; null when no independent denominator is available. */
|
|
10
10
|
total: number | null;
|
|
11
|
-
/** TASK-AGENT events observed for this verb
|
|
11
|
+
/** TASK-AGENT action events observed for this verb; null if unreadable. */
|
|
12
12
|
observedActions: number | null;
|
|
13
13
|
ratio: number | null;
|
|
14
14
|
state: 'measured' | 'no-denominator' | 'unreadable';
|
|
@@ -23,6 +23,8 @@ export interface CoverRow {
|
|
|
23
23
|
/** null means the source could not be read; [] means it was read and had no matching events. */
|
|
24
24
|
export interface TaskAgentCoverInput {
|
|
25
25
|
taskAgentActions: readonly { kind: NextActionKind; result: string; reason?: string; taskId?: string }[] | null;
|
|
26
|
+
/** Actual land executor outcomes; omitted or null means the source was not read. */
|
|
27
|
+
liveLandMoves?: readonly { card: string; executed: boolean; ok: boolean }[] | null;
|
|
26
28
|
allLands: readonly { step: string; ok: boolean }[] | null;
|
|
27
29
|
/** Card-ledger denominator for review·retry·green. undefined = not requested; null = ledger unreadable; [] = no cards. */
|
|
28
30
|
taskCards?: readonly { id: string }[] | null;
|
|
@@ -32,7 +34,26 @@ export interface TaskAgentCoverInput {
|
|
|
32
34
|
/** null means the pending-landing age source could not be read; [] means no pending landings. */
|
|
33
35
|
pendingLandingAgeSeconds?: readonly number[] | null;
|
|
34
36
|
/** A source reached the query safety limit; its aggregate is not known to be complete. */
|
|
35
|
-
truncated?: { taskAgentActions: boolean; allLands: boolean };
|
|
37
|
+
truncated?: { taskAgentActions: boolean; allLands: boolean; liveLandMoves?: boolean; goalAsks?: boolean; goalRuns?: boolean; implementCardRuns?: boolean; implementRunMerges?: boolean };
|
|
38
|
+
/** Completed authoring provenance and terminal goal records. undefined = not wired; null = unreadable. */
|
|
39
|
+
goalAsks?: readonly { authorRunId: string; goalId: string }[] | null;
|
|
40
|
+
goalRuns?: readonly { goalId: string; outcome: string; stage?: string }[] | null;
|
|
41
|
+
/** 구현 출처 — TA 카드의 런 결합. undefined = 이 동사의 출처 배선이 없다(no-source); null = 카드 원장을 못 읽었다; [] = 읽고 카드 없음. */
|
|
42
|
+
implementCardRuns?: readonly ImplementCardRunBinding[] | null;
|
|
43
|
+
/** 구현 출처 — 런 원장의 merged 사건. undefined = 배선 없음; null = 런 원장을 못 읽음. */
|
|
44
|
+
implementRunMerges?: ReadonlyMap<string, ImplementRunMergeEvent> | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 구현 동사의 카드 결합 — runId·runChildId 는 그 카드가 발사한 런이다. */
|
|
48
|
+
export interface ImplementCardRunBinding {
|
|
49
|
+
cardId: string;
|
|
50
|
+
runId?: string | null;
|
|
51
|
+
runChildId?: string | null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 구현 동사의 원장 merged 사건 — merged=true 만 완료 구현으로 센다. */
|
|
55
|
+
export interface ImplementRunMergeEvent {
|
|
56
|
+
merged: boolean;
|
|
36
57
|
}
|
|
37
58
|
|
|
38
59
|
export type LandingFailureReason = 'load' | 'oom' | 'time-limit' | 'conflict' | 'review' | 'other';
|
|
@@ -49,6 +70,24 @@ export interface LandingThroughput {
|
|
|
49
70
|
|
|
50
71
|
const FAILURE_REASONS: readonly LandingFailureReason[] = ['load', 'oom', 'time-limit', 'conflict', 'review', 'other'];
|
|
51
72
|
|
|
73
|
+
function successfulLandCount(input: TaskAgentCoverInput): number | null {
|
|
74
|
+
if (input.taskAgentActions === null || input.truncated?.taskAgentActions
|
|
75
|
+
|| input.liveLandMoves == null || input.truncated?.liveLandMoves) return null;
|
|
76
|
+
const cards = new Set<string>();
|
|
77
|
+
for (const action of input.taskAgentActions) {
|
|
78
|
+
if (action.kind !== 'land' || action.result !== 'done') continue;
|
|
79
|
+
// Without a card identity an action may duplicate a live-move; its count is unknown.
|
|
80
|
+
if (!action.taskId) return null;
|
|
81
|
+
cards.add(action.taskId);
|
|
82
|
+
}
|
|
83
|
+
for (const move of input.liveLandMoves) {
|
|
84
|
+
if (!move.executed || !move.ok) continue;
|
|
85
|
+
if (!move.card) return null;
|
|
86
|
+
cards.add(move.card);
|
|
87
|
+
}
|
|
88
|
+
return cards.size;
|
|
89
|
+
}
|
|
90
|
+
|
|
52
91
|
function landingFailureReason(action: { kind: NextActionKind; reason?: string }): LandingFailureReason {
|
|
53
92
|
const reason = action.reason ?? '';
|
|
54
93
|
if (/\boom\b|out[- ]of[- ]memory|oomkilled|메모리.{0,12}(초과|부족|킬)/i.test(reason)) return 'oom';
|
|
@@ -62,9 +101,7 @@ function landingFailureReason(action: { kind: NextActionKind; reason?: string })
|
|
|
62
101
|
/** Daily funnel, failure-reason distribution, and oldest pending-landing age. */
|
|
63
102
|
export function measureLandingThroughput(input: TaskAgentCoverInput): LandingThroughput {
|
|
64
103
|
const actionsReadable = input.taskAgentActions !== null && input.truncated?.taskAgentActions !== true;
|
|
65
|
-
const taskAgentLandings = actionsReadable
|
|
66
|
-
? input.taskAgentActions!.filter((action) => action.kind === 'land' && action.result === 'done').length
|
|
67
|
-
: null;
|
|
104
|
+
const taskAgentLandings = actionsReadable ? successfulLandCount(input) : null;
|
|
68
105
|
const failureReasons = actionsReadable
|
|
69
106
|
? Object.fromEntries(FAILURE_REASONS.map((reason) => [reason, 0])) as Record<LandingFailureReason, number>
|
|
70
107
|
: null;
|
|
@@ -109,7 +146,8 @@ export function measureTaskAgentCover(input: TaskAgentCoverInput): CoverRow[] {
|
|
|
109
146
|
const rows = verbs.map((verb): CoverRow => {
|
|
110
147
|
const actions = input.taskAgentActions?.filter((action) => action.kind === verb);
|
|
111
148
|
const actionsUnreadable = actions === undefined || input.truncated?.taskAgentActions === true;
|
|
112
|
-
const byTaskAgent =
|
|
149
|
+
const byTaskAgent = verb === 'land' ? successfulLandCount(input)
|
|
150
|
+
: actionsUnreadable ? null : actions.filter((action) => action.result === 'done').length;
|
|
113
151
|
const observedActions = actionsUnreadable ? null : actions.length;
|
|
114
152
|
const liveActions = actionsUnreadable ? null : actions.filter((action) => action.result !== 'shadow').length;
|
|
115
153
|
const shadowActions = actionsUnreadable ? null : actions.filter((action) => action.result === 'shadow').length;
|
|
@@ -134,7 +172,7 @@ export function measureTaskAgentCover(input: TaskAgentCoverInput): CoverRow[] {
|
|
|
134
172
|
};
|
|
135
173
|
const total = input.allLands === null || input.truncated?.allLands
|
|
136
174
|
? null : input.allLands.filter((land) => land.step === 'merge' && land.ok === true).length;
|
|
137
|
-
const state =
|
|
175
|
+
const state = byTaskAgent === null || total === null ? 'unreadable'
|
|
138
176
|
: total === 0 ? 'no-denominator' : 'measured';
|
|
139
177
|
const ratio = state === 'measured' && byTaskAgent !== null && total !== null ? byTaskAgent / total : null;
|
|
140
178
|
return { verb, byTaskAgent, total, observedActions, ratio, state, ...counts,
|
|
@@ -161,23 +199,23 @@ export function formatTaskAgentCover(row: CoverRow): string {
|
|
|
161
199
|
/** 7동사의 이름 — 리뷰·착지·수리·검증·저작·구현·정리 (decision·deliver 는 7동사 밖이라 세지 않는다). */
|
|
162
200
|
export type SevenVerbName = '리뷰' | '착지' | '수리' | '검증' | '저작' | '구현' | '정리';
|
|
163
201
|
|
|
164
|
-
/** no-source 는 출처 배선이
|
|
202
|
+
/** no-source 는 출처 배선이 없는 동사 · unreadable 은 해당 동사의 원천을 완전히 못 읽은 것이다. */
|
|
165
203
|
export type SevenVerbState = 'live' | 'shadow-only' | 'none' | 'no-source' | 'unreadable';
|
|
166
204
|
|
|
167
|
-
/** 7동사 출처 표의 한 칸 — source 는 그 동사를 관측하는
|
|
205
|
+
/** 7동사 출처 표의 한 칸 — source 는 그 동사를 관측하는 원천, null 이면 출처가 아직 없다. */
|
|
168
206
|
export interface SevenVerbSource {
|
|
169
207
|
verb: SevenVerbName;
|
|
170
|
-
source: Exclude<NextActionKind, 'decision' | 'deliver'> | null;
|
|
208
|
+
source: Exclude<NextActionKind, 'decision' | 'deliver'> | 'goal-ask+goal-run' | 'card-run+merged' | null;
|
|
171
209
|
}
|
|
172
210
|
|
|
173
|
-
/** TA-THEME-VERBS-LIVE —
|
|
211
|
+
/** TA-THEME-VERBS-LIVE — 저작은 완료 골 조인; 구현은 카드 런 결합 ⊕ 원장 merged 조인; 정리는 아직 출처가 없다. */
|
|
174
212
|
export const SEVEN_VERB_SOURCES: readonly SevenVerbSource[] = [
|
|
175
213
|
{ verb: '리뷰', source: 'review' },
|
|
176
214
|
{ verb: '착지', source: 'land' },
|
|
177
215
|
{ verb: '수리', source: 'retry' },
|
|
178
216
|
{ verb: '검증', source: 'green' },
|
|
179
|
-
{ verb: '저작', source:
|
|
180
|
-
{ verb: '구현', source:
|
|
217
|
+
{ verb: '저작', source: 'goal-ask+goal-run' },
|
|
218
|
+
{ verb: '구현', source: 'card-run+merged' },
|
|
181
219
|
{ verb: '정리', source: null },
|
|
182
220
|
];
|
|
183
221
|
|
|
@@ -197,28 +235,76 @@ export interface SevenVerbLive {
|
|
|
197
235
|
measuredVerbs: number;
|
|
198
236
|
}
|
|
199
237
|
|
|
200
|
-
/**
|
|
238
|
+
/** 행동 동사는 창 안의 done, 저작은 완료한 저작 원장과 착지 골의 조인, 구현은 카드 런 결합과 원장 merged 사건의 조인. 각 출처의 null·절단은 해당 동사만 unreadable. */
|
|
201
239
|
export function measureSevenVerbLive(input: TaskAgentCoverInput): SevenVerbLive {
|
|
202
240
|
const unreadable = input.taskAgentActions === null || input.truncated?.taskAgentActions === true;
|
|
203
241
|
const verbs = SEVEN_VERB_SOURCES.map(({ verb, source }): SevenVerbLiveRow => {
|
|
204
|
-
if (
|
|
242
|
+
if (source === 'card-run+merged') {
|
|
243
|
+
// ⛔ 배선이 없으면 출처가 «있는» 동사로 거짓 측정을 하지 않는다 — no-source 로 그대로 둔다.
|
|
244
|
+
if (input.implementCardRuns === undefined && input.implementRunMerges === undefined) {
|
|
245
|
+
return { verb, source: null, liveDone: null, shadow: null, state: 'no-source' };
|
|
246
|
+
}
|
|
247
|
+
if (input.implementCardRuns == null || input.implementRunMerges == null
|
|
248
|
+
|| input.truncated?.implementCardRuns === true || input.truncated?.implementRunMerges === true) {
|
|
249
|
+
return { verb, source, liveDone: null, shadow: null, state: 'unreadable' };
|
|
250
|
+
}
|
|
251
|
+
const merges = input.implementRunMerges;
|
|
252
|
+
const cards = new Set<string>();
|
|
253
|
+
for (const binding of input.implementCardRuns) {
|
|
254
|
+
// 카드 신원 없이는 중복 없는 분자를 낼 수 없다 — 그 원장은 못 읽은 것으로 한다.
|
|
255
|
+
if (!binding.cardId) return { verb, source, liveDone: null, shadow: null, state: 'unreadable' };
|
|
256
|
+
const bound = [binding.runId, binding.runChildId];
|
|
257
|
+
if (bound.some((runId) => typeof runId === 'string' && runId && merges.get(runId)?.merged === true)) {
|
|
258
|
+
cards.add(binding.cardId);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
const liveDone = cards.size;
|
|
262
|
+
return { verb, source, liveDone, shadow: 0, state: liveDone >= 1 ? 'live' : 'none' };
|
|
263
|
+
}
|
|
205
264
|
if (source === null) return { verb, source: null, liveDone: null, shadow: null, state: 'no-source' };
|
|
265
|
+
if (source !== 'goal-ask+goal-run' && (unreadable || (source === 'land'
|
|
266
|
+
&& (input.liveLandMoves == null || input.truncated?.liveLandMoves)))) {
|
|
267
|
+
return { verb, source, liveDone: null, shadow: null, state: 'unreadable' };
|
|
268
|
+
}
|
|
269
|
+
if (source === 'goal-ask+goal-run') {
|
|
270
|
+
if (input.goalAsks == null || input.goalRuns == null || input.truncated?.goalAsks || input.truncated?.goalRuns) {
|
|
271
|
+
return { verb, source, liveDone: null, shadow: null, state: 'unreadable' };
|
|
272
|
+
}
|
|
273
|
+
const landed = new Set(input.goalRuns.filter((run) =>
|
|
274
|
+
run.stage === 'merged' && (run.outcome === 'completed' || run.outcome === 'merged')).map((run) => run.goalId));
|
|
275
|
+
const liveDone = new Set(input.goalAsks.filter((ask) => ask.authorRunId && landed.has(ask.goalId)).map((ask) => ask.goalId)).size;
|
|
276
|
+
return { verb, source, liveDone, shadow: 0, state: liveDone > 0 ? 'live' : 'none' };
|
|
277
|
+
}
|
|
206
278
|
const actions = input.taskAgentActions!.filter((action) => action.kind === source);
|
|
207
|
-
const liveDone = actions.filter((action) => action.result === 'done').length;
|
|
279
|
+
const liveDone = source === 'land' ? successfulLandCount(input) : actions.filter((action) => action.result === 'done').length;
|
|
280
|
+
if (liveDone === null) return { verb, source, liveDone: null, shadow: null, state: 'unreadable' };
|
|
208
281
|
const shadow = actions.filter((action) => action.result === 'shadow').length;
|
|
209
282
|
return { verb, source, liveDone, shadow, state: liveDone >= 1 ? 'live' : shadow >= 1 ? 'shadow-only' : 'none' };
|
|
210
283
|
});
|
|
211
284
|
return {
|
|
212
285
|
verbs,
|
|
213
|
-
liveVerbs: unreadable ? null : verbs.filter((row) => row.state === 'live').length,
|
|
214
|
-
measuredVerbs:
|
|
286
|
+
liveVerbs: verbs.some((row) => row.state === 'unreadable' && row.source !== null) ? null : verbs.filter((row) => row.state === 'live').length,
|
|
287
|
+
measuredVerbs: verbs.filter((row) => row.source !== null).length,
|
|
215
288
|
};
|
|
216
289
|
}
|
|
217
290
|
|
|
218
291
|
/** 한 줄 요약 — 못 읽었으면 n 자리를 `-` 로 적는다(0 으로 읽히지 않게). */
|
|
219
292
|
export function formatSevenVerbLive(measurement: SevenVerbLive): string {
|
|
220
293
|
const total = SEVEN_VERB_SOURCES.length;
|
|
221
|
-
if (measurement.liveVerbs === null)
|
|
294
|
+
if (measurement.liveVerbs === null) {
|
|
295
|
+
const actionUnreadable = measurement.verbs.some((row) => row.source === 'review' && row.state === 'unreadable');
|
|
296
|
+
const landUnreadable = measurement.verbs.some((row) => row.source === 'land' && row.state === 'unreadable');
|
|
297
|
+
const authorUnreadable = measurement.verbs.some((row) => row.source === 'goal-ask+goal-run' && row.state === 'unreadable');
|
|
298
|
+
const implementUnreadable = measurement.verbs.some((row) => row.source === 'card-run+merged' && row.state === 'unreadable');
|
|
299
|
+
const failures = [
|
|
300
|
+
actionUnreadable ? '행동' : landUnreadable ? '착지' : null,
|
|
301
|
+
authorUnreadable ? '저작·골' : null,
|
|
302
|
+
implementUnreadable ? '구현 카드·런' : null,
|
|
303
|
+
].filter((part): part is string => part !== null);
|
|
304
|
+
const reason = `${failures.join('·') || '저작·골'} 원장 null·절단`;
|
|
305
|
+
const noSource = measurement.verbs.filter((row) => row.state === 'no-source').map((row) => row.verb).join('·');
|
|
306
|
+
return `${total}동사 live -/${total} · 못 잼(${reason})${noSource ? ` · 출처 없음 ${noSource}` : ''}`;
|
|
307
|
+
}
|
|
222
308
|
const noSource = measurement.verbs.filter((row) => row.state === 'no-source').map((row) => row.verb).join('·');
|
|
223
309
|
return `${total}동사 live ${measurement.liveVerbs}/${total}${noSource ? ` · 출처 없음 ${noSource}` : ''}`;
|
|
224
310
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { defaultMsgStorePath, openMsgStore } from '../msg/msg-store.js';
|
|
2
|
+
import type { RunLedgerEntry } from '../self-implement/run-ledger.js';
|
|
3
|
+
import type { CardRunSnapshot, CardRunEvidence } from './card-evidence.js';
|
|
4
|
+
import { remoteParentAlive, type SshRunner } from './parent-host.js';
|
|
5
|
+
import type { TaskCard } from './task-hand.js';
|
|
6
|
+
|
|
7
|
+
const STOP_LINE = /\[supervisor\] 정지\((needs-human|no-progress)\)\s*[—-]\s*(.*)/u;
|
|
8
|
+
|
|
9
|
+
/** A remote parent's terminal stop is usable only after every referenced child ledger was read. */
|
|
10
|
+
export function remoteFailureReason(card: TaskCard, snapshot: CardRunSnapshot, evidence: CardRunEvidence, ssh?: SshRunner): string | undefined {
|
|
11
|
+
if (!card.parentHost || !card.runId || !snapshot.host || snapshot.readErrors?.length || card.pr || evidence.pr) return undefined;
|
|
12
|
+
const launchRows = snapshot.host.filter((row) => row.event === 'pod-child-run');
|
|
13
|
+
const childRefs = launchRows.map((row) => row.data?.childRunId);
|
|
14
|
+
// An unreadable or malformed child reference is unknown evidence, never "no children".
|
|
15
|
+
if (childRefs.some((id) => typeof id !== 'string' || !/^run-[A-Za-z0-9_-]{4,64}$/.test(id) || id === card.runId)) return undefined;
|
|
16
|
+
const children = childRefs as string[];
|
|
17
|
+
if (children.some((id) => !snapshot.children[id])) return undefined;
|
|
18
|
+
// The parent can stop while a child still runs. That child may publish a PR or salvage
|
|
19
|
+
// branch later, so every launched child must have a terminal last status first.
|
|
20
|
+
if (children.some((id) => {
|
|
21
|
+
const lastStatus = [...(snapshot.children[id] ?? [])].reverse().find((row) => row.event === 'run-status');
|
|
22
|
+
return !lastStatus || !['completed', 'failed', 'cancelled'].includes(String(lastStatus.data?.runStatus));
|
|
23
|
+
})) return undefined;
|
|
24
|
+
const rows: RunLedgerEntry[] = [...snapshot.host, ...children.flatMap((id) => snapshot.children[id] ?? [])];
|
|
25
|
+
if (rows.some((row) => row.event === 'pr-opened' || row.event === 'salvaged'
|
|
26
|
+
|| (typeof row.data.harvestBranch === 'string' && row.data.harvestBranch.trim())
|
|
27
|
+
|| (typeof row.data.salvageBranch === 'string' && row.data.salvageBranch.trim()))) return undefined;
|
|
28
|
+
const parentRows = snapshot.host.filter((row) => row.runId === card.runId);
|
|
29
|
+
const stopIndex = parentRows.map((row) => row.event).lastIndexOf('stop');
|
|
30
|
+
const stop = parentRows[stopIndex];
|
|
31
|
+
if (!stop) return undefined;
|
|
32
|
+
// A running status before stop is superseded by the supervisor's terminal stop.
|
|
33
|
+
// A fresh execution event after stop makes that stop stale for the current parent.
|
|
34
|
+
if (parentRows.slice(stopIndex + 1).some((row) => row.event === 'start' || row.event === 'pod-child-run'
|
|
35
|
+
|| (row.event === 'run-status' && !['completed', 'failed', 'cancelled'].includes(String(row.data?.runStatus))))) return undefined;
|
|
36
|
+
const tail = Array.isArray(stop?.data.tail) ? stop.data.tail : [];
|
|
37
|
+
const lines = tail.filter((line): line is string => typeof line === 'string');
|
|
38
|
+
// A parent or terminal child stop can carry a PR or salvage branch only in raw text.
|
|
39
|
+
// Any nonempty branch token is harvest evidence, regardless of its naming prefix.
|
|
40
|
+
const stopText = rows.filter((row) => row.event === 'stop')
|
|
41
|
+
.flatMap((row) => [row.data.cause, ...(Array.isArray(row.data.tail) ? row.data.tail : [])])
|
|
42
|
+
.filter((value): value is string => typeof value === 'string').join('\n');
|
|
43
|
+
if (/https:\/\/github\.com\/[^\s]+\/pull\/\d+|ELANOUS_POD_SALVAGE(?:\s*=\s*|\s+)\S+|수확할 브랜치:\s*\S+/.test(stopText)) return undefined;
|
|
44
|
+
const matched = [...lines].reverse().map((line) => STOP_LINE.exec(line.replace(/\x1b\[[0-9;]*m/g, ''))).find((match) => match !== null);
|
|
45
|
+
if (!matched || remoteParentAlive({ ...card.parentHost, runId: card.runId }, ssh) !== 'dead') return undefined;
|
|
46
|
+
const raw = matched[2]?.replace(/[\r\n]+/g, ' ').trim();
|
|
47
|
+
if (!raw) return undefined;
|
|
48
|
+
return `${matched[1]} — ${raw}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** SQLite provenance makes repeated refreshes insert precisely one message for this card/run/seat. */
|
|
52
|
+
export function notifyRemoteFailure(card: TaskCard, reason: string, messageStorePath = defaultMsgStorePath()): void {
|
|
53
|
+
if (!card.seat || !card.runId || !card.parentHost) return;
|
|
54
|
+
const log = card.parentHost.log ?? `${card.parentHost.configDir ?? '$HOME/.elanous'}/remote-parents/${card.runId}.log`;
|
|
55
|
+
const store = openMsgStore(messageStorePath);
|
|
56
|
+
try {
|
|
57
|
+
store.appendWithProvenance({ from: 'TC', to: card.seat, kind: 'incident',
|
|
58
|
+
body: `TA 카드 ${card.id} · 원격 부모 ${card.parentHost.host} 종결 · 사유: ${reason}\n원격 로그: ${log}` },
|
|
59
|
+
`ta-failure:${card.id}-${card.runId}:${card.seat}`);
|
|
60
|
+
} finally { store.close(); }
|
|
61
|
+
}
|