elanous 0.2.8 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +1 -0
- package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8067-87ce253ec2f23697.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8195-6fde3d7a39859206.js} +5 -5
- package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-d4b6df905f185055.js → page-54b32bd27726588d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-186fc6952f027acc.js → page-f8116559c7424da7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-cbbe03c0da7b081a.js → page-54e1f966ab60a4dc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7ed95f296f5ee8e9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-c0c0d19d51342d3e.js → page-b31610deda76337b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-2e002ad8faa43291.js → page-6cb0e1bbefe5fb3d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-7464c1987dd76e6d.js → page-e168fa1030af0548.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-d8ab4d1489223459.js → page-4fddc90b23feb215.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-26102fd7350f171e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-139ba5c367bbf60a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +1 -0
- package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +30 -29
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +30 -29
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +30 -29
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +27 -26
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +27 -26
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +27 -26
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +30 -29
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +30 -29
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +30 -29
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +30 -29
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +30 -29
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +30 -29
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +27 -26
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +30 -29
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +30 -29
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +27 -26
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +27 -26
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +30 -29
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +30 -29
- package/apps/pwa/out/ops/checklist/index.html +1 -0
- package/apps/pwa/out/ops/checklist/index.txt +30 -0
- package/apps/pwa/out/ops/release/index.html +1 -0
- package/apps/pwa/out/ops/release/index.txt +30 -0
- package/apps/pwa/out/ops/seats/index.html +1 -0
- package/apps/pwa/out/ops/seats/index.txt +30 -0
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +28 -27
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +30 -29
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +27 -26
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +30 -29
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +30 -29
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +30 -29
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +30 -29
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +30 -29
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +27 -26
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +30 -29
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +30 -29
- package/apps/pwa/out/today/index.html +1 -0
- package/apps/pwa/out/today/index.txt +33 -0
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +30 -29
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +30 -29
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +30 -29
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +30 -29
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +30 -29
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +30 -29
- package/graphs/field/field-feed.yaml +2 -0
- package/graphs/implement-loop.yaml +1 -1
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +3 -1
- package/graphs/test-diet/nightly-audit.yaml +18 -0
- package/graphs/test-diet/recipes.yaml +3 -0
- package/package.json +1 -1
- package/plugins/card-followup/graphs/run-step.ts +48 -13
- package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
- package/plugins/doc-draft/graphs/run-step.ts +14 -7
- package/scripts/ci-pwa-build-gate.ts +160 -0
- package/scripts/coord-channel-watch.sh +42 -8
- package/scripts/coord-digest.ts +199 -0
- package/scripts/coord-post.sh +60 -3
- package/scripts/install.ps1 +68 -9
- package/scripts/install.sh +39 -12
- package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
- package/scripts/lib/tui-regress-checks.ts +146 -0
- package/scripts/memory-lifecycle-cycle.ts +2 -2
- package/scripts/ops-now-probe.ts +327 -0
- package/scripts/publish-internal-dist.ts +11 -1
- package/scripts/release-loop/cutoff.ts +31 -2
- package/scripts/release-loop/export-check-node.ts +85 -0
- package/scripts/release-loop/gate-node.ts +18 -4
- package/scripts/release-loop/node-verdict.ts +8 -1
- package/scripts/release-loop/ops-upgrade-node.ts +72 -16
- package/scripts/release-loop/release-readiness.ts +9 -0
- package/scripts/release-loop/version-node.ts +58 -10
- package/scripts/repo-watch-cycle.ts +1 -1
- package/scripts/retro-cycle.ts +2 -2
- package/scripts/schedule-health-report.ts +1 -1
- package/scripts/storyboard/storyboard.ts +5 -161
- package/scripts/test-diet/lib.ts +31 -1
- package/scripts/test-diet/node.ts +134 -17
- package/scripts/test-timeout-audit.ts +114 -0
- package/scripts/tree-sync-alert.ts +1 -1
- package/scripts/tui-regress.ts +71 -0
- package/scripts/waitlist-daily-digest.ts +1 -1
- package/skills/explainer-video/SKILL.md +4 -0
- package/skills/explainer-video/engine/reel.mjs +18 -13
- package/skills/explainer-video/engine/reel.sh +24 -5
- package/skills/explainer-video/engine/stitch.mjs +145 -0
- package/skills/pitch-deck/SKILL.md +36 -0
- package/skills/pitch-deck/template.html +80 -0
- package/src/acp/codex-approval-adapter.ts +9 -3
- package/src/acp/core-turn-bridge.ts +117 -18
- package/src/acp/fact-question.ts +11 -0
- package/src/acp/server.ts +48 -7
- package/src/acp/tool-approval.ts +126 -0
- package/src/acp/turn-runner.ts +36 -0
- package/src/agent/monad-agent-turn.ts +8 -0
- package/src/ask-user-question/pending-questions.ts +25 -10
- package/src/ask-user-question/types.ts +9 -0
- package/src/auth/setup-link-tokens.fixture.ts +19 -0
- package/src/auth/setup-link-tokens.ts +164 -0
- package/src/autopilot/build/integrity-gate.ts +12 -0
- package/src/boot/daemon-prompt-turn.ts +3 -0
- package/src/boot/daemon-tools/index.ts +8 -0
- package/src/boot/daemon-tools/types.ts +2 -0
- package/src/boot/daemon-tools/web-search.ts +1 -0
- package/src/budget/unified-usage.ts +75 -0
- package/src/channel-bot-token.ts +37 -10
- package/src/chat/ctrl-c-in-input.ts +14 -0
- package/src/chat/index.ts +61 -9
- package/src/chat/pickers/state.ts +6 -2
- package/src/cli/agent-cli.ts +7 -0
- package/src/cli/decisions-cli.ts +25 -3
- package/src/cli/doctor-bundle-upload.ts +68 -0
- package/src/cli/doctor-cli.ts +38 -4
- package/src/cli/op-proxy-sync.ts +83 -0
- package/src/cli/ops-cli.ts +20 -0
- package/src/cli/pod-cli.ts +49 -0
- package/src/cli/pr-cli.ts +37 -4
- package/src/cli/pwa-stop.ts +163 -13
- package/src/cli/pwa-test.ts +3 -0
- package/src/cli/release-cli.ts +98 -15
- package/src/cli/self-update.ts +14 -0
- package/src/cli/storyboard-cli.ts +37 -0
- package/src/cli/test-state-dir-flag.ts +11 -4
- package/src/connectors/linear.ts +146 -0
- package/src/context-bus/coord-events.ts +115 -0
- package/src/dashboard/dashboard-boot-fixture.ts +23 -0
- package/src/dashboard/dashboard-status-lines.ts +45 -0
- package/src/dashboard/first-screen-band.ts +35 -0
- package/src/dashboard/index.ts +128 -34
- package/src/dashboard/provider-slash-runtime.ts +28 -11
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +17 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
- package/src/dashboard/turn-stream-formatter.ts +1 -1
- package/src/decisions/decision-cards.ts +50 -2
- package/src/decisions/decision-ledger.ts +75 -4
- package/src/decisions/decision-linear-projection.ts +157 -0
- package/src/decisions/web-push-decision-cards.ts +50 -0
- package/src/directive/interpretation-card.ts +36 -0
- package/src/discord.ts +2 -1
- package/src/domains/coo-admin-tool.ts +62 -0
- package/src/domains/core-tools.ts +11 -3
- package/src/domains/elanous-card.ts +29 -0
- package/src/domains/ops-facts-tool.ts +76 -0
- package/src/domains/outbound-alert.ts +41 -13
- package/src/domains/release-tool.ts +168 -0
- package/src/domains/telegram-kind-route.ts +19 -1
- package/src/exec-requests/planner.ts +98 -28
- package/src/exec-requests/runner.ts +107 -10
- package/src/exec-requests/store.ts +8 -2
- package/src/field/field-media.ts +10 -7
- package/src/field/field-reel.ts +14 -4
- package/src/git-fs/worktree.ts +45 -6
- package/src/graph-runner/graph-approval-card.ts +113 -0
- package/src/graph-runner/graph-cli.ts +129 -4
- package/src/graph-runner/graph-notify.ts +25 -3
- package/src/graph-runner/runner.ts +142 -16
- package/src/harness/browser-verify.ts +25 -2
- package/src/harness/harness-cli-command.ts +231 -35
- package/src/harness/harness-incidents-writer.fixture.ts +24 -0
- package/src/harness/harness-incidents.ts +129 -0
- package/src/harness/harness-stop.ts +53 -3
- package/src/hitl/clarification-policy.ts +3 -1
- package/src/index.ts +172 -41
- package/src/intake-plane/author-goals.ts +37 -4
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/discord-seat-work.ts +47 -2
- package/src/intake-plane/seat-doc-route.ts +170 -6
- package/src/intake-plane/telegram-seat-work.ts +44 -2
- package/src/intake-plane/telegram-work.ts +1 -0
- package/src/maturity/cli-maturity.ts +34 -0
- package/src/maturity/feature-maturity.ts +100 -1
- package/src/maturity/tui-slash-maturity.ts +31 -0
- package/src/memory.ts +32 -4
- package/src/nexus/api/exec-requests.ts +33 -3
- package/src/nexus/api/http-server.ts +73 -1
- package/src/nexus/api/meta-api.ts +38 -0
- package/src/nexus/api/operator.ts +61 -0
- package/src/nexus/api/ops-api.ts +148 -0
- package/src/nexus/api/ops-seats-sources.ts +95 -0
- package/src/nexus/api/ops-seats.ts +124 -0
- package/src/nexus/api/public-routes.ts +14 -1
- package/src/nexus/api/seat-requests.ts +88 -8
- package/src/nexus/api/sessions-store.ts +63 -0
- package/src/nexus/api/setup-claim.ts +21 -0
- package/src/nexus/api/setup-codex-login.ts +160 -0
- package/src/nexus/api/usage-api.ts +102 -0
- package/src/nexus/config/secrets/file-backend.ts +56 -18
- package/src/nexus/index.ts +45 -4
- package/src/onboarding/first-run-doctor.ts +54 -0
- package/src/onboarding/first-run.ts +89 -0
- package/src/onboarding/lan-address.ts +45 -0
- package/src/onboarding/web-first.ts +119 -24
- package/src/onboarding.ts +4 -0
- package/src/ops-now/ops-now-refresh.ts +100 -0
- package/src/project/project-store.ts +115 -0
- package/src/release-loop/checklist.ts +76 -61
- package/src/release-loop/feature-store.ts +460 -0
- package/src/release-loop/manifest.ts +59 -5
- package/src/release-loop/release-schedule.ts +53 -0
- package/src/repl/index.ts +7 -0
- package/src/seat-dispatch/ceo-commands.ts +98 -0
- package/src/seat-dispatch/ceo-intent.ts +38 -0
- package/src/seat-loop/seat-loop.ts +231 -0
- package/src/seat-loop/seat-report.ts +64 -0
- package/src/self-dev/decompose.ts +66 -11
- package/src/self-dev/run-store.ts +10 -1
- package/src/self-implement/execution-clarification.ts +115 -0
- package/src/self-implement/gate-cli.ts +6 -3
- package/src/self-implement/orchestrator.ts +37 -3
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/run-status-mapping.ts +5 -3
- package/src/session/chat.ts +2 -0
- package/src/session/index.ts +9 -0
- package/src/shell-env-bootstrap.ts +17 -0
- package/src/skills/tools/usage-slash.ts +8 -7
- package/src/steward/directive.ts +51 -2
- package/src/steward/launch.ts +250 -0
- package/src/steward/outcome-notify.ts +115 -0
- package/src/steward/steward-cards.ts +8 -2
- package/src/steward/triage-plan.ts +32 -4
- package/src/steward/triage.ts +93 -9
- package/src/steward/working-backwards.ts +64 -0
- package/src/storyboard/storyboard.ts +141 -0
- package/src/task-cards/card-store.ts +2 -2
- package/src/task-orchestrator/surfaces/pod-lease.ts +260 -0
- package/src/telegram-card-followup.ts +233 -0
- package/src/telegram-commands.ts +16 -0
- package/src/telegram-report.ts +15 -9
- package/src/telegram-run.ts +90 -13
- package/src/telegram.ts +29 -8
- package/src/thinking-line.ts +28 -20
- package/src/tool-runtime/types.ts +4 -0
- package/src/tui/chat/execution-badge.ts +1 -1
- package/src/tui-client/dashboard-session.ts +2 -0
- package/src/user-config.ts +118 -11
- package/src/ux-sim/turn-states.ts +1 -1
- package/src/version/packed-revision.json +1 -1
- package/src/web-push/notify-exec-request.ts +32 -0
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4836-3084c58c1725a40b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8894-7317998f6cb97d8c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-9aa47b0bc6d570ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f4d2ecbbae6088d6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-2053e49930c82748.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a70ba27c33003dd0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-18166f885d831d3e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-1c9d6cfe68b121c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c108ff1fdb665c97.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-147a328753d983e2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-799395a9df7d95aa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-6a8158b71d1fe9d7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-9567560270857531.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-e7ed3d010153d818.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-b672e02e4bec8deb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-1ba4a5d857276334.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-9db2226f38c542fb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-70ee65f5f7a151dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-0bb8e43203efb23b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-092e1bcf27d751cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-eb52580c4b75b905.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-e573437b0a1c728f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-294300625e49e4d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-085847b088675978.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-9fe9d38c36999eb4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-6141f67f531d8b6a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-295a02b1026b8255.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-1eb9a0e1e054209d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-82efa177a30f3741.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d0be51cec4f62606.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-2e367075ffdbc062.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-647ed2b66c1d49f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ba058c56e38f2792.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
- package/apps/pwa/out/_next/static/css/5ef72a618a8994f3.css +0 -1
- package/apps/pwa/out/_next/static/lywc8m_HDzH9MKSDQhyEj/_buildManifest.js +0 -1
- package/src/dashboard/turn-typeahead-echo.ts +0 -23
- /package/apps/pwa/out/_next/static/{lywc8m_HDzH9MKSDQhyEj → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,124 @@
|
|
|
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
|
+
// A source that could not be read is null, never 0 («못 읽음» ≠ «없음»). Contract: 내부 문서 `DESIGN-ops3-seat-board-and-public-demo-2026-10-02` §1.
|
|
3
|
+
import { debug, redactSecretText } from '../../debug/log.js';
|
|
4
|
+
import { parseOwner, type ChecklistItem } from '../../release-loop/checklist.js';
|
|
5
|
+
import { canonicalSeatId } from '../../msg/msg-store.js';
|
|
6
|
+
|
|
7
|
+
export const SEATS = [
|
|
8
|
+
{ seat: 'OP', role: 'COO' }, { seat: 'TC', role: 'CTO' }, { seat: 'MK', role: 'CMO' }, { seat: 'UX', role: 'CXO' },
|
|
9
|
+
] as const;
|
|
10
|
+
export const SEATS_CACHE_MS = 60_000;
|
|
11
|
+
|
|
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;
|
|
19
|
+
export interface MergedPr { number: number; title: string; body: string; mergedAt: string }
|
|
20
|
+
export interface SeatsSources {
|
|
21
|
+
/** Coordination-channel comments created on `date` (KST) or later; null when unreadable. */
|
|
22
|
+
channel(date: string): Promise<ChannelResult>;
|
|
23
|
+
/** PRs merged on `date` (KST); null when unreadable. */
|
|
24
|
+
merged(date: string): Promise<MergedPr[] | null>;
|
|
25
|
+
/** Checklist items of the version being worked on (counts, red) and of every version known (id → owner). */
|
|
26
|
+
checklist(): { current: ChecklistItem[]; all: ChecklistItem[] } | null;
|
|
27
|
+
/** `raisedBy.agent` of every open decision. */
|
|
28
|
+
openDecisionRaisers(): string[] | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface SeatRow {
|
|
32
|
+
seat: string; role: string;
|
|
33
|
+
channelSource: ChannelSource;
|
|
34
|
+
now: { text: string; at: string } | null;
|
|
35
|
+
landed: Array<{ pr: number; title: string; at: string; checklistId: string | null }> | null;
|
|
36
|
+
blocked: Array<{ id: string; title: string; status: 'red' }> | null;
|
|
37
|
+
pendingDecisions: number | null;
|
|
38
|
+
checklist: { green: number; yellow: number; red: number; done: number } | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function seatOf(value: string | undefined): string | null {
|
|
42
|
+
if (!value) return null;
|
|
43
|
+
try { return canonicalSeatId(value.includes('/') ? parseOwner(value).seat : value); } catch { return null; }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const clip = (text: string, max: number) => redactSecretText(text).slice(0, max);
|
|
47
|
+
|
|
48
|
+
/** First checklist id that appears as a whole word in the title, else in the body. */
|
|
49
|
+
function checklistIdOf(pr: MergedPr, ids: string[]): string | null {
|
|
50
|
+
for (const text of [pr.title, pr.body]) {
|
|
51
|
+
const found = ids.map((id) => ({ id, at: text.search(new RegExp(`(?<![A-Za-z0-9])${id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![A-Za-z0-9])`)) }))
|
|
52
|
+
.filter((hit) => hit.at >= 0).sort((a, b) => a.at - b.at || b.id.length - a.id.length);
|
|
53
|
+
if (found.length) return found[0]!.id;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function buildSeatsBoard(date: string, sources: SeatsSources): Promise<{ date: string; seats: SeatRow[] }> {
|
|
59
|
+
const [channelResult, merged] = await Promise.all([
|
|
60
|
+
sources.channel(date).catch(() => null), sources.merged(date).catch(() => null),
|
|
61
|
+
]);
|
|
62
|
+
const channel = Array.isArray(channelResult) ? channelResult : channelResult?.comments ?? null;
|
|
63
|
+
const channelSource: ChannelSource = channelResult === null ? 'unreadable' : 'unknown';
|
|
64
|
+
let checklist: ReturnType<SeatsSources['checklist']> = null;
|
|
65
|
+
try { checklist = sources.checklist(); } catch { checklist = null; }
|
|
66
|
+
let raisers: string[] | null = null;
|
|
67
|
+
try { raisers = sources.openDecisionRaisers(); } catch { raisers = null; }
|
|
68
|
+
|
|
69
|
+
const owners = new Map<string, string>();
|
|
70
|
+
for (const item of checklist?.all ?? []) { const seat = seatOf(item.owner); if (seat) owners.set(item.id, seat); }
|
|
71
|
+
const ids = [...owners.keys()];
|
|
72
|
+
const landedBySeat = new Map<string, NonNullable<SeatRow['landed']>>();
|
|
73
|
+
for (const pr of merged ?? []) {
|
|
74
|
+
const id = checklistIdOf(pr, ids);
|
|
75
|
+
const seat = id ? owners.get(id) : undefined;
|
|
76
|
+
if (!seat) continue; // ⛔ not by commit author — every seat lands through the same account.
|
|
77
|
+
landedBySeat.set(seat, [...(landedBySeat.get(seat) ?? []), { pr: pr.number, title: clip(pr.title, 80), at: pr.mergedAt, checklistId: id }]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const seats = SEATS.map(({ seat, role }): SeatRow => {
|
|
81
|
+
const latest = channel?.filter((c) => c.body.trimStart().startsWith(`**[${seat}]**`)).sort((a, b) => b.createdAt.localeCompare(a.createdAt))[0];
|
|
82
|
+
const mine = checklist?.current.filter((item) => seatOf(item.owner) === seat);
|
|
83
|
+
return {
|
|
84
|
+
seat, role, channelSource: channelResult && !Array.isArray(channelResult)
|
|
85
|
+
? channelResult.seatSources?.[seat] ?? channelSource : channelSource,
|
|
86
|
+
now: latest ? { text: clip(latest.body.trimStart().split('\n')[0]!, 120), at: latest.createdAt } : null,
|
|
87
|
+
landed: merged === null || checklist === null ? null : (landedBySeat.get(seat) ?? []).sort((a, b) => a.at.localeCompare(b.at)),
|
|
88
|
+
blocked: mine ? mine.filter((item) => item.status === 'red').map((item) => ({ id: item.id, title: clip(item.title, 80), status: 'red' as const })) : null,
|
|
89
|
+
pendingDecisions: raisers === null ? null : raisers.filter((agent) => seatOf(agent) === seat).length,
|
|
90
|
+
checklist: mine ? {
|
|
91
|
+
green: mine.filter((i) => i.status === 'green').length, yellow: mine.filter((i) => i.status === 'yellow').length,
|
|
92
|
+
red: mine.filter((i) => i.status === 'red').length, done: mine.filter((i) => i.status === 'done').length,
|
|
93
|
+
} : null,
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
debug.log('ops.seats', 'built', {
|
|
97
|
+
date, channel: channel === null ? 'unreadable' : channel.length, merged: merged === null ? 'unreadable' : merged.length,
|
|
98
|
+
checklist: checklist !== null, decisions: raisers !== null,
|
|
99
|
+
});
|
|
100
|
+
return { date, seats };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** KST calendar day → [start, end) in UTC ISO. */
|
|
104
|
+
export function kstDayRange(date: string): { start: string; end: string } {
|
|
105
|
+
const start = new Date(`${date}T00:00:00+09:00`);
|
|
106
|
+
return { start: start.toISOString(), end: new Date(start.getTime() + 86_400_000).toISOString() };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function todayKst(now = new Date()): string {
|
|
110
|
+
return new Intl.DateTimeFormat('sv-SE', { timeZone: 'Asia/Seoul' }).format(now);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** One snapshot per date for 60 s; concurrent requests share one build. */
|
|
114
|
+
export function createSeatsCache(sources: SeatsSources, now: () => number = Date.now, ttlMs = SEATS_CACHE_MS) {
|
|
115
|
+
const slots = new Map<string, { at: number; value: Promise<{ date: string; seats: SeatRow[] }> }>();
|
|
116
|
+
return (date: string) => {
|
|
117
|
+
const slot = slots.get(date);
|
|
118
|
+
if (slot && now() - slot.at < ttlMs) return slot.value;
|
|
119
|
+
const value = buildSeatsBoard(date, sources);
|
|
120
|
+
slots.set(date, { at: now(), value });
|
|
121
|
+
value.catch(() => slots.delete(date));
|
|
122
|
+
return value;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -36,6 +36,12 @@ export const PUBLIC_ROUTES: Array<PublicRoute> = [
|
|
|
36
36
|
path: '/v1/push/vapid-public-key',
|
|
37
37
|
why: 'PWA fetches the VAPID public key before a bearer is pasted',
|
|
38
38
|
},
|
|
39
|
+
{
|
|
40
|
+
method: 'GET',
|
|
41
|
+
match: 'exact',
|
|
42
|
+
path: '/v1/me',
|
|
43
|
+
why: 'operator signal only (draw the ops area or not); false unless operator.enabled, no other data',
|
|
44
|
+
},
|
|
39
45
|
{
|
|
40
46
|
method: 'GET',
|
|
41
47
|
match: 'exact',
|
|
@@ -56,6 +62,13 @@ export const PUBLIC_ROUTES: Array<PublicRoute> = [
|
|
|
56
62
|
selfVerified: true,
|
|
57
63
|
why: 'Pod GitHub App credential relay verifies its own run-scoped gh-credential token before minting a repository-scoped installation token',
|
|
58
64
|
},
|
|
65
|
+
{
|
|
66
|
+
method: 'POST',
|
|
67
|
+
match: 'exact',
|
|
68
|
+
path: '/v1/setup/claim',
|
|
69
|
+
selfVerified: true,
|
|
70
|
+
why: 'one-use setup link is verified by the claim handler without an owner bearer',
|
|
71
|
+
},
|
|
59
72
|
{
|
|
60
73
|
method: 'GET',
|
|
61
74
|
match: 'prefix',
|
|
@@ -76,7 +89,7 @@ export function isPublicRoute(
|
|
|
76
89
|
): boolean {
|
|
77
90
|
for (const route of PUBLIC_ROUTES) {
|
|
78
91
|
if (route.method !== method) continue;
|
|
79
|
-
if (route.path.startsWith('/v1/setup/') && !ctx.setupMode) continue;
|
|
92
|
+
if (route.path.startsWith('/v1/setup/') && route.path !== '/v1/setup/claim' && !ctx.setupMode) continue;
|
|
80
93
|
if (route.match === 'exact') {
|
|
81
94
|
if (pathname === route.path) return true;
|
|
82
95
|
} else if (pathname === route.path || pathname.startsWith(route.path)) {
|
|
@@ -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 });
|
|
@@ -18,6 +18,11 @@ import {
|
|
|
18
18
|
isHarnessSessionOrigin,
|
|
19
19
|
type SessionMeta,
|
|
20
20
|
} from '../../session/index.js';
|
|
21
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmdirSync, statSync, writeFileSync } from 'node:fs';
|
|
22
|
+
import { join } from 'node:path';
|
|
23
|
+
import { randomUUID } from 'node:crypto';
|
|
24
|
+
import { getProject } from '../../project/project-store.js';
|
|
25
|
+
import { sessionRoot } from '../../session/index.js';
|
|
21
26
|
import { checkAuth, type MetaApiOpts } from './meta-api.js';
|
|
22
27
|
import { jsonResponse } from './http-server.js';
|
|
23
28
|
|
|
@@ -32,6 +37,7 @@ export interface SessionStoreCard {
|
|
|
32
37
|
* `harness` = 하니스 자식이 만든 세션. 기본 목록에서 숨기고 includeHarness=1 로 포함. */
|
|
33
38
|
origin?: string;
|
|
34
39
|
sourceKind?: string;
|
|
40
|
+
projectId?: string;
|
|
35
41
|
createdAt: string;
|
|
36
42
|
updatedAt: string;
|
|
37
43
|
messageCount: number;
|
|
@@ -50,6 +56,7 @@ function toCard(m: SessionMeta, preview: string, nowMs: number): SessionStoreCar
|
|
|
50
56
|
source: m.source,
|
|
51
57
|
...(m.origin ? { origin: m.origin } : {}),
|
|
52
58
|
...(m.sourceKind ? { sourceKind: m.sourceKind } : {}),
|
|
59
|
+
...(m.projectId !== undefined ? { projectId: m.projectId } : {}),
|
|
53
60
|
createdAt: m.createdAt,
|
|
54
61
|
updatedAt: m.updatedAt,
|
|
55
62
|
messageCount: m.messageCount,
|
|
@@ -82,11 +89,13 @@ export function handleSessionsStoreList(req: Request, opts: MetaApiOpts): Respon
|
|
|
82
89
|
// 하니스 자식 세션(origin:'harness')은 기본 숨김 — includeHarness=1 로 포함.
|
|
83
90
|
// 신분이 없는 옛 세션은 사람 대화로 본다(숨기지 않음). 제목 문면으로 판정하지 않는다.
|
|
84
91
|
const includeHarness = url.searchParams.get('includeHarness') === '1';
|
|
92
|
+
const projectId = url.searchParams.get('projectId');
|
|
85
93
|
const limit = Math.min(Math.max(Number(url.searchParams.get('limit')) || 100, 1), 300);
|
|
86
94
|
const nowMs = Date.now();
|
|
87
95
|
|
|
88
96
|
let metas = listSessions({
|
|
89
97
|
limit: 600,
|
|
98
|
+
...(projectId !== null ? { projectId } : {}),
|
|
90
99
|
...(includeOperational ? {} : { excludeSourceKinds: ['scheduled'] }),
|
|
91
100
|
...(includeHarness ? {} : { excludeOrigins: [HARNESS_SESSION_ORIGIN] }),
|
|
92
101
|
}); // 이미 updatedAt DESC
|
|
@@ -157,6 +166,60 @@ export async function handleSessionsStoreFork(
|
|
|
157
166
|
);
|
|
158
167
|
}
|
|
159
168
|
|
|
169
|
+
/** PATCH /v1/sessions/store/:id — only project membership; leave transcript and other meta untouched. */
|
|
170
|
+
export async function handleSessionsStorePatch(req: Request, id: string, opts: MetaApiOpts): Promise<Response> {
|
|
171
|
+
if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
172
|
+
if (!id || id.includes('/') || id.includes('\\') || id.includes('..')) {
|
|
173
|
+
return jsonResponse({ ok: false, error: 'invalid_session_id' }, 400);
|
|
174
|
+
}
|
|
175
|
+
let body: unknown;
|
|
176
|
+
try { body = await req.json(); }
|
|
177
|
+
catch { return jsonResponse({ ok: false, error: 'invalid_json' }, 400); }
|
|
178
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)
|
|
179
|
+
|| !Object.hasOwn(body, 'projectId')
|
|
180
|
+
|| Object.keys(body).some((key) => key !== 'projectId')) {
|
|
181
|
+
return jsonResponse({ ok: false, error: 'invalid_project_id' }, 400);
|
|
182
|
+
}
|
|
183
|
+
const projectId = (body as { projectId: unknown }).projectId;
|
|
184
|
+
if (projectId !== null && (typeof projectId !== 'string' || !getProject(projectId))) {
|
|
185
|
+
return jsonResponse({ ok: false, error: 'invalid_project_id' }, 400);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const root = sessionRoot();
|
|
189
|
+
const lock = join(root, '.index.lock');
|
|
190
|
+
const deadline = Date.now() + 10_000;
|
|
191
|
+
for (;;) {
|
|
192
|
+
try { mkdirSync(lock); break; }
|
|
193
|
+
catch {
|
|
194
|
+
if (!existsSync(lock)) continue;
|
|
195
|
+
try {
|
|
196
|
+
if (Date.now() - statSync(lock).mtimeMs > 30_000) { rmdirSync(lock); continue; }
|
|
197
|
+
} catch { continue; }
|
|
198
|
+
if (Date.now() >= deadline) throw new Error(`timed out acquiring lock ${lock}`);
|
|
199
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
const path = join(root, 'index.json');
|
|
204
|
+
const entries = JSON.parse(readFileSync(path, 'utf8')) as SessionMeta[];
|
|
205
|
+
const pos = entries.findIndex((meta) => meta.id === id);
|
|
206
|
+
if (pos < 0) return jsonResponse({ ok: false, error: 'not_found' }, 404);
|
|
207
|
+
const meta = { ...entries[pos] };
|
|
208
|
+
if (projectId === null) delete meta.projectId;
|
|
209
|
+
else meta.projectId = projectId;
|
|
210
|
+
entries[pos] = meta;
|
|
211
|
+
const temp = `${path}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
|
|
212
|
+
writeFileSync(temp, JSON.stringify(entries, null, 2) + '\n', 'utf8');
|
|
213
|
+
renameSync(temp, path);
|
|
214
|
+
return jsonResponse({ ok: true, meta }, 200);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return jsonResponse({ ok: false, error: 'not_found' }, 404);
|
|
217
|
+
throw error;
|
|
218
|
+
} finally {
|
|
219
|
+
rmdirSync(lock);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
160
223
|
/** DELETE /v1/sessions/store/:id — on-disk 세션 삭제(index.json + <id>.jsonl 제거).
|
|
161
224
|
* 파괴적·복구불가. in-flight turn abort + in-memory history forget 도 fail-soft 로 동반. */
|
|
162
225
|
export function handleSessionsStoreDelete(req: Request, id: string, opts: MetaApiOpts): Response {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { claimSetupLinkToken } from '../../auth/setup-link-tokens.js';
|
|
2
|
+
import { jsonResponse } from './json-response.js';
|
|
3
|
+
|
|
4
|
+
/** A setup link is its own credential; this handler never reads the owner bearer. */
|
|
5
|
+
export async function handleSetupClaim(req: Request): Promise<Response> {
|
|
6
|
+
let body: unknown;
|
|
7
|
+
try {
|
|
8
|
+
body = await req.json();
|
|
9
|
+
} catch {
|
|
10
|
+
return jsonResponse({ error: 'bad_request' }, 400);
|
|
11
|
+
}
|
|
12
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)
|
|
13
|
+
|| typeof (body as Record<string, unknown>).t !== 'string'
|
|
14
|
+
|| !(body as { t: string }).t.trim()) {
|
|
15
|
+
return jsonResponse({ error: 'bad_request' }, 400);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const result = claimSetupLinkToken((body as { t: string }).t);
|
|
19
|
+
if (!result.ok) return jsonResponse({ error: result.reason }, 401);
|
|
20
|
+
return jsonResponse({ scope: 'setup', bearer: result.bearer, expiresAt: result.expiresAt });
|
|
21
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { debug } from '../../debug/log.js';
|
|
2
|
+
import { loginWithCodexBrowser } from '../../oauth/codex-browser-login.js';
|
|
3
|
+
import {
|
|
4
|
+
CODEX_DEVICE_LOGIN_URL,
|
|
5
|
+
exchangeDeviceAuthCode,
|
|
6
|
+
pollDeviceCode,
|
|
7
|
+
requestDeviceCode,
|
|
8
|
+
} from '../../oauth/codex.js';
|
|
9
|
+
import { expiresAtFromSeconds, saveTokens } from '../../oauth/store.js';
|
|
10
|
+
import { getUserConfig, reloadUserConfig, saveUserConfig } from '../../user-config.js';
|
|
11
|
+
|
|
12
|
+
type Mode = 'browser' | 'device';
|
|
13
|
+
type CodexLoginStatus =
|
|
14
|
+
| { state: 'idle' }
|
|
15
|
+
| { state: 'pending'; mode: Mode; authorizeUrl?: string; userCode?: string; verificationUrl?: string }
|
|
16
|
+
| { state: 'ok'; mode: Mode }
|
|
17
|
+
| { state: 'error'; mode: Mode; error: string };
|
|
18
|
+
|
|
19
|
+
const TIMEOUT_MS = 5 * 60_000;
|
|
20
|
+
const SAFE_ERRORS = [
|
|
21
|
+
'timeout', 'authorization-denied', 'browser-unavailable', 'port-unavailable',
|
|
22
|
+
'token-exchange', 'device-code-failed', 'device-poll-failed', 'config-save-failed',
|
|
23
|
+
] as const;
|
|
24
|
+
|
|
25
|
+
type Failure = typeof SAFE_ERRORS[number];
|
|
26
|
+
function reason(error: unknown, mode: Mode): Failure {
|
|
27
|
+
const message = error instanceof Error ? error.message : '';
|
|
28
|
+
for (const value of SAFE_ERRORS) if (message === value || message === `codex browser login failed: ${value}`) return value;
|
|
29
|
+
return mode === 'browser' ? 'browser-unavailable' : 'device-poll-failed';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface LoginDeps {
|
|
33
|
+
browser: (openBrowser: (url: string) => boolean) => Promise<unknown>;
|
|
34
|
+
device: (ready: (userCode: string, verificationUrl: string) => void) => Promise<unknown>;
|
|
35
|
+
saveProvider: () => void;
|
|
36
|
+
observe: (event: 'started' | 'ok' | 'error', mode: Mode) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function deviceLogin(ready: (userCode: string, verificationUrl: string) => void): Promise<void> {
|
|
40
|
+
const controller = new AbortController();
|
|
41
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
42
|
+
const fetchImpl = ((input, init) => fetch(input, { ...init, signal: controller.signal })) as typeof fetch;
|
|
43
|
+
try {
|
|
44
|
+
let code: Awaited<ReturnType<typeof requestDeviceCode>>;
|
|
45
|
+
try { code = await requestDeviceCode({ fetchImpl }); }
|
|
46
|
+
catch { throw new Error('device-code-failed'); }
|
|
47
|
+
if (controller.signal.aborted) throw new Error('timeout');
|
|
48
|
+
ready(code.user_code, code.verification_uri || CODEX_DEVICE_LOGIN_URL);
|
|
49
|
+
const interval = Math.max(3000, (code.interval ?? 0) * 1000);
|
|
50
|
+
const deadline = Date.now() + TIMEOUT_MS;
|
|
51
|
+
while (!controller.signal.aborted && Date.now() < deadline) {
|
|
52
|
+
await new Promise<void>((resolve) => setTimeout(resolve, Math.min(interval, Math.max(0, deadline - Date.now()))));
|
|
53
|
+
if (controller.signal.aborted || Date.now() >= deadline) break;
|
|
54
|
+
let result: Awaited<ReturnType<typeof pollDeviceCode>>;
|
|
55
|
+
try { result = await pollDeviceCode(code.device_auth_id, code.user_code, { fetchImpl }); }
|
|
56
|
+
catch { throw new Error('device-poll-failed'); }
|
|
57
|
+
if (!result) continue;
|
|
58
|
+
let token: Awaited<ReturnType<typeof exchangeDeviceAuthCode>>;
|
|
59
|
+
try { token = await exchangeDeviceAuthCode(result.authorization_code, result.code_verifier, { fetchImpl }); }
|
|
60
|
+
catch { throw new Error('token-exchange'); }
|
|
61
|
+
if (controller.signal.aborted) break;
|
|
62
|
+
saveTokens('openai-codex', {
|
|
63
|
+
accessToken: token.access_token,
|
|
64
|
+
refreshToken: token.refresh_token,
|
|
65
|
+
...(token.id_token ? { idToken: token.id_token } : {}),
|
|
66
|
+
expiresAt: expiresAtFromSeconds(token.expires_in),
|
|
67
|
+
scope: token.scope,
|
|
68
|
+
tokenType: token.token_type ?? 'Bearer',
|
|
69
|
+
}, { authMode: 'chatgpt' });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
throw new Error('timeout');
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (controller.signal.aborted) throw new Error('timeout');
|
|
75
|
+
throw error;
|
|
76
|
+
} finally {
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function saveCodexProvider(): void {
|
|
82
|
+
const cur = getUserConfig();
|
|
83
|
+
saveUserConfig({ ...cur, llm: { ...cur.llm, provider: 'openai-codex', apiKey: undefined, baseUrl: undefined, model: undefined } });
|
|
84
|
+
reloadUserConfig();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const defaultDeps: LoginDeps = {
|
|
88
|
+
browser: (openBrowser) => loginWithCodexBrowser({ openBrowser, timeoutMs: TIMEOUT_MS }),
|
|
89
|
+
device: deviceLogin,
|
|
90
|
+
saveProvider: saveCodexProvider,
|
|
91
|
+
observe: (event, mode) => { debug.log('setup.codex-login', event, { mode }); },
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
function json(body: CodexLoginStatus | { error: string }, status = 200): Response {
|
|
95
|
+
return new Response(JSON.stringify(body), {
|
|
96
|
+
status, headers: { 'content-type': 'application/json; charset=utf-8', 'access-control-allow-origin': '*' },
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** One in-process login survives leaving or reloading the setup page. */
|
|
101
|
+
export function createCodexLoginHandlers(deps: LoginDeps = defaultDeps) {
|
|
102
|
+
let status: CodexLoginStatus = { state: 'idle' };
|
|
103
|
+
let preparing: Promise<void> | undefined;
|
|
104
|
+
let generation = 0;
|
|
105
|
+
|
|
106
|
+
const get = (): Response => json(status);
|
|
107
|
+
|
|
108
|
+
const post = async (req: Request): Promise<Response> => {
|
|
109
|
+
let body: unknown;
|
|
110
|
+
try { body = await req.json(); } catch { return json({ error: 'invalid-json' }, 400); }
|
|
111
|
+
const mode = body && typeof body === 'object' && 'mode' in body ? body.mode : undefined;
|
|
112
|
+
if (mode !== 'browser' && mode !== 'device') return json({ error: 'invalid-mode' }, 400);
|
|
113
|
+
// Same method again → the login already in flight. A different method → start over; the older
|
|
114
|
+
// attempt's result is ignored (generation). Browser → device is the recovery path when a popup
|
|
115
|
+
// was blocked; device needs no local port, so it never collides with the browser's callback server.
|
|
116
|
+
if (status.state === 'pending' && status.mode === mode) {
|
|
117
|
+
if (preparing) await preparing;
|
|
118
|
+
return json(status);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
status = { state: 'pending', mode };
|
|
122
|
+
const attempt = ++generation;
|
|
123
|
+
deps.observe('started', mode);
|
|
124
|
+
let resolveReady!: () => void;
|
|
125
|
+
preparing = new Promise<void>((resolve) => { resolveReady = resolve; });
|
|
126
|
+
const ready = (fields: Pick<Extract<CodexLoginStatus, { state: 'pending' }>, 'authorizeUrl' | 'userCode' | 'verificationUrl'>) => {
|
|
127
|
+
if (generation === attempt && status.state === 'pending' && status.mode === mode) {
|
|
128
|
+
status = { ...status, ...fields };
|
|
129
|
+
resolveReady();
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
void (async () => {
|
|
133
|
+
try {
|
|
134
|
+
if (mode === 'browser') {
|
|
135
|
+
await deps.browser((url) => { ready({ authorizeUrl: url }); return true; });
|
|
136
|
+
} else {
|
|
137
|
+
await deps.device((userCode, verificationUrl) => ready({ userCode, verificationUrl }));
|
|
138
|
+
}
|
|
139
|
+
if (generation !== attempt) return;
|
|
140
|
+
try { deps.saveProvider(); } catch { throw new Error('config-save-failed'); }
|
|
141
|
+
status = { state: 'ok', mode };
|
|
142
|
+
deps.observe('ok', mode);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (generation !== attempt) return;
|
|
145
|
+
status = { state: 'error', mode, error: reason(error, mode) };
|
|
146
|
+
deps.observe('error', mode);
|
|
147
|
+
} finally {
|
|
148
|
+
resolveReady();
|
|
149
|
+
if (generation === attempt) preparing = undefined;
|
|
150
|
+
}
|
|
151
|
+
})();
|
|
152
|
+
await preparing;
|
|
153
|
+
return json(status);
|
|
154
|
+
};
|
|
155
|
+
return { get, post };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const login = createCodexLoginHandlers();
|
|
159
|
+
export const handleSetupCodexLoginGet = login.get;
|
|
160
|
+
export const handleSetupCodexLoginPost = login.post;
|