elanous 0.2.9 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +1 -0
- package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8067-87ce253ec2f23697.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8195-6fde3d7a39859206.js} +5 -5
- package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-f972fc1c00a62e88.js → page-54b32bd27726588d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-f8116559c7424da7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-54e1f966ab60a4dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-973996d584f008c1.js → page-7ed95f296f5ee8e9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-8b0402a46a749a3d.js → page-b31610deda76337b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-037f83e2a1edc8e2.js → page-6cb0e1bbefe5fb3d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-0df30e0c7e50148a.js → page-e168fa1030af0548.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-4fddc90b23feb215.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-90670589b87abea8.js → page-26102fd7350f171e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-35bedde6d885f08f.js → page-139ba5c367bbf60a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +1 -0
- package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +14 -14
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -0
- package/apps/pwa/out/ops/seats/index.txt +30 -0
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -0
- package/apps/pwa/out/today/index.txt +33 -0
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/graphs/field/field-feed.yaml +2 -0
- package/graphs/implement-loop.yaml +1 -1
- package/package.json +1 -1
- package/plugins/card-followup/graphs/run-step.ts +48 -13
- package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
- package/plugins/doc-draft/graphs/run-step.ts +13 -6
- package/scripts/ci-pwa-build-gate.ts +160 -0
- package/scripts/coord-channel-watch.sh +42 -8
- package/scripts/coord-digest.ts +199 -0
- package/scripts/coord-post.sh +60 -3
- package/scripts/install.ps1 +68 -9
- package/scripts/install.sh +39 -12
- package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
- package/scripts/lib/tui-regress-checks.ts +146 -0
- package/scripts/memory-lifecycle-cycle.ts +2 -2
- package/scripts/ops-now-probe.ts +327 -0
- package/scripts/publish-internal-dist.ts +11 -1
- package/scripts/release-loop/cutoff.ts +19 -8
- package/scripts/release-loop/ops-upgrade-node.ts +72 -16
- package/scripts/release-loop/release-readiness.ts +9 -0
- package/scripts/release-loop/version-node.ts +58 -10
- package/scripts/repo-watch-cycle.ts +1 -1
- package/scripts/retro-cycle.ts +2 -2
- package/scripts/schedule-health-report.ts +1 -1
- package/scripts/storyboard/storyboard.ts +5 -161
- package/scripts/test-diet/lib.ts +30 -1
- package/scripts/test-diet/node.ts +82 -7
- package/scripts/test-timeout-audit.ts +114 -0
- package/scripts/tree-sync-alert.ts +1 -1
- package/scripts/tui-regress.ts +71 -0
- package/scripts/waitlist-daily-digest.ts +1 -1
- package/skills/explainer-video/SKILL.md +4 -0
- package/skills/explainer-video/engine/reel.mjs +18 -13
- package/skills/explainer-video/engine/reel.sh +24 -5
- package/skills/explainer-video/engine/stitch.mjs +145 -0
- package/skills/pitch-deck/SKILL.md +36 -0
- package/skills/pitch-deck/template.html +80 -0
- package/src/acp/codex-approval-adapter.ts +9 -3
- package/src/acp/core-turn-bridge.ts +117 -18
- package/src/acp/fact-question.ts +11 -0
- package/src/acp/server.ts +48 -7
- package/src/acp/tool-approval.ts +126 -0
- package/src/acp/turn-runner.ts +36 -0
- package/src/agent/monad-agent-turn.ts +8 -0
- package/src/ask-user-question/pending-questions.ts +25 -10
- package/src/ask-user-question/types.ts +9 -0
- package/src/auth/setup-link-tokens.ts +16 -5
- package/src/autopilot/build/integrity-gate.ts +12 -0
- package/src/boot/daemon-prompt-turn.ts +3 -0
- package/src/boot/daemon-tools/index.ts +8 -0
- package/src/boot/daemon-tools/types.ts +2 -0
- package/src/boot/daemon-tools/web-search.ts +1 -0
- package/src/chat/ctrl-c-in-input.ts +14 -0
- package/src/chat/index.ts +52 -0
- package/src/chat/pickers/state.ts +6 -2
- package/src/cli/agent-cli.ts +7 -0
- package/src/cli/decisions-cli.ts +25 -3
- package/src/cli/doctor-bundle-upload.ts +68 -0
- package/src/cli/doctor-cli.ts +38 -4
- package/src/cli/ops-cli.ts +20 -0
- package/src/cli/pod-cli.ts +49 -0
- package/src/cli/pr-cli.ts +31 -2
- package/src/cli/pwa-stop.ts +163 -13
- package/src/cli/pwa-test.ts +3 -0
- package/src/cli/release-cli.ts +63 -20
- package/src/cli/self-update.ts +14 -0
- package/src/cli/storyboard-cli.ts +37 -0
- package/src/cli/test-state-dir-flag.ts +11 -4
- package/src/connectors/linear.ts +146 -0
- package/src/context-bus/coord-events.ts +115 -0
- package/src/dashboard/dashboard-boot-fixture.ts +23 -0
- package/src/dashboard/index.ts +58 -21
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +7 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
- package/src/decisions/decision-cards.ts +49 -1
- package/src/decisions/decision-ledger.ts +75 -4
- package/src/decisions/decision-linear-projection.ts +157 -0
- package/src/decisions/web-push-decision-cards.ts +50 -0
- package/src/directive/interpretation-card.ts +36 -0
- package/src/discord.ts +2 -1
- package/src/domains/coo-admin-tool.ts +62 -0
- package/src/domains/core-tools.ts +11 -3
- package/src/domains/elanous-card.ts +29 -0
- package/src/domains/ops-facts-tool.ts +76 -0
- package/src/domains/outbound-alert.ts +41 -13
- package/src/domains/release-tool.ts +168 -0
- package/src/domains/telegram-kind-route.ts +19 -1
- package/src/exec-requests/planner.ts +98 -28
- package/src/exec-requests/runner.ts +107 -10
- package/src/exec-requests/store.ts +8 -2
- package/src/field/field-media.ts +10 -7
- package/src/field/field-reel.ts +14 -4
- package/src/git-fs/worktree.ts +45 -6
- package/src/graph-runner/graph-approval-card.ts +113 -0
- package/src/graph-runner/graph-cli.ts +129 -4
- package/src/graph-runner/graph-notify.ts +25 -3
- package/src/graph-runner/runner.ts +142 -16
- package/src/harness/browser-verify.ts +25 -2
- package/src/harness/harness-cli-command.ts +231 -35
- package/src/harness/harness-incidents-writer.fixture.ts +24 -0
- package/src/harness/harness-incidents.ts +129 -0
- package/src/harness/harness-stop.ts +53 -3
- package/src/hitl/clarification-policy.ts +3 -1
- package/src/index.ts +156 -37
- package/src/intake-plane/author-goals.ts +37 -4
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/discord-seat-work.ts +47 -2
- package/src/intake-plane/seat-doc-route.ts +170 -6
- package/src/intake-plane/telegram-seat-work.ts +44 -2
- package/src/intake-plane/telegram-work.ts +1 -0
- package/src/maturity/cli-maturity.ts +34 -0
- package/src/maturity/feature-maturity.ts +98 -1
- package/src/maturity/tui-slash-maturity.ts +31 -0
- package/src/memory.ts +32 -4
- package/src/nexus/api/exec-requests.ts +33 -3
- package/src/nexus/api/http-server.ts +53 -1
- package/src/nexus/api/meta-api.ts +11 -0
- package/src/nexus/api/ops-api.ts +10 -1
- package/src/nexus/api/ops-seats-sources.ts +37 -7
- package/src/nexus/api/ops-seats.ts +16 -6
- package/src/nexus/api/seat-requests.ts +88 -8
- package/src/nexus/api/sessions-store.ts +63 -0
- package/src/nexus/api/setup-codex-login.ts +160 -0
- package/src/nexus/api/usage-api.ts +102 -0
- package/src/nexus/index.ts +45 -4
- package/src/onboarding/first-run.ts +89 -0
- package/src/onboarding/lan-address.ts +45 -0
- package/src/onboarding/web-first.ts +119 -24
- package/src/onboarding.ts +4 -0
- package/src/ops-now/ops-now-refresh.ts +100 -0
- package/src/project/project-store.ts +115 -0
- package/src/release-loop/checklist.ts +78 -4
- package/src/release-loop/feature-store.ts +154 -21
- package/src/release-loop/release-schedule.ts +53 -0
- package/src/repl/index.ts +7 -0
- package/src/seat-dispatch/ceo-commands.ts +41 -8
- package/src/seat-dispatch/ceo-intent.ts +38 -0
- package/src/seat-loop/seat-loop.ts +231 -0
- package/src/seat-loop/seat-report.ts +64 -0
- package/src/self-dev/decompose.ts +66 -11
- package/src/self-dev/run-store.ts +10 -1
- package/src/self-implement/execution-clarification.ts +115 -0
- package/src/self-implement/gate-cli.ts +6 -3
- package/src/self-implement/orchestrator.ts +37 -3
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/run-status-mapping.ts +5 -3
- package/src/session/chat.ts +2 -0
- package/src/session/index.ts +9 -0
- package/src/shell-env-bootstrap.ts +17 -0
- package/src/steward/directive.ts +51 -2
- package/src/steward/launch.ts +250 -0
- package/src/steward/outcome-notify.ts +115 -0
- package/src/steward/steward-cards.ts +8 -2
- package/src/steward/triage-plan.ts +32 -4
- package/src/steward/triage.ts +93 -9
- package/src/steward/working-backwards.ts +64 -0
- package/src/storyboard/storyboard.ts +141 -0
- package/src/task-cards/card-store.ts +2 -2
- package/src/task-orchestrator/surfaces/pod-lease.ts +260 -0
- package/src/telegram-card-followup.ts +233 -0
- package/src/telegram-report.ts +15 -9
- package/src/telegram-run.ts +7 -0
- package/src/telegram.ts +29 -8
- package/src/tool-runtime/types.ts +4 -0
- package/src/tui-client/dashboard-session.ts +2 -0
- package/src/user-config.ts +118 -11
- package/src/version/packed-revision.json +1 -1
- package/src/web-push/notify-exec-request.ts +32 -0
- package/apps/pwa/out/_next/static/bFI5EEwg7T6aG88ibX4Zq/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8630-cdfc61e82c653ffc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8894-e263b332208e3957.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-139040d37c79dbc9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-fb7c2d458f32fcad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-35e2713597dd24d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-918b656f064577ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-5f30e00693527ebd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-7fbae173b3c103a7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-3b4edcce364062ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-c5794f484c251f44.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-1130d8eefcd87e76.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-016e2bda04eecb1f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-b4d6d9a3b56f2940.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-ec49a466121233ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-f72db356744ceade.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-a6fd9b50d1b85378.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-adfbd2ba9598e59a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-46fedff57d4bec67.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-0cf318e9dcee2bbf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-899b327cb8b6845c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-6c1e6ba743a29dfe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-1a8e4807292f6b17.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-28deba6bcb834916.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-de68e2205794037d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-c71c33a573230672.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4fc56790fe0d2682.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-59df2aebb38a7c1e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4647be5dbcbd0795.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e025892bda74b743.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a0955de73b89f078.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-369846d2921af584.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-72cbc1c8cdd0f65d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-77b28bfb0996d5ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-3220c4934f49c6c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-7df9243ed021a4cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-4498c3853f4f688d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
- package/apps/pwa/out/_next/static/css/f92f63a5df0ba229.css +0 -1
- package/src/dashboard/turn-typeahead-echo.ts +0 -23
- /package/apps/pwa/out/_next/static/{bFI5EEwg7T6aG88ibX4Zq → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { realpathSync, statSync } from 'node:fs';
|
|
2
|
+
import { extname, isAbsolute, join, sep } from 'node:path';
|
|
2
3
|
import { ExecRequestRunner } from '../../exec-requests/runner.js';
|
|
4
|
+
import type { ExecAttachment } from '../../exec-requests/store.js';
|
|
5
|
+
import { getElanousConfigDir } from '../../elanous-config-dir.js';
|
|
6
|
+
import { defaultAttachmentBaseDir } from '../../boot/attachment-store.js';
|
|
3
7
|
import { jsonResponse } from './json-response.js';
|
|
4
8
|
|
|
5
9
|
const mime: Record<string, string> = {
|
|
@@ -8,7 +12,21 @@ const mime: Record<string, string> = {
|
|
|
8
12
|
'.md': 'text/markdown; charset=utf-8', '.txt': 'text/plain; charset=utf-8',
|
|
9
13
|
};
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
function resolveAttachment(path: string, roots: string[]): string | null {
|
|
16
|
+
if (!isAbsolute(path)) return null;
|
|
17
|
+
try {
|
|
18
|
+
const real = realpathSync(path);
|
|
19
|
+
if (!statSync(real).isFile()) return null;
|
|
20
|
+
return roots.some(root => {
|
|
21
|
+
try {
|
|
22
|
+
const base = realpathSync(root);
|
|
23
|
+
return real.startsWith(`${base}${sep}`);
|
|
24
|
+
} catch { return false; }
|
|
25
|
+
}) ? real : null;
|
|
26
|
+
} catch { return null; }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function handleExecRequests(req: Request, runner: ExecRequestRunner, uploadDir = defaultAttachmentBaseDir(), fieldDir = join(getElanousConfigDir(), 'field')): Promise<Response> {
|
|
12
30
|
const path = new URL(req.url).pathname;
|
|
13
31
|
if (path === '/v1/exec-requests') {
|
|
14
32
|
if (req.method === 'POST') {
|
|
@@ -16,7 +34,19 @@ export async function handleExecRequests(req: Request, runner: ExecRequestRunner
|
|
|
16
34
|
try { body = await req.json(); } catch { return jsonResponse({ error: 'bad_request' }, 400); }
|
|
17
35
|
const text = body && typeof body === 'object' && !Array.isArray(body) ? (body as { text?: unknown }).text : undefined;
|
|
18
36
|
if (typeof text !== 'string' || !text.trim()) return jsonResponse({ error: 'bad_request', reason: 'text required' }, 400);
|
|
19
|
-
const
|
|
37
|
+
const supplied = (body as { attachments?: unknown }).attachments;
|
|
38
|
+
if (supplied !== undefined && (!Array.isArray(supplied) || supplied.some(entry =>
|
|
39
|
+
!entry || typeof entry !== 'object' || Array.isArray(entry)
|
|
40
|
+
|| typeof entry.name !== 'string' || !entry.name.trim() || typeof entry.path !== 'string'))) {
|
|
41
|
+
return jsonResponse({ error: 'bad_request', reason: 'attachments must be [{name,path}]' }, 400);
|
|
42
|
+
}
|
|
43
|
+
const roots = [uploadDir, fieldDir];
|
|
44
|
+
const resolved: Array<{ name: string; path: string | null }> | undefined = supplied?.map((entry: ExecAttachment) => ({
|
|
45
|
+
name: entry.name, path: resolveAttachment(entry.path, roots),
|
|
46
|
+
}));
|
|
47
|
+
if (resolved?.some(entry => !entry.path)) return jsonResponse({ error: 'attachment-outside' }, 400);
|
|
48
|
+
const attachments: ExecAttachment[] | undefined = resolved?.map(entry => ({ name: entry.name, path: entry.path! }));
|
|
49
|
+
const item = runner.submit(text.trim(), attachments);
|
|
20
50
|
return jsonResponse({ id: item.id, status: 'planning' }, 202);
|
|
21
51
|
}
|
|
22
52
|
if (req.method === 'GET') return jsonResponse({ items: runner.store.list().map(item => {
|
|
@@ -19,6 +19,8 @@ import { setInProcessOutbound } from '../../domains/outbound-alert.js';
|
|
|
19
19
|
import { getUserConfig } from '../../user-config.js';
|
|
20
20
|
import type { TelegramEvent } from '../../workflow-runtime/triggers/telegram-source.js';
|
|
21
21
|
import { spawnSync } from 'node:child_process';
|
|
22
|
+
import { isAbsolute } from 'node:path';
|
|
23
|
+
import { createProject, listProjects } from '../../project/project-store.js';
|
|
22
24
|
import { compareTokenConstTime } from '../../acp/transport/auth.js';
|
|
23
25
|
import { ensureAuthToken } from '../../auth/acp-token.js';
|
|
24
26
|
import type { NexusState } from '../state/state.js';
|
|
@@ -134,6 +136,7 @@ import {
|
|
|
134
136
|
type ReflectionRouteOpts,
|
|
135
137
|
} from './reflection.js';
|
|
136
138
|
import { handleSignalBoard } from './signal-board-api.js';
|
|
139
|
+
import { handleUsageGet } from './usage-api.js';
|
|
137
140
|
import {
|
|
138
141
|
handleDashboard,
|
|
139
142
|
handleDashboardRefreshLive,
|
|
@@ -163,6 +166,7 @@ import { handleLlmModels } from './llm-models.js';
|
|
|
163
166
|
import { handleLlmHostsConfig } from './llm-hosts-config.js';
|
|
164
167
|
import { handleLlmRotationGet, handleLlmRotationNext } from './llm-rotation.js';
|
|
165
168
|
import { handleSetupClaim } from './setup-claim.js';
|
|
169
|
+
import { handleSetupCodexLoginGet, handleSetupCodexLoginPost } from './setup-codex-login.js';
|
|
166
170
|
import {
|
|
167
171
|
handleLlmProvidersList,
|
|
168
172
|
handleLlmProviderSet,
|
|
@@ -278,7 +282,7 @@ import { handleSchedulesActionPost } from './schedules-action.js';
|
|
|
278
282
|
import { handleSchedulesList, handleScheduleDetail, handleScheduleRuns } from './schedules-read.js';
|
|
279
283
|
import { handleIntakeRunsList } from './intake-runs.js';
|
|
280
284
|
import { handleMissionsList, handleMissionDetail } from './missions.js';
|
|
281
|
-
import { handleSessionsStoreList, handleSessionsStoreGet, handleSessionsStoreFork, handleSessionsStoreDelete } from './sessions-store.js';
|
|
285
|
+
import { handleSessionsStoreList, handleSessionsStoreGet, handleSessionsStoreFork, handleSessionsStoreDelete, handleSessionsStorePatch } from './sessions-store.js';
|
|
282
286
|
import { handleLogsQuery, handleLogsStream, handleLogsLevelGet, handleLogsLevelPost, handleLogsFacets, handleLogsHistogram, handleLogsInstances } from './log-fabric.js';
|
|
283
287
|
import { handleTrace, handleTraceEvidence } from './trace.js';
|
|
284
288
|
import { handleDispatchRunsList } from './dispatch-runs.js';
|
|
@@ -465,6 +469,8 @@ export interface NexusHttpServerOpts {
|
|
|
465
469
|
state: NexusState;
|
|
466
470
|
registry: TabRegistry;
|
|
467
471
|
eventBus: NexusEventBus;
|
|
472
|
+
/** Test seam for the read-only usage collector; production uses the cached handler. */
|
|
473
|
+
usageHandler?: () => Promise<Response>;
|
|
468
474
|
/** Supervisor handle — required for the write API (PR ι). When omitted
|
|
469
475
|
* (skipSupervisor mode), mutation routes return 503. */
|
|
470
476
|
supervisor?: Supervisor;
|
|
@@ -795,6 +801,27 @@ export function startNexusHttpServer(opts: NexusHttpServerOpts): NexusHttpServer
|
|
|
795
801
|
};
|
|
796
802
|
}
|
|
797
803
|
|
|
804
|
+
export function handleProjectsGet(): Response {
|
|
805
|
+
return jsonResponse({ projects: listProjects() });
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
export async function handleProjectsPost(req: Request): Promise<Response> {
|
|
809
|
+
let body: unknown;
|
|
810
|
+
try { body = await req.json(); }
|
|
811
|
+
catch { return jsonResponse({ error: 'invalid_json' }, 400); }
|
|
812
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
813
|
+
return jsonResponse({ error: 'invalid_name' }, 400);
|
|
814
|
+
}
|
|
815
|
+
const { name, primaryFolder } = body as Record<string, unknown>;
|
|
816
|
+
if (typeof name !== 'string' || !name.trim() || name.length > 80) {
|
|
817
|
+
return jsonResponse({ error: 'invalid_name' }, 400);
|
|
818
|
+
}
|
|
819
|
+
if (primaryFolder !== undefined && (typeof primaryFolder !== 'string' || !isAbsolute(primaryFolder))) {
|
|
820
|
+
return jsonResponse({ error: 'invalid_primary_folder' }, 400);
|
|
821
|
+
}
|
|
822
|
+
return jsonResponse({ project: createProject({ name, ...(primaryFolder !== undefined ? { primaryFolder } : {}) }) }, 201);
|
|
823
|
+
}
|
|
824
|
+
|
|
798
825
|
function isPortBusy(err: unknown): boolean {
|
|
799
826
|
if (!err) return false;
|
|
800
827
|
// Bun.serve errors expose `.code === 'EADDRINUSE'` (Node-compatible)
|
|
@@ -1285,11 +1312,22 @@ export async function routeRequest(
|
|
|
1285
1312
|
return handleGraphsValidatePost(req, opts.metaApi);
|
|
1286
1313
|
}
|
|
1287
1314
|
|
|
1315
|
+
if (pathname === '/v1/projects' && (method === 'GET' || method === 'POST')) {
|
|
1316
|
+
if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
1317
|
+
return method === 'GET' ? handleProjectsGet() : handleProjectsPost(req);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1288
1320
|
if (pathname === '/v1/config/chat-fast-path' && (method === 'GET' || method === 'PUT')) {
|
|
1289
1321
|
if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
1290
1322
|
return handleChatFastPathConfig(req);
|
|
1291
1323
|
}
|
|
1292
1324
|
|
|
1325
|
+
if (pathname === '/v1/usage') {
|
|
1326
|
+
if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
1327
|
+
if (method !== 'GET') return jsonResponse({ error: 'method-not-allowed' }, 405);
|
|
1328
|
+
return (opts.usageHandler ?? handleUsageGet)();
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1293
1331
|
// Mutation routes (PR ι) — POST/PATCH/DELETE on /v1/nexus/tabs[/:id[/action]].
|
|
1294
1332
|
// Templates POST handled here too (PR κ).
|
|
1295
1333
|
// Config / secrets PUT/POST/DELETE here too (PR μ).
|
|
@@ -1540,6 +1578,11 @@ export async function routeRequest(
|
|
|
1540
1578
|
// PWA `/setup` wizard Phase 1 (2026-05-19) — LLM provider 첫 셋업.
|
|
1541
1579
|
// POST 는 mutation 블록 안에서 등록 (memory `feedback_post_route_must_be_in_method_block`).
|
|
1542
1580
|
// GET sibling 은 아래 GET-only 블록에 등록.
|
|
1581
|
+
if (pathname === '/v1/setup/codex-login' && (method === 'POST' || method === 'OPTIONS')) {
|
|
1582
|
+
if (method === 'OPTIONS') return corsPreflight('GET, POST, OPTIONS');
|
|
1583
|
+
if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
1584
|
+
return handleSetupCodexLoginPost(req);
|
|
1585
|
+
}
|
|
1543
1586
|
if (pathname === '/v1/setup/llm-provider' && (method === 'POST' || method === 'OPTIONS')) {
|
|
1544
1587
|
if (method === 'POST') { if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401); }
|
|
1545
1588
|
return handleLlmProviderSet(req);
|
|
@@ -1904,6 +1947,11 @@ export async function routeRequest(
|
|
|
1904
1947
|
const id = decodeURIComponent(pathname.slice('/v1/sessions/store/'.length, -'/fork'.length));
|
|
1905
1948
|
return handleSessionsStoreFork(req, id, opts.metaApi);
|
|
1906
1949
|
}
|
|
1950
|
+
if (pathname.startsWith('/v1/sessions/store/') && !pathname.endsWith('/fork') && method === 'PATCH') {
|
|
1951
|
+
if (!opts.metaApi) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
1952
|
+
const id = decodeURIComponent(pathname.slice('/v1/sessions/store/'.length));
|
|
1953
|
+
return handleSessionsStorePatch(req, id, opts.metaApi);
|
|
1954
|
+
}
|
|
1907
1955
|
// on-disk 세션 삭제(파괴적) — 라이브 세션 관리. /store/:id (fork/transcript 와 구분).
|
|
1908
1956
|
if (pathname.startsWith('/v1/sessions/store/') && !pathname.endsWith('/fork') && method === 'DELETE') {
|
|
1909
1957
|
if (!opts.metaApi) return jsonResponse({ error: 'meta-api-runtime-not-wired' }, 503);
|
|
@@ -2425,6 +2473,10 @@ export async function routeRequest(
|
|
|
2425
2473
|
}
|
|
2426
2474
|
// PWA `/setup` wizard Phase 1 (2026-05-19) — provider catalog 표시.
|
|
2427
2475
|
// POST sibling (`/v1/setup/llm-provider`) 는 위 mutation 블록.
|
|
2476
|
+
if (pathname === '/v1/setup/codex-login') {
|
|
2477
|
+
if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
2478
|
+
return handleSetupCodexLoginGet();
|
|
2479
|
+
}
|
|
2428
2480
|
if (pathname === '/v1/setup/llm-providers') {
|
|
2429
2481
|
return handleLlmProvidersList(req);
|
|
2430
2482
|
}
|
|
@@ -147,6 +147,10 @@ export interface MetaApiOpts {
|
|
|
147
147
|
* compared constant-time against the `Authorization: Bearer …`
|
|
148
148
|
* header. */
|
|
149
149
|
bearerToken?: string;
|
|
150
|
+
/** Server-side human identity verification, independent of the shared bearer credential.
|
|
151
|
+
* Never derive this result from request body, claimed source, or bearer possession alone.
|
|
152
|
+
* Without a verifier, prompt tool mutations remain unavailable. */
|
|
153
|
+
resolveVerifiedOwner?: (req: Request) => { id: string } | undefined;
|
|
150
154
|
/** Convenience for Tailscale-only / dogfood-loopback mode. */
|
|
151
155
|
noAuth?: boolean;
|
|
152
156
|
// ── PR k · runtime DI for the lifted endpoints ─────────────────
|
|
@@ -788,6 +792,11 @@ function notFound(payload: Record<string, unknown> = {}): Response {
|
|
|
788
792
|
|
|
789
793
|
// ── /v1/prompt ────────────────────────────────────────────────────
|
|
790
794
|
|
|
795
|
+
export function promptVerifiedOwner(req: Request, opts: MetaApiOpts): { id: string } | undefined {
|
|
796
|
+
const owner = opts.resolveVerifiedOwner?.(req);
|
|
797
|
+
return typeof owner?.id === 'string' && owner.id.trim() ? { id: owner.id.trim() } : undefined;
|
|
798
|
+
}
|
|
799
|
+
|
|
791
800
|
export async function handlePromptPost(
|
|
792
801
|
req: Request,
|
|
793
802
|
opts: MetaApiOpts,
|
|
@@ -831,6 +840,7 @@ export async function handlePromptPost(
|
|
|
831
840
|
surface: surfaceResolution.surface,
|
|
832
841
|
surfaceResolutionReason: surfaceResolution.reason,
|
|
833
842
|
...(parsed.value.userContent ? { promptBlocks: parsed.value.userContent } : {}),
|
|
843
|
+
...((owner) => owner ? { verifiedOwner: owner } : {})(promptVerifiedOwner(req, opts)),
|
|
834
844
|
...(resolvePromptHitlChannels(typeof body.sessionId === 'string' ? body.sessionId : '', opts) ?? {}),
|
|
835
845
|
...(effectiveSurface ? { toolSurface: effectiveSurface } : {}),
|
|
836
846
|
...(opts.toolCwd ? { toolCwd: opts.toolCwd } : {}),
|
|
@@ -1188,6 +1198,7 @@ export async function handlePromptStreamPost(
|
|
|
1188
1198
|
surface: surfaceResolution.surface,
|
|
1189
1199
|
surfaceResolutionReason: surfaceResolution.reason,
|
|
1190
1200
|
...(parsed.value.userContent ? { promptBlocks: parsed.value.userContent } : {}),
|
|
1201
|
+
...((owner) => owner ? { verifiedOwner: owner } : {})(promptVerifiedOwner(req, opts)),
|
|
1191
1202
|
...(resolvePromptHitlChannels(typeof body.sessionId === 'string' ? body.sessionId : '', opts) ?? {}),
|
|
1192
1203
|
...(effectiveSurface ? { toolSurface: effectiveSurface } : {}),
|
|
1193
1204
|
...(opts.toolCwd ? { toolCwd: opts.toolCwd } : {}),
|
package/src/nexus/api/ops-api.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { debug, redactSecretText } from '../../debug/log.js';
|
|
4
4
|
import { effectiveInstanceRoot } from '../../instance/resolve.js';
|
|
5
5
|
import { listChecklist } from '../../release-loop/checklist.js';
|
|
6
|
+
import { getSchedule } from '../../release-loop/release-schedule.js';
|
|
6
7
|
import { jsonResponse } from './json-response.js';
|
|
7
8
|
import { operatorSignal } from './operator.js';
|
|
8
9
|
import { createSeatsCache, todayKst } from './ops-seats.js';
|
|
@@ -90,7 +91,15 @@ export function handleOpsApi(req: Request, metaApi: MetaApiOpts | undefined): Re
|
|
|
90
91
|
const version = url.searchParams.get('version');
|
|
91
92
|
if (pathname === '/v1/ops/checklist') {
|
|
92
93
|
if (!version || !VERSION.test(version)) return jsonResponse({ error: 'invalid-version' }, 400);
|
|
93
|
-
|
|
94
|
+
// OPS2 header «컷 · 착지 마감»: the version's row in the release schedule (REL9a), or null when none is set.
|
|
95
|
+
let schedule: { cutAt: string; landBy: string | null } | null = null;
|
|
96
|
+
try {
|
|
97
|
+
const row = getSchedule(version);
|
|
98
|
+
schedule = row ? { cutAt: row.cutAt, landBy: row.landBy ?? null } : null;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
debug.log('ops.api', 'schedule-unreadable', { version, reason: error instanceof Error ? error.message.slice(0, 80) : 'unknown' }, { level: 'warn' });
|
|
101
|
+
}
|
|
102
|
+
return served(jsonResponse({ ...listChecklist(version), schedule }));
|
|
94
103
|
}
|
|
95
104
|
if (pathname === '/v1/ops/release/runs') {
|
|
96
105
|
if (version !== null && !VERSION.test(version)) return jsonResponse({ error: 'invalid-version' }, 400);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// Live sources for /v1/ops/seats: the coordination PR and merged PRs through gh (automation App identity, proxy variables
|
|
2
2
|
// removed — same rule as decision replies), the release checklist, and the decision ledger. Each failure becomes null upstream.
|
|
3
3
|
import { DecisionLedger } from '../../decisions/decision-ledger.js';
|
|
4
|
+
import { listCoordEvents, type CoordEvent } from '../../context-bus/coord-events.js';
|
|
4
5
|
import { devVersion, listChecklist, type ChecklistItem } from '../../release-loop/checklist.js';
|
|
5
6
|
import { getUserConfig } from '../../user-config.js';
|
|
6
|
-
import { kstDayRange, type ChannelComment, type MergedPr, type SeatsSources } from './ops-seats.js';
|
|
7
|
+
import { SEATS, kstDayRange, type ChannelComment, type ChannelSource, type MergedPr, type SeatsSources } from './ops-seats.js';
|
|
7
8
|
|
|
8
9
|
const GH_TIMEOUT_MS = 20_000;
|
|
9
10
|
|
|
@@ -29,19 +30,48 @@ function channelTarget(): { repo: string; pr: string } | null {
|
|
|
29
30
|
return m ? { repo: m[1]!, pr: m[2]! } : null;
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
export async function channelCommentsSince(sinceIso: string): Promise<Array<{ body: string; createdAt: string; url: string }> | null> {
|
|
34
|
+
const target = channelTarget();
|
|
35
|
+
if (!target) return null;
|
|
36
|
+
const lines = await ghLines(['api', '--paginate', `repos/${target.repo}/issues/${target.pr}/comments?since=${sinceIso}&per_page=100`,
|
|
37
|
+
'--jq', '.[] | {body: .body, createdAt: .created_at, url: .html_url} | tojson']);
|
|
38
|
+
return lines?.map((line) => JSON.parse(line) as { body: string; createdAt: string; url: string }) ?? null;
|
|
39
|
+
}
|
|
40
|
+
|
|
32
41
|
function nextPatch(version: string): string {
|
|
33
42
|
const [major, minor, patch] = version.split('.').map(Number);
|
|
34
43
|
return `${major}.${minor}.${patch! + 1}`;
|
|
35
44
|
}
|
|
36
45
|
|
|
37
|
-
export function liveSeatsSources(): SeatsSources {
|
|
46
|
+
export function liveSeatsSources(deps: { listEvents?: typeof listCoordEvents; githubChannel?: typeof channelCommentsSince } = {}): SeatsSources {
|
|
38
47
|
return {
|
|
39
48
|
async channel(date) {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
const since = kstDayRange(date).start;
|
|
50
|
+
let events: CoordEvent[] = [];
|
|
51
|
+
try { events = (deps.listEvents ?? listCoordEvents)({ since }); } catch { /* GitHub fallback */ }
|
|
52
|
+
const latest = new Map<string, CoordEvent>();
|
|
53
|
+
for (const event of events) {
|
|
54
|
+
if (!SEATS.some(({ seat }) => seat === event.refs.seat)) continue;
|
|
55
|
+
const prior = latest.get(event.refs.seat);
|
|
56
|
+
if (!prior || event.at > prior.at) latest.set(event.refs.seat, event);
|
|
57
|
+
}
|
|
58
|
+
const seatSources: Partial<Record<(typeof SEATS)[number]['seat'], ChannelSource>> = {};
|
|
59
|
+
if (latest.size) {
|
|
60
|
+
const comments: ChannelComment[] = [];
|
|
61
|
+
for (const { seat } of SEATS) {
|
|
62
|
+
const event = latest.get(seat);
|
|
63
|
+
if (!event) { seatSources[seat] = 'unknown'; continue; }
|
|
64
|
+
comments.push({ body: `**[${seat}]** ${event.text}`, createdAt: event.at, url: event.refs.url ?? '' });
|
|
65
|
+
seatSources[seat] = 'ledger';
|
|
66
|
+
}
|
|
67
|
+
return { source: 'ledger', comments, seatSources };
|
|
68
|
+
}
|
|
69
|
+
let github: ChannelComment[] | null;
|
|
70
|
+
try { github = await (deps.githubChannel ?? channelCommentsSince)(since); }
|
|
71
|
+
catch { github = null; }
|
|
72
|
+
if (github === null) return null;
|
|
73
|
+
for (const { seat } of SEATS) seatSources[seat] = 'github';
|
|
74
|
+
return { source: 'github', comments: github, seatSources };
|
|
45
75
|
},
|
|
46
76
|
async merged(date) {
|
|
47
77
|
const target = channelTarget();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// GET /v1/ops/seats?date=YYYY-MM-DD — one row per seat: latest channel line, PRs landed that day, red items, open decisions.
|
|
2
2
|
// A source that could not be read is null, never 0 («못 읽음» ≠ «없음»). Contract: 내부 문서 `DESIGN-ops3-seat-board-and-public-demo-2026-10-02` §1.
|
|
3
3
|
import { debug, redactSecretText } from '../../debug/log.js';
|
|
4
|
-
import type
|
|
4
|
+
import { parseOwner, type ChecklistItem } from '../../release-loop/checklist.js';
|
|
5
5
|
import { canonicalSeatId } from '../../msg/msg-store.js';
|
|
6
6
|
|
|
7
7
|
export const SEATS = [
|
|
@@ -9,11 +9,17 @@ export const SEATS = [
|
|
|
9
9
|
] as const;
|
|
10
10
|
export const SEATS_CACHE_MS = 60_000;
|
|
11
11
|
|
|
12
|
-
export interface ChannelComment { body: string; createdAt: string }
|
|
12
|
+
export interface ChannelComment { body: string; createdAt: string; url?: string }
|
|
13
|
+
export type ChannelSource = 'ledger' | 'github' | 'unreadable' | 'unknown';
|
|
14
|
+
export type ChannelResult = ChannelComment[] | {
|
|
15
|
+
comments: ChannelComment[];
|
|
16
|
+
source: 'ledger' | 'github';
|
|
17
|
+
seatSources?: Partial<Record<(typeof SEATS)[number]['seat'], ChannelSource>>;
|
|
18
|
+
} | null;
|
|
13
19
|
export interface MergedPr { number: number; title: string; body: string; mergedAt: string }
|
|
14
20
|
export interface SeatsSources {
|
|
15
21
|
/** Coordination-channel comments created on `date` (KST) or later; null when unreadable. */
|
|
16
|
-
channel(date: string): Promise<
|
|
22
|
+
channel(date: string): Promise<ChannelResult>;
|
|
17
23
|
/** PRs merged on `date` (KST); null when unreadable. */
|
|
18
24
|
merged(date: string): Promise<MergedPr[] | null>;
|
|
19
25
|
/** Checklist items of the version being worked on (counts, red) and of every version known (id → owner). */
|
|
@@ -24,6 +30,7 @@ export interface SeatsSources {
|
|
|
24
30
|
|
|
25
31
|
export interface SeatRow {
|
|
26
32
|
seat: string; role: string;
|
|
33
|
+
channelSource: ChannelSource;
|
|
27
34
|
now: { text: string; at: string } | null;
|
|
28
35
|
landed: Array<{ pr: number; title: string; at: string; checklistId: string | null }> | null;
|
|
29
36
|
blocked: Array<{ id: string; title: string; status: 'red' }> | null;
|
|
@@ -33,7 +40,7 @@ export interface SeatRow {
|
|
|
33
40
|
|
|
34
41
|
function seatOf(value: string | undefined): string | null {
|
|
35
42
|
if (!value) return null;
|
|
36
|
-
try { return canonicalSeatId(value); } catch { return null; }
|
|
43
|
+
try { return canonicalSeatId(value.includes('/') ? parseOwner(value).seat : value); } catch { return null; }
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
const clip = (text: string, max: number) => redactSecretText(text).slice(0, max);
|
|
@@ -49,9 +56,11 @@ function checklistIdOf(pr: MergedPr, ids: string[]): string | null {
|
|
|
49
56
|
}
|
|
50
57
|
|
|
51
58
|
export async function buildSeatsBoard(date: string, sources: SeatsSources): Promise<{ date: string; seats: SeatRow[] }> {
|
|
52
|
-
const [
|
|
59
|
+
const [channelResult, merged] = await Promise.all([
|
|
53
60
|
sources.channel(date).catch(() => null), sources.merged(date).catch(() => null),
|
|
54
61
|
]);
|
|
62
|
+
const channel = Array.isArray(channelResult) ? channelResult : channelResult?.comments ?? null;
|
|
63
|
+
const channelSource: ChannelSource = channelResult === null ? 'unreadable' : 'unknown';
|
|
55
64
|
let checklist: ReturnType<SeatsSources['checklist']> = null;
|
|
56
65
|
try { checklist = sources.checklist(); } catch { checklist = null; }
|
|
57
66
|
let raisers: string[] | null = null;
|
|
@@ -72,7 +81,8 @@ export async function buildSeatsBoard(date: string, sources: SeatsSources): Prom
|
|
|
72
81
|
const latest = channel?.filter((c) => c.body.trimStart().startsWith(`**[${seat}]**`)).sort((a, b) => b.createdAt.localeCompare(a.createdAt))[0];
|
|
73
82
|
const mine = checklist?.current.filter((item) => seatOf(item.owner) === seat);
|
|
74
83
|
return {
|
|
75
|
-
seat, role,
|
|
84
|
+
seat, role, channelSource: channelResult && !Array.isArray(channelResult)
|
|
85
|
+
? channelResult.seatSources?.[seat] ?? channelSource : channelSource,
|
|
76
86
|
now: latest ? { text: clip(latest.body.trimStart().split('\n')[0]!, 120), at: latest.createdAt } : null,
|
|
77
87
|
landed: merged === null || checklist === null ? null : (landedBySeat.get(seat) ?? []).sort((a, b) => a.at.localeCompare(b.at)),
|
|
78
88
|
blocked: mine ? mine.filter((item) => item.status === 'red').map((item) => ({ id: item.id, title: clip(item.title, 80), status: 'red' as const })) : null,
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
-
import { dirname, join } from 'node:path';
|
|
3
|
+
import { basename, dirname, join } from 'node:path';
|
|
4
4
|
import { debug } from '../../debug/log.js';
|
|
5
|
+
import { resolveAttachmentPath } from '../../boot/attachment-store.js';
|
|
6
|
+
import { runGh } from '../../decisions/decision-cards.js';
|
|
7
|
+
import { openMsgStore, canonicalSeatId } from '../../msg/msg-store.js';
|
|
8
|
+
import { dispatchCeoTask, type CeoCommandDeps } from '../../seat-dispatch/ceo-commands.js';
|
|
9
|
+
import { getUserConfig } from '../../user-config.js';
|
|
5
10
|
import { effectiveInstanceRoot } from '../../instance/resolve.js';
|
|
6
11
|
import { submitIntakeWork } from '../../intake-plane/submit-intake-work.js';
|
|
7
12
|
import { parseSeatAddress, resolveSeat } from '../../seat-address/seat-address.js';
|
|
@@ -16,6 +21,9 @@ interface SeatRequestItem {
|
|
|
16
21
|
text: string;
|
|
17
22
|
queuedAt: string;
|
|
18
23
|
status: 'queued';
|
|
24
|
+
attachments?: string[];
|
|
25
|
+
channel?: 'posted' | 'failed' | 'unknown';
|
|
26
|
+
channelError?: string;
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
interface PendingRequest {
|
|
@@ -25,6 +33,7 @@ interface PendingRequest {
|
|
|
25
33
|
text: string;
|
|
26
34
|
queuedAt: string;
|
|
27
35
|
ref: string;
|
|
36
|
+
attachments?: string[];
|
|
28
37
|
}
|
|
29
38
|
|
|
30
39
|
interface RejectedRequest extends Omit<PendingRequest, 'status'> {
|
|
@@ -38,10 +47,17 @@ export interface SeatRequestsDeps {
|
|
|
38
47
|
submit?: typeof submitIntakeWork;
|
|
39
48
|
now?: () => string;
|
|
40
49
|
append?: (path: string, entry: RecordEntry) => void;
|
|
50
|
+
resolveAttachment?: typeof resolveAttachmentPath;
|
|
51
|
+
dispatch?: typeof dispatchCeoTask;
|
|
52
|
+
ceoDeps?: () => CeoCommandDeps;
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
const seats = trackData.tracks.filter((entry): entry is typeof entry & { title: string } => 'title' in entry)
|
|
44
56
|
.map(({ id, title }) => ({ id, title }));
|
|
57
|
+
|
|
58
|
+
function requestSeat(address: string) {
|
|
59
|
+
return resolveSeat(address) ?? trackData.tracks.find((entry) => entry.id.toLowerCase() === address.replace(/^@/, '').toLowerCase());
|
|
60
|
+
}
|
|
45
61
|
const inFlight = new Map<string, { payload: string; task: Promise<Response> }>();
|
|
46
62
|
|
|
47
63
|
function unknownSeat(): Response {
|
|
@@ -140,7 +156,28 @@ function persistOutcome(path: string, entry: RecordEntry, write: typeof append):
|
|
|
140
156
|
}
|
|
141
157
|
|
|
142
158
|
function receipt(item: SeatRequestItem): Response {
|
|
143
|
-
return jsonResponse({ receiptId: item.receiptId, seat: item.seat, queuedAt: item.queuedAt
|
|
159
|
+
return jsonResponse({ receiptId: item.receiptId, seat: item.seat, queuedAt: item.queuedAt,
|
|
160
|
+
// Only a CEO-seat dispatch knows whether the channel line went out; anything else is reported as unknown.
|
|
161
|
+
attachments: item.attachments?.length ?? 0, channel: item.channel ?? 'unknown',
|
|
162
|
+
...(item.channelError ? { channelError: item.channelError } : {}) }, 202);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function defaultCeoDeps(): CeoCommandDeps {
|
|
166
|
+
const cfg = getUserConfig();
|
|
167
|
+
const replyTarget = (cfg.raw?.decisions as { replyGhPr?: unknown } | undefined)?.replyGhPr;
|
|
168
|
+
return { ownerId: null, replyTarget: typeof replyTarget === 'string' ? replyTarget : null, runGh,
|
|
169
|
+
append: (message) => { const store = openMsgStore(); try { return store.append(message); } finally { store.close(); } },
|
|
170
|
+
hasMessage: (seat, ref) => {
|
|
171
|
+
const store = openMsgStore();
|
|
172
|
+
try {
|
|
173
|
+
return store.db.query("SELECT 1 FROM msg_messages WHERE recipient = ? AND kind = 'ceo-task' AND body LIKE ? LIMIT 1")
|
|
174
|
+
.get(seat, `%\n요청: ${ref}`) !== null;
|
|
175
|
+
} finally { store.close(); }
|
|
176
|
+
} };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function canonicalSeatIdSafe(id: string): string {
|
|
180
|
+
try { return canonicalSeatId(id); } catch { return id; }
|
|
144
181
|
}
|
|
145
182
|
|
|
146
183
|
function unavailable(): Response {
|
|
@@ -153,7 +190,7 @@ export async function handleSeatRequests(req: Request, deps: SeatRequestsDeps =
|
|
|
153
190
|
if (req.method === 'GET') {
|
|
154
191
|
const params = new URL(req.url).searchParams;
|
|
155
192
|
const requestedSeat = params.get('seat');
|
|
156
|
-
const seat = requestedSeat ?
|
|
193
|
+
const seat = requestedSeat ? requestSeat(requestedSeat) : undefined;
|
|
157
194
|
if (requestedSeat && !seat) return unknownSeat();
|
|
158
195
|
const rawLimit = params.get('limit');
|
|
159
196
|
const limit = rawLimit === null ? 20 : Number(rawLimit);
|
|
@@ -183,18 +220,30 @@ export async function handleSeatRequests(req: Request, deps: SeatRequestsDeps =
|
|
|
183
220
|
const firstAddress = parsed && input.text.startsWith(prefix)
|
|
184
221
|
&& (input.text.length === prefix.length || /[ \t\r\n]/.test(input.text[prefix.length]!)) ? parsed : null;
|
|
185
222
|
const address = input.seat ?? (firstAddress?.seats.length === 1 ? firstAddress.seats[0] : undefined);
|
|
186
|
-
const seat = address ?
|
|
223
|
+
const seat = address ? requestSeat(address) : undefined;
|
|
187
224
|
if (!seat) return unknownSeat();
|
|
188
225
|
const text = input.seat === undefined ? firstAddress!.body.trim() : input.text.trim();
|
|
189
226
|
if (!text) {
|
|
190
227
|
debug.log('seat-address.pwa', 'rejected', { reason: 'empty-text', seat: seat.id });
|
|
191
228
|
return jsonResponse({ error: 'bad_request' }, 400);
|
|
192
229
|
}
|
|
230
|
+
if (input.attachments !== undefined && (!Array.isArray(input.attachments) || input.attachments.length > 4
|
|
231
|
+
|| input.attachments.some((attachment: unknown) => !attachment || typeof attachment !== 'object' || Array.isArray(attachment)
|
|
232
|
+
|| typeof (attachment as { id?: unknown }).id !== 'string' || !(attachment as { id: string }).id.trim()))) {
|
|
233
|
+
debug.log('seat.dispatch', 'refused', { seat: seat.id, reason: 'invalid-attachments' });
|
|
234
|
+
return jsonResponse({ error: 'bad_request' }, 400);
|
|
235
|
+
}
|
|
236
|
+
const attachmentIds = (input.attachments as Array<{ id: string }> | undefined)?.map(({ id }) => id) ?? [];
|
|
237
|
+
// Intake (non-CEO seats) carries text only — refuse attachments there instead of accepting and dropping them.
|
|
238
|
+
if (attachmentIds.length && !['OP', 'TC', 'MK', 'UX'].includes(canonicalSeatIdSafe(seat.id))) {
|
|
239
|
+
debug.log('seat.dispatch', 'refused', { seat: seat.id, reason: 'attachments-unsupported' });
|
|
240
|
+
return jsonResponse({ error: 'attachments-unsupported', seat: seat.id }, 400);
|
|
241
|
+
}
|
|
193
242
|
debug.log('seat-address.pwa', 'parsed', { seat: seat.id, textLength: text.length });
|
|
194
243
|
const now = (deps.now ?? (() => new Date().toISOString()))();
|
|
195
244
|
const header = req.headers.get('idempotency-key');
|
|
196
245
|
if (header !== null && (!header.trim() || header.length > 200)) return jsonResponse({ error: 'bad_request' }, 400);
|
|
197
|
-
const payload =
|
|
246
|
+
const payload = JSON.stringify([seat.id, text, attachmentIds]);
|
|
198
247
|
const key = header === null ? createHash('sha256').update(randomUUID()).digest('hex')
|
|
199
248
|
: createHash('sha256').update(`client\0${header}`).digest('hex');
|
|
200
249
|
const lock = `${path}\0${key}`;
|
|
@@ -213,11 +262,23 @@ export async function handleSeatRequests(req: Request, deps: SeatRequestsDeps =
|
|
|
213
262
|
try { existing = records(path).get(key); }
|
|
214
263
|
catch { return unavailable(); }
|
|
215
264
|
if (existing) {
|
|
216
|
-
if (existing.seat !== seat.id || existing.text !== text
|
|
265
|
+
if (existing.seat !== seat.id || existing.text !== text
|
|
266
|
+
|| JSON.stringify(existing.attachments ?? []) !== JSON.stringify(attachmentIds)) return jsonResponse({ error: 'idempotency-conflict' }, 409);
|
|
217
267
|
if (existing.status === 'queued') return receipt(existing);
|
|
218
268
|
}
|
|
269
|
+
const attachments: Array<{ name: string; path: string }> = [];
|
|
270
|
+
for (const id of attachmentIds) {
|
|
271
|
+
const resolved = (deps.resolveAttachment ?? resolveAttachmentPath)(id);
|
|
272
|
+
if (!resolved) {
|
|
273
|
+
debug.log('seat.dispatch', 'refused', { seat: seat.id, reason: 'unknown-attachment' });
|
|
274
|
+
return jsonResponse({ error: 'unknown-attachment', id }, 400);
|
|
275
|
+
}
|
|
276
|
+
const metadata = (input.attachments as Array<{ name?: unknown }>)[attachments.length];
|
|
277
|
+
attachments.push({ name: typeof metadata?.name === 'string' && metadata.name.trim() ? metadata.name : basename(resolved), path: resolved });
|
|
278
|
+
}
|
|
219
279
|
const intent: PendingRequest = existing?.status === 'pending'
|
|
220
|
-
? existing : { key, seat: seat.id, text, queuedAt: now, status: 'pending', ref: `pwa:${randomUUID()}
|
|
280
|
+
? existing : { key, seat: seat.id, text, queuedAt: now, status: 'pending', ref: `pwa:${randomUUID()}`,
|
|
281
|
+
...(attachmentIds.length ? { attachments: attachmentIds } : {}) };
|
|
221
282
|
if (existing?.status !== 'pending') {
|
|
222
283
|
try {
|
|
223
284
|
(deps.append ?? append)(path, intent);
|
|
@@ -227,6 +288,24 @@ export async function handleSeatRequests(req: Request, deps: SeatRequestsDeps =
|
|
|
227
288
|
}
|
|
228
289
|
} catch { return unavailable(); }
|
|
229
290
|
}
|
|
291
|
+
const ceoSeat = canonicalSeatId(seat.id);
|
|
292
|
+
if (['OP', 'TC', 'MK', 'UX'].includes(ceoSeat)) {
|
|
293
|
+
let result: Awaited<ReturnType<typeof dispatchCeoTask>>;
|
|
294
|
+
try {
|
|
295
|
+
result = await (deps.dispatch ?? dispatchCeoTask)(ceoSeat, text, (deps.ceoDeps ?? defaultCeoDeps)(), { attachments, via: 'pwa', ref: intent.ref });
|
|
296
|
+
} catch {
|
|
297
|
+
debug.log('seat.dispatch', 'refused', { seat: seat.id, reason: 'dispatch-failed' });
|
|
298
|
+
return unavailable();
|
|
299
|
+
}
|
|
300
|
+
const item: RecordEntry & SeatRequestItem = {
|
|
301
|
+
key, receiptId: intent.ref, seat: seat.id, text, queuedAt: intent.queuedAt, status: 'queued',
|
|
302
|
+
...(attachmentIds.length ? { attachments: attachmentIds } : {}), channel: result.channel,
|
|
303
|
+
...(result.channelError ? { channelError: result.channelError } : {}),
|
|
304
|
+
};
|
|
305
|
+
try { persistOutcome(path, item, deps.append ?? append); }
|
|
306
|
+
catch { return unavailable(); }
|
|
307
|
+
return receipt(item);
|
|
308
|
+
}
|
|
230
309
|
let result: Awaited<ReturnType<typeof submitIntakeWork>>;
|
|
231
310
|
try {
|
|
232
311
|
result = await (deps.submit ?? submitIntakeWork)({
|
|
@@ -241,7 +320,8 @@ export async function handleSeatRequests(req: Request, deps: SeatRequestsDeps =
|
|
|
241
320
|
catch { return unavailable(); }
|
|
242
321
|
return unavailable();
|
|
243
322
|
}
|
|
244
|
-
const item: RecordEntry & SeatRequestItem = { key, receiptId: result.acceptanceId, seat: seat.id, text, queuedAt: intent.queuedAt,
|
|
323
|
+
const item: RecordEntry & SeatRequestItem = { key, receiptId: result.acceptanceId, seat: seat.id, text, queuedAt: intent.queuedAt,
|
|
324
|
+
status: 'queued', ...(attachmentIds.length ? { attachments: attachmentIds } : {}) };
|
|
245
325
|
try { persistOutcome(path, item, deps.append ?? append); }
|
|
246
326
|
catch { return unavailable(); }
|
|
247
327
|
debug.log('seat-address.pwa', 'enqueued', { seat: seat.id, receiptId: item.receiptId });
|