elanous 0.2.8 → 0.2.10
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/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +1 -0
- package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8067-87ce253ec2f23697.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8195-6fde3d7a39859206.js} +5 -5
- package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-d4b6df905f185055.js → page-54b32bd27726588d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-186fc6952f027acc.js → page-f8116559c7424da7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-cbbe03c0da7b081a.js → page-54e1f966ab60a4dc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7ed95f296f5ee8e9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-c0c0d19d51342d3e.js → page-b31610deda76337b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-2e002ad8faa43291.js → page-6cb0e1bbefe5fb3d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-7464c1987dd76e6d.js → page-e168fa1030af0548.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-d8ab4d1489223459.js → page-4fddc90b23feb215.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-26102fd7350f171e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-139ba5c367bbf60a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +1 -0
- package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +30 -29
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +30 -29
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +30 -29
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +27 -26
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +27 -26
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +27 -26
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +30 -29
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +30 -29
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +30 -29
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +30 -29
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +30 -29
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +30 -29
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +27 -26
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +30 -29
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +30 -29
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +27 -26
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +27 -26
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +30 -29
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +30 -29
- package/apps/pwa/out/ops/checklist/index.html +1 -0
- package/apps/pwa/out/ops/checklist/index.txt +30 -0
- package/apps/pwa/out/ops/release/index.html +1 -0
- package/apps/pwa/out/ops/release/index.txt +30 -0
- package/apps/pwa/out/ops/seats/index.html +1 -0
- package/apps/pwa/out/ops/seats/index.txt +30 -0
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +28 -27
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +30 -29
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +27 -26
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +30 -29
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +30 -29
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +30 -29
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +30 -29
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +30 -29
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +27 -26
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +30 -29
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +30 -29
- package/apps/pwa/out/today/index.html +1 -0
- package/apps/pwa/out/today/index.txt +33 -0
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +30 -29
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +30 -29
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +30 -29
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +30 -29
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +30 -29
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +30 -29
- package/graphs/field/field-feed.yaml +2 -0
- package/graphs/implement-loop.yaml +1 -1
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +3 -1
- package/graphs/test-diet/nightly-audit.yaml +18 -0
- package/graphs/test-diet/recipes.yaml +3 -0
- package/package.json +1 -1
- package/plugins/card-followup/graphs/run-step.ts +48 -13
- package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
- package/plugins/doc-draft/graphs/run-step.ts +14 -7
- package/scripts/ci-pwa-build-gate.ts +160 -0
- package/scripts/coord-channel-watch.sh +42 -8
- package/scripts/coord-digest.ts +199 -0
- package/scripts/coord-post.sh +60 -3
- package/scripts/install.ps1 +68 -9
- package/scripts/install.sh +39 -12
- package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
- package/scripts/lib/tui-regress-checks.ts +146 -0
- package/scripts/memory-lifecycle-cycle.ts +2 -2
- package/scripts/ops-now-probe.ts +327 -0
- package/scripts/publish-internal-dist.ts +11 -1
- package/scripts/release-loop/cutoff.ts +31 -2
- package/scripts/release-loop/export-check-node.ts +85 -0
- package/scripts/release-loop/gate-node.ts +18 -4
- package/scripts/release-loop/node-verdict.ts +8 -1
- package/scripts/release-loop/ops-upgrade-node.ts +72 -16
- package/scripts/release-loop/release-readiness.ts +9 -0
- package/scripts/release-loop/version-node.ts +58 -10
- package/scripts/repo-watch-cycle.ts +1 -1
- package/scripts/retro-cycle.ts +2 -2
- package/scripts/schedule-health-report.ts +1 -1
- package/scripts/storyboard/storyboard.ts +5 -161
- package/scripts/test-diet/lib.ts +31 -1
- package/scripts/test-diet/node.ts +134 -17
- package/scripts/test-timeout-audit.ts +114 -0
- package/scripts/tree-sync-alert.ts +1 -1
- package/scripts/tui-regress.ts +71 -0
- package/scripts/waitlist-daily-digest.ts +1 -1
- package/skills/explainer-video/SKILL.md +4 -0
- package/skills/explainer-video/engine/reel.mjs +18 -13
- package/skills/explainer-video/engine/reel.sh +24 -5
- package/skills/explainer-video/engine/stitch.mjs +145 -0
- package/skills/pitch-deck/SKILL.md +36 -0
- package/skills/pitch-deck/template.html +80 -0
- package/src/acp/codex-approval-adapter.ts +9 -3
- package/src/acp/core-turn-bridge.ts +117 -18
- package/src/acp/fact-question.ts +11 -0
- package/src/acp/server.ts +48 -7
- package/src/acp/tool-approval.ts +126 -0
- package/src/acp/turn-runner.ts +36 -0
- package/src/agent/monad-agent-turn.ts +8 -0
- package/src/ask-user-question/pending-questions.ts +25 -10
- package/src/ask-user-question/types.ts +9 -0
- package/src/auth/setup-link-tokens.fixture.ts +19 -0
- package/src/auth/setup-link-tokens.ts +164 -0
- package/src/autopilot/build/integrity-gate.ts +12 -0
- package/src/boot/daemon-prompt-turn.ts +3 -0
- package/src/boot/daemon-tools/index.ts +8 -0
- package/src/boot/daemon-tools/types.ts +2 -0
- package/src/boot/daemon-tools/web-search.ts +1 -0
- package/src/budget/unified-usage.ts +75 -0
- package/src/channel-bot-token.ts +37 -10
- package/src/chat/ctrl-c-in-input.ts +14 -0
- package/src/chat/index.ts +61 -9
- package/src/chat/pickers/state.ts +6 -2
- package/src/cli/agent-cli.ts +7 -0
- package/src/cli/decisions-cli.ts +25 -3
- package/src/cli/doctor-bundle-upload.ts +68 -0
- package/src/cli/doctor-cli.ts +38 -4
- package/src/cli/op-proxy-sync.ts +83 -0
- package/src/cli/ops-cli.ts +20 -0
- package/src/cli/pod-cli.ts +49 -0
- package/src/cli/pr-cli.ts +37 -4
- package/src/cli/pwa-stop.ts +163 -13
- package/src/cli/pwa-test.ts +3 -0
- package/src/cli/release-cli.ts +98 -15
- package/src/cli/self-update.ts +14 -0
- package/src/cli/storyboard-cli.ts +37 -0
- package/src/cli/test-state-dir-flag.ts +11 -4
- package/src/connectors/linear.ts +146 -0
- package/src/context-bus/coord-events.ts +115 -0
- package/src/dashboard/dashboard-boot-fixture.ts +23 -0
- package/src/dashboard/dashboard-status-lines.ts +45 -0
- package/src/dashboard/first-screen-band.ts +35 -0
- package/src/dashboard/index.ts +128 -34
- package/src/dashboard/provider-slash-runtime.ts +28 -11
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +17 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
- package/src/dashboard/turn-stream-formatter.ts +1 -1
- package/src/decisions/decision-cards.ts +50 -2
- package/src/decisions/decision-ledger.ts +75 -4
- package/src/decisions/decision-linear-projection.ts +157 -0
- package/src/decisions/web-push-decision-cards.ts +50 -0
- package/src/directive/interpretation-card.ts +36 -0
- package/src/discord.ts +2 -1
- package/src/domains/coo-admin-tool.ts +62 -0
- package/src/domains/core-tools.ts +11 -3
- package/src/domains/elanous-card.ts +29 -0
- package/src/domains/ops-facts-tool.ts +76 -0
- package/src/domains/outbound-alert.ts +41 -13
- package/src/domains/release-tool.ts +168 -0
- package/src/domains/telegram-kind-route.ts +19 -1
- package/src/exec-requests/planner.ts +98 -28
- package/src/exec-requests/runner.ts +107 -10
- package/src/exec-requests/store.ts +8 -2
- package/src/field/field-media.ts +10 -7
- package/src/field/field-reel.ts +14 -4
- package/src/git-fs/worktree.ts +45 -6
- package/src/graph-runner/graph-approval-card.ts +113 -0
- package/src/graph-runner/graph-cli.ts +129 -4
- package/src/graph-runner/graph-notify.ts +25 -3
- package/src/graph-runner/runner.ts +142 -16
- package/src/harness/browser-verify.ts +25 -2
- package/src/harness/harness-cli-command.ts +231 -35
- package/src/harness/harness-incidents-writer.fixture.ts +24 -0
- package/src/harness/harness-incidents.ts +129 -0
- package/src/harness/harness-stop.ts +53 -3
- package/src/hitl/clarification-policy.ts +3 -1
- package/src/index.ts +172 -41
- package/src/intake-plane/author-goals.ts +37 -4
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/discord-seat-work.ts +47 -2
- package/src/intake-plane/seat-doc-route.ts +170 -6
- package/src/intake-plane/telegram-seat-work.ts +44 -2
- package/src/intake-plane/telegram-work.ts +1 -0
- package/src/maturity/cli-maturity.ts +34 -0
- package/src/maturity/feature-maturity.ts +100 -1
- package/src/maturity/tui-slash-maturity.ts +31 -0
- package/src/memory.ts +32 -4
- package/src/nexus/api/exec-requests.ts +33 -3
- package/src/nexus/api/http-server.ts +73 -1
- package/src/nexus/api/meta-api.ts +38 -0
- package/src/nexus/api/operator.ts +61 -0
- package/src/nexus/api/ops-api.ts +148 -0
- package/src/nexus/api/ops-seats-sources.ts +95 -0
- package/src/nexus/api/ops-seats.ts +124 -0
- package/src/nexus/api/public-routes.ts +14 -1
- package/src/nexus/api/seat-requests.ts +88 -8
- package/src/nexus/api/sessions-store.ts +63 -0
- package/src/nexus/api/setup-claim.ts +21 -0
- package/src/nexus/api/setup-codex-login.ts +160 -0
- package/src/nexus/api/usage-api.ts +102 -0
- package/src/nexus/config/secrets/file-backend.ts +56 -18
- package/src/nexus/index.ts +45 -4
- package/src/onboarding/first-run-doctor.ts +54 -0
- package/src/onboarding/first-run.ts +89 -0
- package/src/onboarding/lan-address.ts +45 -0
- package/src/onboarding/web-first.ts +119 -24
- package/src/onboarding.ts +4 -0
- package/src/ops-now/ops-now-refresh.ts +100 -0
- package/src/project/project-store.ts +115 -0
- package/src/release-loop/checklist.ts +76 -61
- package/src/release-loop/feature-store.ts +460 -0
- package/src/release-loop/manifest.ts +59 -5
- package/src/release-loop/release-schedule.ts +53 -0
- package/src/repl/index.ts +7 -0
- package/src/seat-dispatch/ceo-commands.ts +98 -0
- package/src/seat-dispatch/ceo-intent.ts +38 -0
- package/src/seat-loop/seat-loop.ts +231 -0
- package/src/seat-loop/seat-report.ts +64 -0
- package/src/self-dev/decompose.ts +66 -11
- package/src/self-dev/run-store.ts +10 -1
- package/src/self-implement/execution-clarification.ts +115 -0
- package/src/self-implement/gate-cli.ts +6 -3
- package/src/self-implement/orchestrator.ts +37 -3
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/run-status-mapping.ts +5 -3
- package/src/session/chat.ts +2 -0
- package/src/session/index.ts +9 -0
- package/src/shell-env-bootstrap.ts +17 -0
- package/src/skills/tools/usage-slash.ts +8 -7
- package/src/steward/directive.ts +51 -2
- package/src/steward/launch.ts +250 -0
- package/src/steward/outcome-notify.ts +115 -0
- package/src/steward/steward-cards.ts +8 -2
- package/src/steward/triage-plan.ts +32 -4
- package/src/steward/triage.ts +93 -9
- package/src/steward/working-backwards.ts +64 -0
- package/src/storyboard/storyboard.ts +141 -0
- package/src/task-cards/card-store.ts +2 -2
- package/src/task-orchestrator/surfaces/pod-lease.ts +260 -0
- package/src/telegram-card-followup.ts +233 -0
- package/src/telegram-commands.ts +16 -0
- package/src/telegram-report.ts +15 -9
- package/src/telegram-run.ts +90 -13
- package/src/telegram.ts +29 -8
- package/src/thinking-line.ts +28 -20
- package/src/tool-runtime/types.ts +4 -0
- package/src/tui/chat/execution-badge.ts +1 -1
- package/src/tui-client/dashboard-session.ts +2 -0
- package/src/user-config.ts +118 -11
- package/src/ux-sim/turn-states.ts +1 -1
- package/src/version/packed-revision.json +1 -1
- package/src/web-push/notify-exec-request.ts +32 -0
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4836-3084c58c1725a40b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8894-7317998f6cb97d8c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-9aa47b0bc6d570ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f4d2ecbbae6088d6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-2053e49930c82748.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a70ba27c33003dd0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-18166f885d831d3e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-1c9d6cfe68b121c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c108ff1fdb665c97.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-147a328753d983e2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-799395a9df7d95aa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-6a8158b71d1fe9d7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-9567560270857531.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-e7ed3d010153d818.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-b672e02e4bec8deb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-1ba4a5d857276334.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-9db2226f38c542fb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-70ee65f5f7a151dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-0bb8e43203efb23b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-092e1bcf27d751cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-eb52580c4b75b905.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-e573437b0a1c728f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-294300625e49e4d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-085847b088675978.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-9fe9d38c36999eb4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-6141f67f531d8b6a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-295a02b1026b8255.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-1eb9a0e1e054209d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-82efa177a30f3741.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d0be51cec4f62606.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-2e367075ffdbc062.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-647ed2b66c1d49f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ba058c56e38f2792.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
- package/apps/pwa/out/_next/static/css/5ef72a618a8994f3.css +0 -1
- package/apps/pwa/out/_next/static/lywc8m_HDzH9MKSDQhyEj/_buildManifest.js +0 -1
- package/src/dashboard/turn-typeahead-echo.ts +0 -23
- /package/apps/pwa/out/_next/static/{lywc8m_HDzH9MKSDQhyEj → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
|
@@ -22,6 +22,8 @@ import { IMAGE_LOAD_STATE_SOURCE } from './image-load-state.js';
|
|
|
22
22
|
const execFileAsync = promisify(execFile);
|
|
23
23
|
// Longer than aside's own ~10 s daemon auth-challenge timeout, so «Aside isn't running» reaches stderr before we kill it (10-01 live).
|
|
24
24
|
const DEFAULT_ASIDE_TIMEOUT_MS = 30_000;
|
|
25
|
+
const ASIDE_SHOT_MAX_BYTES = 8 * 1024 * 1024;
|
|
26
|
+
const ASIDE_SHOT_MARKER = 'ELANOUS_SHOT_BASE64:';
|
|
25
27
|
// Observed load events and complete page content arrive within this window; timeout remains non-blocking.
|
|
26
28
|
const CDP_LOAD_WAIT_TIMEOUT_MS = 3_000;
|
|
27
29
|
|
|
@@ -188,6 +190,7 @@ function buildAsideScript(url: string): string {
|
|
|
188
190
|
` const state = await page.evaluate(() => { const imageCounts = ${IMAGE_COUNTS_EXPRESSION}; return { bodyLength: document.body?.innerText.length ?? 0, unloadedImageCount: imageCounts.broken, pendingLazyImages: imageCounts.pendingLazy }; });`,
|
|
189
191
|
' const screenshot = await page.screenshot({ fullPage: true });',
|
|
190
192
|
' console.log(JSON.stringify({ title, ...state, screenshotBytes: screenshot?.length ?? 0 }));',
|
|
193
|
+
` if (screenshot?.length > 0 && screenshot.length <= ${ASIDE_SHOT_MAX_BYTES}) console.log('${ASIDE_SHOT_MARKER}' + Buffer.from(screenshot).toString('base64'));`,
|
|
191
194
|
' } finally { try { await closeTab(page); } catch {} }',
|
|
192
195
|
'})();',
|
|
193
196
|
].join('\n');
|
|
@@ -223,6 +226,18 @@ function parseAsideObservation(stdout: string): { title: string; bodyLength: num
|
|
|
223
226
|
throw new Error('aside observation output missing JSON');
|
|
224
227
|
}
|
|
225
228
|
|
|
229
|
+
function decodeAsideScreenshot(stdout: string, screenshotBytes: number): Buffer | undefined {
|
|
230
|
+
if (screenshotBytes === 0 || screenshotBytes > ASIDE_SHOT_MAX_BYTES) return undefined;
|
|
231
|
+
const lines = stripScreenAnsi(stdout).split(/\r?\n/);
|
|
232
|
+
const shots = lines.filter((line) => line.startsWith(ASIDE_SHOT_MARKER));
|
|
233
|
+
if (shots.length !== 1) return undefined;
|
|
234
|
+
const encoded = shots[0]!.slice(ASIDE_SHOT_MARKER.length);
|
|
235
|
+
if (encoded.length > Math.ceil(ASIDE_SHOT_MAX_BYTES / 3) * 4 || !/^[A-Za-z0-9+/]+={0,2}$/.test(encoded)) return undefined;
|
|
236
|
+
const buf = Buffer.from(encoded, 'base64');
|
|
237
|
+
if (buf.length !== screenshotBytes || buf.toString('base64') !== encoded) return undefined;
|
|
238
|
+
return buf;
|
|
239
|
+
}
|
|
240
|
+
|
|
226
241
|
function asideErrorResult(url: string, error: unknown, kind?: AsideRunStatus['kind']): DeployVerifyResult {
|
|
227
242
|
const message = error instanceof Error ? error.message : String(error);
|
|
228
243
|
const errorFinding = finding('aside-error', `aside 검증 오류: ${message.slice(0, 120)}${kind ? ` (${kind})` : ''}`, 'confirmed');
|
|
@@ -240,7 +255,7 @@ async function verifyAsidePage(url: string, deps: DeployVerifyDeps): Promise<Dep
|
|
|
240
255
|
const script = await readFile(scriptPath, 'utf8');
|
|
241
256
|
const runAside = deps.runAside ?? (async (command, args, options) => {
|
|
242
257
|
try {
|
|
243
|
-
const result = await execFileAsync(command, args, { ...options, encoding: 'utf8' });
|
|
258
|
+
const result = await execFileAsync(command, args, { ...options, encoding: 'utf8', maxBuffer: Math.ceil(ASIDE_SHOT_MAX_BYTES / 3) * 4 + 1024 * 1024 });
|
|
244
259
|
return { stdout: result.stdout, stderr: result.stderr, exitCode: 0 };
|
|
245
260
|
} catch (error) {
|
|
246
261
|
if ((error as NodeJS.ErrnoException).code === 'ENOENT') throw error;
|
|
@@ -273,12 +288,20 @@ async function verifyAsidePage(url: string, deps: DeployVerifyDeps): Promise<Dep
|
|
|
273
288
|
if (status.kind === 'repl-error') return asideErrorResult(url, new Error('aside repl reported error'), status.kind);
|
|
274
289
|
if (status.kind === 'no-marker') return asideErrorResult(url, new Error('aside observation output missing completion marker'), status.kind);
|
|
275
290
|
const state = parseAsideObservation(observation.stdout);
|
|
291
|
+
const screenshot = decodeAsideScreenshot(observation.stdout, state.screenshotBytes);
|
|
292
|
+
let shotReason = state.screenshotBytes === 0 ? '스크린샷 0바이트' : state.screenshotBytes > ASIDE_SHOT_MAX_BYTES ? '8MB 상한 초과' : screenshot ? undefined : '스크린샷 데이터 없음 또는 불일치';
|
|
293
|
+
if (screenshot && deps.onScreenshot) {
|
|
294
|
+
try { deps.onScreenshot(screenshot); }
|
|
295
|
+
catch (error) { shotReason = (error as NodeJS.ErrnoException)?.code ?? '저장 콜백 오류'; }
|
|
296
|
+
}
|
|
297
|
+
observe('aside-shot', { bytes: state.screenshotBytes, written: !!screenshot && !shotReason && !!deps.onScreenshot, reason: shotReason ?? (deps.onScreenshot ? '없음' : '저장 요청 없음') });
|
|
276
298
|
const structuredFindings: DeployVerifyFinding[] = [];
|
|
299
|
+
if (state.screenshotBytes === 0) structuredFindings.push(finding('aside-error', '스크린샷 0바이트 — 렌더 증거 없음', 'confirmed'));
|
|
277
300
|
if (state.bodyLength < minBody) structuredFindings.push(finding('empty-body', `페이지 본문이 비어있음(len=${state.bodyLength}·렌더 실패 의심)`, 'suspected'));
|
|
278
301
|
if (state.unloadedImageCount) structuredFindings.push(finding('unloaded-image', `로드되지 못한 그림 ${state.unloadedImageCount}개`, 'confirmed'));
|
|
279
302
|
if (!state.title.trim()) structuredFindings.push(finding('empty-title', '문서 제목이 비어있음', 'confirmed'));
|
|
280
303
|
const findings = structuredFindings.map(({ message }) => message);
|
|
281
|
-
const result: DeployVerifyResult = { ok: findings.length === 0, url, title: state.title, bodyLength: state.bodyLength, screenshotBytes: state.screenshotBytes, findings, unmeasured: ['javascript-errors'], ...(state.pendingLazyImages ? { pendingLazyImages: state.pendingLazyImages } : {}), ...(structuredFindings.length ? { structuredFindings } : {}) };
|
|
304
|
+
const result: DeployVerifyResult = { ok: findings.length === 0, url, title: state.title, bodyLength: state.bodyLength, screenshotBytes: state.screenshotBytes, findings, unmeasured: shotReason && (state.screenshotBytes === 0 || state.screenshotBytes > ASIDE_SHOT_MAX_BYTES || !!deps.onScreenshot) ? ['javascript-errors', 'screenshot'] : ['javascript-errors'], ...(state.pendingLazyImages ? { pendingLazyImages: state.pendingLazyImages } : {}), ...(structuredFindings.length ? { structuredFindings } : {}) };
|
|
282
305
|
observe('verified-aside', { url: url.slice(0, 80), ok: result.ok, bodyLength: state.bodyLength, bytes: state.screenshotBytes, findings: findings.length, unmeasured: result.unmeasured });
|
|
283
306
|
return result;
|
|
284
307
|
} catch (error) {
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { constants as osConstants } from 'node:os';
|
|
4
5
|
import { resolve } from 'node:path';
|
|
6
|
+
import { LogStore, logsDbPath } from '../mss/logging/log-store.js';
|
|
7
|
+
import { loadSelfDevRun } from '../self-dev/run-store.js';
|
|
8
|
+
import { normalizeRunId } from './harness-space.js';
|
|
9
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
10
|
+
import { detectBursts, formatIncidentBurstWarning, readRunExits, recentBurst, recordRunExit } from './harness-incidents.js';
|
|
5
11
|
import { Command, Option } from 'commander';
|
|
6
12
|
import { runGitCommand } from '../git-fs/runner.js';
|
|
7
13
|
import { GOAL_TYPES, lintGoalFile, parseGoalId, parseGoalType, tracedPathReferences, type GoalType } from '../self-implement/goal-author.js';
|
|
@@ -323,6 +329,191 @@ function resolveLaunchSubstrate(opts: HarnessSubstrateOpts): ResolvedHarnessSubs
|
|
|
323
329
|
else console.log(head);
|
|
324
330
|
return resolved;
|
|
325
331
|
}
|
|
332
|
+
type PodExit = { status: number | null; signal?: string | null };
|
|
333
|
+
type PodExitReason = 'signal' | 'human-stop' | 'supervisor' | 'no-launch' | 'unknown';
|
|
334
|
+
const STOPPING_SUPERVISOR_VERDICTS = new Set(['UNCONVERGEABLE', 'CONTRACT-CONFLICT']);
|
|
335
|
+
// Ledger events that only exist once the child actually ran — any of them rules out «never reached the Pod».
|
|
336
|
+
const LAUNCH_EVIDENCE_EVENTS = new Set(['job-applied', 'pod-child-run', 'reviewed', 'rework-budget', 'pr-opened']);
|
|
337
|
+
|
|
338
|
+
/** Keep the run identity seen at launch even if the bounded output tail later drops it. */
|
|
339
|
+
export function harnessPodRunId(output: string): string | undefined {
|
|
340
|
+
return output.match(/\[self-dev\][^\r\n]*\brun\s+(run-[a-z0-9_-]{8,64})(?=\s|$)/i)?.[1];
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Classify a missing Pod result from run-scoped evidence, never from a nonzero exit alone. */
|
|
344
|
+
export function classifyHarnessPodExit(
|
|
345
|
+
exit: PodExit,
|
|
346
|
+
output: string,
|
|
347
|
+
deps: {
|
|
348
|
+
loadLedger?: typeof loadRunLedger;
|
|
349
|
+
hasJobApplied?: (runId: string | undefined, output: string) => boolean | undefined;
|
|
350
|
+
supervisorDecision?: (runId: string) => { stopReason?: string; why?: string } | undefined;
|
|
351
|
+
loadCheckpoint?: typeof loadSelfDevRun;
|
|
352
|
+
runId?: string;
|
|
353
|
+
} = {},
|
|
354
|
+
): { lines: string[]; reason: PodExitReason | 'pod-failure' } {
|
|
355
|
+
let podFailure: string | undefined;
|
|
356
|
+
let podJobFailed = false;
|
|
357
|
+
for (const line of output.split('\n').reverse()) {
|
|
358
|
+
if (!line.trimStart().startsWith('[{') || !line.includes('"error"')) continue;
|
|
359
|
+
try {
|
|
360
|
+
const results: unknown = JSON.parse(line);
|
|
361
|
+
if (!Array.isArray(results)) continue;
|
|
362
|
+
for (const result of results) {
|
|
363
|
+
if (!result || typeof result !== 'object') continue;
|
|
364
|
+
const error = (result as { error?: { code?: string; message?: string } }).error;
|
|
365
|
+
if (typeof error?.code !== 'string' || typeof error.message !== 'string' || !error.code.startsWith('pod-')) continue;
|
|
366
|
+
podFailure = error.message;
|
|
367
|
+
podJobFailed = error.code === 'pod-job-failed';
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
} catch { /* Non-result stdout cannot establish that a Pod child ran. */ }
|
|
371
|
+
if (podFailure) break;
|
|
372
|
+
}
|
|
373
|
+
if (!podFailure) {
|
|
374
|
+
const summary = output.split('\n').find((line) => /\s❌\sfailed · .* — pod-[\w-]+: /.test(line));
|
|
375
|
+
podFailure = summary?.split(/\s❌\sfailed · .* — pod-[\w-]+: /)[1];
|
|
376
|
+
podJobFailed = summary?.includes(' — pod-job-failed: ') ?? false;
|
|
377
|
+
}
|
|
378
|
+
// Only the run announced at launch — another run id quoted in the output is not this run (review round 3).
|
|
379
|
+
const observedRunId = deps.runId ?? harnessPodRunId(output);
|
|
380
|
+
const runId = observedRunId ? normalizeRunId(observedRunId) : undefined;
|
|
381
|
+
if (podFailure) {
|
|
382
|
+
const childError = podJobFailed ? podFailure.split('childError=')[1]?.split('\\n', 1)[0]?.split('\n', 1)[0] : undefined;
|
|
383
|
+
try { debug.log('harness.pod', 'exit-classified', { runId, reason: 'pod-failure', status: exit.status, signal: exit.signal ?? null }); } catch { /* observation is fail-soft */ }
|
|
384
|
+
return { reason: 'pod-failure', lines: [podJobFailed && childError && childError !== 'no-result-line'
|
|
385
|
+
? `Pod 안 자식이 실패했다 — ${childError}`
|
|
386
|
+
: `Pod 실행이 실패했다 — ${podFailure.split('\\n', 1)[0]}`] };
|
|
387
|
+
}
|
|
388
|
+
let ledger: ReturnType<typeof loadRunLedger> = null;
|
|
389
|
+
let ledgerReadable = false;
|
|
390
|
+
if (runId) {
|
|
391
|
+
try { ledger = (deps.loadLedger ?? loadRunLedger)(runId); ledgerReadable = ledger !== null; } catch { /* unreadable is not absent */ }
|
|
392
|
+
}
|
|
393
|
+
const signal = exit.signal ?? (exit.status !== null && exit.status > 128
|
|
394
|
+
? Object.entries(osConstants.signals).find(([, number]) => number === exit.status! - 128)?.[0] : undefined);
|
|
395
|
+
const humanStop = ledger?.some((entry) => entry.event === 'human-stop');
|
|
396
|
+
// A continuing verdict (EXTEND · SUFFICIENT) is not why the run ended — only stopping verdicts count.
|
|
397
|
+
const supervisor = [...(ledger ?? [])].reverse().find((entry) =>
|
|
398
|
+
(entry.event === 'supervisor-decision' || entry.event === 'supervisor-verdict' || entry.event === 'rework-budget')
|
|
399
|
+
&& STOPPING_SUPERVISOR_VERDICTS.has(String(entry.data.supervisorVerdict ?? entry.data.verdict))
|
|
400
|
+
|| entry.event === 'run-status' && entry.data.stage === 'review-blocked');
|
|
401
|
+
let decision: { stopReason?: string; why?: string } | undefined;
|
|
402
|
+
if (runId) {
|
|
403
|
+
try { decision = (deps.supervisorDecision ?? lookupHarnessSupervisorDecision)(runId); } catch { /* unknown */ }
|
|
404
|
+
}
|
|
405
|
+
const verdict = decision?.stopReason ?? supervisor?.data.supervisorVerdict ?? supervisor?.data.verdict
|
|
406
|
+
?? (supervisor?.event === 'run-status' ? supervisor.data.stage : undefined);
|
|
407
|
+
const reasonText = decision?.why ?? supervisor?.data.supervisorReason ?? supervisor?.data.reason;
|
|
408
|
+
const ledgerPrUrl = [...(ledger ?? [])].reverse().find((entry) => entry.event === 'pr-opened'
|
|
409
|
+
&& (typeof entry.data.url === 'string' || typeof entry.data.prUrl === 'string'))?.data;
|
|
410
|
+
let checkpoint: ReturnType<typeof loadSelfDevRun> = null;
|
|
411
|
+
if (runId) {
|
|
412
|
+
try { checkpoint = (deps.loadCheckpoint ?? loadSelfDevRun)(runId); } catch { /* unreadable is not absent */ }
|
|
413
|
+
}
|
|
414
|
+
const pr = output.match(/https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/pull\/\d+\b/)?.[0]
|
|
415
|
+
?? (typeof ledgerPrUrl?.url === 'string' ? ledgerPrUrl.url
|
|
416
|
+
: typeof ledgerPrUrl?.prUrl === 'string' ? ledgerPrUrl.prUrl : undefined)
|
|
417
|
+
?? checkpoint?.results.find((result) => result.prUrl)?.prUrl;
|
|
418
|
+
let reason: PodExitReason;
|
|
419
|
+
let line: string;
|
|
420
|
+
if (humanStop) {
|
|
421
|
+
reason = 'human-stop';
|
|
422
|
+
line = '사람이 멈춘 런이다';
|
|
423
|
+
} else if (signal) {
|
|
424
|
+
reason = 'signal';
|
|
425
|
+
line = `런이 멈췄다(${signal}) — \`harness stop\` 이나 세션 종료일 수 있다 · 그 전까지 만든 PR 은 남아 있다`;
|
|
426
|
+
} else if (typeof verdict === 'string' || checkpoint?.supervisorStopReason) {
|
|
427
|
+
reason = 'supervisor';
|
|
428
|
+
line = `감독 판정: ${verdict ?? checkpoint?.supervisorStopReason}${typeof reasonText === 'string' && reasonText.trim() ? ` — ${reasonText.trim().split(/\r?\n/, 1)[0]}` : ''}`;
|
|
429
|
+
} else {
|
|
430
|
+
let applied: boolean | undefined;
|
|
431
|
+
try { applied = (deps.hasJobApplied ?? hasHarnessPodJobApplied)(runId, output); } catch { /* unknown */ }
|
|
432
|
+
const noLaunch = ledgerReadable && !ledger?.some((entry) => LAUNCH_EVIDENCE_EVENTS.has(entry.event))
|
|
433
|
+
&& applied === false && !pr;
|
|
434
|
+
reason = noLaunch ? 'no-launch' : 'unknown';
|
|
435
|
+
line = noLaunch
|
|
436
|
+
? 'Pod 실행에 닿지 못했다 — 풀·컨텍스트·SSH 연결을 확인하거나 `--substrate local` 로 명시하라'
|
|
437
|
+
: 'Pod 실행 여부 또는 종료 이유를 확인하지 못했다';
|
|
438
|
+
}
|
|
439
|
+
try { debug.log('harness.pod', 'exit-classified', { runId, reason, status: exit.status, signal: signal ?? null }); } catch { /* observation is fail-soft */ }
|
|
440
|
+
return { reason, lines: [line, ...(pr ? [`PR 이 남아 있다: ${pr}`] : [])] };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** The classified status is the same exit handed to the launcher; ledger failure is never an exit failure. */
|
|
444
|
+
export function recordClassifiedHarnessPodExit(runId: string | undefined, reason: string, status: number): void {
|
|
445
|
+
if (!runId) return;
|
|
446
|
+
try {
|
|
447
|
+
const signal = status > 128
|
|
448
|
+
? Object.entries(osConstants.signals).find(([, number]) => number === status - 128)?.[0] ?? null : null;
|
|
449
|
+
recordRunExit({ runId, reason, status, signal, at: new Date().toISOString() }, effectiveInstanceRoot());
|
|
450
|
+
} catch { /* incident writes cannot change Pod output or exit */ }
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** Every Pod launch entrance (harness ask/say · dev ask --substrate pod) warns once after a recent burst; it never blocks the launch. */
|
|
454
|
+
export function warnRecentIncidentBurst(
|
|
455
|
+
root: string = effectiveInstanceRoot(), now: Date = new Date(), write: (line: string) => void = (line) => console.error(line),
|
|
456
|
+
): boolean {
|
|
457
|
+
try {
|
|
458
|
+
const burst = recentBurst(root, now);
|
|
459
|
+
if (!burst) return false;
|
|
460
|
+
write(formatIncidentBurstWarning(burst));
|
|
461
|
+
try { debug.log('harness.incident', 'burst', { reason: burst.reason, count: burst.count }); } catch { /* observation is fail-soft */ }
|
|
462
|
+
return true;
|
|
463
|
+
} catch { return false; /* incident reads cannot block a launch */ }
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function lookupHarnessSupervisorDecision(runId: string): { stopReason?: string; why?: string } | undefined {
|
|
467
|
+
const path = logsDbPath();
|
|
468
|
+
if (!existsSync(path)) return undefined;
|
|
469
|
+
const store = LogStore.openReadOnly(path);
|
|
470
|
+
try {
|
|
471
|
+
let beforeId: number | undefined;
|
|
472
|
+
for (;;) {
|
|
473
|
+
const rows = store.query({ exactCategories: ['self-dev.supervisor'], events: ['decision'], grep: runId,
|
|
474
|
+
limit: 1_000, ...(beforeId === undefined ? {} : { beforeId }) });
|
|
475
|
+
for (const row of rows) {
|
|
476
|
+
try {
|
|
477
|
+
const data = JSON.parse(row.data ?? '{}') as Record<string, unknown>;
|
|
478
|
+
if (data.runId !== runId) continue;
|
|
479
|
+
return data.action === 'stop' && typeof data.stopReason === 'string'
|
|
480
|
+
? { stopReason: data.stopReason, ...(typeof data.why === 'string' ? { why: data.why } : {}) }
|
|
481
|
+
: undefined;
|
|
482
|
+
} catch { /* Malformed observation is not evidence. */ }
|
|
483
|
+
}
|
|
484
|
+
if (rows.length < 1_000) return undefined;
|
|
485
|
+
beforeId = rows.at(-1)!.id;
|
|
486
|
+
}
|
|
487
|
+
} finally { store.close(); }
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function hasHarnessPodJobApplied(runId: string | undefined, output: string): boolean | undefined {
|
|
491
|
+
const job = output.match(/\bsi-[a-z0-9-]+\b/i)?.[0];
|
|
492
|
+
if (!runId && !job) return undefined;
|
|
493
|
+
const path = logsDbPath();
|
|
494
|
+
if (!existsSync(path)) return undefined;
|
|
495
|
+
const store = LogStore.openReadOnly(path);
|
|
496
|
+
try {
|
|
497
|
+
for (const needle of new Set([runId, job].filter((value): value is string => value !== undefined))) {
|
|
498
|
+
let beforeId: number | undefined;
|
|
499
|
+
for (;;) {
|
|
500
|
+
const rows = store.query({ exactCategories: ['self-implement.pod'], events: ['job-applied'],
|
|
501
|
+
grep: needle, limit: 1_000, ...(beforeId === undefined ? {} : { beforeId }) });
|
|
502
|
+
if (rows.some((row) => {
|
|
503
|
+
try {
|
|
504
|
+
const data = JSON.parse(row.data ?? '{}') as Record<string, unknown>;
|
|
505
|
+
return (runId !== undefined && data.runId === runId)
|
|
506
|
+
|| (job !== undefined && data.job === job && (runId === undefined || data.runId === undefined || data.runId === runId));
|
|
507
|
+
} catch { return false; }
|
|
508
|
+
})) return true;
|
|
509
|
+
if (rows.length < 1_000) break;
|
|
510
|
+
beforeId = rows.at(-1)!.id;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return false;
|
|
514
|
+
} finally { store.close(); }
|
|
515
|
+
}
|
|
516
|
+
|
|
326
517
|
/** ⭐ 런 계약의 실행 칸 = pod — 호스트는 그래프를 안 돌리고 Pod 로 보낸다(harness-pod-dispatch.ts). */
|
|
327
518
|
async function onPod(opts: unknown, entrance: 'cli-harness-ask' | 'cli-harness-say', input: string, pool: string,
|
|
328
519
|
recordDispatch: (input: DispatchTaskInput, deps?: DispatchTaskDeps) => ReturnType<typeof dispatchTask> = dispatchTask): Promise<void> {
|
|
@@ -334,6 +525,7 @@ async function onPod(opts: unknown, entrance: 'cli-harness-ask' | 'cli-harness-s
|
|
|
334
525
|
process.exitCode = 2;
|
|
335
526
|
return;
|
|
336
527
|
}
|
|
528
|
+
warnRecentIncidentBurst();
|
|
337
529
|
let dispatchRecorded = false;
|
|
338
530
|
try {
|
|
339
531
|
const goalText = entrance === 'cli-harness-ask' ? readFileSync(input, 'utf8') : input;
|
|
@@ -351,42 +543,16 @@ async function onPod(opts: unknown, entrance: 'cli-harness-ask' | 'cli-harness-s
|
|
|
351
543
|
}
|
|
352
544
|
const { dispatchHarnessOnPod } = await import('./harness-pod-dispatch.js');
|
|
353
545
|
let output = '';
|
|
546
|
+
let runId: string | undefined;
|
|
354
547
|
const status = await dispatchHarnessOnPod({ entrance, input, podPool: pool, ...(dispatchRecorded ? { dispatchRecorded: true } : {}), ...(o.podMemory ? { podMemory: o.podMemory } : {}), ...(o.autoMerge === false ? { autoMerge: false } : {}), ...(o.base ? { base: o.base } : {}), ...(o.json ? { json: true } : {}), ...(o.source ? { source: o.source } : {}) },
|
|
355
|
-
|
|
548
|
+
{ onOutput: (text) => {
|
|
549
|
+
runId ??= harnessPodRunId(output + text);
|
|
550
|
+
output = (output + text).slice(-16_000);
|
|
551
|
+
} });
|
|
356
552
|
if (status !== 0) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
if (entrance === 'cli-harness-ask') {
|
|
361
|
-
for (const line of output.split('\n').reverse()) {
|
|
362
|
-
if (!line.trimStart().startsWith('[{') || !line.includes('"error"')) continue;
|
|
363
|
-
try {
|
|
364
|
-
const results: unknown = JSON.parse(line);
|
|
365
|
-
if (!Array.isArray(results)) continue;
|
|
366
|
-
for (const result of results) {
|
|
367
|
-
if (!result || typeof result !== 'object') continue;
|
|
368
|
-
const error = (result as { error?: { code?: string; message?: string } }).error;
|
|
369
|
-
if (typeof error?.code !== 'string' || typeof error.message !== 'string' || !error.code.startsWith('pod-')) continue;
|
|
370
|
-
if (error.code === 'pod-job-failed') {
|
|
371
|
-
podJobFailed = true;
|
|
372
|
-
childError = error.message.split('childError=')[1]?.split('\n', 1)[0];
|
|
373
|
-
}
|
|
374
|
-
podFailure = error.message;
|
|
375
|
-
break;
|
|
376
|
-
}
|
|
377
|
-
} catch { /* Non-result stdout cannot establish that a Pod child ran. */ }
|
|
378
|
-
if (podFailure) break;
|
|
379
|
-
}
|
|
380
|
-
if (!podFailure) {
|
|
381
|
-
const summary = output.split('\n').find((line) => /\s❌\sfailed · .* — pod-[\w-]+: /.test(line));
|
|
382
|
-
podFailure = summary?.split(/\s❌\sfailed · .* — pod-[\w-]+: /)[1];
|
|
383
|
-
podJobFailed = summary?.includes(' — pod-job-failed: ') ?? false;
|
|
384
|
-
if (podJobFailed) childError = podFailure?.split('childError=')[1]?.split('\n', 1)[0];
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
if (podJobFailed && childError && childError !== 'no-result-line') console.error(`Pod 안 자식이 실패했다 — ${childError}`);
|
|
388
|
-
else if (podFailure) console.error(`Pod 실행이 실패했다 — ${podFailure.split('\n', 1)[0]}`);
|
|
389
|
-
else console.error('Pod 실행에 닿지 못했다 — 풀·컨텍스트·SSH 연결을 확인하거나 `--substrate local` 로 명시하라');
|
|
553
|
+
const classified = classifyHarnessPodExit({ status }, output, { ...(runId ? { runId } : {}) });
|
|
554
|
+
recordClassifiedHarnessPodExit(runId ?? harnessPodRunId(output), classified.reason, status);
|
|
555
|
+
for (const line of classified.lines) console.error(line);
|
|
390
556
|
process.exitCode = status;
|
|
391
557
|
}
|
|
392
558
|
}
|
|
@@ -1596,6 +1762,36 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
|
|
|
1596
1762
|
.command('harness')
|
|
1597
1763
|
.description('dev-harness worktree 수명 — 워크트리 생성(worktree add)·조회(worktrees)·정리(clean)·프로세스 관찰(processes)');
|
|
1598
1764
|
installHarnessCliSinkHook(harnessCmd, deps.registerSink, deps.resolveSurface);
|
|
1765
|
+
harnessCmd.command('incidents').description('최근 하니스 Pod 종료 사고 원장을 읽는다')
|
|
1766
|
+
.option('--since <minutes>', '최근 몇 분의 종료를 볼지 (기본 15분)', '15')
|
|
1767
|
+
.option('--json', '구조화 출력')
|
|
1768
|
+
.action((opts: { since: string; json?: boolean }) => {
|
|
1769
|
+
const since = Number(opts.since);
|
|
1770
|
+
if (!Number.isFinite(since) || since < 0) {
|
|
1771
|
+
console.error('❌ --since 는 0 이상의 분이어야 합니다');
|
|
1772
|
+
process.exitCode = 2;
|
|
1773
|
+
return;
|
|
1774
|
+
}
|
|
1775
|
+
try {
|
|
1776
|
+
const root = effectiveInstanceRoot();
|
|
1777
|
+
const rows = readRunExits(root);
|
|
1778
|
+
if (rows.length === 0) {
|
|
1779
|
+
console.log(opts.json ? JSON.stringify({ rows: [], bursts: [] }) : '기록 없음 — 사고 0 이 아니라 아직 기록이 없다');
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
const now = Date.now();
|
|
1783
|
+
const selected = rows.filter((row) => Date.parse(row.at) >= now - since * 60_000 && Date.parse(row.at) <= now);
|
|
1784
|
+
const bursts = detectBursts(rows).filter((burst) => Date.parse(burst.lastAt) >= now - since * 60_000 && Date.parse(burst.lastAt) <= now);
|
|
1785
|
+
if (opts.json) console.log(JSON.stringify({ rows: selected, bursts }));
|
|
1786
|
+
else {
|
|
1787
|
+
for (const row of selected) console.log(`${row.at} run ${row.runId} · ${row.reason} · status=${row.status} signal=${row.signal ?? '-'}`);
|
|
1788
|
+
for (const burst of bursts) console.log(`${burst.firstAt}–${burst.lastAt} 묶음 ${burst.reason} ×${burst.count} · ${burst.runIds.join(', ')}`);
|
|
1789
|
+
}
|
|
1790
|
+
} catch (error) {
|
|
1791
|
+
console.error(humanErrorLine(error));
|
|
1792
|
+
process.exitCode = 1;
|
|
1793
|
+
}
|
|
1794
|
+
});
|
|
1599
1795
|
installDeliverableVerifyCliCommand(harnessCmd, deps.deliverableVerify);
|
|
1600
1796
|
installHarnessProcessObservationCommand(harnessCmd, deps.processObservation);
|
|
1601
1797
|
installHarnessBudgetCommand(harnessCmd);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { recordRunExit, type RunExit } from './harness-incidents.js';
|
|
5
|
+
|
|
6
|
+
const [root, json, mode] = process.argv.slice(2);
|
|
7
|
+
if (!root || !json) throw new Error('root and exit are required');
|
|
8
|
+
const exit = JSON.parse(json) as RunExit;
|
|
9
|
+
if (mode === 'hold') {
|
|
10
|
+
const db = new Database(join(root, '.incident-write-lock.sqlite'));
|
|
11
|
+
db.exec('BEGIN IMMEDIATE');
|
|
12
|
+
process.stdout.write('locked\n');
|
|
13
|
+
// Released by a file, not a signal: SIGUSR2 handlers are not delivered reliably under Bun on macOS.
|
|
14
|
+
const release = join(root, 'release-hold');
|
|
15
|
+
await new Promise<void>((done) => {
|
|
16
|
+
const timer = setInterval(() => { if (existsSync(release)) { clearInterval(timer); done(); } }, 20);
|
|
17
|
+
});
|
|
18
|
+
db.exec('COMMIT');
|
|
19
|
+
db.close();
|
|
20
|
+
recordRunExit(exit, root);
|
|
21
|
+
process.exit(0);
|
|
22
|
+
} else {
|
|
23
|
+
recordRunExit(exit, root);
|
|
24
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
export interface RunExit {
|
|
6
|
+
runId: string;
|
|
7
|
+
reason: string;
|
|
8
|
+
status: number | null;
|
|
9
|
+
signal: string | null;
|
|
10
|
+
at: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface IncidentBurst {
|
|
14
|
+
reason: string;
|
|
15
|
+
count: number;
|
|
16
|
+
firstAt: string;
|
|
17
|
+
lastAt: string;
|
|
18
|
+
runIds: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const KST_OFFSET_MS = 9 * 60 * 60_000;
|
|
22
|
+
const INCIDENT_FILE = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
23
|
+
|
|
24
|
+
function kstDate(at: number): string {
|
|
25
|
+
return new Date(at + KST_OFFSET_MS).toISOString().slice(0, 10);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Read only recorded exits. Missing ledgers are not evidence that no runs failed. */
|
|
29
|
+
export function readRunExits(root: string): RunExit[] {
|
|
30
|
+
const dir = join(root, 'incidents');
|
|
31
|
+
if (!existsSync(dir)) return [];
|
|
32
|
+
const rows: RunExit[] = [];
|
|
33
|
+
const seen = new Set<string>();
|
|
34
|
+
for (const file of readdirSync(dir).filter((name) => INCIDENT_FILE.test(name)).sort()) {
|
|
35
|
+
const path = join(dir, file);
|
|
36
|
+
let text: string;
|
|
37
|
+
try {
|
|
38
|
+
if (!statSync(path).isFile()) continue;
|
|
39
|
+
text = readFileSync(path, 'utf8');
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') continue;
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
for (const line of text.split('\n')) {
|
|
45
|
+
if (!line.trim()) continue;
|
|
46
|
+
let value: unknown;
|
|
47
|
+
try { value = JSON.parse(line); } catch { continue; }
|
|
48
|
+
if (!value || typeof value !== 'object') continue;
|
|
49
|
+
const row = value as Partial<RunExit>;
|
|
50
|
+
if (typeof row.runId !== 'string' || !row.runId.trim() || seen.has(row.runId)
|
|
51
|
+
|| typeof row.reason !== 'string' || typeof row.at !== 'string' || !Number.isFinite(Date.parse(row.at))
|
|
52
|
+
|| !(row.status === null || typeof row.status === 'number')
|
|
53
|
+
|| !(row.signal === null || typeof row.signal === 'string')) continue;
|
|
54
|
+
seen.add(row.runId);
|
|
55
|
+
rows.push(row as RunExit);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return rows.sort((a, b) => Date.parse(a.at) - Date.parse(b.at) || a.runId.localeCompare(b.runId));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** SQLite's process-owned write lock serializes ledger checks and appends across processes. */
|
|
62
|
+
export function recordRunExit(row: RunExit, root: string): void {
|
|
63
|
+
if (!row.runId?.trim()) return;
|
|
64
|
+
const at = Date.parse(row.at);
|
|
65
|
+
if (!Number.isFinite(at)) throw new Error('invalid incident exit time');
|
|
66
|
+
const dir = join(root, 'incidents');
|
|
67
|
+
mkdirSync(dir, { recursive: true });
|
|
68
|
+
const lock = new Database(join(root, '.incident-write-lock.sqlite'));
|
|
69
|
+
try {
|
|
70
|
+
lock.exec('PRAGMA busy_timeout = 60000');
|
|
71
|
+
lock.exec('BEGIN IMMEDIATE');
|
|
72
|
+
try {
|
|
73
|
+
if (!readRunExits(root).some((record) => record.runId === row.runId)) {
|
|
74
|
+
appendFileSync(join(dir, `${kstDate(at)}.jsonl`), `${JSON.stringify(row)}\n`);
|
|
75
|
+
}
|
|
76
|
+
lock.exec('COMMIT');
|
|
77
|
+
} catch (error) {
|
|
78
|
+
lock.exec('ROLLBACK');
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
} finally {
|
|
82
|
+
lock.close();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Non-overlapping, maximal same-reason windows; no run contributes to two bursts. */
|
|
87
|
+
export function detectBursts(rows: readonly RunExit[], { windowMs = 60_000, threshold = 3 }: {
|
|
88
|
+
windowMs?: number; threshold?: number;
|
|
89
|
+
} = {}): IncidentBurst[] {
|
|
90
|
+
const bursts: IncidentBurst[] = [];
|
|
91
|
+
const byReason = new Map<string, RunExit[]>();
|
|
92
|
+
const seen = new Set<string>();
|
|
93
|
+
for (const row of rows) {
|
|
94
|
+
if (!row.runId || seen.has(row.runId) || !Number.isFinite(Date.parse(row.at))) continue;
|
|
95
|
+
seen.add(row.runId);
|
|
96
|
+
const group = byReason.get(row.reason) ?? [];
|
|
97
|
+
group.push(row);
|
|
98
|
+
byReason.set(row.reason, group);
|
|
99
|
+
}
|
|
100
|
+
for (const [reason, group] of byReason) {
|
|
101
|
+
group.sort((a, b) => Date.parse(a.at) - Date.parse(b.at) || a.runId.localeCompare(b.runId));
|
|
102
|
+
for (let start = 0; start < group.length;) {
|
|
103
|
+
let end = start + 1;
|
|
104
|
+
while (end < group.length && Date.parse(group[end]!.at) - Date.parse(group[start]!.at) <= windowMs) end++;
|
|
105
|
+
if (end - start < threshold) { start++; continue; }
|
|
106
|
+
const members = group.slice(start, end);
|
|
107
|
+
bursts.push({ reason, count: members.length, firstAt: members[0]!.at,
|
|
108
|
+
lastAt: members[members.length - 1]!.at, runIds: members.map((row) => row.runId) });
|
|
109
|
+
start = end;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return bursts.sort((a, b) => Date.parse(a.firstAt) - Date.parse(b.firstAt) || a.reason.localeCompare(b.reason));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function recentBurst(root: string, now: Date | number, { lookbackMs = 15 * 60_000 }: {
|
|
116
|
+
lookbackMs?: number;
|
|
117
|
+
} = {}): IncidentBurst | null {
|
|
118
|
+
const time = typeof now === 'number' ? now : now.getTime();
|
|
119
|
+
const recent = detectBursts(readRunExits(root)).filter((burst) => {
|
|
120
|
+
const last = Date.parse(burst.lastAt);
|
|
121
|
+
return last <= time && last >= time - lookbackMs;
|
|
122
|
+
});
|
|
123
|
+
return recent.sort((a, b) => Date.parse(a.lastAt) - Date.parse(b.lastAt)).at(-1) ?? null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function formatIncidentBurstWarning(burst: IncidentBurst): string {
|
|
127
|
+
const time = new Date(Date.parse(burst.lastAt) + KST_OFFSET_MS).toISOString().slice(11, 16);
|
|
128
|
+
return `⚠ 최근 묶음 사고: ${burst.reason} ×${burst.count} (${time} KST) — 원인 확인 전 재발사는 중복·재사고 위험 · elanous harness incidents`;
|
|
129
|
+
}
|
|
@@ -7,15 +7,51 @@
|
|
|
7
7
|
//
|
|
8
8
|
// ⛔ 불변식: 기록된 시작 시각과 실제 프로세스 시작 시각이 다르면 신호를 보내지 않는다(PID 재사용 방지).
|
|
9
9
|
// ⛔ 불변식: `elanous.run=<runId>` 라벨이 붙은 Job 만 지운다.
|
|
10
|
+
// ⛔ 불변식: 짧은 runId 의 후보가 둘 이상이면 프로세스 신호·Job 삭제 전에 중단한다.
|
|
10
11
|
|
|
11
|
-
import { readFileSync, readlinkSync } from 'node:fs';
|
|
12
|
+
import { existsSync, readdirSync, readFileSync, readlinkSync } from 'node:fs';
|
|
12
13
|
import { isAbsolute, join, resolve } from 'node:path';
|
|
13
14
|
import { spawnSync } from 'node:child_process';
|
|
14
15
|
import { debug } from '../debug/log.js';
|
|
15
16
|
import { selfDevRunsDir } from '../self-dev/run-store.js';
|
|
16
|
-
import { loadRunLedger, runLedgerDir } from '../self-implement/run-ledger.js';
|
|
17
|
+
import { loadRunLedger, resolveFederatedRunLedgerDirectories, runLedgerDir } from '../self-implement/run-ledger.js';
|
|
17
18
|
|
|
18
19
|
const RUN_ID = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
20
|
+
const FULL_RUN_ID = /^run-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
21
|
+
|
|
22
|
+
export class AmbiguousHarnessStopRunIdError extends Error {
|
|
23
|
+
constructor(given: string, candidates: readonly string[]) {
|
|
24
|
+
super(`harness stop ${given}: 더 긴 id 를 주십시오\n${candidates.map((id) => ` ${id}`).join('\n')}`);
|
|
25
|
+
this.name = 'AmbiguousHarnessStopRunIdError';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** List names, not ledger bodies: a run may have written its pid.json before its ledger. */
|
|
30
|
+
export function stopRunIdCandidates(given: string, ledgerDirs: readonly string[], runsDir: string): string[] {
|
|
31
|
+
const found = new Set<string>();
|
|
32
|
+
for (const dir of ledgerDirs) {
|
|
33
|
+
let names: string[];
|
|
34
|
+
try { names = readdirSync(dir); }
|
|
35
|
+
catch (error) {
|
|
36
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') continue;
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
for (const name of names) {
|
|
40
|
+
if (!name.endsWith('.jsonl')) continue;
|
|
41
|
+
const id = name.slice(0, -'.jsonl'.length);
|
|
42
|
+
if (FULL_RUN_ID.test(id) && id.startsWith(given)) found.add(id);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
for (const entry of readdirSync(runsDir, { withFileTypes: true })) {
|
|
47
|
+
if (entry.isDirectory() && entry.name.startsWith(given) && FULL_RUN_ID.test(entry.name)
|
|
48
|
+
&& existsSync(join(runsDir, entry.name, 'pid.json'))) found.add(entry.name);
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
52
|
+
}
|
|
53
|
+
return [...found].sort();
|
|
54
|
+
}
|
|
19
55
|
/** `pid.json` 의 `startedAt` 은 `Date.now() − uptime` 근삿값이다 — 이만큼의 차이는 같은 프로세스로 본다. */
|
|
20
56
|
export const START_TOLERANCE_MS = 5_000;
|
|
21
57
|
|
|
@@ -101,6 +137,7 @@ function scanStopProcessTable(runId: string, goalPath: string | null): StopProce
|
|
|
101
137
|
|
|
102
138
|
export interface HarnessStopDeps {
|
|
103
139
|
readPidRecord: (runId: string) => PidRecord | null;
|
|
140
|
+
runIdCandidates?: (given: string) => string[];
|
|
104
141
|
readGoalPath: (runId: string) => string | null;
|
|
105
142
|
scanProcesses: (runId: string, goalPath: string | null) => StopProcessScan;
|
|
106
143
|
/** 살아 있는 프로세스의 시작 시각(ms) · 없으면 null. */
|
|
@@ -115,6 +152,7 @@ export interface HarnessStopDeps {
|
|
|
115
152
|
|
|
116
153
|
export interface HarnessStopResult {
|
|
117
154
|
runId: string;
|
|
155
|
+
givenRunId?: string;
|
|
118
156
|
process: 'stopped' | 'killed' | 'absent' | 'owner-mismatch' | 'unmeasured' | 'dry-run';
|
|
119
157
|
pid?: number;
|
|
120
158
|
candidates: StopProcessCandidate[];
|
|
@@ -154,6 +192,10 @@ export function defaultHarnessStopDeps(overrides: Partial<HarnessStopDeps> = {})
|
|
|
154
192
|
};
|
|
155
193
|
return {
|
|
156
194
|
readPidRecord: (runId) => readPidRecordFrom(selfDevRunsDir(), runId),
|
|
195
|
+
runIdCandidates: (given) => {
|
|
196
|
+
const ledgerDirs = new Set([runLedgerDir(), ...resolveFederatedRunLedgerDirectories({})]);
|
|
197
|
+
return stopRunIdCandidates(given, [...ledgerDirs], selfDevRunsDir());
|
|
198
|
+
},
|
|
157
199
|
readGoalPath: stopGoalPathFromLedger,
|
|
158
200
|
scanProcesses: scanStopProcessTable,
|
|
159
201
|
processStartMs: psProcessStartMs,
|
|
@@ -172,6 +214,13 @@ export function defaultHarnessStopDeps(overrides: Partial<HarnessStopDeps> = {})
|
|
|
172
214
|
|
|
173
215
|
export async function stopHarnessRun(runId: string, deps: HarnessStopDeps, contexts?: readonly string[], dryRun = false): Promise<HarnessStopResult> {
|
|
174
216
|
if (!RUN_ID.test(runId)) throw new Error(`invalid run id: ${runId}`);
|
|
217
|
+
const given = runId;
|
|
218
|
+
if (!FULL_RUN_ID.test(given)) {
|
|
219
|
+
const matches = [...new Set(deps.runIdCandidates?.(given) ?? [])].sort();
|
|
220
|
+
if (matches.length === 1) runId = matches[0]!;
|
|
221
|
+
debug.log('harness.stop', 'run-id-resolved', { given, resolved: runId, candidates: matches });
|
|
222
|
+
if (matches.length > 1) throw new AmbiguousHarnessStopRunIdError(given, matches);
|
|
223
|
+
}
|
|
175
224
|
// ── ⓐ 프로세스 ────────────────────────────────────────────
|
|
176
225
|
let processState: HarnessStopResult['process'] = 'absent';
|
|
177
226
|
const record = deps.readPidRecord(runId);
|
|
@@ -235,7 +284,7 @@ export async function stopHarnessRun(runId: string, deps: HarnessStopDeps, conte
|
|
|
235
284
|
if (r.status === 0) jobsDeleted += r.stdout.split('\n').filter((line) => line.startsWith('job.batch/')).length;
|
|
236
285
|
else debug.log('harness.stop', 'job-delete-failed', { runId, context, error: r.stderr.slice(0, 200) }, { level: 'warn' });
|
|
237
286
|
}
|
|
238
|
-
const result: HarnessStopResult = { runId, process: processState, ...(record ? { pid: record.pid } : {}), candidates, scanned: scan.status === 'ok', pidRecordFound: record !== null, dryRun, jobsDeleted, contexts: targets };
|
|
287
|
+
const result: HarnessStopResult = { runId, ...(given !== runId ? { givenRunId: given } : {}), process: processState, ...(record ? { pid: record.pid } : {}), candidates, scanned: scan.status === 'ok', pidRecordFound: record !== null, dryRun, jobsDeleted, contexts: targets };
|
|
239
288
|
debug.log('harness.stop', 'stopped', { runId, process: processState, jobsDeleted, contexts: targets.length });
|
|
240
289
|
return result;
|
|
241
290
|
}
|
|
@@ -249,6 +298,7 @@ export function formatHarnessStop(r: HarnessStopResult): string {
|
|
|
249
298
|
'owner-mismatch': '소유 불일치(건드리지 않음 — PID 가 다른 프로세스로 재사용됐다)',
|
|
250
299
|
};
|
|
251
300
|
return [
|
|
301
|
+
...(r.givenRunId ? [`↳ ${r.givenRunId} → ${r.runId}(전체)`] : []),
|
|
252
302
|
`harness stop ${r.runId}`,
|
|
253
303
|
` 프로세스: ${label[r.process]}${r.pid ? ` · pid ${r.pid}` : ''}`,
|
|
254
304
|
...(!r.scanned && r.process !== 'unmeasured' ? [' 프로세스 표: 못 잼(기록 밖 발사 프로세스는 확인하지 못함)'] : []),
|
|
@@ -182,7 +182,7 @@ export function decideAndObserveClarification(
|
|
|
182
182
|
impact: candidate.impact,
|
|
183
183
|
action: decision.action,
|
|
184
184
|
reason: decision.reason,
|
|
185
|
-
recommendedOption: decision.recommendedOption,
|
|
185
|
+
...(context.phase === 'execution' ? {} : { recommendedOption: decision.recommendedOption }),
|
|
186
186
|
budgetUsed: context.budget.used,
|
|
187
187
|
budgetLimit: context.budget.limit,
|
|
188
188
|
});
|
|
@@ -210,6 +210,8 @@ export function toAskUserQuestionRequest(decision: ClarificationDecision): AskUs
|
|
|
210
210
|
header: 'Direction',
|
|
211
211
|
question: `${candidate.prompt}\n\nRecommended: ${decision.recommendedOption}. ${candidate.whyNow}`,
|
|
212
212
|
options: candidate.options.map(({ recommended: _recommended, ...option }) => option),
|
|
213
|
+
impact: candidate.impact,
|
|
214
|
+
recommendedIndex: candidate.options.findIndex(option => option.recommended),
|
|
213
215
|
includeOther: true,
|
|
214
216
|
};
|
|
215
217
|
return { questions: [question] };
|