elanous 0.2.16 → 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-b46d45b705e8c9ae.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/9247-269e4464cc4b491c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d92f7ee3994e84c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-3cb52e5ea8fc2fa5.js → page-4733c967b56a4277.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-d22fd849cc22f104.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-c1e20d7d338713f1.js → page-bb742f995c6769e7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-482c5dbde699947c.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-a0eb02638b1e6f31.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-a0e2a6d1bfb52fc5.js → page-308111abadf35670.js} +1 -1
- 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-c53598cfce6156f2.js → page-5d97b2901e0d1ea6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-13a63953f53c6a23.js → page-c39554c321576982.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-5d3c8429d04f3831.js → page-8c3d8e63ae2e1c98.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-3193bdc0bbab047a.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-6107ca8ddd5e3bdd.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-c130b63862437814.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-fa244bd146a00ae8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-44d56d6391965a6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-4167348049166852.js → page-83a700d4c754d802.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-de52e8fdf9095f7a.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-ca3952cd78a83ca2.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-e8e1ecaea7cd71d2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-1ebfe049c640e171.js → page-1467047104141297.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +1 -0
- 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-abe8e3b74bea1ffc.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-d28573d6792b4275.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-d751de5f8062ff5e.js → page-b17cd46cf0e9b8cf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/{page-fe1759f94003b32b.js → page-61553851aa606146.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/{page-d64c984e855c8c68.js → page-a1f585e154e61c59.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-7bf54c9bb9274af8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-9afdee66b820c76e.js → page-5150ef17b5383a57.js} +1 -1
- 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-3544f154c87b43ee.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/graphs/intake/intake-daily.yaml +9 -2
- package/graphs/intake/news-sources.yaml +4 -0
- package/graphs/intake/recipes.yaml +6 -1
- 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/__fixtures__/intake-news.xml +7 -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/intake-news.ts +169 -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 +77 -11
- 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 +10 -3
- package/scripts/release-loop/release-docs.ts +134 -0
- 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 +149 -41
- package/scripts/release-story/draft.ts +42 -14
- 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/driver.ts +53 -10
- 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/autopilot/track-agent.ts +5 -3
- package/src/briefing/brief-items.ts +86 -17
- package/src/cli/autopilot-cli.ts +86 -1
- package/src/cli/brief-cli.ts +36 -2
- package/src/cli/decisions-cli.ts +4 -1
- package/src/cli/freeze-cli.ts +3 -2
- package/src/cli/intake-cli.ts +74 -2
- package/src/cli/log-event-names.ts +1 -0
- package/src/cli/notify-cli.ts +75 -0
- package/src/cli/pr-cli.ts +12 -1
- package/src/cli/provider-cli.ts +24 -0
- package/src/cli/release-cli.ts +101 -17
- package/src/cli/self-update-canary.ts +46 -0
- package/src/cli/self-update.ts +200 -1
- 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/context-card/index.ts +45 -0
- package/src/context-card/task.ts +76 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -0
- package/src/decisions/decision-ledger.ts +10 -1
- 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 +151 -11
- package/src/harness/harness-pod-dispatch.ts +11 -0
- package/src/harness/harness-queue.ts +466 -32
- package/src/harness/helper-scan.ts +10 -3
- package/src/harness/nested-elanous-policy.ts +23 -4
- package/src/hq/fence-wrapper.ts +123 -0
- package/src/hq/hq.ts +8 -0
- package/src/hq/seats.ts +369 -0
- package/src/index.ts +190 -23
- 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/digest.ts +39 -4
- 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/grid.ts +61 -0
- package/src/nexus/api/http-server.ts +19 -1
- package/src/nexus/api/loop-edges.ts +69 -13
- package/src/nexus/api/ops-seats-sources.ts +2 -2
- package/src/nexus/api/task-cards-api.ts +19 -0
- package/src/nexus/api/workflow-templates.ts +3 -0
- package/src/oauth/codex.ts +120 -2
- 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 +24 -14
- 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 +107 -37
- package/src/self-dev/ask-launch-flow.ts +50 -10
- 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-store.ts +5 -1
- package/src/self-dev/run-supervisor.ts +21 -1
- package/src/self-dev/supervisor-verdict-edges.ts +46 -0
- package/src/self-implement/follow-up-goals.ts +2 -2
- package/src/self-implement/frozen-merges.ts +25 -20
- package/src/self-implement/gate-remote.ts +427 -0
- package/src/self-implement/goal-author-clarification.ts +81 -0
- package/src/self-implement/goal-author-cli.ts +33 -5
- package/src/self-implement/goal-author.ts +93 -12
- package/src/self-implement/goal-clarification-escalation.ts +37 -1
- package/src/self-implement/goal-document-write.ts +28 -0
- package/src/self-implement/heal-intake.ts +4 -0
- package/src/self-implement/host-regate.ts +3 -1
- package/src/self-implement/l8-merge-queue.ts +29 -5
- package/src/self-implement/orchestrator.ts +78 -13
- package/src/self-implement/prior-draft-findings.ts +4 -3
- package/src/skills/skill-feedback.ts +76 -0
- package/src/task-agent/actions.ts +151 -0
- package/src/task-agent/judge.ts +122 -0
- 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-cards/card-store.ts +4 -1
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +43 -4
- package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
- package/src/task-orchestrator/surfaces/pod-pool.ts +81 -13
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +225 -35
- package/src/telegram-commands.ts +1 -1
- package/src/telegram-loops-command.ts +22 -15
- package/src/telegram.ts +5 -20
- package/src/user-config.ts +86 -4
- 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-7c6208705c284124.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/8827-e2f5b09a8d37e83b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8949-ecb4128a0858b911.js +0 -1
- package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9247-29d889b809288e65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1e962b31d1f11081.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-8c2d9526164f5d09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-76270efaee638f17.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-dce686e582c23937.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d3d53b146416e422.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-1a61c7ca1ceb3ae7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-51f811fa119308b6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-dacff46049e1557e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-36a7d994f4ca0299.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-5b042736aa113756.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-acb749f1e432bcf3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-57924393e3e4e539.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-31fa1ce4c4aff9ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-0f5cd57ca928f42b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-59f03fda76c03d22.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d4aef67abf264874.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-1ea64acadd704ace.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-30c2073bfc21c004.js +0 -1
- package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +0 -1
- package/apps/pwa/out/_next/static/rv9cierV0jSOvDBm1g0XB/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{rv9cierV0jSOvDBm1g0XB → P6YTVgzuZz5oTZhY4Din_}/_ssgManifest.js +0 -0
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
4
|
+
import { effectiveInstanceRoot, prodInstanceRoot } from '../instance/resolve.js';
|
|
5
5
|
|
|
6
6
|
export interface LandingFreeze {
|
|
7
7
|
reason: string;
|
|
8
8
|
startedAt: string;
|
|
9
9
|
until: string | null;
|
|
10
10
|
by: string;
|
|
11
|
+
holdLaunches?: boolean;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export function landingFreezePath(root = effectiveInstanceRoot()): string {
|
|
@@ -25,21 +26,22 @@ export function readLandingFreeze(root = effectiveInstanceRoot(), now = new Date
|
|
|
25
26
|
const freeze = record as Partial<LandingFreeze>;
|
|
26
27
|
if (typeof freeze.reason !== 'string' || typeof freeze.by !== 'string' ||
|
|
27
28
|
typeof freeze.startedAt !== 'string' || !Number.isFinite(Date.parse(freeze.startedAt)) ||
|
|
28
|
-
!(freeze.until === null || (typeof freeze.until === 'string' && Number.isFinite(Date.parse(freeze.until))))
|
|
29
|
+
!(freeze.until === null || (typeof freeze.until === 'string' && Number.isFinite(Date.parse(freeze.until)))) ||
|
|
30
|
+
(freeze.holdLaunches !== undefined && typeof freeze.holdLaunches !== 'boolean')) {
|
|
29
31
|
throw new Error(`invalid landing freeze: ${path}`);
|
|
30
32
|
}
|
|
31
33
|
// An expired freeze reads as «off». The file is left alone: deleting it here could race a concurrent `freeze on`
|
|
32
34
|
// that just wrote a new one (only `freeze off` removes it; the next `freeze on` overwrites it).
|
|
33
35
|
if (freeze.until && Date.parse(freeze.until) <= now.getTime()) return null;
|
|
34
|
-
return freeze as LandingFreeze;
|
|
36
|
+
return { ...freeze, holdLaunches: freeze.holdLaunches ?? false } as LandingFreeze;
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
export function enableLandingFreeze(opts: { reason?: string; until?: string; by?: string }, root = effectiveInstanceRoot(), now = new Date()): LandingFreeze {
|
|
39
|
+
export function enableLandingFreeze(opts: { reason?: string; until?: string; by?: string; holdLaunches?: boolean }, root = effectiveInstanceRoot(), now = new Date()): LandingFreeze {
|
|
38
40
|
const until = opts.until === undefined ? null : opts.until;
|
|
39
41
|
if (until !== null && (!/^\d{4}-\d\d-\d\dT\d\d:\d\d(?::\d\d(?:\.\d+)?)?(?:Z|[+-]\d\d:\d\d)$/.test(until) || !Number.isFinite(Date.parse(until)) || Date.parse(until) <= now.getTime())) {
|
|
40
42
|
throw new Error('--until must be a future ISO timestamp with timezone');
|
|
41
43
|
}
|
|
42
|
-
const freeze: LandingFreeze = { reason: opts.reason?.trim() || 'operator freeze', startedAt: now.toISOString(), until: until === null ? null : new Date(until).toISOString(), by: opts.by?.trim() || process.env.ELANOUS_TRACK || process.env.USER || 'cli' };
|
|
44
|
+
const freeze: LandingFreeze = { reason: opts.reason?.trim() || 'operator freeze', startedAt: now.toISOString(), until: until === null ? null : new Date(until).toISOString(), by: opts.by?.trim() || process.env.ELANOUS_TRACK || process.env.USER || 'cli', holdLaunches: opts.holdLaunches ?? false };
|
|
43
45
|
const path = landingFreezePath(root);
|
|
44
46
|
mkdirSync(root, { recursive: true });
|
|
45
47
|
const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
@@ -60,15 +62,23 @@ function mergesDir(root: string): string { return join(root, 'landing-merges');
|
|
|
60
62
|
|
|
61
63
|
export interface LandingMergeGuard { frozen: LandingFreeze | null; end: () => void }
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
/** Mark both authorities before reading either freeze: the host and the child each drain their own in-flight merges. */
|
|
66
|
+
export function beginLandingMerge(root = effectiveInstanceRoot(), now = new Date(), prodFreezeRoot = prodInstanceRoot(), forceReason?: string): LandingMergeGuard {
|
|
67
|
+
const markers: string[] = [];
|
|
68
|
+
const end = () => { for (const marker of markers) rmSync(marker, { force: true }); };
|
|
69
|
+
try {
|
|
70
|
+
for (const authority of new Set([prodFreezeRoot, root])) {
|
|
71
|
+
const dir = mergesDir(authority);
|
|
72
|
+
mkdirSync(dir, { recursive: true });
|
|
73
|
+
const marker = join(dir, `${process.pid}-${randomUUID()}.json`);
|
|
74
|
+
markers.push(marker);
|
|
75
|
+
writeFileSync(marker, `${JSON.stringify({ pid: process.pid, startedAt: now.toISOString() })}\n`, { mode: 0o600 });
|
|
76
|
+
}
|
|
77
|
+
} catch (error) { end(); throw error; }
|
|
69
78
|
let frozen: LandingFreeze | null;
|
|
70
|
-
try { frozen = readLandingFreeze(
|
|
71
|
-
|
|
79
|
+
try { frozen = readLandingFreeze(prodFreezeRoot, now) ?? (root === prodFreezeRoot ? null : readLandingFreeze(root, now)); }
|
|
80
|
+
catch (error) { end(); throw error; }
|
|
81
|
+
if (frozen && !forceReason?.trim()) { end(); return { frozen, end: () => {} }; }
|
|
72
82
|
return { frozen: null, end };
|
|
73
83
|
}
|
|
74
84
|
|
|
@@ -8,6 +8,7 @@ import { userConfigPath } from '../user-config.js';
|
|
|
8
8
|
import { addItem, listChecklist, ownerMatches, parseOwner, setItem, validateCeoLoad, type ChecklistItem } from './checklist.js';
|
|
9
9
|
import { move, releasedVersion } from './feature-store.js';
|
|
10
10
|
import { listSchedules, type ReleaseSchedule } from './release-schedule.js';
|
|
11
|
+
import { parseRubric, rubricScore } from './rubric.js';
|
|
11
12
|
|
|
12
13
|
export type PlacementPriority = 'P0' | 'P1' | 'P2';
|
|
13
14
|
export interface PlacementCell { id: string; title: string; owner: string; priority: PlacementPriority; predecessors: string[]; deadlineVersion?: string; ceoMinutes?: number; ceoDate?: string; accelerator?: boolean }
|
|
@@ -271,20 +272,29 @@ export function rebalance(version: string, deps: PlacementDeps = {}): RebalanceR
|
|
|
271
272
|
const backlog = deps.backlog ?? BACKLOG_VERSIONS;
|
|
272
273
|
const next = rows.find((row) => versionOrder(row.version, version) > 0 && !backlog.includes(row.version));
|
|
273
274
|
const snapshots = new Map(rows.map((row) => [row.version, [...(deps.checklist ?? listChecklist)(row.version).items]]));
|
|
274
|
-
const unstarted = snapshots.get(version)!.filter((item) => item.status === 'yellow' && !item.evidence)
|
|
275
|
+
const unstarted = snapshots.get(version)!.filter((item) => item.status === 'yellow' && !item.evidence)
|
|
276
|
+
.map((item, index) => {
|
|
277
|
+
const rubric = parseRubric(`${item.title}\n${item.evidence ?? ''}`);
|
|
278
|
+
return { item, index, score: rubric ? rubricScore(rubric) : null };
|
|
279
|
+
})
|
|
280
|
+
.sort((a, b) => {
|
|
281
|
+
if (a.score === null) return b.score === null ? a.index - b.index : 1;
|
|
282
|
+
if (b.score === null) return -1;
|
|
283
|
+
return a.score - b.score || a.index - b.index;
|
|
284
|
+
});
|
|
275
285
|
if (unstarted.length && !next) throw new CliUserError(`${version} 다음 판이 없다`);
|
|
276
286
|
if (!unstarted.length) return { decisions: [], blocked: [] };
|
|
277
287
|
const rate = deps.merged24h ?? mergedPrsLast24h(new Date(now));
|
|
278
288
|
const caps = deps.seatCap ?? placementSeatCap();
|
|
279
289
|
const ceoCap = validCeoCap(deps.ceoDailyCap ?? placementCeoDailyCap());
|
|
280
290
|
const result: RebalanceResult = { decisions: [], blocked: [] };
|
|
281
|
-
for (const item of unstarted) {
|
|
291
|
+
for (const { item, score } of unstarted) {
|
|
282
292
|
const violation = moveConstraint(item, next!, backlog, snapshots, now, rate, caps, rows, ceoCap);
|
|
283
293
|
if (violation) {
|
|
284
294
|
result.blocked.push({ id: item.id, from: version, to: next!.version, reason: violation });
|
|
285
295
|
continue;
|
|
286
296
|
}
|
|
287
|
-
const reason = `${version} 착지 마감 2시간 전 미시작 칸
|
|
297
|
+
const reason = `${version} 착지 마감 2시간 전 미시작 칸 이월${score === null ? '' : ` · 루브릭 ${score}`}`;
|
|
288
298
|
result.decisions.push({ id: item.id, from: version, version: next!.version, reason, displaced: [] });
|
|
289
299
|
snapshots.set(version, snapshots.get(version)!.filter((cell) => cell.id !== item.id));
|
|
290
300
|
snapshots.get(next!.version)!.push(item);
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import type { ChecklistItem } from './checklist.js';
|
|
5
|
+
|
|
6
|
+
export interface Rubric {
|
|
7
|
+
A: number;
|
|
8
|
+
E: number;
|
|
9
|
+
R: number;
|
|
10
|
+
D: number;
|
|
11
|
+
M: number;
|
|
12
|
+
B: number;
|
|
13
|
+
S: number;
|
|
14
|
+
X: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const AXES = ['A', 'E', 'R', 'D', 'M', 'B', 'S', 'X'] as const;
|
|
18
|
+
|
|
19
|
+
/** Read one explicit rubric line; an old score in parentheses is not an axis. */
|
|
20
|
+
export function parseRubric(text: string): Rubric | null {
|
|
21
|
+
for (const line of text.split(/\r?\n/)) {
|
|
22
|
+
const match = /(?:^|\s)루브릭:\s*([^\r\n]*)/.exec(line);
|
|
23
|
+
if (!match) continue;
|
|
24
|
+
const values: Partial<Rubric> = {};
|
|
25
|
+
const body = match[1]!.replace(/\([^)]*\)/g, ' ');
|
|
26
|
+
for (const token of body.trim().split(/\s+/)) {
|
|
27
|
+
const axis = /^([AERDMBSX])([0-3])$/.exec(token);
|
|
28
|
+
if (axis) values[axis[1] as keyof Rubric] = Number(axis[2]);
|
|
29
|
+
}
|
|
30
|
+
if (AXES.every((axis) => values[axis] !== undefined)) return values as Rubric;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function rubricScore(r: Rubric): number {
|
|
36
|
+
return 2 * r.A + 2 * r.E + 2 * r.R + 1.5 * r.D + r.M + r.B - 0.5 * r.S - 0.5 * r.X;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function rubricGrade(score: number): 'P1' | 'P2' | 'P3' | 'P4' {
|
|
40
|
+
if (score >= 11) return 'P1';
|
|
41
|
+
if (score >= 8) return 'P2';
|
|
42
|
+
if (score >= 5) return 'P3';
|
|
43
|
+
return 'P4';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Inspect an existing checklist without initializing, importing, or migrating its ledger. */
|
|
47
|
+
export function readRubricItems(version: string, ledgerRoot: string): Pick<ChecklistItem, 'id' | 'title' | 'evidence' | 'priority'>[] {
|
|
48
|
+
const dbPath = join(ledgerRoot, 'release', 'features.sqlite');
|
|
49
|
+
if (existsSync(dbPath)) {
|
|
50
|
+
const db = new Database(dbPath, { readonly: true, strict: true });
|
|
51
|
+
try {
|
|
52
|
+
const columns = new Set((db.query('PRAGMA table_info(assignments)').all() as Array<{ name: string }>).map((row) => row.name));
|
|
53
|
+
const priority = columns.has('priority') ? 'a.priority' : 'NULL';
|
|
54
|
+
const rows = db.query(`SELECT f.id, COALESCE(a.title_override, f.title) AS title, a.evidence, ${priority} AS priority
|
|
55
|
+
FROM assignments a JOIN features f ON f.id = a.feature_id WHERE a.version = ? ORDER BY a.rowid`)
|
|
56
|
+
.all(version) as Array<{ id: string; title: string; evidence: string | null; priority: ChecklistItem['priority'] | null }>;
|
|
57
|
+
const imported = db.query('SELECT 1 FROM imported_versions WHERE version = ?').get(version);
|
|
58
|
+
if (rows.length || imported) {
|
|
59
|
+
const refs = db.query('SELECT feature_id, ref FROM evidence WHERE version = ? ORDER BY rowid')
|
|
60
|
+
.all(version) as Array<{ feature_id: string; ref: string }>;
|
|
61
|
+
const refsById = new Map<string, string[]>();
|
|
62
|
+
for (const { feature_id, ref } of refs) {
|
|
63
|
+
const list = refsById.get(feature_id) ?? [];
|
|
64
|
+
list.push(ref);
|
|
65
|
+
refsById.set(feature_id, list);
|
|
66
|
+
}
|
|
67
|
+
return rows.map((row) => {
|
|
68
|
+
const evidence = [row.evidence, ...(refsById.get(row.id) ?? [])]
|
|
69
|
+
.filter((value): value is string => value !== null).join('\n');
|
|
70
|
+
return { id: row.id, title: row.title, ...(evidence ? { evidence } : {}), ...(row.priority ? { priority: row.priority } : {}) };
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
} finally { db.close(); }
|
|
74
|
+
}
|
|
75
|
+
const path = join(ledgerRoot, 'release', version, 'checklist.json');
|
|
76
|
+
if (!existsSync(path)) return [];
|
|
77
|
+
const legacy = JSON.parse(readFileSync(path, 'utf8')) as { version: string; items: ChecklistItem[] };
|
|
78
|
+
if (legacy.version !== version || !Array.isArray(legacy.items)) throw new Error(`잘못된 체크리스트: ${path}`);
|
|
79
|
+
return legacy.items;
|
|
80
|
+
}
|
|
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { debug } from '../debug/log.js';
|
|
4
4
|
import { openMsgStore, type MsgStore } from '../msg/msg-store.js';
|
|
5
|
-
import { dispatchCeoTask, type CeoCommandDeps } from './ceo-commands.js';
|
|
5
|
+
import { CEO_SEAT_COMMANDS, dispatchCeoTask, type CeoCommandDeps } from './ceo-commands.js';
|
|
6
6
|
import type { SeatId } from './seat-questions.js';
|
|
7
7
|
|
|
8
8
|
export type AskOrigin =
|
|
@@ -23,7 +23,7 @@ function createTable(store: MsgStore): void {
|
|
|
23
23
|
answer TEXT, answered_at INTEGER, asked_at INTEGER
|
|
24
24
|
); CREATE TABLE IF NOT EXISTS seat_ask_outbox (
|
|
25
25
|
id TEXT PRIMARY KEY, origin TEXT NOT NULL, text TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending',
|
|
26
|
-
claim_until INTEGER NOT NULL DEFAULT 0, claim_token TEXT
|
|
26
|
+
claim_until INTEGER NOT NULL DEFAULT 0, claim_token TEXT, sent_at INTEGER
|
|
27
27
|
)`);
|
|
28
28
|
const askColumns = store.db.query('PRAGMA table_info(seat_asks)').all() as Array<{ name: string }>;
|
|
29
29
|
if (!askColumns.some(({ name }) => name === 'answer')) store.db.exec('ALTER TABLE seat_asks ADD COLUMN answer TEXT');
|
|
@@ -32,6 +32,7 @@ function createTable(store: MsgStore): void {
|
|
|
32
32
|
const columns = store.db.query('PRAGMA table_info(seat_ask_outbox)').all() as Array<{ name: string }>;
|
|
33
33
|
if (!columns.some(({ name }) => name === 'claim_until')) store.db.exec('ALTER TABLE seat_ask_outbox ADD COLUMN claim_until INTEGER NOT NULL DEFAULT 0');
|
|
34
34
|
if (!columns.some(({ name }) => name === 'claim_token')) store.db.exec('ALTER TABLE seat_ask_outbox ADD COLUMN claim_token TEXT');
|
|
35
|
+
if (!columns.some(({ name }) => name === 'sent_at')) store.db.exec('ALTER TABLE seat_ask_outbox ADD COLUMN sent_at INTEGER');
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
export interface SeatAskShadow {
|
|
@@ -77,10 +78,24 @@ export interface SeatAskDeps {
|
|
|
77
78
|
botId?: string;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
const seatTitle = (seat: SeatId): string => Object.entries(CEO_SEAT_COMMANDS)
|
|
82
|
+
.find(([, id]) => id === seat)![0]!.toUpperCase();
|
|
83
|
+
|
|
84
|
+
/** Persist the return address before delivering through the coordination channel and seat inbox. */
|
|
85
|
+
export function askSeatAs(seat: SeatId, body: string, origin: AskOrigin, command: CeoCommandDeps, deps: SeatAskDeps,
|
|
86
|
+
kind: 'task' | 'question' = 'task'): Promise<string> {
|
|
87
|
+
return recordAndDispatchSeatAsk(seat, body, origin, command, deps, kind);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Preserve the CTO question form and its original receipt. */
|
|
91
|
+
export function askSeat(text: string, origin: AskOrigin, command: CeoCommandDeps, deps: SeatAskDeps): Promise<string> {
|
|
82
92
|
const body = parseSeatAsk(text);
|
|
83
93
|
if (!body) throw new Error('not a CTO ask');
|
|
94
|
+
return askSeatAs('TC', body, origin, command, deps, 'question');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function recordAndDispatchSeatAsk(seat: SeatId, body: string, origin: AskOrigin, command: CeoCommandDeps, deps: SeatAskDeps,
|
|
98
|
+
kind: 'task' | 'question'): Promise<string> {
|
|
84
99
|
const id = randomUUID();
|
|
85
100
|
const timeout = deps.timeoutMs ?? DEFAULT_ASK_TIMEOUT_MS;
|
|
86
101
|
if (!Number.isSafeInteger(timeout) || timeout < 1) throw new Error('invalid seat ask timeout');
|
|
@@ -90,13 +105,14 @@ export async function askSeat(text: string, origin: AskOrigin, command: CeoComma
|
|
|
90
105
|
createTable(store);
|
|
91
106
|
const askedAt = (deps.now ?? Date.now)();
|
|
92
107
|
store.db.query('INSERT INTO seat_asks (id, seat, origin, deadline, timeout_minutes, status, asked_at) VALUES (?, ?, ?, ?, ?, ?, ?)')
|
|
93
|
-
.run(id,
|
|
108
|
+
.run(id, seat, JSON.stringify(origin), askedAt + timeout, minutes, 'pending', askedAt);
|
|
94
109
|
} finally { store.close(); }
|
|
95
110
|
try {
|
|
96
|
-
const result = await (deps.dispatch ?? dispatchCeoTask)('
|
|
111
|
+
const result = await (deps.dispatch ?? dispatchCeoTask)(seat, `${kind === 'question' ? '질문' : '일'}: ${body}\n답장 요청: ${id}\n회신: elanous seat answer ${id} "<답>"`, command,
|
|
97
112
|
{ via: origin.channel, ref: `ask:${id}` });
|
|
98
|
-
debug.log('seat.ask', 'sent', { seat
|
|
99
|
-
|
|
113
|
+
debug.log('seat.ask', 'sent', { seat, id, via: origin.channel, channel: result.channel });
|
|
114
|
+
const title = seatTitle(seat);
|
|
115
|
+
return `${kind === 'question' ? `${title}에게 물었습니다. 답을 기다립니다` : `${title}에게 맡겼습니다. 결과를 이 대화로 돌려드립니다`} (최대 ${minutes}분). 요청: ${id}${result.channel === 'failed' ? ` · 조율 채널 전송 실패: ${result.channelError}` : ''}`;
|
|
100
116
|
} catch (error) {
|
|
101
117
|
const failed = (deps.open ?? openMsgStore)();
|
|
102
118
|
try { createTable(failed); failed.db.query('DELETE FROM seat_asks WHERE id = ?').run(id); }
|
|
@@ -114,10 +130,11 @@ export function answerSeatAsk(id: string, text: string, deps: Pick<SeatAskDeps,
|
|
|
114
130
|
createTable(store);
|
|
115
131
|
const now = (deps.now ?? Date.now)();
|
|
116
132
|
const updated = store.db.query(`UPDATE seat_asks SET answer = ?, answered_at = ?
|
|
117
|
-
WHERE id = ? AND
|
|
133
|
+
WHERE id = ? AND status = 'pending' AND answer IS NULL AND deadline > ?`)
|
|
118
134
|
.run(answer, now, id, now);
|
|
119
135
|
if (updated.changes !== 1) throw new Error(`seat ask not pending or deadline passed: ${id}`);
|
|
120
|
-
|
|
136
|
+
const row = store.db.query('SELECT seat FROM seat_asks WHERE id = ?').get(id) as { seat: string };
|
|
137
|
+
debug.log('seat.ask', 'reply-recorded', { seat: row.seat, id });
|
|
121
138
|
} finally { store.close(); }
|
|
122
139
|
}
|
|
123
140
|
|
|
@@ -133,6 +150,49 @@ export function seatAskReplyInfo(id: string, open: () => MsgStore = openMsgStore
|
|
|
133
150
|
} finally { store.close(); }
|
|
134
151
|
}
|
|
135
152
|
|
|
153
|
+
export interface SeatAskRoundTrip {
|
|
154
|
+
id: string;
|
|
155
|
+
surface: AskOrigin['channel'];
|
|
156
|
+
askedAt: number | null;
|
|
157
|
+
answeredAt: number | null;
|
|
158
|
+
deliveredAt: number | null;
|
|
159
|
+
roundTripMs: number | null;
|
|
160
|
+
verdict: '10분 안' | '10분 넘음' | '미답' | '답 대기' | '전달 대기' | '시각 미상';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Read one row per CTO question, including undelivered and legacy rows. since is an epoch-millisecond lower bound. */
|
|
164
|
+
export function listSeatAskRoundTrips({ since, now = Date.now(), open = openMsgStore }: {
|
|
165
|
+
since: number; now?: number; open?: () => MsgStore;
|
|
166
|
+
}): SeatAskRoundTrip[] {
|
|
167
|
+
if (!Number.isFinite(since) || !Number.isFinite(now)) throw new Error('invalid seat ask time window');
|
|
168
|
+
const store = open();
|
|
169
|
+
try {
|
|
170
|
+
createTable(store);
|
|
171
|
+
const rows = store.db.query(`SELECT a.id, a.origin, a.asked_at, a.answered_at, a.deadline, a.status,
|
|
172
|
+
a.answer IS NOT NULL AS has_answer, o.status AS outbox_status, o.sent_at
|
|
173
|
+
FROM seat_asks a LEFT JOIN seat_ask_outbox o ON o.id = a.id
|
|
174
|
+
WHERE a.seat = 'TC' AND a.status != 'shadow'
|
|
175
|
+
AND COALESCE(a.asked_at, a.deadline - a.timeout_minutes * 60000) >= ?
|
|
176
|
+
ORDER BY COALESCE(a.asked_at, a.deadline - a.timeout_minutes * 60000) DESC, a.id DESC`)
|
|
177
|
+
.all(since) as Array<{ id: string; origin: string; asked_at: number | null; answered_at: number | null;
|
|
178
|
+
deadline: number; status: string; has_answer: number; outbox_status: string | null; sent_at: number | null }>;
|
|
179
|
+
return rows.map(row => {
|
|
180
|
+
const surface = (JSON.parse(row.origin) as AskOrigin).channel;
|
|
181
|
+
const deliveredAt = row.outbox_status === 'sent' ? row.sent_at : null;
|
|
182
|
+
const roundTripMs = row.answered_at === null || row.asked_at === null || deliveredAt === null ? null : deliveredAt - row.asked_at;
|
|
183
|
+
const verdict: SeatAskRoundTrip['verdict'] = row.asked_at === null ? '시각 미상'
|
|
184
|
+
: row.status === 'expired' || (!row.has_answer && row.status === 'pending' && now >= row.deadline) ? '미답'
|
|
185
|
+
: row.status === 'answered' || row.has_answer ? row.outbox_status === 'sent' && deliveredAt === null ? '시각 미상'
|
|
186
|
+
: deliveredAt === null ? '전달 대기'
|
|
187
|
+
: row.answered_at === null || roundTripMs === null ? '시각 미상'
|
|
188
|
+
: roundTripMs <= 600_000 ? '10분 안' : '10분 넘음'
|
|
189
|
+
: '답 대기';
|
|
190
|
+
return { id: row.id, surface, askedAt: row.asked_at, answeredAt: row.answered_at,
|
|
191
|
+
deliveredAt, roundTripMs, verdict };
|
|
192
|
+
});
|
|
193
|
+
} finally { store.close(); }
|
|
194
|
+
}
|
|
195
|
+
|
|
136
196
|
/** Deliver recorded seat answers or expiration notices via the original surface. */
|
|
137
197
|
export async function deliverSeatAnswers(deps: SeatAskDeps & { askId?: string }): Promise<void> {
|
|
138
198
|
const store = (deps.open ?? openMsgStore)();
|
|
@@ -150,9 +210,11 @@ export async function deliverSeatAnswers(deps: SeatAskDeps & { askId?: string })
|
|
|
150
210
|
: reply && Date.parse(reply.created_at) < ask.deadline ? reply.body.slice(id.length + 2).trim() : '';
|
|
151
211
|
const status = answer ? 'answered' : now >= ask.deadline ? 'expired' : null;
|
|
152
212
|
if (!status) return null;
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
213
|
+
const title = seatTitle(ask.seat as SeatId);
|
|
214
|
+
const text = status === 'answered' ? `${title} 답변 (${id}): ${answer}`
|
|
215
|
+
: `${title} 미답 (${id}): 아직 답 없음 (${ask.timeout_minutes}분 경과).`;
|
|
216
|
+
const updated = store.db.query("UPDATE seat_asks SET status = ?, answered_at = COALESCE(answered_at, ?) WHERE id = ? AND status = 'pending'")
|
|
217
|
+
.run(status, status === 'answered' && reply ? Date.parse(reply.created_at) : null, id);
|
|
156
218
|
if (updated.changes !== 1) return null;
|
|
157
219
|
store.db.query('INSERT INTO seat_ask_outbox (id, origin, text) VALUES (?, ?, ?)').run(id, ask.origin, text);
|
|
158
220
|
return status;
|
|
@@ -184,7 +246,8 @@ export async function deliverSeatAnswers(deps: SeatAskDeps & { askId?: string })
|
|
|
184
246
|
if (claimed.changes !== 1) continue;
|
|
185
247
|
try {
|
|
186
248
|
await deps.send(origin, item.text);
|
|
187
|
-
store.db.query("UPDATE seat_ask_outbox SET status = 'sent', claim_token = NULL WHERE id = ? AND claim_token = ?")
|
|
249
|
+
store.db.query("UPDATE seat_ask_outbox SET status = 'sent', sent_at = ?, claim_token = NULL WHERE id = ? AND claim_token = ?")
|
|
250
|
+
.run((deps.now ?? Date.now)(), item.id, token);
|
|
188
251
|
} catch (error) {
|
|
189
252
|
store.db.query('UPDATE seat_ask_outbox SET claim_until = 0, claim_token = NULL WHERE id = ? AND claim_token = ?').run(item.id, token);
|
|
190
253
|
throw error;
|
|
@@ -209,7 +272,7 @@ export function acknowledgeSeatAnswers(clientId: string, ids: string[], open: ()
|
|
|
209
272
|
const store = open();
|
|
210
273
|
try {
|
|
211
274
|
createTable(store);
|
|
212
|
-
const ack = store.db.query("UPDATE seat_ask_outbox SET status = 'sent' WHERE id = ? AND origin = ? AND status = 'pending'");
|
|
213
|
-
for (const id of ids) ack.run(id, JSON.stringify({ channel: 'pwa', clientId }));
|
|
275
|
+
const ack = store.db.query("UPDATE seat_ask_outbox SET status = 'sent', sent_at = ? WHERE id = ? AND origin = ? AND status = 'pending'");
|
|
276
|
+
for (const id of ids) ack.run(Date.now(), id, JSON.stringify({ channel: 'pwa', clientId }));
|
|
214
277
|
} finally { store.close(); }
|
|
215
278
|
}
|