elanous 0.2.17 → 0.2.18
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/P6YTVgzuZz5oTZhY4Din_/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1307-aac7332576c4d6fc.js → 1307-30dff22c9e1f3073.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6158.9c0449a9a8d0064b.js → 6158.af2e93b7adff709b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/{page-3143b07a817d1da9.js → page-d92f7ee3994e84c7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-17ba086ef82d339b.js → page-4733c967b56a4277.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-7eb661ff23af7da2.js → page-d22fd849cc22f104.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-bb742f995c6769e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-6e5c3a867557298b.js → page-02b073a2ac793b83.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-8a9b3594bc23289b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-5e77cd1802567c89.js → page-a0eb02638b1e6f31.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-308111abadf35670.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-4187666132fc98c4.js → page-5d97b2901e0d1ea6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-09328d499162bae4.js → page-c39554c321576982.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-29ff3a8f84f03b6a.js → page-8c3d8e63ae2e1c98.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-80ec7401bc83f511.js → page-9b72a07d424701c6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-fc7d86467291ae38.js → page-a572f58cd3ca5864.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-be5ea965a07fbc41.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/{page-0e2d6985fb42cd2f.js → page-c130b63862437814.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/market/{page-13e4108999f58d13.js → page-fa244bd146a00ae8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-e8296ee284aa1293.js → page-44d56d6391965a6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-043a9111e74f6b24.js → page-83a700d4c754d802.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-23a4601ceb7f452e.js → not-found-7b3170f374f77b9d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-10d958605a77b3bd.js → page-a3c58f5c72a5f3f9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/{page-7a1d85e121065cc8.js → page-e8e1ecaea7cd71d2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-12916918988ac97e.js → page-1467047104141297.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-d8a3347a9d0c3f4a.js → page-2da6f9b8a38c6978.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-102310930791197a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-6ed0b73bae8351d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-1d824510df025d18.js → page-44d5f767491692bf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-43336055451f6e37.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-7f49fe645d5132f7.js → page-3cc47dca0922ab9a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a77ee355a2304ff0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-27d71f131acd9320.js → page-b17cd46cf0e9b8cf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-61553851aa606146.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-a1f585e154e61c59.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-424659bacfb8cdd7.js → page-7bf54c9bb9274af8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-5150ef17b5383a57.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-5d4fef9ccd7d406b.js → page-134da468538baf48.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-539cde99554be20f.js → webpack-c2782b330447ffb6.js} +1 -1
- package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.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/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/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/decisions/index.html +1 -0
- package/apps/pwa/out/decisions/index.txt +30 -0
- 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/inside/index.html +1 -1
- package/apps/pwa/out/inside/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/loops/index.html +1 -1
- package/apps/pwa/out/loops/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 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- 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 -1
- package/apps/pwa/out/today/index.txt +14 -14
- 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/package.json +2 -1
- package/samples/workflows/templates/approval-gated-deploy.yaml +30 -0
- package/samples/workflows/templates/chat-driven-research.yaml +35 -0
- package/samples/workflows/templates/discord-listen-and-respond.yaml +29 -0
- package/samples/workflows/templates/http-poll-then-alert.yaml +30 -0
- package/samples/workflows/templates/manual-quickstart.yaml +21 -0
- package/samples/workflows/templates/scheduled-daily-digest.yaml +30 -0
- package/samples/workflows/templates/webhook-deploy-hook.yaml +27 -0
- package/scripts/ci-public-export-import-gate.ts +7 -2
- package/scripts/ci-typecheck-changed.ts +21 -6
- package/scripts/discord-seat-probe.ts +3 -2
- package/scripts/docs/generate-cli-anatomy.ts +162 -0
- package/scripts/goals/authoring-metrics.ts +73 -0
- package/scripts/landing-heal/must-fix-match.ts +7 -7
- package/scripts/landing-heal/run-mission.ts +3 -2
- package/scripts/public-export-test-run.ts +37 -2
- package/scripts/release-loop/checklist-gate-node.ts +13 -11
- package/scripts/release-loop/cut-branch.ts +2 -1
- package/scripts/release-loop/cutoff.ts +4 -2
- package/scripts/release-loop/docs-land-node.ts +8 -1
- package/scripts/release-loop/docs-node.ts +1 -1
- package/scripts/release-loop/gate-node.ts +99 -14
- package/scripts/release-loop/node-verdict.ts +2 -1
- package/scripts/release-loop/npm-publish-node.ts +11 -7
- package/scripts/release-loop/ops-upgrade-node.ts +8 -1
- package/scripts/release-loop/preflight.ts +246 -0
- package/scripts/release-loop/publish-node.ts +8 -1
- package/scripts/release-loop/release-readiness.ts +11 -6
- package/scripts/release-loop/release-version.ts +48 -0
- package/scripts/release-loop/resume-release.ts +115 -0
- package/scripts/release-loop/unattended-release.ts +43 -7
- package/scripts/release-loop/version-node.ts +145 -40
- package/scripts/rhythm/daily.ts +25 -4
- package/scripts/skills/public-check.ts +62 -12
- package/scripts/test-deterministic.ts +51 -33
- package/scripts/test-diet/effectiveness.ts +62 -1
- package/scripts/test-diet/lib.ts +1 -1
- package/scripts/test-diet/node.ts +8 -1
- package/src/agent-mission/review-loop.ts +4 -3
- package/src/agent-substrate/pr-comment-meta.ts +20 -0
- package/src/autopilot/build/llm-conflict-merge.ts +28 -8
- package/src/briefing/brief-items.ts +86 -17
- package/src/cli/autopilot-cli.ts +30 -11
- package/src/cli/brief-cli.ts +36 -2
- package/src/cli/freeze-cli.ts +3 -2
- package/src/cli/intake-cli.ts +73 -1
- package/src/cli/notify-cli.ts +75 -0
- package/src/cli/provider-cli.ts +12 -2
- package/src/cli/release-cli.ts +101 -17
- package/src/cli/tasks-cli.ts +309 -3
- package/src/context-bus/condense.ts +19 -3
- package/src/context-bus/context-day.ts +77 -24
- package/src/context-bus/context-first.ts +33 -0
- package/src/discord-self-message.ts +20 -2
- package/src/discord-slash-wire.ts +1 -0
- package/src/domains/outbound-alert.ts +200 -27
- package/src/domains/outbound-log-probe.ts +16 -1
- package/src/domains/release-tool.ts +2 -2
- package/src/execution-loop/gates.ts +11 -2
- package/src/flow-loop/split.ts +8 -4
- package/src/git-fs/worktree.ts +23 -3
- package/src/harness/harness-cli-command.ts +10 -2
- package/src/harness/harness-queue.ts +388 -33
- package/src/harness/helper-scan.ts +10 -3
- package/src/hq/seats.ts +369 -0
- package/src/index.ts +133 -15
- package/src/intake-plane/adopt-gate.ts +49 -0
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/collect-github.ts +57 -25
- package/src/intake-plane/discord-seat-work.ts +8 -2
- package/src/intake-plane/requirement-funnel.ts +316 -0
- package/src/knowledge/know-find.ts +10 -1
- package/src/loops/budget.ts +36 -0
- package/src/loops/orchestrator/finish-rate.ts +73 -1
- package/src/loops/orchestrator/grid-place.ts +11 -0
- package/src/loops/orchestrator/tick.ts +295 -20
- package/src/maturity/feature-maturity.ts +3 -2
- package/src/nexus/api/decisions-route.ts +11 -1
- package/src/nexus/api/loop-edges.ts +69 -13
- package/src/nexus/api/ops-seats-sources.ts +2 -2
- package/src/nexus/api/workflow-templates.ts +3 -0
- package/src/oauth/codex.ts +38 -0
- package/src/onboarding/first-chat-gate.ts +29 -0
- package/src/release-loop/checklist.ts +104 -6
- package/src/release-loop/feature-store.ts +10 -9
- package/src/release-loop/landing-freeze.ts +6 -4
- package/src/release-loop/placement.ts +13 -3
- package/src/release-loop/rubric.ts +80 -0
- package/src/seat-dispatch/seat-ask.ts +79 -16
- package/src/seat-loop/seat-loop.ts +12 -6
- package/src/self-dev/ask-launch-flow.ts +24 -3
- package/src/self-dev/ask-path-candidates.ts +68 -0
- package/src/self-dev/dev-pipeline.ts +4 -0
- package/src/self-dev/draft-triage-rules.ts +1 -1
- package/src/self-dev/orchestrate-cli.ts +22 -0
- package/src/self-dev/round-pr-supersede.ts +244 -0
- package/src/self-dev/run-supervisor.ts +14 -0
- package/src/self-implement/follow-up-goals.ts +2 -2
- package/src/self-implement/gate-remote.ts +427 -0
- package/src/self-implement/goal-author.ts +8 -3
- package/src/self-implement/l8-merge-queue.ts +29 -5
- package/src/self-implement/orchestrator.ts +59 -12
- package/src/self-implement/prior-draft-findings.ts +4 -3
- package/src/skills/skill-feedback.ts +76 -0
- package/src/task-agent/actions.ts +39 -6
- package/src/task-agent/judge.ts +31 -5
- package/src/task-agent/mission.ts +423 -0
- package/src/task-agent/shadow.ts +90 -0
- package/src/task-agent/task-hand.ts +235 -0
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +10 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +86 -18
- package/src/telegram-loops-command.ts +1 -1
- package/src/telegram.ts +5 -20
- package/src/user-config.ts +63 -3
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4614-21403aac730d4586.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6877-fe94e76a5841ee9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8401-4ce1c42c8d89903f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8949-ecb4128a0858b911.js +0 -1
- package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-7110f732f45bf944.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-9a41d65355fc25b9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-41afec0b255ed2bb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ab93b240f687d69a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-de43a186e5ba5683.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-8b08cafad27759c9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-166745e51ac0c7b0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a04260b8ccc968d4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-5a64cb5208c6a8fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-1407ec83ea64c1f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-6e878ebbe1737d00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-81a4e7f8dbfe74cb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-fc2df261f8a2c7df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-09461366af82ef13.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ea90a8ac73ce2749.js +0 -1
- package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +0 -1
- package/apps/pwa/out/_next/static/yRA5ur6DcPl0YHSRheH2O/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{yRA5ur6DcPl0YHSRheH2O → P6YTVgzuZz5oTZhY4Din_}/_ssgManifest.js +0 -0
|
@@ -4,6 +4,7 @@ import { dirname, join } from 'node:path';
|
|
|
4
4
|
import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
5
5
|
import { debug } from '../debug/log.js';
|
|
6
6
|
import { dateKey } from '../time/format.js';
|
|
7
|
+
import { readRequirementFunnel, type RequirementFunnelResult } from '../intake-plane/requirement-funnel.js';
|
|
7
8
|
|
|
8
9
|
export const BRIEF_DOMAINS = ['운영', '판', '흡수', '시장', '행정', '코나투스'] as const;
|
|
9
10
|
export const BRIEF_PRIORITIES = ['P0', 'P1', 'P2'] as const;
|
|
@@ -41,6 +42,7 @@ export interface BriefItemsOptions {
|
|
|
41
42
|
stateDir?: string;
|
|
42
43
|
now?: () => Date;
|
|
43
44
|
log?: (category: string, event: string, data?: unknown) => void;
|
|
45
|
+
requirementFunnel?: (now: Date) => RequirementFunnelResult;
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
export class BriefItemsInputError extends Error {
|
|
@@ -89,8 +91,15 @@ function needsDecision(item: BriefItem, today: string): boolean {
|
|
|
89
91
|
* duplicate claims dropped (same normalized sentence, most urgent kept).
|
|
90
92
|
*/
|
|
91
93
|
export function composeBriefMarkdown(items: readonly BriefItem[], slot: BriefSlot, now: Date): string {
|
|
94
|
+
return composeBriefPage(items, slot, now).markdown;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The ids are exactly the rows represented by the composed page, after deduplication. */
|
|
98
|
+
export function composeBriefPage(items: readonly BriefItem[], slot: BriefSlot, now: Date): { markdown: string; ids: number[] } {
|
|
92
99
|
const today = dateKey(now, { timeZone: KST });
|
|
93
|
-
|
|
100
|
+
// A duplicate left out of a delivered page stays unsent in the ledger, but must not reappear on a later page.
|
|
101
|
+
const deliveredClaims = new Set(items.filter(item => item.sent_at !== null).map(item => normalizeClaim(item.text)));
|
|
102
|
+
const eligible = items.filter(item => item.sent_at === null && beforeSlot(item.created_at, slot, now) && !deliveredClaims.has(normalizeClaim(item.text)));
|
|
94
103
|
// Duplicate claims keep the copy that needs a decision today (P0 or due today), then the higher priority, then the earliest (ACP must-fix).
|
|
95
104
|
const rank = (item: BriefItem) => [needsDecision(item, today) ? 0 : 1, BRIEF_PRIORITIES.indexOf(item.priority), item.id];
|
|
96
105
|
const better = (a: BriefItem, b: BriefItem) => { const x = rank(a), y = rank(b); return x[0]! - y[0]! || x[1]! - y[1]! || x[2]! - y[2]!; };
|
|
@@ -117,7 +126,7 @@ export function composeBriefMarkdown(items: readonly BriefItem[], slot: BriefSlo
|
|
|
117
126
|
for (const item of group.sort((a, b) => a.id - b.id)) lines.push(itemLine(item));
|
|
118
127
|
lines.push('');
|
|
119
128
|
}
|
|
120
|
-
return `${lines.join('\n').trimEnd()}\n
|
|
129
|
+
return { markdown: `${lines.join('\n').trimEnd()}\n`, ids: unique.map(item => item.id) };
|
|
121
130
|
}
|
|
122
131
|
|
|
123
132
|
function itemLine(item: BriefItem): string {
|
|
@@ -166,11 +175,13 @@ export class BriefItemsLedger {
|
|
|
166
175
|
readonly path: string;
|
|
167
176
|
private readonly now: () => Date;
|
|
168
177
|
private readonly log: (category: string, event: string, data?: unknown) => void;
|
|
178
|
+
private readonly requirementFunnel: (now: Date) => RequirementFunnelResult;
|
|
169
179
|
|
|
170
180
|
constructor(options: BriefItemsOptions = {}) {
|
|
171
181
|
this.path = join(options.stateDir ?? elanousStateRoot(), 'briefing', 'items.sqlite');
|
|
172
182
|
this.now = options.now ?? (() => new Date());
|
|
173
183
|
this.log = options.log ?? ((category, event, data) => debug.log(category, event, data));
|
|
184
|
+
this.requirementFunnel = options.requirementFunnel ?? ((now) => readRequirementFunnel({ stateDir: options.stateDir, now }));
|
|
174
185
|
}
|
|
175
186
|
|
|
176
187
|
private using<T>(work: (db: Database) => T): T {
|
|
@@ -246,15 +257,81 @@ export class BriefItemsLedger {
|
|
|
246
257
|
|
|
247
258
|
/**
|
|
248
259
|
* Markdown for one slot. Reads only — never marks, rewrites, or sends an item.
|
|
249
|
-
* Items created at or after the slot instant are left out.
|
|
260
|
+
* Items created at or after the slot instant are left out. At 08:30 the
|
|
261
|
+
* requirement funnel contributes ephemeral lines without adding ledger rows.
|
|
250
262
|
*/
|
|
251
263
|
compose(slot: string): string {
|
|
264
|
+
return this.composeWithIds(slot).markdown;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private itemsFromDb(db: Database): BriefItem[] {
|
|
268
|
+
return db.query('SELECT id, text, domain, priority, deadline, evidence, source, created_at, (SELECT MIN(sent_at) FROM sends WHERE sends.item_id = items.id) AS sent_at FROM items ORDER BY id').all() as BriefItem[];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private pageFromDb(db: Database, slot: BriefSlot): { markdown: string; ids: number[] } {
|
|
272
|
+
const items = this.itemsFromDb(db);
|
|
273
|
+
const now = this.now();
|
|
274
|
+
const morningCutoff = Date.parse(`${dateKey(now, { timeZone: KST })}T08:30:00+09:00`);
|
|
275
|
+
let funnelItems: BriefItem[] = [];
|
|
276
|
+
if (slot === '08:30' && now.getTime() >= morningCutoff) {
|
|
277
|
+
// A funnel failure must not take the morning brief down with it.
|
|
278
|
+
try {
|
|
279
|
+
const extra = this.requirementFunnel(new Date(morningCutoff - 1)).briefItems;
|
|
280
|
+
// Negative ids never collide with ledger rows and keep the funnel's own order (summary first).
|
|
281
|
+
funnelItems = extra.map((item, index): BriefItem => ({
|
|
282
|
+
id: index - extra.length, text: item.text, domain: item.domain, priority: item.priority, deadline: null,
|
|
283
|
+
evidence: item.evidence ?? null, source: item.source,
|
|
284
|
+
created_at: new Date(morningCutoff - 1).toISOString(), sent_at: null,
|
|
285
|
+
}));
|
|
286
|
+
} catch (error) {
|
|
287
|
+
this.log('briefing.items', 'requirement-funnel-failed', { slot, error: error instanceof Error ? error.message : String(error) });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const composed = composeBriefPage([...items, ...funnelItems], slot, now);
|
|
291
|
+
// Ephemeral funnel lines appear on the page but have no ledger row to mark sent.
|
|
292
|
+
const page = { markdown: composed.markdown, ids: composed.ids.filter(id => id > 0) };
|
|
293
|
+
const candidates = items.filter(item => item.sent_at === null && beforeSlot(item.created_at, slot, now)).length;
|
|
294
|
+
this.log('briefing.items', 'composed', { slot, candidates, bytes: Buffer.byteLength(page.markdown) });
|
|
295
|
+
return page;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Read-only page and the exact ledger item ids printed on it (after deduplication). */
|
|
299
|
+
composeWithIds(slot: string): { markdown: string; ids: number[] } {
|
|
252
300
|
if (!(BRIEF_SLOTS as readonly string[]).includes(slot)) throw new BriefItemsInputError('invalid slot');
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
301
|
+
return this.using(db => this.pageFromDb(db, slot as BriefSlot));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Hold the ledger's SQLite writer lock from selecting unsent rows through delivery and marking.
|
|
305
|
+
* A failed delivery rolls back and releases the lock; the next sender can retry.
|
|
306
|
+
*/
|
|
307
|
+
withSendLock<T>(slot: string, work: (page: { markdown: string; ids: number[] }, markSent: (ids: readonly number[], slot: BriefSlot) => number) => T): T {
|
|
308
|
+
if (!(BRIEF_SLOTS as readonly string[]).includes(slot)) throw new BriefItemsInputError('invalid slot');
|
|
309
|
+
return this.using(db => {
|
|
310
|
+
db.exec('BEGIN IMMEDIATE');
|
|
311
|
+
try {
|
|
312
|
+
const page = this.pageFromDb(db, slot as BriefSlot);
|
|
313
|
+
const result = work(page, (ids, sentSlot) => {
|
|
314
|
+
if (sentSlot !== slot || ids.length !== page.ids.length || ids.some((id, index) => id !== page.ids[index])) {
|
|
315
|
+
throw new BriefItemsInputError('marked ids must match composed page');
|
|
316
|
+
}
|
|
317
|
+
return this.markSentInDb(db, ids, sentSlot);
|
|
318
|
+
});
|
|
319
|
+
db.exec('COMMIT');
|
|
320
|
+
return result;
|
|
321
|
+
} catch (cause) {
|
|
322
|
+
db.exec('ROLLBACK');
|
|
323
|
+
throw cause;
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private markSentInDb(db: Database, ids: readonly number[], slot: BriefSlot): number {
|
|
329
|
+
const at = this.now().toISOString();
|
|
330
|
+
const insert = db.query('INSERT INTO sends (item_id, slot, sent_at) VALUES (?, ?, ?)');
|
|
331
|
+
let count = 0;
|
|
332
|
+
for (const id of ids) { insert.run(id, slot, at); count++; }
|
|
333
|
+
this.log('briefing.items', 'marked-sent', { slot, count });
|
|
334
|
+
return count;
|
|
258
335
|
}
|
|
259
336
|
|
|
260
337
|
/**
|
|
@@ -263,14 +340,6 @@ export class BriefItemsLedger {
|
|
|
263
340
|
*/
|
|
264
341
|
markSent(ids: readonly number[], slot: BriefSlot): number {
|
|
265
342
|
if (!(BRIEF_SLOTS as readonly string[]).includes(slot)) throw new BriefItemsInputError('invalid slot');
|
|
266
|
-
|
|
267
|
-
const count = this.using(db => {
|
|
268
|
-
const insert = db.query('INSERT INTO sends (item_id, slot, sent_at) VALUES (?, ?, ?)');
|
|
269
|
-
let n = 0;
|
|
270
|
-
db.transaction(() => { for (const id of ids) { insert.run(id, slot, at); n++; } })();
|
|
271
|
-
return n;
|
|
272
|
-
});
|
|
273
|
-
this.log('briefing.items', 'marked-sent', { slot, count });
|
|
274
|
-
return count;
|
|
343
|
+
return this.using(db => db.transaction(() => this.markSentInDb(db, ids, slot))());
|
|
275
344
|
}
|
|
276
345
|
}
|
package/src/cli/autopilot-cli.ts
CHANGED
|
@@ -6,7 +6,12 @@ import { writeStdoutJson } from './stdout-json.js';
|
|
|
6
6
|
// runner. Spawns an ACP backend, opens a single session, runs the
|
|
7
7
|
// AutopilotLoopDriver against the mission, prints agent deltas on
|
|
8
8
|
// stdout + envelope/termination summary on stderr.
|
|
9
|
-
export
|
|
9
|
+
export interface AutopilotCliDeps {
|
|
10
|
+
/** logs.db 싱크 등록(시험 주입). 기본 = `registerStandaloneLogSink`. `task-agent-action` 이 부른다. */
|
|
11
|
+
registerSink?: (surface: string) => Promise<unknown>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function registerAutopilotCommands(program: Command, cliDeps: AutopilotCliDeps = {}): void {
|
|
10
15
|
// Explicit runtime entry for a selected action; the task chooser can invoke this entry.
|
|
11
16
|
const autopilotCmd = program
|
|
12
17
|
.command('autopilot')
|
|
@@ -14,18 +19,28 @@ export function registerAutopilotCommands(program: Command): void {
|
|
|
14
19
|
autopilotCmd.command('task-agent-action <actionJson>')
|
|
15
20
|
.description('Execute a selected task-agent action; defaults to shadow')
|
|
16
21
|
.action(async (actionJson: string) => {
|
|
22
|
+
// 독립 프로세스 — 싱크를 붙이지 않으면 `task-agent`/`action` 관측이 logs.db 에 안 닿는다(fail-open).
|
|
23
|
+
const { registerTaskAgentSink } = await import('./tasks-cli.js');
|
|
24
|
+
await registerTaskAgentSink(cliDeps.registerSink);
|
|
17
25
|
const { executeNextAction } = await import('../task-agent/actions.js');
|
|
18
26
|
const { getUserConfig } = await import('../user-config.js');
|
|
19
27
|
const { spawnSync } = await import('node:child_process');
|
|
20
|
-
const {
|
|
28
|
+
const { writeFileSync, mkdirSync } = await import('node:fs');
|
|
29
|
+
// 상태 파일은 «없음»(ENOENT)만 빈 상태다 — 손상·해석 실패면 덮어쓰지 않고 멈춘다(사후 리뷰 must-fix · #24482).
|
|
30
|
+
const { readTaskAgentState, TaskAgentStateError } = await import('../task-agent/task-hand.js');
|
|
21
31
|
const { join, dirname } = await import('node:path');
|
|
22
32
|
const { debug } = await import('../debug/log.js');
|
|
23
33
|
const { withFileLockSync } = await import('../storage/file-lock.js');
|
|
24
34
|
const config = getUserConfig() as ReturnType<typeof getUserConfig> & { taskAgent?: { mode?: string } };
|
|
25
35
|
const { effectiveInstanceRoot } = await import('../instance/resolve.js');
|
|
26
36
|
const statePath = join(effectiveInstanceRoot(), 'task-agent-actions.json');
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
type ActionState = { landingDay?: string; landingsToday?: number; failureCounts?: Record<string, number> };
|
|
38
|
+
let state: ActionState;
|
|
39
|
+
try { state = readTaskAgentState<ActionState>(statePath); } catch (error) {
|
|
40
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
29
44
|
const action = JSON.parse(actionJson) as import('../task-agent/actions.js').NextAction;
|
|
30
45
|
const deps: import('../task-agent/actions.js').ActionDeps = {
|
|
31
46
|
...state,
|
|
@@ -37,8 +52,7 @@ export function registerAutopilotCommands(program: Command): void {
|
|
|
37
52
|
mode: config.taskAgent?.mode,
|
|
38
53
|
// 하루 착지 칸은 상태 파일 잠금 안에서 확보·반환한다 — 두 CLI 프로세스가 같은 잔여 칸을 쓰지 않게.
|
|
39
54
|
reserveLanding: (day, limit) => withFileLockSync(`${statePath}.lock`, () => {
|
|
40
|
-
|
|
41
|
-
try { disk = JSON.parse(readFileSync(statePath, 'utf8')); } catch { /* first */ }
|
|
55
|
+
const disk = readTaskAgentState<ActionState>(statePath);
|
|
42
56
|
const used = disk.landingDay === day ? disk.landingsToday ?? 0 : 0;
|
|
43
57
|
if (used >= limit) return false;
|
|
44
58
|
mkdirSync(dirname(statePath), { recursive: true });
|
|
@@ -47,8 +61,7 @@ export function registerAutopilotCommands(program: Command): void {
|
|
|
47
61
|
return true;
|
|
48
62
|
}),
|
|
49
63
|
incrementFailure: (key) => withFileLockSync(`${statePath}.lock`, () => {
|
|
50
|
-
|
|
51
|
-
try { disk = JSON.parse(readFileSync(statePath, 'utf8')); } catch { /* first */ }
|
|
64
|
+
const disk = readTaskAgentState<ActionState>(statePath);
|
|
52
65
|
const counts = { ...(disk.failureCounts ?? {}) };
|
|
53
66
|
counts[key] = (counts[key] ?? 0) + 1;
|
|
54
67
|
mkdirSync(dirname(statePath), { recursive: true });
|
|
@@ -56,8 +69,7 @@ export function registerAutopilotCommands(program: Command): void {
|
|
|
56
69
|
return counts[key]!;
|
|
57
70
|
}),
|
|
58
71
|
releaseLanding: (day) => withFileLockSync(`${statePath}.lock`, () => {
|
|
59
|
-
|
|
60
|
-
try { disk = JSON.parse(readFileSync(statePath, 'utf8')); } catch { return; }
|
|
72
|
+
const disk = readTaskAgentState<ActionState>(statePath);
|
|
61
73
|
if (disk.landingDay !== day || !disk.landingsToday) return;
|
|
62
74
|
writeFileSync(statePath, JSON.stringify({ ...disk, landingsToday: disk.landingsToday - 1 }));
|
|
63
75
|
deps.landingsToday = disk.landingsToday - 1;
|
|
@@ -72,7 +84,14 @@ export function registerAutopilotCommands(program: Command): void {
|
|
|
72
84
|
return { status: p.status ?? 1, stdout: p.stdout ?? '', stderr: p.stderr ?? '' };
|
|
73
85
|
},
|
|
74
86
|
};
|
|
75
|
-
|
|
87
|
+
let result: string;
|
|
88
|
+
try { result = await executeNextAction(action, deps); } catch (error) {
|
|
89
|
+
// 잠금 안 읽기가 손상을 만나면 쓰지 않고 여기로 온다 — 실패로 끝낸다. 그 밖의 오류는 종전대로 올린다.
|
|
90
|
+
if (!(error instanceof TaskAgentStateError)) throw error;
|
|
91
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
92
|
+
process.exitCode = 1;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
76
95
|
if (config.taskAgent?.mode === 'live') deps.saveState?.();
|
|
77
96
|
process.stdout.write(`${result}\n`);
|
|
78
97
|
});
|
package/src/cli/brief-cli.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
|
-
import { BRIEF_DOMAINS, BRIEF_PRIORITIES, BRIEF_SLOTS, BriefItemsInputError, BriefItemsLedger, type BriefDomain, type BriefPriority } from '../briefing/brief-items.js';
|
|
2
|
+
import { BRIEF_DOMAINS, BRIEF_PRIORITIES, BRIEF_SLOTS, BriefItemsInputError, BriefItemsLedger, type BriefDomain, type BriefPriority, type BriefSlot } from '../briefing/brief-items.js';
|
|
3
|
+
import { kindRouteTarget } from '../domains/telegram-kind-route.js';
|
|
4
|
+
import { sendTelegramReturningId } from '../autopilot/mission-notify.js';
|
|
5
|
+
import { getUserConfig } from '../user-config.js';
|
|
3
6
|
|
|
4
7
|
export interface BriefCliDeps {
|
|
5
8
|
ledger?: BriefItemsLedger;
|
|
@@ -7,6 +10,7 @@ export interface BriefCliDeps {
|
|
|
7
10
|
now?: () => Date;
|
|
8
11
|
output?: (line: string) => void;
|
|
9
12
|
error?: (line: string) => void;
|
|
13
|
+
send?: (markdown: string) => number | null;
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
export function registerBriefCommands(program: Command, deps: BriefCliDeps = {}): void {
|
|
@@ -20,7 +24,7 @@ export function registerBriefCommands(program: Command, deps: BriefCliDeps = {})
|
|
|
20
24
|
error(`brief ${verb}: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
21
25
|
process.exitCode = cause instanceof BriefItemsInputError ? 2 : 1;
|
|
22
26
|
};
|
|
23
|
-
const brief = program.command('brief').description('대표 브리핑 항목
|
|
27
|
+
const brief = program.command('brief').description('대표 브리핑 항목 원장과 슬롯 발송');
|
|
24
28
|
|
|
25
29
|
brief.command('add')
|
|
26
30
|
.description('주장 한 줄을 원장에 추가한다 (원문 그대로)')
|
|
@@ -70,4 +74,34 @@ export function registerBriefCommands(program: Command, deps: BriefCliDeps = {})
|
|
|
70
74
|
try { output(ledger().compose(opts.slot)); }
|
|
71
75
|
catch (cause) { fail('compose', cause); }
|
|
72
76
|
});
|
|
77
|
+
|
|
78
|
+
brief.command('send')
|
|
79
|
+
.description('슬롯 이전의 미발송 항목을 한 장 발송하고 성공한 항목만 표시한다')
|
|
80
|
+
.requiredOption('--slot <slot>', `슬롯 (${BRIEF_SLOTS.join('|')})`)
|
|
81
|
+
.option('--dry-run', '한 장만 출력하고 발송·표시하지 않는다')
|
|
82
|
+
.action((opts: { slot: string; dryRun?: boolean }) => {
|
|
83
|
+
try {
|
|
84
|
+
const store = ledger();
|
|
85
|
+
if (opts.dryRun) {
|
|
86
|
+
const { markdown, ids } = store.composeWithIds(opts.slot);
|
|
87
|
+
output(ids.length === 0 ? '(보낼 항목 없음)' : markdown);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const result = store.withSendLock(opts.slot, ({ markdown, ids }, markSent) => {
|
|
91
|
+
if (ids.length === 0) return '(보낼 항목 없음)';
|
|
92
|
+
const send = deps.send ?? ((text: string) => {
|
|
93
|
+
const target = kindRouteTarget(getUserConfig(), 'ops-report');
|
|
94
|
+
if (!target) return null;
|
|
95
|
+
return sendTelegramReturningId(target.botToken, target.chatId, text);
|
|
96
|
+
});
|
|
97
|
+
const messageId = send(markdown);
|
|
98
|
+
if (typeof messageId !== 'number' || !Number.isFinite(messageId) || messageId <= 0) {
|
|
99
|
+
throw new Error('발송 실패: 양수 메시지 id 없음');
|
|
100
|
+
}
|
|
101
|
+
markSent(ids, opts.slot as BriefSlot);
|
|
102
|
+
return `sent ${messageId} (${ids.length} items)`;
|
|
103
|
+
});
|
|
104
|
+
output(result);
|
|
105
|
+
} catch (cause) { fail('send', cause); }
|
|
106
|
+
});
|
|
73
107
|
}
|
package/src/cli/freeze-cli.ts
CHANGED
|
@@ -7,8 +7,9 @@ import { pendingFrozenMerges, sweepFrozenMerges } from '../self-implement/frozen
|
|
|
7
7
|
export function registerFreezeCommands(program: Command): void {
|
|
8
8
|
const freeze = program.command('freeze').description('착지 동결 상태 on/off/status');
|
|
9
9
|
freeze.command('on').option('--reason <text>', '동결 이유').option('--until <timestamp>', '끝 시각(타임존 포함 ISO)')
|
|
10
|
+
.option('--hold-launches', '착지와 함께 새 하니스 launch도 보류')
|
|
10
11
|
.option('--wait-seconds <n>', '이미 동결 검사를 지난 병합이 끝나기를 기다릴 최대 초', '600')
|
|
11
|
-
.action(async (opts: { reason?: string; until?: string; waitSeconds: string }) => {
|
|
12
|
+
.action(async (opts: { reason?: string; until?: string; holdLaunches?: boolean; waitSeconds: string }) => {
|
|
12
13
|
const waitSeconds = Number(opts.waitSeconds);
|
|
13
14
|
if (!/^\d+$/.test(opts.waitSeconds.trim()) || !Number.isSafeInteger(waitSeconds) || waitSeconds > 86_400) {
|
|
14
15
|
throw new CliUserError(`잘못된 --wait-seconds: ${opts.waitSeconds}`, '0 이상 86400 이하의 정수 초');
|
|
@@ -34,7 +35,7 @@ export function registerFreezeCommands(program: Command): void {
|
|
|
34
35
|
const state = readLandingFreeze();
|
|
35
36
|
const pending = pendingFrozenMerges();
|
|
36
37
|
if (opts.json) console.log(JSON.stringify({ frozen: state !== null, freeze: state, pendingMerges: pending }));
|
|
37
|
-
else console.log(`${state ? landingFreezeMessage(state) : '동결 없음'} · 보류 병합 ${pending}건`);
|
|
38
|
+
else console.log(`${state ? landingFreezeMessage(state) : '동결 없음'} · launch 보류 ${state?.holdLaunches === true ? '예' : '아니오'} · 보류 병합 ${pending}건`);
|
|
38
39
|
});
|
|
39
40
|
}
|
|
40
41
|
|
package/src/cli/intake-cli.ts
CHANGED
|
@@ -12,6 +12,9 @@ import { runGitCommand } from '../git-fs/runner.js';
|
|
|
12
12
|
import { debug } from '../debug/log.js';
|
|
13
13
|
import { getUserConfig } from '../user-config.js';
|
|
14
14
|
import { registerIntakeSourceCommands } from './intake-source-cli.js';
|
|
15
|
+
import type { IntakeItem } from '../intake-plane/items.js';
|
|
16
|
+
import type { TaskCard } from '../task-cards/card-store.js';
|
|
17
|
+
import { collectFunnelCandidates, type FunnelDirectiveRow } from '../intake-plane/requirement-funnel.js';
|
|
15
18
|
|
|
16
19
|
export interface IntakeToTasksCliDeps {
|
|
17
20
|
root: string;
|
|
@@ -21,6 +24,62 @@ export interface IntakeToTasksCliDeps {
|
|
|
21
24
|
llm?: (input: IntakeTaskInput) => Promise<string>;
|
|
22
25
|
}
|
|
23
26
|
|
|
27
|
+
export interface IntakeCandidatesCliDeps {
|
|
28
|
+
directives?: () => FunnelDirectiveRow[] | Promise<FunnelDirectiveRow[]>;
|
|
29
|
+
intakeItems?: () => IntakeItem[] | Promise<IntakeItem[]>;
|
|
30
|
+
cards?: () => TaskCard[] | Promise<TaskCard[]>;
|
|
31
|
+
write?: (text: string) => Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** `--limit` 원 문자열 → 0 이상의 정수. 숫자만(`/^\d+$/`) 받고 나머지('' · 공백 · 음수 · 소수)는 거부한다. */
|
|
35
|
+
export function parseCandidatesLimit(raw: string): number {
|
|
36
|
+
const limit = /^\d+$/.test(raw) ? Number(raw) : NaN;
|
|
37
|
+
if (!Number.isSafeInteger(limit)) throw new Error(`--limit 는 0 이상의 정수여야 한다: ${JSON.stringify(raw)}`);
|
|
38
|
+
return limit;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function runIntakeCandidatesCli(
|
|
42
|
+
opts: { json?: boolean; limit?: string },
|
|
43
|
+
deps: IntakeCandidatesCliDeps = {},
|
|
44
|
+
): Promise<void> {
|
|
45
|
+
// 원 문자열을 «먼저» 본다 — `Number('')`·`Number(' ')` 는 0 이라 빈 목록으로 조용히 통과한다.
|
|
46
|
+
const limit = opts.limit === undefined ? undefined : parseCandidatesLimit(opts.limit);
|
|
47
|
+
const directives = deps.directives ?? (async () => {
|
|
48
|
+
const [{ Database }, { existsSync }, { directiveDbPath }] = await Promise.all([
|
|
49
|
+
import('bun:sqlite'), import('node:fs'), import('../directives/directive-index.js'),
|
|
50
|
+
]);
|
|
51
|
+
const path = directiveDbPath();
|
|
52
|
+
if (!existsSync(path)) return [];
|
|
53
|
+
const db = new Database(path, { readonly: true });
|
|
54
|
+
try { return db.query('SELECT * FROM directives ORDER BY ts DESC').all() as FunnelDirectiveRow[]; }
|
|
55
|
+
finally { db.close(); }
|
|
56
|
+
});
|
|
57
|
+
const intakeItems = deps.intakeItems ?? (async () => {
|
|
58
|
+
const [{ listIntakeItems }, { effectiveInstanceRoot }] = await Promise.all([
|
|
59
|
+
import('../intake-plane/items.js'), import('../instance/resolve.js'),
|
|
60
|
+
]);
|
|
61
|
+
return listIntakeItems(effectiveInstanceRoot());
|
|
62
|
+
});
|
|
63
|
+
const cards = deps.cards ?? (async () => {
|
|
64
|
+
const [{ existsSync }, { CardStore, cardIndexPath }] = await Promise.all([
|
|
65
|
+
import('node:fs'), import('../task-cards/card-store.js'),
|
|
66
|
+
]);
|
|
67
|
+
if (!existsSync(cardIndexPath())) return [];
|
|
68
|
+
const store = new CardStore(undefined, true);
|
|
69
|
+
try { return store.listCards({ open: true }); }
|
|
70
|
+
finally { store.close(); }
|
|
71
|
+
});
|
|
72
|
+
const all = collectFunnelCandidates({
|
|
73
|
+
directives: await directives(), intakeItems: await intakeItems(), cards: await cards(),
|
|
74
|
+
});
|
|
75
|
+
const candidates = limit === undefined ? all : all.slice(0, limit);
|
|
76
|
+
if (opts.json) await (deps.write ?? writeStdoutFully)(JSON.stringify({ candidates }) + '\n');
|
|
77
|
+
else await (deps.write ?? writeStdoutFully)([
|
|
78
|
+
`요구 후보 ${candidates.length}건`,
|
|
79
|
+
...candidates.map((row) => `${row.source}\t${row.id}\t${row.score ?? '-'}\t${row.grade ?? '-'}\t${row.title}`),
|
|
80
|
+
].join('\n') + '\n');
|
|
81
|
+
}
|
|
82
|
+
|
|
24
83
|
export interface IntakeDigestCliDeps {
|
|
25
84
|
root?: string;
|
|
26
85
|
annotateLens?: (root: string, day: string) => Promise<unknown>;
|
|
@@ -134,7 +193,7 @@ export function resolveIntakeCheckRoot(
|
|
|
134
193
|
}
|
|
135
194
|
}
|
|
136
195
|
|
|
137
|
-
export function registerIntakeCommands(program: Command): void {
|
|
196
|
+
export function registerIntakeCommands(program: Command, candidateDeps: IntakeCandidatesCliDeps = {}): void {
|
|
138
197
|
// ── intake check — 바깥 사실을 elanous 현재와 대조 (태스크 등록 없음) ──
|
|
139
198
|
const intakeCmd = program.command('intake').description('바깥 사실·문서를 elanous 현재와 대조하거나 태스크로 받는다');
|
|
140
199
|
registerIntakeSourceCommands(intakeCmd);
|
|
@@ -229,6 +288,19 @@ export function registerIntakeCommands(program: Command): void {
|
|
|
229
288
|
}
|
|
230
289
|
});
|
|
231
290
|
|
|
291
|
+
intakeCmd
|
|
292
|
+
.command('candidates')
|
|
293
|
+
.description('지시·흡수·소원에서 읽기 전용 요구 후보를 모은다')
|
|
294
|
+
.option('--json', '구조화 출력')
|
|
295
|
+
.option('--limit <n>', '출력할 후보 상한')
|
|
296
|
+
.action(async (opts: { json?: boolean; limit?: string }) => {
|
|
297
|
+
if (opts.limit !== undefined) {
|
|
298
|
+
try { parseCandidatesLimit(opts.limit); }
|
|
299
|
+
catch (error) { console.error((error as Error).message); process.exitCode = 2; return; }
|
|
300
|
+
}
|
|
301
|
+
await runIntakeCandidatesCli(opts, candidateDeps);
|
|
302
|
+
});
|
|
303
|
+
|
|
232
304
|
// 정기 외부 흡수 원장 — RFC-regular-external-intake-and-normalization-pipeline §3 (① 모양 · ② 중복).
|
|
233
305
|
intakeCmd
|
|
234
306
|
.command('collect-pod <dir>')
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import { inQuietHours, sendOutbound, userRecentlyActive } from '../domains/outbound-alert.js';
|
|
3
|
+
import { isTradingKind } from '../domains/telegram-kind-route.js';
|
|
4
|
+
|
|
5
|
+
export type NotifyPlan = { ok: true; kind: string } | { ok: false; exitCode: 2; message: string };
|
|
6
|
+
|
|
7
|
+
export function planNotify({ kind = 'op-report', text }: { kind?: string; text: string }): NotifyPlan {
|
|
8
|
+
if (isTradingKind(kind)) {
|
|
9
|
+
return { ok: false, exitCode: 2, message: `매매 kind(${kind})는 운영 보고에 쓸 수 없습니다 — op-report 또는 ops-alert 를 쓰세요` };
|
|
10
|
+
}
|
|
11
|
+
if (!kind.trim() || /[\r\n]/.test(kind)) {
|
|
12
|
+
return { ok: false, exitCode: 2, message: 'kind 가 비었거나 줄바꿈을 포함합니다 — op-report 또는 ops-alert 를 쓰세요' };
|
|
13
|
+
}
|
|
14
|
+
if (!text.trim()) return { ok: false, exitCode: 2, message: '본문이 비었습니다 — elanous notify <text...> 또는 --stdin' };
|
|
15
|
+
return { ok: true, kind };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface NotifyCliDeps {
|
|
19
|
+
send?: (text: string, kind: string) => boolean | Promise<boolean>;
|
|
20
|
+
quiet?: () => boolean;
|
|
21
|
+
readStdin?: () => Promise<string>;
|
|
22
|
+
output?: (line: string) => void;
|
|
23
|
+
error?: (line: string) => void;
|
|
24
|
+
setExitCode?: (code: number) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function registerNotifyCommand(program: Command, deps: NotifyCliDeps = {}): void {
|
|
28
|
+
const send = deps.send ?? sendOutbound;
|
|
29
|
+
const quiet = deps.quiet ?? (() => inQuietHours() && !userRecentlyActive());
|
|
30
|
+
const readStdin = deps.readStdin ?? (async () => {
|
|
31
|
+
if (process.stdin.isTTY) throw new Error('--stdin requires redirected input');
|
|
32
|
+
const chunks: Buffer[] = [];
|
|
33
|
+
for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
|
|
34
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
35
|
+
});
|
|
36
|
+
const output = deps.output ?? ((line: string) => console.log(line));
|
|
37
|
+
const error = deps.error ?? ((line: string) => console.error(line));
|
|
38
|
+
const setExitCode = deps.setExitCode ?? ((code: number) => { process.exitCode = code; });
|
|
39
|
+
|
|
40
|
+
program.command('notify [text...]')
|
|
41
|
+
.description('대표에게 운영 보고를 발송한다 (기본 kind: op-report)')
|
|
42
|
+
.option('--kind <kind>', '운영 알림 kind (매매 report|alert|digest 거부)')
|
|
43
|
+
.option('--stdin', '본문을 표준 입력에서 읽는다')
|
|
44
|
+
.option('--json', '결과를 JSON 한 줄로 출력한다')
|
|
45
|
+
.action(async (parts: string[], opts: { kind?: string; stdin?: boolean; json?: boolean }) => {
|
|
46
|
+
const kind = opts.kind ?? 'op-report';
|
|
47
|
+
if (opts.stdin && parts.length) {
|
|
48
|
+
if (opts.json) output(JSON.stringify({ ok: false, kind, chars: 0, deferred: false }));
|
|
49
|
+
else error('--stdin 과 본문 인자는 함께 쓸 수 없습니다');
|
|
50
|
+
setExitCode(2);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const text = opts.stdin ? await readStdin() : parts.join(' ');
|
|
55
|
+
const plan = planNotify({ kind, text });
|
|
56
|
+
if (!plan.ok) {
|
|
57
|
+
if (opts.json) output(JSON.stringify({ ok: false, kind, chars: [...text].length, deferred: false }));
|
|
58
|
+
else error(plan.message);
|
|
59
|
+
setExitCode(plan.exitCode);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const deferred = quiet();
|
|
63
|
+
const ok = await send(text, plan.kind);
|
|
64
|
+
const chars = [...text].length;
|
|
65
|
+
if (opts.json) output(JSON.stringify({ ok, kind: plan.kind, chars, deferred: ok && deferred }));
|
|
66
|
+
else if (ok) output(`${deferred ? '보류됨' : '보냄'}(${plan.kind}) · ${chars}자`);
|
|
67
|
+
else error(`못 보냄(${plan.kind}) — elanous logs --category outbound.send 로 확인`);
|
|
68
|
+
setExitCode(ok ? 0 : 1);
|
|
69
|
+
} catch {
|
|
70
|
+
if (opts.json) output(JSON.stringify({ ok: false, kind, chars: 0, deferred: false }));
|
|
71
|
+
else error(`못 보냄(${kind}) — elanous logs --category outbound.send 로 확인`);
|
|
72
|
+
setExitCode(1);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
package/src/cli/provider-cli.ts
CHANGED
|
@@ -398,8 +398,18 @@ export function registerProviderCommands(program: Command): void {
|
|
|
398
398
|
.command('refresh <name>')
|
|
399
399
|
.description('그 계정의 codex 토큰을 호스트에서 갱신해 정본 스토어로 반영한다 (⛔ default 거부 · 토큰 값은 안 찍는다 · ⚠️ refresh 토큰은 갱신마다 회전 — 본부 한 곳에서만)')
|
|
400
400
|
.action(async (name: string) => {
|
|
401
|
-
const { refreshCodexAccountHome } = await import('../oauth/codex.js');
|
|
402
|
-
|
|
401
|
+
const { refreshCodexAccountHome, codexAccountRefreshLockPath, withCodexAccountRefreshLock, CodexRefreshLockUnavailableError } = await import('../oauth/codex.js');
|
|
402
|
+
// PREREFRESH-LOCK: the same per-account lock as the Pod pre-refresh — a hand refresh never races a launch.
|
|
403
|
+
// 이름 있는 계정의 잠금 자리를 못 구하면 잠금 없이 갱신하지 않는다(Pod 선갱신과 경합하는 자리) — default 는 refreshCodexAccountHome 이 거부한다.
|
|
404
|
+
const lockPath = name === 'default' ? null : codexAccountRefreshLockPath(name);
|
|
405
|
+
if (name !== 'default' && !lockPath) { console.error(`갱신 거부: ${name} 의 잠금 자리를 못 구했다(계정 홈 없음) — 잠금 없이 갱신하지 않는다`); process.exitCode = 1; return; }
|
|
406
|
+
let r: Awaited<ReturnType<typeof refreshCodexAccountHome>>;
|
|
407
|
+
try { r = await withCodexAccountRefreshLock(lockPath, () => refreshCodexAccountHome(name)); }
|
|
408
|
+
catch (lockError) {
|
|
409
|
+
if (!(lockError instanceof CodexRefreshLockUnavailableError)) throw lockError;
|
|
410
|
+
const why = lockError.reason === 'lock-timeout' ? '다른 갱신이 진행 중 — 잠시 뒤 다시' : `잠금 파일을 못 만들었다: ${lockError.message}`;
|
|
411
|
+
console.error(`갱신 거부: ${name} 의 갱신 잠금을 못 잡았다(${why})`); process.exitCode = 1; return;
|
|
412
|
+
}
|
|
403
413
|
if (!r.ok) { console.error(`갱신 실패(${r.kind}): ${r.message}`); process.exitCode = 1; return; }
|
|
404
414
|
const h = (v: number | null) => (v === null ? '?' : `${v}h`);
|
|
405
415
|
console.log(`✅ ${name} 갱신 — 남은 시간 ${h(r.beforeH)} → ${h(r.afterH)} · storeKey=${r.storeKey}`);
|