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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { realpathSync, statSync } from 'node:fs';
|
|
2
|
+
import { isAbsolute, extname, relative, resolve } from 'node:path';
|
|
3
|
+
import { debug } from '../debug/log.js';
|
|
4
|
+
import { notifyMissionDocument, notifyMissionOrigin } from '../autopilot/mission-notify.js';
|
|
5
|
+
import type { MissionOrigin } from '../autopilot/mission-origin.js';
|
|
6
|
+
import { redactSecrets } from '../task-cards/card-store.js';
|
|
7
|
+
import { loadDirectiveOrigins } from './directive.js';
|
|
8
|
+
import { saveLaunchLedger, type LaunchEntry, type LaunchLedger } from './launch.js';
|
|
9
|
+
|
|
10
|
+
export interface OutcomeNotifyDeps {
|
|
11
|
+
root: string;
|
|
12
|
+
ledger: LaunchLedger;
|
|
13
|
+
shadow?: boolean;
|
|
14
|
+
loadOrigin?: (root: string, issue: string) => MissionOrigin | null;
|
|
15
|
+
loadOrigins?: typeof loadDirectiveOrigins;
|
|
16
|
+
document?: typeof notifyMissionDocument;
|
|
17
|
+
message?: typeof notifyMissionOrigin;
|
|
18
|
+
worktreeCommand?: (args: string[]) => { exitCode: number; stdout: string };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const EXTENSIONS = new Set(['.md', '.pdf', '.png', '.jpg', '.csv', '.txt', '.html', '.json']);
|
|
22
|
+
const MAX_BYTES = 20 * 1024 * 1024;
|
|
23
|
+
|
|
24
|
+
function localArtifact(outcome: LaunchEntry, deps: OutcomeNotifyDeps): string | null {
|
|
25
|
+
const ref = outcome.artifactRef;
|
|
26
|
+
const branch = outcome.artifactBranch;
|
|
27
|
+
if (!ref || !branch || isAbsolute(ref) || !EXTENSIONS.has(extname(ref).toLowerCase())) return null;
|
|
28
|
+
const command = deps.worktreeCommand ?? ((args: string[]) => {
|
|
29
|
+
const result = Bun.spawnSync(args, { cwd: resolve(import.meta.dir, '../..'), stdout: 'pipe', stderr: 'pipe' });
|
|
30
|
+
return { exitCode: result.exitCode, stdout: result.stdout.toString() };
|
|
31
|
+
});
|
|
32
|
+
try {
|
|
33
|
+
const result = command(['git', 'worktree', 'list', '--porcelain']);
|
|
34
|
+
if (result.exitCode !== 0) return null;
|
|
35
|
+
for (const block of result.stdout.split(/\n\s*\n/)) {
|
|
36
|
+
const path = /^worktree (.+)$/m.exec(block)?.[1];
|
|
37
|
+
if (!path || !block.split('\n').includes(`branch refs/heads/${branch}`) ||
|
|
38
|
+
(outcome.artifactWorktree && !path.endsWith(outcome.artifactWorktree))) continue;
|
|
39
|
+
try {
|
|
40
|
+
const base = realpathSync(path);
|
|
41
|
+
const file = realpathSync(resolve(base, ref));
|
|
42
|
+
const inside = relative(base, file);
|
|
43
|
+
if (!inside || inside === '..' || inside.startsWith('../') || isAbsolute(inside)) continue;
|
|
44
|
+
const stat = statSync(file);
|
|
45
|
+
return stat.isFile() && stat.size <= MAX_BYTES ? file : null;
|
|
46
|
+
} catch { /* Worktree or file is gone; try another matching worktree. */ }
|
|
47
|
+
}
|
|
48
|
+
} catch { /* No accessible worktree or artifact: use PR link instead. */ }
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Persist each conversation's delivery independently; only failed recipients retry (up to three times). */
|
|
53
|
+
export function notifyOutcome(outcome: LaunchEntry, deps: OutcomeNotifyDeps): LaunchEntry['notified'] {
|
|
54
|
+
if (outcome.notified === 'shadow') return 'shadow';
|
|
55
|
+
let origins: MissionOrigin[] = [];
|
|
56
|
+
try {
|
|
57
|
+
origins = deps.loadOrigin
|
|
58
|
+
? [deps.loadOrigin(deps.root, outcome.issue)].filter((item): item is MissionOrigin => item !== null)
|
|
59
|
+
: (deps.loadOrigins ?? loadDirectiveOrigins)(deps.root, outcome.issue);
|
|
60
|
+
} catch { /* Damaged or unavailable origin cannot route a result. */ }
|
|
61
|
+
const recipients = new Map<string, MissionOrigin>();
|
|
62
|
+
for (const origin of origins) {
|
|
63
|
+
if (origin.channel !== 'telegram' || !Number.isSafeInteger(origin.chatId)) continue;
|
|
64
|
+
const key = JSON.stringify([origin.chatId, origin.botId ?? null, origin.threadId ?? null]);
|
|
65
|
+
recipients.set(key, origin);
|
|
66
|
+
}
|
|
67
|
+
if (outcome.notified === 'skipped-no-origin' && recipients.size === 0 && !deps.shadow) return outcome.notified;
|
|
68
|
+
if (outcome.notified === 'sent' && !outcome.notifyRecipients) return 'sent';
|
|
69
|
+
const file = outcome.status !== 'failed' && (recipients.size > 0 || deps.shadow) ? localArtifact(outcome, deps) : null;
|
|
70
|
+
const kind = outcome.status === 'failed' ? 'failure' : file ? 'file' : 'link';
|
|
71
|
+
let sent = false;
|
|
72
|
+
if (deps.shadow) {
|
|
73
|
+
outcome.notified = 'shadow';
|
|
74
|
+
outcome.notifyPreview = { hasChatId: recipients.size > 0, kind: file ? 'file' : 'link' };
|
|
75
|
+
} else if (recipients.size === 0) {
|
|
76
|
+
outcome.notified = 'skipped-no-origin';
|
|
77
|
+
} else {
|
|
78
|
+
const title = redactSecrets(outcome.title).replace(/\s+/g, ' ').slice(0, 240);
|
|
79
|
+
// Say what happened in plain words: an open PR is not a merge, and a run that left nothing is not a failure.
|
|
80
|
+
const status = outcome.awaitingMerge ? 'PR 열림(병합 대기)' : outcome.status === 'merged' ? '병합됨'
|
|
81
|
+
: outcome.status === 'completed' ? '완료(병합 없음)' : outcome.status;
|
|
82
|
+
const pr = outcome.prNumber;
|
|
83
|
+
const link = outcome.prUrl && /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+$/.test(outcome.prUrl) &&
|
|
84
|
+
(pr === undefined || outcome.prUrl.endsWith(`/pull/${pr}`)) ? outcome.prUrl : undefined;
|
|
85
|
+
const noOutput = outcome.status === 'completed' && !outcome.artifactRef && pr === undefined && !outcome.prUrl;
|
|
86
|
+
const undeliverable = outcome.status !== 'failed' && !noOutput && !file && !link;
|
|
87
|
+
const text = outcome.status === 'failed'
|
|
88
|
+
? `${title} · 실패 — ${redactSecrets(outcome.reason ?? '런 실패').split(/\r?\n/)[0]?.slice(0, 240) ?? '런 실패'}`
|
|
89
|
+
: noOutput
|
|
90
|
+
? `${title} · ${status} — 남긴 파일·PR 없음`
|
|
91
|
+
: undeliverable
|
|
92
|
+
? `${title} · ${status} · 산출물 전달 불가 — ${outcome.artifactRef ? '파일을 확인할 수 없습니다' : '검증된 PR 링크가 없습니다'}`
|
|
93
|
+
: `${title} · ${status} · PR ${pr ?? '없음'} · ${link}`;
|
|
94
|
+
const legacyAttempts = !outcome.notifyRecipients && outcome.notified === 'failed' ? outcome.notifyAttempts ?? 0 : 0;
|
|
95
|
+
const progress = outcome.notifyRecipients ??= {};
|
|
96
|
+
for (const [key, origin] of recipients) {
|
|
97
|
+
const previous = progress[key] ?? (legacyAttempts ? { notified: 'failed' as const, attempts: legacyAttempts } : undefined);
|
|
98
|
+
if (previous?.notified === 'sent' || (previous?.attempts ?? 0) >= 3) continue;
|
|
99
|
+
let delivered = false;
|
|
100
|
+
try {
|
|
101
|
+
if (!undeliverable || !previous) {
|
|
102
|
+
delivered = file ? (deps.document ?? notifyMissionDocument)(origin, file, redactSecrets(`${title} · ${status}`))
|
|
103
|
+
: (deps.message ?? notifyMissionOrigin)(origin, redactSecrets(text));
|
|
104
|
+
}
|
|
105
|
+
} catch { /* A failed recipient remains eligible on a later tick. */ }
|
|
106
|
+
progress[key] = { notified: delivered && !undeliverable ? 'sent' : 'failed', attempts: (previous?.attempts ?? 0) + 1 };
|
|
107
|
+
sent ||= delivered && !undeliverable;
|
|
108
|
+
}
|
|
109
|
+
outcome.notifyAttempts = Math.max(0, ...Object.values(progress).map(item => item.attempts));
|
|
110
|
+
outcome.notified = [...recipients.keys()].every(key => progress[key]?.notified === 'sent') ? 'sent' : 'failed';
|
|
111
|
+
}
|
|
112
|
+
saveLaunchLedger(deps.root, deps.ledger);
|
|
113
|
+
debug.log('steward.outcome', 'notify', { cardId: `linear:${outcome.issue}`, issue: outcome.issue, kind, sent });
|
|
114
|
+
return outcome.notified;
|
|
115
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { CardStore, redactSecrets, type AppendSectionInput, type CreateCardInput, type TaskCard } from '../task-cards/card-store.js';
|
|
3
3
|
import type { ScheduledDecision, TriageIssue } from './triage.js';
|
|
4
|
+
import type { HitlEntry } from './launch.js';
|
|
4
5
|
|
|
5
6
|
export interface StewardCardStore {
|
|
6
7
|
createCard(input: CreateCardInput): TaskCard;
|
|
@@ -33,7 +34,7 @@ export function recordTriageOnCards(decisions: ScheduledDecision[], issues: Tria
|
|
|
33
34
|
});
|
|
34
35
|
}
|
|
35
36
|
const { rung, dependsOn, priority, disposition, hitlReason, why, owner } = decision;
|
|
36
|
-
const judgment = { rung, dependsOn, priority, disposition: rung === 'hitl' || hitlReason ? 'hitl' : disposition, hitlReason: hitlReason ?? null, why: redactSecrets(why), ...(owner ? { owner } : {}) };
|
|
37
|
+
const judgment = { rung, dependsOn, priority, disposition: rung === 'hitl' || hitlReason ? 'hitl' : disposition, hitlReason: hitlReason ?? null, why: redactSecrets(why), ...(owner ? { owner } : {}), ...(decision.capability ? { capability: decision.capability } : {}) };
|
|
37
38
|
const key = `triage:${createHash('sha256').update(JSON.stringify(judgment)).digest('hex')}`;
|
|
38
39
|
if (!card.sections.some(section => section.key === key)) {
|
|
39
40
|
store.appendSection(card.id, { key, owner: 'steward', content: JSON.stringify(judgment) });
|
|
@@ -44,7 +45,7 @@ export function recordTriageOnCards(decisions: ScheduledDecision[], issues: Tria
|
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
function recordOnCard(section: 'launch' | 'outcome', issue: Pick<TriageIssue, 'identifier' | 'title'>, value: object, deps: StewardCardDeps): void {
|
|
48
|
+
function recordOnCard(section: 'launch' | 'outcome' | 'hitl', issue: Pick<TriageIssue, 'identifier' | 'title'>, value: object, deps: StewardCardDeps): void {
|
|
48
49
|
const store = deps.store ?? new CardStore(deps.root);
|
|
49
50
|
try {
|
|
50
51
|
const content = redactSecrets(JSON.stringify(value));
|
|
@@ -67,3 +68,8 @@ export function recordLaunchOnCard(issue: Pick<TriageIssue, 'identifier' | 'titl
|
|
|
67
68
|
export function recordOutcomeOnCard(issue: Pick<TriageIssue, 'identifier' | 'title'>, outcome: object, deps: StewardCardDeps = {}): void {
|
|
68
69
|
recordOnCard('outcome', issue, outcome, deps);
|
|
69
70
|
}
|
|
71
|
+
|
|
72
|
+
/** Append a HITL handoff revision (pending → raised/unavailable) to the issue card. */
|
|
73
|
+
export function recordHitlOnCard(issue: Pick<TriageIssue, 'identifier' | 'title'>, entry: HitlEntry, deps: StewardCardDeps = {}): void {
|
|
74
|
+
recordOnCard('hitl', issue, entry, deps);
|
|
75
|
+
}
|
|
@@ -1,8 +1,33 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
1
4
|
import { resolveRoleModel } from '../user-config.js';
|
|
2
5
|
import type { HitlReason, Rung, TriageIssue } from './triage.js';
|
|
3
6
|
|
|
4
7
|
export type StewardAsk = (prompt: string, role: 'classify' | 'planning') => Promise<unknown>;
|
|
5
|
-
export interface ClassifiedIssue { issue: string; rung: Rung; why: string; hitlReason?: HitlReason; duplicateOf?: string }
|
|
8
|
+
export interface ClassifiedIssue { issue: string; rung: Rung; why: string; hitlReason?: HitlReason; duplicateOf?: string; capability?: 'new-capability' | 'existing-capability' }
|
|
9
|
+
export interface CapabilityInventory { graphs: readonly string[]; plugins: readonly string[]; commands: readonly string[] }
|
|
10
|
+
|
|
11
|
+
/** Read installed names, not promises in documentation. The caller can supply a test-instance root. */
|
|
12
|
+
export function installedCapabilities(root: string = resolve(import.meta.dir, '../..')): CapabilityInventory {
|
|
13
|
+
const files = (dir: string): string[] => {
|
|
14
|
+
try { return readdirSync(dir, { withFileTypes: true }).flatMap(entry => entry.isDirectory()
|
|
15
|
+
? files(join(dir, entry.name)) : entry.isFile() && /\.ya?ml$/.test(entry.name) ? [join(dir, entry.name)] : []); }
|
|
16
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') return []; throw error; }
|
|
17
|
+
};
|
|
18
|
+
const graphs = files(join(root, 'graphs')).flatMap(file => {
|
|
19
|
+
const match = /^graph_id:\s*['"]?([^\s'"#]+)/m.exec(readFileSync(file, 'utf8'));
|
|
20
|
+
return match ? [match[1]!] : [];
|
|
21
|
+
});
|
|
22
|
+
let plugins: string[];
|
|
23
|
+
try { plugins = readdirSync(join(root, 'plugins'), { withFileTypes: true }).filter(entry => entry.isDirectory() && entry.name !== 'examples').map(entry => entry.name); }
|
|
24
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; plugins = []; }
|
|
25
|
+
const help = spawnSync(process.execPath, [join(root, 'bin/elanous.mjs'), '--test', '--help'], { cwd: root, encoding: 'utf8', timeout: 15_000 });
|
|
26
|
+
// An unconfigured test instance cannot start the CLI. Unknown commands must not be treated as installed.
|
|
27
|
+
const commands = help.status === 0
|
|
28
|
+
? [...help.stdout.matchAll(/^\s{2}(?!-)([\w:-]+)(?:\s|$)/gm)].map(match => match[1]!) : [];
|
|
29
|
+
return { graphs, plugins, commands };
|
|
30
|
+
}
|
|
6
31
|
export interface IssuePlan { issue: string; priority: number; dependsOn: string[]; owner?: string }
|
|
7
32
|
|
|
8
33
|
const unsafe: Array<[HitlReason, RegExp]> = [
|
|
@@ -42,14 +67,17 @@ export async function askSteward(prompt: string, role: 'classify' | 'planning'):
|
|
|
42
67
|
return parsed(result);
|
|
43
68
|
}
|
|
44
69
|
|
|
45
|
-
export async function classifyIssue(issue: TriageIssue, ask: StewardAsk = askSteward): Promise<ClassifiedIssue> {
|
|
70
|
+
export async function classifyIssue(issue: TriageIssue, ask: StewardAsk = askSteward, inventory: CapabilityInventory = installedCapabilities()): Promise<ClassifiedIssue> {
|
|
46
71
|
const candidate = unsafeReason(issue);
|
|
47
|
-
const response = parsed(await ask(`스튜어드 분류. JSON 객체만: {"rung":0|1|2|3|4|5|"hitl","hitlReason":null,"why":"한 문장"}. 0=이미됨, 1=셸, 2=CLI, 3=조사, 4=하니스 골, 5=외부 에이전트. 돈·공개·보안·비가역은 hitl; 그 외 사람 확인은 other. 확인되지 않은 완료를 추측하지 마라. ${candidate ? `안전 후보 ${candidate}: 이유를 확인하라.` : ''}\n이슈: ${JSON.stringify(issue)}`, 'classify'));
|
|
72
|
+
const response = parsed(await ask(`스튜어드 분류. JSON 객체만: {"rung":0|1|2|3|4|5|"hitl","hitlReason":null,"why":"한 문장","capability":"new-capability"|"existing-capability"}. 0=이미됨, 1=셸, 2=CLI, 3=조사, 4=하니스 골, 5=외부 에이전트. 돈·공개·보안·비가역은 hitl; 그 외 사람 확인은 other. 새 능력인지 기존 그래프·플러그인·명령으로 되는 일인지 판정하라. 존재만으로 그 기능이 소원을 충족한다고 추측하지 마라. 확인되지 않은 완료를 추측하지 마라. ${candidate ? `안전 후보 ${candidate}: 이유를 확인하라.` : ''}\n설치된 능력: ${JSON.stringify(inventory)}\n이슈: ${JSON.stringify(issue)}`, 'classify'));
|
|
48
73
|
if (![0, 1, 2, 3, 4, 5, 'hitl'].includes(response.rung as Rung) || typeof response.why !== 'string' || !response.why.trim()) throw new Error(`Invalid steward classification: ${issue.identifier}`);
|
|
49
74
|
const reason = candidate ?? (unsafe.some(([name]) => name === response.hitlReason) ? response.hitlReason as HitlReason : undefined)
|
|
50
75
|
?? (response.rung === 'hitl' ? 'other' : undefined);
|
|
76
|
+
// A missing or unknown verdict does not block triage; working-backwards drafting is simply skipped for that issue.
|
|
77
|
+
const capability = response.capability === 'new-capability' || response.capability === 'existing-capability' ? response.capability : undefined;
|
|
51
78
|
return { issue: issue.identifier, rung: reason ? 'hitl' : response.rung as Rung,
|
|
52
|
-
...(reason ? { hitlReason: reason } : {}), why: response.why.trim().replace(/\s+/g, ' ')
|
|
79
|
+
...(reason ? { hitlReason: reason } : {}), why: response.why.trim().replace(/\s+/g, ' '),
|
|
80
|
+
...(capability ? { capability } : {}) };
|
|
53
81
|
}
|
|
54
82
|
|
|
55
83
|
/** One request for the current batch, never one request per issue. */
|