elanous 0.2.9 → 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-f972fc1c00a62e88.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-f8116559c7424da7.js +1 -0
- 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-54e1f966ab60a4dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-973996d584f008c1.js → page-7ed95f296f5ee8e9.js} +1 -1
- 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-8b0402a46a749a3d.js → page-b31610deda76337b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-037f83e2a1edc8e2.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-0df30e0c7e50148a.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-4fddc90b23feb215.js +1 -0
- 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-90670589b87abea8.js → page-26102fd7350f171e.js} +1 -1
- 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-35bedde6d885f08f.js → page-139ba5c367bbf60a.js} +1 -1
- 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 +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +14 -14
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- 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 +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- 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 +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/graphs/field/field-feed.yaml +2 -0
- package/graphs/implement-loop.yaml +1 -1
- 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 +13 -6
- 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 +19 -8
- 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 +30 -1
- package/scripts/test-diet/node.ts +82 -7
- 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.ts +16 -5
- 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/chat/ctrl-c-in-input.ts +14 -0
- package/src/chat/index.ts +52 -0
- 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/ops-cli.ts +20 -0
- package/src/cli/pod-cli.ts +49 -0
- package/src/cli/pr-cli.ts +31 -2
- package/src/cli/pwa-stop.ts +163 -13
- package/src/cli/pwa-test.ts +3 -0
- package/src/cli/release-cli.ts +63 -20
- 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/index.ts +58 -21
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +7 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
- package/src/decisions/decision-cards.ts +49 -1
- 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 +156 -37
- 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 +98 -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 +53 -1
- package/src/nexus/api/meta-api.ts +11 -0
- package/src/nexus/api/ops-api.ts +10 -1
- package/src/nexus/api/ops-seats-sources.ts +37 -7
- package/src/nexus/api/ops-seats.ts +16 -6
- package/src/nexus/api/seat-requests.ts +88 -8
- package/src/nexus/api/sessions-store.ts +63 -0
- package/src/nexus/api/setup-codex-login.ts +160 -0
- package/src/nexus/api/usage-api.ts +102 -0
- package/src/nexus/index.ts +45 -4
- 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 +78 -4
- package/src/release-loop/feature-store.ts +154 -21
- package/src/release-loop/release-schedule.ts +53 -0
- package/src/repl/index.ts +7 -0
- package/src/seat-dispatch/ceo-commands.ts +41 -8
- 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/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-report.ts +15 -9
- package/src/telegram-run.ts +7 -0
- package/src/telegram.ts +29 -8
- package/src/tool-runtime/types.ts +4 -0
- package/src/tui-client/dashboard-session.ts +2 -0
- package/src/user-config.ts +118 -11
- package/src/version/packed-revision.json +1 -1
- package/src/web-push/notify-exec-request.ts +32 -0
- package/apps/pwa/out/_next/static/bFI5EEwg7T6aG88ibX4Zq/_buildManifest.js +0 -1
- 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/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/8630-cdfc61e82c653ffc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8894-e263b332208e3957.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-139040d37c79dbc9.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-fb7c2d458f32fcad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-35e2713597dd24d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-918b656f064577ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-5f30e00693527ebd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-7fbae173b3c103a7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-3b4edcce364062ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-c5794f484c251f44.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-1130d8eefcd87e76.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-016e2bda04eecb1f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-b4d6d9a3b56f2940.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-ec49a466121233ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-f72db356744ceade.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-a6fd9b50d1b85378.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-adfbd2ba9598e59a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-46fedff57d4bec67.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-0cf318e9dcee2bbf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-899b327cb8b6845c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-6c1e6ba743a29dfe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-1a8e4807292f6b17.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-28deba6bcb834916.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-de68e2205794037d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-c71c33a573230672.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4fc56790fe0d2682.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-59df2aebb38a7c1e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4647be5dbcbd0795.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e025892bda74b743.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a0955de73b89f078.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-369846d2921af584.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-72cbc1c8cdd0f65d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-77b28bfb0996d5ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-3220c4934f49c6c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-7df9243ed021a4cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-4498c3853f4f688d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
- package/apps/pwa/out/_next/static/css/f92f63a5df0ba229.css +0 -1
- package/src/dashboard/turn-typeahead-echo.ts +0 -23
- /package/apps/pwa/out/_next/static/{bFI5EEwg7T6aG88ibX4Zq → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
|
@@ -79,6 +79,7 @@ import { classifyReviewProviderFailure } from '../self-dev/review-provider-fallb
|
|
|
79
79
|
import { classifyError } from '../session-runtime/retry-policy.js';
|
|
80
80
|
import { decomposeSelfDevGoal, inferHotPaths, type SelfDevDecomposeOptions } from '../self-dev/decompose.js';
|
|
81
81
|
import { boundaryGlobs, boundaryViolations } from './goal-boundary.js';
|
|
82
|
+
import { askAtExecution, type ExecutionClarificationResult } from './execution-clarification.js';
|
|
82
83
|
import { targetScopedGoalText } from './goal-text-path-scope.js';
|
|
83
84
|
import type { SelfDevGoalType } from '../self-dev/orchestrate.js';
|
|
84
85
|
import { DEFAULT_BRANCH_WORKTREE_BASE, gateGitResidue, observeGitResidue } from '../git-fs/worktree.js';
|
|
@@ -967,6 +968,8 @@ export interface SelfImplementSeams {
|
|
|
967
968
|
/** 변경 파일 목록 — 게이트 «라우터»가 「코드가 바뀌었나」를 판정하는 입력.
|
|
968
969
|
* ⛔ 미지정이면 실물 `changedFiles(cwd)` 를 쓴다. 못 얻으면 `undefined` 이고, 라우터는 그때 «돌린다»(fail-safe). */
|
|
969
970
|
changedFilesForGateRoute?: (cwd: string) => readonly string[] | undefined;
|
|
971
|
+
/** An execution choice is made only after a measured change crosses the authored target paths. */
|
|
972
|
+
askExecutionClarification?: typeof askAtExecution;
|
|
970
973
|
/** ★ ⑤ 내부 리뷰 seam(2026-07-21·review-gated merge) — gate 통과 diff 를 리뷰어(reviewPullRequest·
|
|
971
974
|
* agent-substrate·staged 하니스와 동일 엔진)가 비평. verdict fail(mustFix) → rework 재주입(A 확장),
|
|
972
975
|
* clean(pass/warn) → 병합 결정. 미주입 시 리뷰 스킵(gate 통과=바로 결정·종전 동작). */
|
|
@@ -1082,7 +1085,7 @@ export const SELF_IMPLEMENT_PROGRESS_STAGES = [
|
|
|
1082
1085
|
'gating', 'gated', 'reviewing', 'reviewed', 'awaiting-approval',
|
|
1083
1086
|
'pr-opening', 'pr-opened', 'merging', 'merged', 'worktree-completed',
|
|
1084
1087
|
'aborted', 'gate-failed', 'review-blocked', 'merge-conflict', 'pr-declined',
|
|
1085
|
-
'awaiting-clarification',
|
|
1088
|
+
'awaiting-clarification', 'parked',
|
|
1086
1089
|
// ⭐⭐ 🩸 2026-09-12 — ***「선언한 대상을 «하나도» 안 만들고 끝났다」를 사람 표면에 낸다.***
|
|
1087
1090
|
// ⛔ 기존 단계 이름을 «빌려 쓰지 않는다» — `progress()` 는 단계 이름으로 원장 행을 만들므로
|
|
1088
1091
|
// 빌려 쓰면 그 단계 집계가 오염된다([T] 2026-09-12 · `#17767` 이후 계약).
|
|
@@ -6268,6 +6271,39 @@ async function runSelfImplementInner(
|
|
|
6268
6271
|
return { ok: false, stage: 'aborted', node: 'implement', ...resolveRunOutcome({ termination: 'abandoned' }), sessionId, worktreePath: wt.path, branch: wt.branch, quotaExhaustionAssessment: quotaExhaustionAssessmentForRun(), ...(impl.completionDisposition ? { completionDisposition: impl.completionDisposition } : {}), ...(pr ? { prUrl: pr.url, prNumber: pr.number } : {}), detail: impl.summary };
|
|
6269
6272
|
}
|
|
6270
6273
|
|
|
6274
|
+
// Measure the existing declared-target boundary after implementation, before gate/PR.
|
|
6275
|
+
// Unknown declaration or changed files cannot justify an invented choice.
|
|
6276
|
+
let gateRouteFiles: readonly string[] | undefined;
|
|
6277
|
+
try { gateRouteFiles = (s.changedFilesForGateRoute ?? changedFiles)(wt.path); } catch { gateRouteFiles = undefined; }
|
|
6278
|
+
if (opts.goalFile) {
|
|
6279
|
+
let goalDocument: string | undefined;
|
|
6280
|
+
try { goalDocument = readFileSync(opts.goalFile, 'utf8'); } catch { /* unknown scope */ }
|
|
6281
|
+
const scope = detectDeclaredScopeDiff({ goalDocument, changedFiles: gateRouteFiles?.filter(file => !harnessSeededPaths.includes(file)), nameCap: gateRouteFiles?.length });
|
|
6282
|
+
if (scope.status === 'known' && scope.outsideCount !== null && scope.outsideCount > 0) {
|
|
6283
|
+
const result: ExecutionClarificationResult = await (s.askExecutionClarification ?? askAtExecution)({
|
|
6284
|
+
id: 'execution_scope', decision: 'Changing files outside the authored target paths',
|
|
6285
|
+
prompt: 'Implementation changed files outside the authored target paths. Keep the original scope or expand it?',
|
|
6286
|
+
whyNow: 'Continuing to gate or publish these edits would expand the requested scope.',
|
|
6287
|
+
impact: scope.outsideNames.some(file => {
|
|
6288
|
+
if (existsSync(join(wt.path, file))) return false;
|
|
6289
|
+
const tracked = runGitCommand(wt.path, ['ls-files', '--error-unmatch', '--', file], { encoding: 'utf8', timeout: 20_000 });
|
|
6290
|
+
return tracked.status === 0;
|
|
6291
|
+
}) ? 'critical' : 'high',
|
|
6292
|
+
options: [
|
|
6293
|
+
{ label: 'Stay in scope', description: 'Do not accept out-of-scope changes.', recommended: true },
|
|
6294
|
+
{ label: 'Expand scope', description: 'Accept the changed files outside the target paths.' },
|
|
6295
|
+
],
|
|
6296
|
+
}, { runId }, { writeRunLedger: s.writeRunLedger });
|
|
6297
|
+
if ('parked' in result || 'deferred' in result || result.choice !== 'Expand scope') {
|
|
6298
|
+
finalizeSupervisorDeliveries('execution-scope-not-approved');
|
|
6299
|
+
observe('execution-scope-held', { outsideCount: scope.outsideCount, disposition: 'parked' in result ? 'parked' : 'in-scope-only' });
|
|
6300
|
+
if ('parked' in result) progress('parked', `critical 질문 ${result.questionId} 미응답 — 이 런만 대기`);
|
|
6301
|
+
return { ok: false, stage: 'parked' in result ? 'parked' : 'soft-stopped', node: 'implement', ...resolveRunOutcome({ termination: 'abandoned' }), sessionId, worktreePath: wt.path, branch: wt.branch,
|
|
6302
|
+
detail: 'parked' in result ? `critical-unanswered: ${result.questionId}` : 'Out-of-scope edits held in the worktree; gate and publication skipped.' };
|
|
6303
|
+
}
|
|
6304
|
+
}
|
|
6305
|
+
}
|
|
6306
|
+
|
|
6271
6307
|
// ⛔⭐ 이 템플릿이 `gate` 노드를 «안 가지면» 그 단계는 돌지 않는다.
|
|
6272
6308
|
// 📌 research-loop 이 그렇다 — 문서만 바뀐 판에는 «변경 파일 범위» 게이트가 돌 시험이 없다.
|
|
6273
6309
|
// ⚠️ 꺼진 상태에서는 언제나 implement-loop 이라 참이다 ⇒ 오늘과 «같은» 걸음이다.
|
|
@@ -6275,8 +6311,6 @@ async function runSelfImplementInner(
|
|
|
6275
6311
|
// 기본이 꺼짐이라 지금 폭발 반경은 0이고, 기본을 켜기 «전»에 이 구멍을 먼저 막아야 한다.
|
|
6276
6312
|
// ⛔⭐ 라우터가 판정한다 — 값은 «코드»가 낸다(RFC §4.3 ⑵). 「모른다」는 «돌린다» 쪽이다.
|
|
6277
6313
|
// 🚨 이 줄이 2026-09-08 승격의 구멍 ⓐ 를 닫는다: research 골이 «코드»를 만지면 시험이 돈다.
|
|
6278
|
-
let gateRouteFiles: readonly string[] | undefined;
|
|
6279
|
-
try { gateRouteFiles = (s.changedFilesForGateRoute ?? changedFiles)(wt.path); } catch { gateRouteFiles = undefined; }
|
|
6280
6314
|
const gateRoute = routeGate(graphTemplate, gateRouteFiles);
|
|
6281
6315
|
const runsGate = gateRoute.runsGate;
|
|
6282
6316
|
progress('gating', !runsGate
|
|
@@ -13,7 +13,7 @@ export type PipelineNodeId =
|
|
|
13
13
|
|
|
14
14
|
/** Each pipeline node's possible terminal stages. Nodes without a terminal exit use an empty array. */
|
|
15
15
|
export const TERMINAL_STAGES_BY_NODE = {
|
|
16
|
-
implement: ['aborted', 'timed-out', 'soft-stopped'],
|
|
16
|
+
implement: ['aborted', 'timed-out', 'soft-stopped', 'parked'],
|
|
17
17
|
gate: [],
|
|
18
18
|
review: ['review-blocked'],
|
|
19
19
|
rework: ['gate-failed', 'review-blocked'],
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type RunStatus = 'completed' | 'failed' | 'cancelled';
|
|
1
|
+
export type RunStatus = 'completed' | 'failed' | 'cancelled' | 'parked';
|
|
2
2
|
/** ⭐ `crashed` — ***잡히지 않은 예외로 죽어 종결을 스스로 못 적은*** 런 (2026-08-11 · 🅣⊕🅢 어휘 합의).
|
|
3
3
|
* ⛔ `aborted`(스스로 중단을 «판정»했다)와 다른 값이다 — 이쪽은 «판정할 겨를이 없었다».
|
|
4
4
|
* 📏 왜 생겼나: 실측 미완 57 중 ≈38 이 어느 원장에도 종결이 없었고, 멈춘 자리가 전부 「노드 안」이었다. */
|
|
@@ -20,7 +20,8 @@ export type SelfImplementStage =
|
|
|
20
20
|
| 'aborted'
|
|
21
21
|
| 'timed-out'
|
|
22
22
|
| 'pr-declined'
|
|
23
|
-
| 'soft-stopped'
|
|
23
|
+
| 'soft-stopped'
|
|
24
|
+
| 'parked';
|
|
24
25
|
|
|
25
26
|
const STAGE_OUTCOMES = {
|
|
26
27
|
merged: { runStatus: 'completed' },
|
|
@@ -34,9 +35,10 @@ const STAGE_OUTCOMES = {
|
|
|
34
35
|
'timed-out': { runStatus: 'failed', failureKind: 'timed-out' },
|
|
35
36
|
'pr-declined': { runStatus: 'cancelled' },
|
|
36
37
|
'soft-stopped': { runStatus: 'cancelled' },
|
|
38
|
+
parked: { runStatus: 'parked' },
|
|
37
39
|
} satisfies Record<SelfImplementStage, MappedRunOutcome>;
|
|
38
40
|
|
|
39
|
-
const RUN_STATUSES = new Set<RunStatus>(['completed', 'failed', 'cancelled']);
|
|
41
|
+
const RUN_STATUSES = new Set<RunStatus>(['completed', 'failed', 'cancelled', 'parked']);
|
|
40
42
|
|
|
41
43
|
/** Runtime guard for the run-status payload produced by observeRunOutcome. */
|
|
42
44
|
export function isRunStatus(value: unknown): value is RunStatus {
|
package/src/session/chat.ts
CHANGED
|
@@ -109,6 +109,8 @@ export interface RunTurnOpts {
|
|
|
109
109
|
* delegated coding agent's approval prompts back to the originating
|
|
110
110
|
* chat via `delegate_code_agent`. */
|
|
111
111
|
hitlConfirmChannel?: import('../hitl/confirm.js').ConfirmChannel;
|
|
112
|
+
/** Verified by the hosting bot from the inbound user id against its server-side owner allowlist. */
|
|
113
|
+
verifiedOwner?: { id: string };
|
|
112
114
|
/** Paired multi-option question channel (Telegram sets this). Surface
|
|
113
115
|
* runTurnImpls thread it into `delegate_code_agent` so a delegated
|
|
114
116
|
* agent's structured questions render as option buttons in the
|
package/src/session/index.ts
CHANGED
|
@@ -202,6 +202,8 @@ export interface SessionMeta {
|
|
|
202
202
|
* 아님). ~/.elanous 공유 스토어 세션은 전부 'prod'. append 는 이 값을 보존(생성자 귀속).
|
|
203
203
|
* 구 세션엔 부재(tolerant read) — 미상 = 필터 비매치. */
|
|
204
204
|
originInstance?: string;
|
|
205
|
+
/** Project association, omitted for sessions without an assigned project (including legacy records). */
|
|
206
|
+
projectId?: string;
|
|
205
207
|
/** For telegram-sourced sessions; undefined for cli. */
|
|
206
208
|
tgChatId?: number;
|
|
207
209
|
tgThreadId?: number;
|
|
@@ -383,6 +385,7 @@ export interface CreateSessionOpts {
|
|
|
383
385
|
tgBotId?: string;
|
|
384
386
|
/** Stable identity for a persona's resident conversation. */
|
|
385
387
|
personaId?: string;
|
|
388
|
+
projectId?: string;
|
|
386
389
|
title?: string;
|
|
387
390
|
/** 표면 origin(cli/pwa/tg/dc/harness) — source 유니온 밖의 세밀 라벨(PWA 챗 구분용). */
|
|
388
391
|
origin?: string;
|
|
@@ -534,6 +537,7 @@ export function createSession(opts: CreateSessionOpts = {}, root: string = sessi
|
|
|
534
537
|
tgThreadId: opts.tgThreadId,
|
|
535
538
|
...(opts.tgBotId ? { tgBotId: opts.tgBotId } : {}),
|
|
536
539
|
...(opts.personaId ? { personaId: opts.personaId } : {}),
|
|
540
|
+
...(opts.projectId !== undefined ? { projectId: opts.projectId } : {}),
|
|
537
541
|
tgSessionKey,
|
|
538
542
|
};
|
|
539
543
|
writeFileSync(sessionFile(id, root), '', 'utf-8');
|
|
@@ -575,6 +579,7 @@ export function adoptSession(id: string, opts: CreateSessionOpts = {}, root: str
|
|
|
575
579
|
sourceKind: opts.sourceKind ?? defaultSessionSourceKind(source),
|
|
576
580
|
...(opts.forkedFromId ? { forkedFromId: opts.forkedFromId } : {}),
|
|
577
581
|
...(origin ? { origin } : {}),
|
|
582
|
+
...(opts.projectId !== undefined ? { projectId: opts.projectId } : {}),
|
|
578
583
|
originInstance: resolveInstanceName(),
|
|
579
584
|
...(opts.tgChatId != null ? { tgChatId: opts.tgChatId } : {}),
|
|
580
585
|
...(opts.tgThreadId != null ? { tgThreadId: opts.tgThreadId } : {}),
|
|
@@ -601,6 +606,7 @@ export function forkSessionFromHistory(
|
|
|
601
606
|
tgThreadId: opts.tgThreadId,
|
|
602
607
|
title: opts.title,
|
|
603
608
|
origin: opts.origin,
|
|
609
|
+
projectId: opts.projectId,
|
|
604
610
|
}, root);
|
|
605
611
|
for (const msg of opts.messages) {
|
|
606
612
|
const content = typeof msg.content === 'string' ? msg.content : '';
|
|
@@ -738,6 +744,8 @@ export interface ListSessionsOpts {
|
|
|
738
744
|
tgThreadId?: number;
|
|
739
745
|
/** 생성 인스턴스 정확 일치(prod · test:<repo> …). 구 세션(부재)은 비매치. */
|
|
740
746
|
originInstance?: string;
|
|
747
|
+
/** Project id exact match; sessions without the field do not match. */
|
|
748
|
+
projectId?: string;
|
|
741
749
|
/** 표면 origin 정확 일치(cli | pwa | tg | dc | harness). */
|
|
742
750
|
origin?: string;
|
|
743
751
|
/** 제외할 origin(들) — 예: ['harness'] 로 하니스 자식 세션을 사용자 대화 목록에서 숨김. */
|
|
@@ -774,6 +782,7 @@ export function listSessions(opts: ListSessionsOpts = {}, root: string = session
|
|
|
774
782
|
idx = idx.filter(m => !m.sourceKind || !opts.excludeSourceKinds!.includes(m.sourceKind));
|
|
775
783
|
}
|
|
776
784
|
if (opts.originInstance) idx = idx.filter(m => m.originInstance === opts.originInstance);
|
|
785
|
+
if (opts.projectId !== undefined) idx = idx.filter(m => m.projectId === opts.projectId);
|
|
777
786
|
if (opts.origin) idx = idx.filter(m => m.origin === opts.origin);
|
|
778
787
|
if (opts.excludeOrigins && opts.excludeOrigins.length > 0) {
|
|
779
788
|
idx = idx.filter((m) => {
|
|
@@ -210,6 +210,23 @@ export function capturedEnvAvailable(): boolean {
|
|
|
210
210
|
return captured && cached !== null;
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
/** Append login-shell PATH entries to the target without replacing its existing environment. */
|
|
214
|
+
export function mergeCapturedPath(target: NodeJS.ProcessEnv = process.env, opts: { capture?: boolean } = {}): number {
|
|
215
|
+
// capture: 아직 캡처 전이면 여기서 한 번 캡처한다(호출부가 getCapturedEnv 를 직접 부르지 않게 — identity-env 래칫).
|
|
216
|
+
const capturedPath = opts.capture || capturedEnvAvailable() ? getCapturedEnv().PATH : undefined;
|
|
217
|
+
const seen = new Set((target.PATH ?? '').split(':'));
|
|
218
|
+
const missing: string[] = [];
|
|
219
|
+
for (const entry of capturedPath?.split(':') ?? []) {
|
|
220
|
+
if (!entry || seen.has(entry)) continue;
|
|
221
|
+
missing.push(entry);
|
|
222
|
+
seen.add(entry);
|
|
223
|
+
}
|
|
224
|
+
if (missing.length) target.PATH = target.PATH ? `${target.PATH}:${missing.join(':')}` : missing.join(':');
|
|
225
|
+
const added = missing.length;
|
|
226
|
+
debug.log('shell.envbootstrap.capture', 'path-merged', { added });
|
|
227
|
+
return added;
|
|
228
|
+
}
|
|
229
|
+
|
|
213
230
|
/** Reset cache — test seam. */
|
|
214
231
|
export function resetCapturedEnvForTesting(): void {
|
|
215
232
|
cached = null;
|
package/src/steward/directive.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
2
5
|
import { getSecretAsync } from '../nexus/config/secrets/index.js';
|
|
3
6
|
import { getUserConfig } from '../user-config.js';
|
|
4
7
|
|
|
5
8
|
export type DirectiveSource = 'telegram' | 'pwa' | 'tui' | 'cli';
|
|
6
9
|
export interface DirectiveIssue { id: string; identifier: string; title: string; description?: string | null }
|
|
10
|
+
export interface DirectiveOrigin { chatId: number; botId?: string; threadId?: number }
|
|
7
11
|
export interface DirectiveDeps {
|
|
12
|
+
root?: string;
|
|
8
13
|
fetch?: typeof fetch;
|
|
9
14
|
getSecret?: (id: string) => Promise<string | undefined>;
|
|
10
15
|
team?: string;
|
|
@@ -31,8 +36,50 @@ async function graphql<T>(key: string, query: string, variables: Record<string,
|
|
|
31
36
|
return payload.data;
|
|
32
37
|
}
|
|
33
38
|
|
|
39
|
+
function originPath(root: string, identifier: string): string {
|
|
40
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(identifier)) throw new Error('Invalid directive identifier');
|
|
41
|
+
return join(root, 'steward', 'origins', `${identifier}.json`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type TelegramOrigin = import('../autopilot/mission-origin.js').MissionOrigin & { channel: 'telegram'; chatId: number };
|
|
45
|
+
|
|
46
|
+
function validOrigin(value: unknown): value is TelegramOrigin {
|
|
47
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
48
|
+
const origin = value as Record<string, unknown>;
|
|
49
|
+
return origin.channel === 'telegram' && Number.isSafeInteger(origin.chatId) &&
|
|
50
|
+
(origin.botId === undefined || typeof origin.botId === 'string' && /^\d+$/.test(origin.botId)) &&
|
|
51
|
+
(origin.threadId === undefined || Number.isSafeInteger(origin.threadId));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function loadDirectiveOrigins(root: string, identifier: string): TelegramOrigin[] {
|
|
55
|
+
try {
|
|
56
|
+
const value: unknown = JSON.parse(readFileSync(originPath(root, identifier), 'utf8'));
|
|
57
|
+
const origins = Array.isArray(value) ? value : [value];
|
|
58
|
+
return origins.filter(validOrigin);
|
|
59
|
+
} catch { return []; }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function saveDirectiveOrigin(root: string, identifier: string, origin: DirectiveOrigin): void {
|
|
63
|
+
if (!Number.isSafeInteger(origin.chatId) || origin.threadId !== undefined && !Number.isSafeInteger(origin.threadId)) throw new Error('Invalid directive origin');
|
|
64
|
+
const path = originPath(root, identifier);
|
|
65
|
+
const next: TelegramOrigin = { channel: 'telegram', chatId: origin.chatId,
|
|
66
|
+
...(origin.botId && /^\d+$/.test(origin.botId) ? { botId: origin.botId } : {}),
|
|
67
|
+
...(origin.threadId !== undefined ? { threadId: origin.threadId } : {}) };
|
|
68
|
+
const origins = loadDirectiveOrigins(root, identifier);
|
|
69
|
+
if (origins.some(item => JSON.stringify(item) === JSON.stringify(next))) return;
|
|
70
|
+
origins.push(next);
|
|
71
|
+
mkdirSync(join(root, 'steward', 'origins'), { recursive: true });
|
|
72
|
+
const temp = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
73
|
+
writeFileSync(temp, JSON.stringify(origins.length === 1 ? next : origins));
|
|
74
|
+
renameSync(temp, path);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function loadDirectiveOrigin(root: string, identifier: string): TelegramOrigin | null {
|
|
78
|
+
return loadDirectiveOrigins(root, identifier)[0] ?? null;
|
|
79
|
+
}
|
|
80
|
+
|
|
34
81
|
/** The same entry point is used by the CLI and conversational surfaces. */
|
|
35
|
-
export async function addDirective(text: string, options: { source?: DirectiveSource; dryRun?: boolean } = {}, deps: DirectiveDeps = {}): Promise<{ status: 'created' | 'repeated' | 'dry-run'; issue?: string; hash: string }> {
|
|
82
|
+
export async function addDirective(text: string, options: { source?: DirectiveSource; dryRun?: boolean; origin?: DirectiveOrigin } = {}, deps: DirectiveDeps = {}): Promise<{ status: 'created' | 'repeated' | 'dry-run'; issue?: string; hash: string }> {
|
|
36
83
|
const original = text.trim();
|
|
37
84
|
if (!original) throw new Error('directive text is required');
|
|
38
85
|
const source = options.source ?? 'cli';
|
|
@@ -74,6 +121,7 @@ export async function addDirective(text: string, options: { source?: DirectiveSo
|
|
|
74
121
|
'mutation($input:CommentCreateInput!){commentCreate(input:$input){success}}',
|
|
75
122
|
{ input: { issueId: duplicate.id, body: `다시 말함 · ${source} · ${at}\n\n${original}` } });
|
|
76
123
|
if (!result.commentCreate?.success) throw new Error('Linear comment creation failed');
|
|
124
|
+
if (source === 'telegram' && options.origin) saveDirectiveOrigin(deps.root ?? effectiveInstanceRoot(), duplicate.identifier, options.origin);
|
|
77
125
|
return { status: 'repeated', issue: duplicate.identifier, hash };
|
|
78
126
|
}
|
|
79
127
|
const labels = await request<{ issueLabels: { nodes: Array<{ id: string; name: string }> } }>(
|
|
@@ -90,6 +138,7 @@ export async function addDirective(text: string, options: { source?: DirectiveSo
|
|
|
90
138
|
'mutation($input:IssueCreateInput!){issueCreate(input:$input){success issue{id identifier title}}}',
|
|
91
139
|
{ input: { teamId, title: original.slice(0, 240), description: `${original}\n\n출처: ${source}\n시각: ${at}\n[directive-hash:${hash}]`, labelIds: [labelId], priority: 0 } });
|
|
92
140
|
if (!created.issueCreate?.success || !created.issueCreate.issue?.identifier) throw new Error('Linear issue creation failed');
|
|
141
|
+
if (source === 'telegram' && options.origin) saveDirectiveOrigin(deps.root ?? effectiveInstanceRoot(), created.issueCreate.issue.identifier, options.origin);
|
|
93
142
|
return { status: 'created', issue: created.issueCreate.issue.identifier, hash };
|
|
94
143
|
}
|
|
95
144
|
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { closeSync, mkdirSync, openSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { debug } from '../debug/log.js';
|
|
5
|
+
import { redactSecrets } from '../task-cards/card-store.js';
|
|
6
|
+
import type { ScheduledDecision, StewardSettings, TriageIssue } from './triage.js';
|
|
7
|
+
|
|
8
|
+
/** `completed` = the run finished without a merge (a non-code artifact, or nothing to deliver) — never reported as a failure. */
|
|
9
|
+
export type LaunchStatus = 'shadow' | 'launched' | 'skipped-budget' | 'failed' | 'running' | 'merged' | 'completed';
|
|
10
|
+
export interface LaunchEntry {
|
|
11
|
+
issue: string;
|
|
12
|
+
title: string;
|
|
13
|
+
source: string;
|
|
14
|
+
command: string;
|
|
15
|
+
status: LaunchStatus;
|
|
16
|
+
runId?: string;
|
|
17
|
+
pid?: number;
|
|
18
|
+
log?: string;
|
|
19
|
+
reason?: string;
|
|
20
|
+
prNumber?: number;
|
|
21
|
+
prUrl?: string;
|
|
22
|
+
reported?: boolean;
|
|
23
|
+
notified?: 'sent' | 'shadow' | 'skipped-no-origin' | 'failed';
|
|
24
|
+
notifyAttempts?: number;
|
|
25
|
+
notifyRecipients?: Record<string, { notified: 'sent' | 'failed'; attempts: number }>;
|
|
26
|
+
notifyPreview?: { hasChatId: boolean; kind: 'file' | 'link' };
|
|
27
|
+
artifactRef?: string;
|
|
28
|
+
artifactBranch?: string;
|
|
29
|
+
artifactWorktree?: string;
|
|
30
|
+
awaitingMerge?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface HitlEntry { issue: string; reason: string; raised: boolean; attempted?: boolean }
|
|
33
|
+
export interface LaunchLedger { launches: Record<string, LaunchEntry>; hitl: Record<string, HitlEntry> }
|
|
34
|
+
export interface LaunchItem { issue: TriageIssue; row: ScheduledDecision; command: string; source: string }
|
|
35
|
+
export interface LaunchPlan { launches: LaunchItem[]; hitl: Array<{ issue: TriageIssue; row: ScheduledDecision }> }
|
|
36
|
+
export interface LaunchDeps {
|
|
37
|
+
root: string;
|
|
38
|
+
settings: StewardSettings;
|
|
39
|
+
ledger: LaunchLedger;
|
|
40
|
+
/** Injected command boundary; the production adapter runs only argv, never a shell. */
|
|
41
|
+
command?: (args: string[]) => { exitCode: number; stdout: string; stderr?: string };
|
|
42
|
+
spawn?: (args: string[], log: string) => { pid: number };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const RUN_ID = /\brun-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i;
|
|
46
|
+
const DEFAULT_POOL = 'pool-node-b@node-b:8';
|
|
47
|
+
// `harness budget --json` outcomes: proceed | next-provider | wait-reset | stop. next-provider still runs (the harness switches provider itself).
|
|
48
|
+
const BUDGET_PROCEED: readonly string[] = ['proceed', 'next-provider'];
|
|
49
|
+
const cli = (args: string[]) => ['bun', 'bin/elanous.mjs', ...(process.env.NODE_ENV === 'test' ? ['--test'] : []), ...args];
|
|
50
|
+
const sourceOf = (issue: TriageIssue): string => /^출처: (telegram|pwa|tui|cli)$/m.exec(issue.body)?.[1] ?? `linear:${issue.identifier}`;
|
|
51
|
+
const promptOf = (issue: TriageIssue): string => `${issue.title}\n${issue.body.slice(0, 1500)}`;
|
|
52
|
+
const argvFor = (issue: TriageIssue, settings: StewardSettings): string[] =>
|
|
53
|
+
// Options first, then `--`: an issue title that starts with `-` must stay part of the sentence.
|
|
54
|
+
cli(['harness', 'say', '--substrate', 'pod', '--pod-pool', settings.podPool ?? DEFAULT_POOL, '--base', 'main', '--json', '--', promptOf(issue)]);
|
|
55
|
+
|
|
56
|
+
export function readLaunchLedger(root: string): LaunchLedger {
|
|
57
|
+
try {
|
|
58
|
+
const value = JSON.parse(readFileSync(join(root, 'steward', 'launches.json'), 'utf8')) as LaunchLedger;
|
|
59
|
+
if (!value || typeof value.launches !== 'object' || !value.launches || typeof value.hitl !== 'object' || !value.hitl) throw new Error('Invalid steward launch ledger');
|
|
60
|
+
return value;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { launches: {}, hitl: {} };
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function saveLaunchLedger(root: string, ledger: LaunchLedger): void {
|
|
68
|
+
const dir = join(root, 'steward');
|
|
69
|
+
mkdirSync(dir, { recursive: true });
|
|
70
|
+
const target = join(dir, 'launches.json');
|
|
71
|
+
const temp = `${target}.${process.pid}.${randomUUID()}.tmp`;
|
|
72
|
+
writeFileSync(temp, JSON.stringify(ledger));
|
|
73
|
+
renameSync(temp, target);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function planLaunches(rows: ScheduledDecision[], issues: TriageIssue[], ledger: LaunchLedger, settings: StewardSettings): LaunchPlan {
|
|
77
|
+
const byId = new Map(issues.map(issue => [issue.identifier, issue]));
|
|
78
|
+
const activeCount = Object.values(ledger.launches).filter(entry => !entry.awaitingMerge && (entry.status === 'launched' || entry.status === 'running')).length;
|
|
79
|
+
const available = Math.max(0, (settings.maxParallel ?? 3) - activeCount);
|
|
80
|
+
const launches: LaunchItem[] = [];
|
|
81
|
+
const hitl: LaunchPlan['hitl'] = [];
|
|
82
|
+
for (const row of rows) {
|
|
83
|
+
const issue = byId.get(row.issue);
|
|
84
|
+
if (!issue) continue;
|
|
85
|
+
if (row.rung === 'hitl' || row.hitlReason || row.disposition === 'hitl') {
|
|
86
|
+
if (settings.launch !== 'off' && (!ledger.hitl[row.issue] || settings.launch === 'live' && !ledger.hitl[row.issue].attempted)) hitl.push({ issue, row });
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if ((settings.launch ?? 'shadow') === 'off' || row.disposition !== 'now' || row.rung !== 4 || (ledger.launches[row.issue] && ledger.launches[row.issue].status !== 'skipped-budget' && !(settings.launch === 'live' && ledger.launches[row.issue].status === 'shadow')) || launches.length >= available) continue;
|
|
90
|
+
const args = argvFor(issue, settings);
|
|
91
|
+
launches.push({ issue, row, source: sourceOf(issue), command: args.map(arg => JSON.stringify(arg)).join(' ') });
|
|
92
|
+
}
|
|
93
|
+
debug.log('steward.launch', 'planned', { count: launches.length, hitl: hitl.length, available });
|
|
94
|
+
return { launches, hitl };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function runCommand(args: string[]): { exitCode: number; stdout: string; stderr: string } {
|
|
98
|
+
const result = Bun.spawnSync(args, { cwd: resolve(import.meta.dir, '../..'), stdout: 'pipe', stderr: 'pipe' });
|
|
99
|
+
return { exitCode: result.exitCode, stdout: result.stdout.toString(), stderr: result.stderr.toString() };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function spawnDetached(args: string[], log: string): { pid: number } {
|
|
103
|
+
const fd = openSync(log, 'a');
|
|
104
|
+
try {
|
|
105
|
+
const child = Bun.spawn(args, { cwd: resolve(import.meta.dir, '../..'), stdout: fd, stderr: fd, stdin: 'ignore', detached: true });
|
|
106
|
+
child.unref();
|
|
107
|
+
return { pid: child.pid };
|
|
108
|
+
} finally { closeSync(fd); }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function lastJson(stdout: string): Record<string, unknown> {
|
|
112
|
+
const line = stdout.trim().split('\n').at(-1);
|
|
113
|
+
const value: unknown = JSON.parse(line ?? '');
|
|
114
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid CLI JSON');
|
|
115
|
+
return value as Record<string, unknown>;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** One issue is reserved before any side effect; budget skips are retriable, never counted as a launch. */
|
|
119
|
+
export function launch(item: LaunchItem, deps: LaunchDeps): LaunchEntry {
|
|
120
|
+
const { ledger, settings, root } = deps;
|
|
121
|
+
if (item.row.rung !== 4 || item.row.hitlReason || item.row.disposition !== 'now' || (ledger.launches[item.issue.identifier] && ledger.launches[item.issue.identifier].status !== 'skipped-budget' && !(settings.launch === 'live' && ledger.launches[item.issue.identifier].status === 'shadow'))) throw new Error('steward launch is not eligible');
|
|
122
|
+
if (settings.launch === 'off') throw new Error('steward launch is off');
|
|
123
|
+
const key = item.issue.identifier;
|
|
124
|
+
const entry: LaunchEntry = { issue: key, title: redactSecrets(item.issue.title), source: item.source, command: redactSecrets(item.command), status: 'shadow' };
|
|
125
|
+
if (settings.launch !== 'live') {
|
|
126
|
+
ledger.launches[key] = entry;
|
|
127
|
+
saveLaunchLedger(root, ledger);
|
|
128
|
+
debug.log('steward.launch', 'shadow', { issue: key, command: entry.command });
|
|
129
|
+
return entry;
|
|
130
|
+
}
|
|
131
|
+
const command = deps.command ?? runCommand;
|
|
132
|
+
try {
|
|
133
|
+
const budget = command(cli(['harness', 'budget', '--json']));
|
|
134
|
+
const decision = budget.exitCode === 0 ? lastJson(budget.stdout) : {};
|
|
135
|
+
if (!BUDGET_PROCEED.includes(String(decision.outcome))) {
|
|
136
|
+
entry.status = 'skipped-budget';
|
|
137
|
+
entry.reason = redactSecrets(budget.exitCode === 0 ? JSON.stringify(decision.reasons ?? ['budget unavailable']) : budget.stderr ?? 'budget unavailable');
|
|
138
|
+
// A budget skip may be reconsidered on the next cycle, but the reason remains observable now.
|
|
139
|
+
ledger.launches[key] = entry;
|
|
140
|
+
saveLaunchLedger(root, ledger);
|
|
141
|
+
debug.log('steward.launch', 'skipped-budget', { issue: key, reason: entry.reason });
|
|
142
|
+
return entry;
|
|
143
|
+
}
|
|
144
|
+
} catch (error) {
|
|
145
|
+
entry.status = 'skipped-budget';
|
|
146
|
+
entry.reason = redactSecrets(String(error));
|
|
147
|
+
ledger.launches[key] = entry;
|
|
148
|
+
saveLaunchLedger(root, ledger);
|
|
149
|
+
debug.log('steward.launch', 'skipped-budget', { issue: key, reason: entry.reason });
|
|
150
|
+
return entry;
|
|
151
|
+
}
|
|
152
|
+
entry.status = 'launched';
|
|
153
|
+
entry.log = join(root, 'steward', 'launch', `${key.replace(/[^a-zA-Z0-9_-]/g, '_')}.log`);
|
|
154
|
+
mkdirSync(join(root, 'steward', 'launch'), { recursive: true });
|
|
155
|
+
ledger.launches[key] = entry;
|
|
156
|
+
saveLaunchLedger(root, ledger);
|
|
157
|
+
try {
|
|
158
|
+
entry.pid = (deps.spawn ?? spawnDetached)(argvFor(item.issue, settings), entry.log).pid;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
entry.status = 'failed';
|
|
161
|
+
entry.reason = redactSecrets(String(error));
|
|
162
|
+
}
|
|
163
|
+
if (entry.pid) {
|
|
164
|
+
try { entry.runId = readFileSync(entry.log, 'utf8').match(RUN_ID)?.[0]; }
|
|
165
|
+
catch { /* The detached child may not have written its log yet. */ }
|
|
166
|
+
}
|
|
167
|
+
saveLaunchLedger(root, ledger);
|
|
168
|
+
debug.log('steward.launch', 'launched', { issue: key, runId: entry.runId, pid: entry.pid, status: entry.status });
|
|
169
|
+
return entry;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function raiseHitl(issue: TriageIssue, row: ScheduledDecision, deps: LaunchDeps): HitlEntry {
|
|
173
|
+
const old = deps.ledger.hitl[issue.identifier];
|
|
174
|
+
if (old && (old.attempted || deps.settings.launch !== 'live')) return old;
|
|
175
|
+
const entry: HitlEntry = old ?? { issue: issue.identifier, reason: redactSecrets(row.hitlReason ?? row.why), raised: false };
|
|
176
|
+
deps.ledger.hitl[issue.identifier] = entry;
|
|
177
|
+
if (deps.settings.launch !== 'live') {
|
|
178
|
+
saveLaunchLedger(deps.root, deps.ledger);
|
|
179
|
+
return entry;
|
|
180
|
+
}
|
|
181
|
+
entry.attempted = true;
|
|
182
|
+
saveLaunchLedger(deps.root, deps.ledger);
|
|
183
|
+
try {
|
|
184
|
+
const category = row.hitlReason === 'public' ? 'publish' : ['money', 'security', 'irreversible'].includes(row.hitlReason ?? '') ? row.hitlReason! : 'other';
|
|
185
|
+
const title = redactSecrets(issue.title);
|
|
186
|
+
const result = (deps.command ?? runCommand)(cli(['decisions', 'raise', '--title', title, '--category', category,
|
|
187
|
+
'--s', title.slice(0, 240), '--c', redactSecrets(row.why).slice(0, 240), '--option', 'a=approve:execute after human decision',
|
|
188
|
+
'--option', 'b=decline:do not execute', '--skip-recommend', 'human decision required', '--agent', 'steward', ...(issue.ref ? ['--ref', issue.ref] : []), '--json']));
|
|
189
|
+
entry.raised = result.exitCode === 0;
|
|
190
|
+
} catch { /* Command may not be installed; card remains the human handoff. */ }
|
|
191
|
+
saveLaunchLedger(deps.root, deps.ledger);
|
|
192
|
+
if (entry.raised) debug.log('steward.launch', 'hitl-raised', { issue: issue.identifier });
|
|
193
|
+
return entry;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function alive(pid: number | undefined): boolean {
|
|
197
|
+
if (!pid) return false;
|
|
198
|
+
try { process.kill(pid, 0); return true; } catch (error) { return (error as NodeJS.ErrnoException).code === 'EPERM'; }
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function collectOutcomes(ledger: LaunchLedger, deps: LaunchDeps): LaunchEntry[] {
|
|
202
|
+
if (deps.settings.launch !== 'live') return [];
|
|
203
|
+
const done: LaunchEntry[] = [];
|
|
204
|
+
for (const entry of Object.values(ledger.launches)) {
|
|
205
|
+
if (entry.reported || entry.status === 'shadow' || entry.status === 'skipped-budget') continue;
|
|
206
|
+
if (!entry.runId && entry.log) {
|
|
207
|
+
try { entry.runId = readFileSync(entry.log, 'utf8').match(RUN_ID)?.[0]; } catch { /* Wait for log. */ }
|
|
208
|
+
}
|
|
209
|
+
if (entry.runId) {
|
|
210
|
+
try {
|
|
211
|
+
const result = (deps.command ?? runCommand)(cli(['self', 'run-ledger', entry.runId, '--json']));
|
|
212
|
+
if (result.exitCode !== 0) throw new Error(result.stderr ?? 'run ledger unavailable');
|
|
213
|
+
const events = result.stdout.trim().split('\n').filter(Boolean).map(line => JSON.parse(line) as { event?: string; data?: Record<string, unknown> });
|
|
214
|
+
if (!events.length) throw new Error('empty run ledger');
|
|
215
|
+
const reversed = [...events].reverse();
|
|
216
|
+
const mergedOffset = reversed.findIndex(event => event.event === 'merged' && event.data?.merged === true);
|
|
217
|
+
const terminalOffset = reversed.findIndex(event => event.event === 'run-status' || event.event === 'terminal');
|
|
218
|
+
const mergedIndex = mergedOffset < 0 ? -1 : events.length - 1 - mergedOffset;
|
|
219
|
+
const terminalIndex = terminalOffset < 0 ? -1 : events.length - 1 - terminalOffset;
|
|
220
|
+
const merged = mergedIndex >= 0 ? events[mergedIndex] : undefined;
|
|
221
|
+
const terminal = terminalIndex >= 0 ? events[terminalIndex] : undefined;
|
|
222
|
+
const pr = reversed.find(event => event.event === 'pr-opened');
|
|
223
|
+
const artifact = reversed.find(event => event.event === 'deploy-noncode' && typeof event.data?.ref === 'string');
|
|
224
|
+
entry.artifactRef = typeof artifact?.data?.ref === 'string' ? artifact.data.ref : undefined;
|
|
225
|
+
entry.artifactBranch = typeof artifact?.data?.branch === 'string' ? artifact.data.branch : undefined;
|
|
226
|
+
const worktree = reversed.find(event => event.event === 'planned' && typeof event.data?.worktree === 'string' && event.data.worktree);
|
|
227
|
+
entry.artifactWorktree = typeof worktree?.data?.worktree === 'string' ? worktree.data.worktree : undefined;
|
|
228
|
+
const number = merged?.data?.number ?? pr?.data?.number;
|
|
229
|
+
const url = merged?.data?.url ?? pr?.data?.url;
|
|
230
|
+
const validUrl = typeof url === 'string' && /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+$/.test(url) ? url : undefined;
|
|
231
|
+
entry.prNumber = typeof number === 'number' ? number : validUrl ? Number(validUrl.split('/').at(-1)) : undefined;
|
|
232
|
+
entry.prUrl = validUrl && (entry.prNumber === undefined || validUrl.endsWith(`/pull/${entry.prNumber}`)) ? validUrl : undefined;
|
|
233
|
+
const failed = terminalIndex > mergedIndex && (terminal?.data?.runStatus === 'failed' || terminal?.data?.terminal === 'failed');
|
|
234
|
+
entry.awaitingMerge = !failed && !merged && !artifact && !!pr && terminal?.data?.runStatus === 'completed';
|
|
235
|
+
const completed = !failed && !merged && terminal?.data?.runStatus === 'completed';
|
|
236
|
+
// A finished run is only «merged» when a merge event exists; finishing with an artifact or with nothing is `completed`, not a failure.
|
|
237
|
+
entry.status = failed ? 'failed' : merged ? 'merged' : entry.awaitingMerge ? 'running' : completed ? 'completed' : terminal ? 'failed' : 'running';
|
|
238
|
+
if (entry.status === 'failed' && !entry.reason) {
|
|
239
|
+
const reason = terminal?.data?.reason ?? terminal?.data?.terminal;
|
|
240
|
+
entry.reason = redactSecrets(typeof reason === 'string' ? reason : '런 실패');
|
|
241
|
+
}
|
|
242
|
+
} catch { if (entry.status !== 'failed') entry.status = 'running'; }
|
|
243
|
+
} else if (entry.status !== 'failed') entry.status = alive(entry.pid) ? 'running' : 'failed';
|
|
244
|
+
if (entry.status === 'running') continue;
|
|
245
|
+
done.push(entry);
|
|
246
|
+
debug.log('steward.launch', 'outcome', { issue: entry.issue, status: entry.status, runId: entry.runId, prNumber: entry.prNumber });
|
|
247
|
+
}
|
|
248
|
+
saveLaunchLedger(deps.root, ledger);
|
|
249
|
+
return done;
|
|
250
|
+
}
|