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,50 @@
|
|
|
1
|
+
import { debug } from '../debug/log.js';
|
|
2
|
+
import { listSubscriptions } from '../web-push/subscriptions.js';
|
|
3
|
+
import { sendPushToAll } from '../web-push/sender.js';
|
|
4
|
+
import { type CardTransport, type CardView } from './decision-cards.js';
|
|
5
|
+
import { DecisionLedger } from './decision-ledger.js';
|
|
6
|
+
|
|
7
|
+
const ID_IN_CARD = /^🗳 결정 요청 (D-\d{8}-\d+)(?: ·|\n)/;
|
|
8
|
+
const ID_IN_REMINDER = /^⏰ 기한 2시간 전 — (D-\d{8}-\d+) /;
|
|
9
|
+
const TITLE_LIMIT = 60;
|
|
10
|
+
|
|
11
|
+
export function webPushDecisionTransport(
|
|
12
|
+
ledger: DecisionLedger,
|
|
13
|
+
deps: { subscriptions?: typeof listSubscriptions; push?: typeof sendPushToAll } = {},
|
|
14
|
+
): CardTransport {
|
|
15
|
+
const subscriptions = deps.subscriptions ?? listSubscriptions;
|
|
16
|
+
const push = deps.push ?? sendPushToAll;
|
|
17
|
+
const title = (prefix: string, name: string) => prefix + Array.from(name).slice(0, TITLE_LIMIT - Array.from(prefix).length).join('');
|
|
18
|
+
const url = (id: string) => `/approvals?decision=${encodeURIComponent(id)}`;
|
|
19
|
+
const tag = (id: string) => `decision-${id}`;
|
|
20
|
+
return {
|
|
21
|
+
platform: 'webpush',
|
|
22
|
+
ownerChats: async () => subscriptions().length ? ['webpush'] : [],
|
|
23
|
+
send: async (_chat, view: CardView) => {
|
|
24
|
+
const id = ID_IN_CARD.exec(view.text)?.[1];
|
|
25
|
+
if (!id || !subscriptions().length) return null;
|
|
26
|
+
const entry = ledger.show(id);
|
|
27
|
+
if (entry.status !== 'open') return null;
|
|
28
|
+
const recommendation = 'skipped' in entry.recommendation ? '추천안 없음' : `추천안: ${entry.recommendation.option.toUpperCase()}`;
|
|
29
|
+
const body = `${recommendation}${entry.dueAt ? ` · 기한: ${new Intl.DateTimeFormat('ko-KR', { timeZone: 'Asia/Seoul', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false }).format(new Date(entry.dueAt))}` : ''}`;
|
|
30
|
+
const result = await push({ title: title('대표 결정 · ', entry.title), body, url: url(id), tag: tag(id) });
|
|
31
|
+
debug.log('decisions.webpush', 'card-sent', { id, delivered: result.delivered });
|
|
32
|
+
return result.delivered ? { chat: 'webpush', message: id } : null;
|
|
33
|
+
},
|
|
34
|
+
edit: async (ref, _view) => {
|
|
35
|
+
const id = ref.message;
|
|
36
|
+
const entry = ledger.show(id);
|
|
37
|
+
if (entry.status === 'open' || !subscriptions().length) return;
|
|
38
|
+
const result = await push({ title: title('결정됨 · ', entry.title), url: url(id), tag: tag(id), data: { silent: true } });
|
|
39
|
+
debug.log('decisions.webpush', 'card-closed', { id, delivered: result.delivered });
|
|
40
|
+
},
|
|
41
|
+
notify: async (_chat, text) => {
|
|
42
|
+
const id = ID_IN_REMINDER.exec(text)?.[1];
|
|
43
|
+
if (!id || !subscriptions().length) return;
|
|
44
|
+
const entry = ledger.show(id);
|
|
45
|
+
if (entry.status !== 'open') return;
|
|
46
|
+
const result = await push({ title: title('기한 알림 · ', entry.title), body: `기한 2시간 전 · ${id}`, url: url(id), tag: tag(id) });
|
|
47
|
+
debug.log('decisions.webpush', 'card-sent', { id, delivered: result.delivered });
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { listChecklist, type ChecklistItem } from '../release-loop/checklist.js';
|
|
2
|
+
|
|
3
|
+
export interface InterpretationCard {
|
|
4
|
+
what: string;
|
|
5
|
+
cell: string | null;
|
|
6
|
+
unknowns: string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type CellCandidate = Pick<ChecklistItem, 'id' | 'title'>;
|
|
10
|
+
|
|
11
|
+
/** The same SQLite-backed cells exposed by `elanous release checklist list --version <v>`. */
|
|
12
|
+
export function readCellCandidates(version: string): CellCandidate[] {
|
|
13
|
+
return listChecklist(version).items.map(({ id, title }) => ({ id, title }));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Interpret a single directive without querying state or inventing a release cell. */
|
|
17
|
+
export function interpretDirective(line: string, candidates: readonly CellCandidate[]): InterpretationCard {
|
|
18
|
+
if (/\r|\n/.test(line)) throw new Error('지시는 한 줄이어야 한다');
|
|
19
|
+
const what = line.trim();
|
|
20
|
+
if (!what) throw new Error('빈 지시는 해석할 수 없다');
|
|
21
|
+
|
|
22
|
+
const matches = candidates.filter(({ id, title }) =>
|
|
23
|
+
(id && new RegExp(`(^|[^\\p{L}\\p{N}])${id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?=$|[^\\p{L}\\p{N}]|[을를은는이가에의와과])`, 'u').test(what)) ||
|
|
24
|
+
(title && what.includes(title)));
|
|
25
|
+
|
|
26
|
+
if (matches.length === 1) return { what, cell: matches[0]!.id, unknowns: [] };
|
|
27
|
+
return {
|
|
28
|
+
what,
|
|
29
|
+
cell: null,
|
|
30
|
+
unknowns: [matches.length ? `칸 후보가 여럿이다: ${matches.map(({ id }) => id).join(', ')}` : '연결할 칸을 확인해야 한다'],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function renderInterpretationCard(card: InterpretationCard): string {
|
|
35
|
+
return `무엇: ${card.what}\n칸: ${card.cell ?? '확인 필요'}\n미확인: ${card.unknowns.join(', ') || '없음'}`;
|
|
36
|
+
}
|
package/src/discord.ts
CHANGED
|
@@ -837,13 +837,14 @@ export class DiscordBot {
|
|
|
837
837
|
}
|
|
838
838
|
|
|
839
839
|
// Addressed work is submitted only by configured owners; ordinary chat keeps its existing path.
|
|
840
|
-
if (this.allowedUsers.has(author.id) && parseSeatAddress(ctx.text)) {
|
|
840
|
+
if (this.allowedUsers.has(author.id) && (parseSeatAddress(ctx.text) || (ctx.isDm && ctx.text && !ctx.text.startsWith('/')))) {
|
|
841
841
|
const threadId = typeof m.thread_id === 'string' && m.thread_id ? m.thread_id : undefined;
|
|
842
842
|
const replyChannelId = threadId ?? ctx.channelId;
|
|
843
843
|
try {
|
|
844
844
|
const seatReply = await handleDiscordSeatWork(ctx.text, {
|
|
845
845
|
channelId: replyChannelId,
|
|
846
846
|
messageId: ctx.messageId,
|
|
847
|
+
userId: ctx.userId, isDm: ctx.isDm,
|
|
847
848
|
...(threadId ? { threadId } : {}),
|
|
848
849
|
}, this.seatWorkDeps);
|
|
849
850
|
if (seatReply !== null) {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { LLMToolSpec } from '../llm.js';
|
|
2
|
+
import { debug } from '../debug/log.js';
|
|
3
|
+
import { getUserConfig } from '../user-config.js';
|
|
4
|
+
import { getSecretAsync } from '../nexus/config/secrets/index.js';
|
|
5
|
+
import { fetchLinearProjectIssues, type LinearProjectIssue } from '../connectors/linear.js';
|
|
6
|
+
import { formatElanousCard } from './elanous-card.js';
|
|
7
|
+
|
|
8
|
+
export const COO_ADMIN_SPEC: LLMToolSpec = {
|
|
9
|
+
name: 'coo_admin',
|
|
10
|
+
description: '읽기 전용 Linear COO 행정 조회 — «행정 뭐 남았어 · COO 할 일 · 마감 다가오는 행정» 질문에 사용. 외부 행정 프로젝트의 열린 일을 마감 순으로 읽는다.',
|
|
11
|
+
parameters: { type: 'object', properties: {}, required: [] },
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export interface CooAdminDeps {
|
|
15
|
+
getSecret?: (id: string) => Promise<string | undefined>;
|
|
16
|
+
fetch?: typeof fetch;
|
|
17
|
+
project?: string;
|
|
18
|
+
now?: Date;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Reads the configured Linear project without changing any Linear issue. */
|
|
22
|
+
export async function dispatchCooAdmin(_args: Record<string, unknown>, deps: CooAdminDeps = {}): Promise<string> {
|
|
23
|
+
let count = 0;
|
|
24
|
+
let overdue = 0;
|
|
25
|
+
try {
|
|
26
|
+
const apiKey = await (deps.getSecret ?? getSecretAsync)('connector.linear.apiKey');
|
|
27
|
+
if (!apiKey) {
|
|
28
|
+
debug.log('coo.admin', 'failed', { count, overdue, reason: 'missing-key' });
|
|
29
|
+
return 'Linear 키가 없습니다 — `elanous connector linear set-key`';
|
|
30
|
+
}
|
|
31
|
+
const project = deps.project ?? getUserConfig().coo?.linearProject ?? '외부 행정·큰 일 (COO)';
|
|
32
|
+
const issues = await fetchLinearProjectIssues({ apiKey, project, fetch: deps.fetch ?? fetch });
|
|
33
|
+
const today = deps.now ?? new Date();
|
|
34
|
+
const todayUtc = Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate());
|
|
35
|
+
const daysUntil = (date: string) => Math.round((Date.parse(`${date.slice(0, 10)}T00:00:00Z`) - todayUtc) / 86_400_000);
|
|
36
|
+
issues.sort((a, b) => {
|
|
37
|
+
const deadlineOrder = a.dueDate == null ? (b.dueDate == null ? 0 : 1)
|
|
38
|
+
: b.dueDate == null ? -1 : daysUntil(a.dueDate) - daysUntil(b.dueDate);
|
|
39
|
+
return deadlineOrder || (a.priority === 0 ? 5 : a.priority) - (b.priority === 0 ? 5 : b.priority);
|
|
40
|
+
});
|
|
41
|
+
count = issues.length;
|
|
42
|
+
const lines = issues.map((item: LinearProjectIssue) => {
|
|
43
|
+
const days = item.dueDate ? daysUntil(item.dueDate) : null;
|
|
44
|
+
if (days !== null && days < 0) overdue++;
|
|
45
|
+
const deadline = days === null ? '마감 없음' : days < 0 ? `지남 ${-days}일` : `D-${days}`;
|
|
46
|
+
return `${deadline} · ${item.title} · ${item.state.name} · ${item.assignee?.name ?? '미배정'}\n${item.url}`;
|
|
47
|
+
});
|
|
48
|
+
debug.log('coo.admin', 'read', { count, overdue, reason: 'ok' });
|
|
49
|
+
const scope = issues.truncated ? '최대 250건 중 조회한 항목만 마감 순으로 표시 (이후 항목은 포함되지 않음)\n' : '';
|
|
50
|
+
const prose = lines.length ? `${scope}${lines.join('\n')}` : '남은 행정 0건';
|
|
51
|
+
const card = formatElanousCard({ kind: 'coo-admin', items: issues.map(item => ({
|
|
52
|
+
title: item.title, due: item.dueDate?.slice(0, 10) ?? null,
|
|
53
|
+
daysLeft: item.dueDate ? daysUntil(item.dueDate) : null,
|
|
54
|
+
state: item.state.name, owner: item.assignee?.name ?? '미배정', url: item.url,
|
|
55
|
+
})) });
|
|
56
|
+
return `${prose}\n${card}`;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
59
|
+
debug.log('coo.admin', 'failed', { count, overdue, reason: 'linear-read-failed' });
|
|
60
|
+
return `못 읽었습니다(${reason})`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -28,6 +28,9 @@ import { OPS_STATUS_SPEC, dispatchOpsStatus } from './ops-status-tool.js';
|
|
|
28
28
|
import { SE_BUILD_SPEC, dispatchSeBuild } from './se-build-tool.js';
|
|
29
29
|
import { LOGS_QUERY_SPEC, dispatchLogsQuery } from './logs-tool.js';
|
|
30
30
|
import { MISSION_DECIDE_SPEC, dispatchMissionDecide } from '../autopilot/mission-decide-tool.js';
|
|
31
|
+
import { COO_ADMIN_SPEC, dispatchCooAdmin } from './coo-admin-tool.js';
|
|
32
|
+
import { RELEASE_STATUS_SPEC, RELEASE_CHANGE_SPEC, dispatchReleaseStatus, dispatchReleaseChange } from './release-tool.js';
|
|
33
|
+
import { OPS_SEATS_SPEC, DECISIONS_PENDING_SPEC, dispatchOpsSeats, dispatchDecisionsPending } from './ops-facts-tool.js';
|
|
31
34
|
|
|
32
35
|
/** memory_recall — 크로스서피스 기억(자기 발송 원장 회상). 도메인 무관(domain 필터는 옵션).
|
|
33
36
|
* finance-tools 에서 L2 로 이관(2026-07-08). */
|
|
@@ -118,6 +121,8 @@ const CORE_TOOL_HANDLERS: Record<string, (args: Record<string, unknown>, onParti
|
|
|
118
121
|
se_build: dispatchSeBuild, // SE 격리 빌드 관측(빌드 안 뭐 하나·로그 tail·worktree diff)·코어
|
|
119
122
|
logs_query: dispatchLogsQuery, // 크로스서피스 디버그 로그 조회(logs.db·LF3)·READ-ONLY 코어
|
|
120
123
|
mission_decide: dispatchMissionDecide, // 미션 결정 기록(re-ground·defer·boundary…)·3박자 주입·코어
|
|
124
|
+
coo_admin: (args) => dispatchCooAdmin(args),
|
|
125
|
+
release_status: async (args) => dispatchReleaseStatus(args),
|
|
121
126
|
};
|
|
122
127
|
|
|
123
128
|
/** Worker-side search entry: dispatch must not re-enter its own budget wrapper. */
|
|
@@ -136,7 +141,7 @@ const SELF_RECALL_BUDGET_SPEC: LLMToolSpec = {
|
|
|
136
141
|
},
|
|
137
142
|
},
|
|
138
143
|
};
|
|
139
|
-
export const CORE_TOOL_SPECS: LLMToolSpec[] = [SCHEDULE_MANAGE_SPEC, SESSION_MANAGE_SPEC, MEMORY_RECALL_SPEC, FACT_CHECK_SPEC, SELF_RECALL_BUDGET_SPEC, AUTOPILOT_MISSION_SPEC, OPS_STATUS_SPEC, SE_BUILD_SPEC, LOGS_QUERY_SPEC, MISSION_DECIDE_SPEC];
|
|
144
|
+
export const CORE_TOOL_SPECS: LLMToolSpec[] = [SCHEDULE_MANAGE_SPEC, SESSION_MANAGE_SPEC, MEMORY_RECALL_SPEC, FACT_CHECK_SPEC, SELF_RECALL_BUDGET_SPEC, AUTOPILOT_MISSION_SPEC, OPS_STATUS_SPEC, SE_BUILD_SPEC, LOGS_QUERY_SPEC, MISSION_DECIDE_SPEC, COO_ADMIN_SPEC, RELEASE_STATUS_SPEC, RELEASE_CHANGE_SPEC, OPS_SEATS_SPEC, DECISIONS_PENDING_SPEC];
|
|
140
145
|
|
|
141
146
|
export interface CoreTools {
|
|
142
147
|
specs: LLMToolSpec[];
|
|
@@ -153,11 +158,14 @@ export function buildCoreTools(recallSearch?: RecallSearch): CoreTools {
|
|
|
153
158
|
names,
|
|
154
159
|
dispatch: async (name, args, onPartial, context) => {
|
|
155
160
|
const h = CORE_TOOL_HANDLERS[name];
|
|
156
|
-
if (!h) return { error: `unknown core tool: ${name}` };
|
|
161
|
+
if (!h && name !== 'release_change' && name !== 'ops_seats' && name !== 'decisions_pending') return { error: `unknown core tool: ${name}` };
|
|
162
|
+
if (name === 'release_change') return dispatchReleaseChange(args, context);
|
|
163
|
+
if (name === 'ops_seats') return dispatchOpsSeats(args, context);
|
|
164
|
+
if (name === 'decisions_pending') return dispatchDecisionsPending(context);
|
|
157
165
|
if (name === 'memory_recall' || name === 'self_recall') {
|
|
158
166
|
return runRecall(name, args, context ?? { surface: 'skill' }, recallSearch, onPartial);
|
|
159
167
|
}
|
|
160
|
-
return h(args, onPartial);
|
|
168
|
+
return h!(args, onPartial);
|
|
161
169
|
},
|
|
162
170
|
};
|
|
163
171
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface ElanousCard {
|
|
2
|
+
kind: 'coo-admin' | 'release-schedule' | 'release-checklist';
|
|
3
|
+
items: Array<{ title: string; due: string | null; daysLeft: number | null; state: string; owner: string; url?: string }>;
|
|
4
|
+
meta?: Record<string, unknown>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function formatElanousCard(card: ElanousCard): string {
|
|
8
|
+
return `\`\`\`elanous-card\n${JSON.stringify(card)}\n\`\`\``;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Reads every valid card block in a tool response; ignores unrelated prose and malformed blocks. */
|
|
12
|
+
export function parseElanousCard(text: string): ElanousCard[] {
|
|
13
|
+
const cards: ElanousCard[] = [];
|
|
14
|
+
for (const match of text.matchAll(/```elanous-card\r?\n([^\r\n]+)\r?\n```/g)) {
|
|
15
|
+
try {
|
|
16
|
+
const value: unknown = JSON.parse(match[1]!);
|
|
17
|
+
if (!value || typeof value !== 'object') continue;
|
|
18
|
+
const card = value as Partial<ElanousCard>;
|
|
19
|
+
if (!['coo-admin', 'release-schedule', 'release-checklist'].includes(card.kind ?? '') || !Array.isArray(card.items)) continue;
|
|
20
|
+
if (!card.items.every(item => item && typeof item.title === 'string' &&
|
|
21
|
+
(item.due === null || /^\d{4}-\d{2}-\d{2}$/.test(item.due)) &&
|
|
22
|
+
(item.daysLeft === null || typeof item.daysLeft === 'number') &&
|
|
23
|
+
typeof item.state === 'string' && typeof item.owner === 'string' &&
|
|
24
|
+
(item.url === undefined || typeof item.url === 'string'))) continue;
|
|
25
|
+
cards.push(card as ElanousCard);
|
|
26
|
+
} catch { /* Other fenced content is not a card. */ }
|
|
27
|
+
}
|
|
28
|
+
return cards;
|
|
29
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { LLMToolSpec } from '../llm.js';
|
|
2
|
+
import type { ToolRuntimeContext } from '../tool-runtime/types.js';
|
|
3
|
+
import { debug } from '../debug/log.js';
|
|
4
|
+
import { DecisionLedger, type DecisionEntry } from '../decisions/decision-ledger.js';
|
|
5
|
+
import { buildSeatsBoard, todayKst, type SeatsSources } from '../nexus/api/ops-seats.js';
|
|
6
|
+
import { liveSeatsSources } from '../nexus/api/ops-seats-sources.js';
|
|
7
|
+
|
|
8
|
+
export const OPS_SEATS_SPEC: LLMToolSpec = {
|
|
9
|
+
name: 'ops_seats',
|
|
10
|
+
description: '오너 전용 읽기 — 지금 자리(OP·TC·MK·UX)별 진행(now), 오늘 착지(landed), 빨강(red), 미결 결정 수를 원장에서 확인한다. null은 못 읽은 원천이며 0이나 빈 목록이 아니다.',
|
|
11
|
+
parameters: { type: 'object', properties: { date: { type: 'string', description: '조회할 KST 날짜 YYYY-MM-DD. 생략하면 KST 오늘.' } }, required: [] },
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const DECISIONS_PENDING_SPEC: LLMToolSpec = {
|
|
15
|
+
name: 'decisions_pending',
|
|
16
|
+
description: '오너 전용 읽기 — 결정 원장의 열린 결정을 기한 순으로 조회한다. 결정 선택·기록은 하지 않는다.',
|
|
17
|
+
parameters: { type: 'object', properties: {}, required: [] },
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export interface OpsFactsDeps {
|
|
21
|
+
seatsSources?: () => SeatsSources;
|
|
22
|
+
listDecisions?: (filters: { status: 'open' }) => DecisionEntry[];
|
|
23
|
+
today?: () => string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function ownerVerified(context?: ToolRuntimeContext): boolean {
|
|
27
|
+
return context?.requestOrigin !== 'external-agent' && !!context?.sessionId && !!context?.verifiedOwner?.id.trim();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function logCall(tool: 'ops_seats' | 'decisions_pending', ok: boolean, nulls: number): void {
|
|
31
|
+
debug.log('ops.facts', 'call', { tool, ok, nulls });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Unreadable seat sources remain null; only rename the board's `blocked` field to `red` for this tool. */
|
|
35
|
+
export async function dispatchOpsSeats(args: Record<string, unknown>, context?: ToolRuntimeContext, deps: OpsFactsDeps = {}) {
|
|
36
|
+
if (!ownerVerified(context)) {
|
|
37
|
+
logCall('ops_seats', false, 0);
|
|
38
|
+
return { error: '오너 확인이 필요한 조회다' };
|
|
39
|
+
}
|
|
40
|
+
const date = args.date === undefined ? (deps.today ?? todayKst)() : args.date;
|
|
41
|
+
if (typeof date !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(date) ||
|
|
42
|
+
!Number.isFinite(Date.parse(`${date}T00:00:00Z`)) || new Date(`${date}T00:00:00Z`).toISOString().slice(0, 10) !== date) {
|
|
43
|
+
logCall('ops_seats', false, 0);
|
|
44
|
+
return { error: 'KST 날짜는 YYYY-MM-DD 여야 한다' };
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const board = await buildSeatsBoard(date, (deps.seatsSources ?? liveSeatsSources)());
|
|
48
|
+
const seats = board.seats.map(({ seat, role, now, landed, blocked, pendingDecisions }) =>
|
|
49
|
+
({ seat, role, now, landed, red: blocked, pendingDecisions }));
|
|
50
|
+
const nulls = seats.reduce((n, row) => n + [row.now, row.landed, row.red, row.pendingDecisions].filter(v => v === null).length, 0);
|
|
51
|
+
logCall('ops_seats', true, nulls);
|
|
52
|
+
return { note: 'null = 못 읽음(없다가 아님); now는 오늘 기록이 없어도 null일 수 있다.', date: board.date, seats };
|
|
53
|
+
} catch {
|
|
54
|
+
logCall('ops_seats', false, 1);
|
|
55
|
+
return { error: '자리 현황을 읽지 못했다', seats: null };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Only the five requested fields leave the decision ledger. Undated entries follow dated entries. */
|
|
60
|
+
export function dispatchDecisionsPending(context?: ToolRuntimeContext, deps: OpsFactsDeps = {}) {
|
|
61
|
+
if (!ownerVerified(context)) {
|
|
62
|
+
logCall('decisions_pending', false, 0);
|
|
63
|
+
return { error: '오너 확인이 필요한 조회다' };
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const entries = (deps.listDecisions ?? ((filters) => new DecisionLedger().list(filters)))({ status: 'open' });
|
|
67
|
+
const items = entries.filter(e => e.status === 'open')
|
|
68
|
+
.sort((a, b) => (a.dueAt ?? '\uffff').localeCompare(b.dueAt ?? '\uffff') || a.id.localeCompare(b.id))
|
|
69
|
+
.map(({ id, title, category, dueAt, raisedBy }) => ({ id, title, category, dueAt: dueAt ?? null, raisedBy: { track: raisedBy.track ?? null } }));
|
|
70
|
+
logCall('decisions_pending', true, items.reduce((n, item) => n + Number(item.dueAt === null) + Number(item.raisedBy.track === null), 0));
|
|
71
|
+
return { items };
|
|
72
|
+
} catch {
|
|
73
|
+
logCall('decisions_pending', false, 1);
|
|
74
|
+
return { error: '결정 원장을 읽지 못했다', items: null };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -13,9 +13,9 @@ import { conatusEnv } from './conatus-env.js';
|
|
|
13
13
|
import { spillLongContent } from '../storage/content-spill.js';
|
|
14
14
|
import { openSurfaceEventsDb, recordEvent } from './surface-events.js';
|
|
15
15
|
import { latestUserIntentTs } from '../user-intent/index.js';
|
|
16
|
-
import { getUserConfig } from '../user-config.js';
|
|
16
|
+
import { getUserConfig, type UserConfig } from '../user-config.js';
|
|
17
17
|
import { resolveChannelBotToken } from '../channel-bot-token.js';
|
|
18
|
-
import { kindRouteTarget } from './telegram-kind-route.js';
|
|
18
|
+
import { isOperationalKind, kindRouteTarget, logKindRouteFallback, mainHomeTarget } from './telegram-kind-route.js';
|
|
19
19
|
import { debug } from '../debug/log.js';
|
|
20
20
|
import { resolveDaemonEndpoint } from '../nexus/daemon-endpoint.js';
|
|
21
21
|
import { effectiveInstanceRoot, prodInstanceRoot } from '../instance/resolve.js';
|
|
@@ -210,9 +210,14 @@ export function flushDeferred(path = DEFERRED_PATH): number {
|
|
|
210
210
|
].join('\n');
|
|
211
211
|
observeFlush(items.length, lagMin, [...new Set(items.map(i => i.kind))]);
|
|
212
212
|
let delivered = 0;
|
|
213
|
-
// origin 없는
|
|
213
|
+
// origin 없는 finance 는 기존 report 묶음. 운영 보류분은 별도로 묶어 운영 봇 경계를 유지한다.
|
|
214
214
|
const noOrigin = items.filter(i => !i.origin);
|
|
215
|
-
|
|
215
|
+
const tradingBatch = noOrigin.filter(i => !isOperationalKind(i.kind));
|
|
216
|
+
if (tradingBatch.length && deliver(fmt(tradingBatch), 'report')) delivered += tradingBatch.length;
|
|
217
|
+
for (const kind of new Set(noOrigin.filter(i => isOperationalKind(i.kind)).map(i => i.kind))) {
|
|
218
|
+
const batch = noOrigin.filter(i => i.kind === kind);
|
|
219
|
+
if (deliver(fmt(batch), kind)) delivered += batch.length;
|
|
220
|
+
}
|
|
216
221
|
// origin 있는 것 = 발신 채널·스레드별로 묶어 서로 다른 수신자에게 섞이지 않게 발송.
|
|
217
222
|
const groups = new Map<string, { origin: MissionOrigin; list: Item[] }>();
|
|
218
223
|
for (const it of items) {
|
|
@@ -402,20 +407,43 @@ function sendTelegramRaw(token: string, chatId: string | number, text: string, t
|
|
|
402
407
|
return ok;
|
|
403
408
|
}
|
|
404
409
|
|
|
405
|
-
/** 텔레그램 직접
|
|
406
|
-
function sendTelegramDirect(
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
410
|
+
/** 텔레그램 직접 발송. 운영 kind 는 메인 봇/홈 밖의 env 로 내려가지 않는다. */
|
|
411
|
+
export function sendTelegramDirect(
|
|
412
|
+
text: string, kind?: string,
|
|
413
|
+
deps: { config?: UserConfig; sendRaw?: typeof sendTelegramRaw; legacyEnv?: typeof conatusEnv } = {},
|
|
414
|
+
): boolean {
|
|
415
|
+
const sendRaw = deps.sendRaw ?? sendTelegramRaw;
|
|
416
|
+
let cfg: UserConfig | undefined;
|
|
417
|
+
try { cfg = deps.config ?? getUserConfig(); } catch { /* unavailable config: operational delivery still fails closed */ }
|
|
418
|
+
if (cfg) {
|
|
419
|
+
try {
|
|
420
|
+
const target = kindRouteTarget(cfg, kind);
|
|
421
|
+
if (target) return sendRaw(target.botToken, String(target.chatId), text);
|
|
422
|
+
} catch { /* unavailable channel routing: fall through */ }
|
|
423
|
+
}
|
|
424
|
+
if (isOperationalKind(kind)) {
|
|
425
|
+
let home: ReturnType<typeof mainHomeTarget> = null;
|
|
426
|
+
let mainToken = '';
|
|
427
|
+
try {
|
|
428
|
+
if (cfg) {
|
|
429
|
+
mainToken = resolveChannelBotToken('telegram', cfg).token;
|
|
430
|
+
home = mainHomeTarget(cfg);
|
|
431
|
+
}
|
|
432
|
+
} catch { /* unavailable main bot */ }
|
|
433
|
+
const rcToken = cfg?.telegram.reportChannel?.botToken;
|
|
434
|
+
logKindRouteFallback(kind, home ? 'main-home' : 'none', !!mainToken && !!cfg?.telegram.reportChannel && (!rcToken || rcToken === mainToken));
|
|
435
|
+
if (home) return sendRaw(home.botToken, String(home.chatId), text);
|
|
436
|
+
console.error('[outbound] 운영 kind 메인 봇/홈 미설정 — 콘솔 출력\n' + text);
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
412
439
|
let tok = process.env.TELEGRAM_BOT_TOKEN || '';
|
|
413
440
|
let chat = process.env.TELEGRAM_CHAT_ID || '';
|
|
414
441
|
if (!tok || !chat) {
|
|
415
|
-
const env = conatusEnv();
|
|
442
|
+
const env = (deps.legacyEnv ?? conatusEnv)();
|
|
416
443
|
tok = tok || env.TELEGRAM_BOT_TOKEN || '';
|
|
417
444
|
chat = chat || env.TELEGRAM_CHAT_ID || '';
|
|
418
445
|
}
|
|
446
|
+
logKindRouteFallback(kind, tok && chat ? 'conatus-env' : 'none', false);
|
|
419
447
|
if (!tok || !chat) { console.error('[outbound] 토큰/chat 미설정 — 콘솔 출력\n' + text); return false; }
|
|
420
|
-
return
|
|
448
|
+
return sendRaw(tok, chat, text);
|
|
421
449
|
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { LLMToolSpec } from '../llm.js';
|
|
2
|
+
import type { ToolRuntimeContext } from '../tool-runtime/types.js';
|
|
3
|
+
import { debug } from '../debug/log.js';
|
|
4
|
+
import { requestConfirmation, type ConfirmOpts, type ConfirmResult } from '../hitl/confirm.js';
|
|
5
|
+
import { devVersion, listChecklist, summarizeChecklist, type Checklist } from '../release-loop/checklist.js';
|
|
6
|
+
import { add, move, validateVersion } from '../release-loop/feature-store.js';
|
|
7
|
+
import { formatKst, getSchedule, setSchedule, type ReleaseSchedule } from '../release-loop/release-schedule.js';
|
|
8
|
+
import { latestGraphRun, type GraphRunState } from '../graph-runner/runner.js';
|
|
9
|
+
import { formatElanousCard, type ElanousCard } from './elanous-card.js';
|
|
10
|
+
|
|
11
|
+
export const RELEASE_STATUS_SPEC: LLMToolSpec = {
|
|
12
|
+
name: 'release_status',
|
|
13
|
+
description: '판 어디까지 · 컷 언제 · 릴리스 상황 · 판올림 · 체크리스트 · 남은 칸을 읽는다. 판 일정, 빨강 칸, 담당별 노랑, 최근 발행 런을 조회하는 읽기 전용 도구. 행정 업무는 coo_admin.',
|
|
14
|
+
parameters: { type: 'object', properties: { version: { type: 'string', description: '조회할 판. 생략하면 개발 판과 다음 판.' } }, required: [] },
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const RELEASE_CHANGE_SPEC: LLMToolSpec = {
|
|
18
|
+
name: 'release_change',
|
|
19
|
+
description: '오너의 판올림 변경 요청 — 체크리스트 칸 추가, 칸을 다른 판으로 이동, 판 컷·착지 마감 수정. 실행 전에 사람에게 한 번 확인받는다. 읽기 요청에는 release_status 사용.',
|
|
20
|
+
parameters: { type: 'object', properties: {
|
|
21
|
+
action: { type: 'string', enum: ['add-item', 'move-item', 'set-schedule'] },
|
|
22
|
+
version: { type: 'string', description: 'add-item 또는 set-schedule 대상 판' },
|
|
23
|
+
id: { type: 'string', description: '칸 id (add-item 또는 move-item)' },
|
|
24
|
+
title: { type: 'string', description: '추가할 칸 제목' },
|
|
25
|
+
owner: { type: 'string', description: '추가할 칸 담당(선택)' },
|
|
26
|
+
from: { type: 'string', description: '이동 전 판' },
|
|
27
|
+
to: { type: 'string', description: '이동 후 판' },
|
|
28
|
+
cutAt: { type: 'string', description: 'ISO 오프셋 포함 컷 시각(선택)' },
|
|
29
|
+
landBy: { type: 'string', description: 'ISO 오프셋 포함 착지 마감(선택)' },
|
|
30
|
+
}, required: ['action'] },
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export interface ReleaseToolDeps {
|
|
34
|
+
now?: Date;
|
|
35
|
+
devVersion?: () => string;
|
|
36
|
+
checklist?: (version: string) => Checklist;
|
|
37
|
+
schedule?: (version: string) => ReleaseSchedule | null;
|
|
38
|
+
latestRun?: (graphId: string) => GraphRunState | null;
|
|
39
|
+
confirm?: (options: ConfirmOpts) => Promise<ConfirmResult>;
|
|
40
|
+
add?: typeof add;
|
|
41
|
+
move?: typeof move;
|
|
42
|
+
setSchedule?: typeof setSchedule;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function nextVersion(version: string): string {
|
|
46
|
+
const [major, minor, patch] = version.split('.').map(Number);
|
|
47
|
+
return `${major}.${minor}.${patch! + 1}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function kstDate(iso: string): string {
|
|
51
|
+
return new Date(Date.parse(iso) + 9 * 60 * 60_000).toISOString().slice(0, 10);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function daysLeft(iso: string, now: Date): number {
|
|
55
|
+
const due = Date.parse(`${kstDate(iso)}T00:00:00Z`);
|
|
56
|
+
const today = Date.parse(`${kstDate(now.toISOString())}T00:00:00Z`);
|
|
57
|
+
return (due - today) / 86_400_000;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function dDay(days: number): string {
|
|
61
|
+
return days < 0 ? `D+${-days}` : days === 0 ? 'D-day' : `D-${days}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function dispatchReleaseStatus(args: Record<string, unknown>, deps: ReleaseToolDeps = {}): { text: string; structured: Record<string, unknown> } {
|
|
65
|
+
const requested = args.version;
|
|
66
|
+
if (requested !== undefined && (typeof requested !== 'string' || !requested.trim())) throw new Error('판 이름이 필요합니다');
|
|
67
|
+
const version = requested ? null : (deps.devVersion?.() ?? devVersion());
|
|
68
|
+
const versions = requested ? [requested as string] : [version!, nextVersion(version!)];
|
|
69
|
+
const now = deps.now ?? new Date();
|
|
70
|
+
const run = (deps.latestRun ?? latestGraphRun)('release-loop');
|
|
71
|
+
const latest = run ? { status: run.status, lastNode: run.path.at(-1) ?? null, startedAt: run.startedAt ?? null } : null;
|
|
72
|
+
const entries = versions.map(v => {
|
|
73
|
+
validateVersion(v);
|
|
74
|
+
const schedule = (deps.schedule ?? getSchedule)(v);
|
|
75
|
+
const data = (deps.checklist ?? listChecklist)(v);
|
|
76
|
+
const summary = summarizeChecklist(data);
|
|
77
|
+
const red = data.items.filter(item => item.status === 'red').map(({ id, title, owner }) => ({ id, title, owner: owner ?? '미배정' }));
|
|
78
|
+
const yellowByOwner: Record<string, number> = {};
|
|
79
|
+
for (const item of data.items.filter(item => item.status === 'yellow')) {
|
|
80
|
+
const owner = item.owner ?? '미배정';
|
|
81
|
+
yellowByOwner[owner] = (yellowByOwner[owner] ?? 0) + 1;
|
|
82
|
+
}
|
|
83
|
+
const cutDaysLeft = schedule ? daysLeft(schedule.cutAt, now) : null;
|
|
84
|
+
const landDaysLeft = schedule?.landBy ? daysLeft(schedule.landBy, now) : null;
|
|
85
|
+
const cutHoursLeft = schedule ? Math.ceil((Date.parse(schedule.cutAt) - now.getTime()) / 3_600_000) : null;
|
|
86
|
+
const landHoursLeft = schedule?.landBy ? Math.ceil((Date.parse(schedule.landBy) - now.getTime()) / 3_600_000) : null;
|
|
87
|
+
return { version: v, schedule: schedule ? { cutAt: schedule.cutAt, landBy: schedule.landBy, cutDaysLeft, landDaysLeft, cutHoursLeft, landHoursLeft } : null,
|
|
88
|
+
counts: { green: summary.green, yellow: summary.yellow, red: summary.red, done: summary.done }, red, yellowByOwner };
|
|
89
|
+
});
|
|
90
|
+
const lines = entries.map(entry => {
|
|
91
|
+
const schedule = entry.schedule;
|
|
92
|
+
return `${entry.version} · 컷 ${schedule ? formatKst(schedule.cutAt) : '미정'}${schedule?.landBy ? ` · 착지 마감 ${formatKst(schedule.landBy)}` : ' · 착지 마감 미정'}${schedule ? ` · 컷 ${dDay(schedule.cutDaysLeft!)} (${schedule.cutHoursLeft}시간)${schedule.landDaysLeft !== null ? ` · 착지 ${dDay(schedule.landDaysLeft)} (${schedule.landHoursLeft}시간)` : ''}` : ''}\n` +
|
|
93
|
+
`초록 ${entry.counts.green} · 노랑 ${entry.counts.yellow} · 빨강 ${entry.counts.red} · 끝 ${entry.counts.done}\n` +
|
|
94
|
+
`빨강 칸: ${entry.red.length ? entry.red.map(item => `${item.id} ${item.title} (${item.owner})`).join(', ') : '없음'}\n` +
|
|
95
|
+
`담당별 노랑: ${Object.keys(entry.yellowByOwner).length ? Object.entries(entry.yellowByOwner).map(([owner, count]) => `${owner} ${count}`).join(', ') : '없음'}`;
|
|
96
|
+
});
|
|
97
|
+
lines.push(latest ? `최근 발행 런: ${latest.status} · 끝 노드 ${latest.lastNode ?? '없음'} · 시작 ${latest.startedAt ?? '미상'}` : '최근 발행 런: 없음');
|
|
98
|
+
const cards: ElanousCard[] = [
|
|
99
|
+
{ kind: 'release-schedule', items: entries.map(entry => ({ title: `${entry.version} 컷`, due: entry.schedule ? kstDate(entry.schedule.cutAt) : null, daysLeft: entry.schedule?.cutDaysLeft ?? null, state: entry.schedule ? 'scheduled' : 'undecided', owner: 'release' })), meta: { versions: entries.map(entry => entry.version) } },
|
|
100
|
+
{ kind: 'release-checklist', items: entries.flatMap(entry => entry.red.map(item => ({ title: `${item.id} ${item.title}`, due: null, daysLeft: null, state: 'red', owner: item.owner }))), meta: { counts: entries.map(entry => ({ version: entry.version, ...entry.counts })) } },
|
|
101
|
+
];
|
|
102
|
+
const structured = { releases: entries, latestRun: latest };
|
|
103
|
+
debug.log('release.tool', 'status', { action: 'status', version: versions.join(',') });
|
|
104
|
+
return { text: `${lines.join('\n\n')}\n${cards.map(formatElanousCard).join('\n')}`, structured };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function requiredString(args: Record<string, unknown>, key: string): string {
|
|
108
|
+
const value = args[key];
|
|
109
|
+
if (typeof value !== 'string' || !value.trim()) throw new Error(`${key} 값이 필요합니다`);
|
|
110
|
+
return value.trim();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Never accepts a user-supplied owner claim: only a human, server-assigned owner context can authorize a mutation. */
|
|
114
|
+
export async function dispatchReleaseChange(args: Record<string, unknown>, context?: ToolRuntimeContext, deps: ReleaseToolDeps = {}): Promise<string> {
|
|
115
|
+
const action = typeof args.action === 'string' ? args.action : '';
|
|
116
|
+
const version = typeof args.version === 'string' ? args.version : typeof args.from === 'string' ? args.from : '';
|
|
117
|
+
const ownerId = context?.verifiedOwner?.id;
|
|
118
|
+
if (context?.requestOrigin === 'external-agent' || !context?.sessionId || !ownerId?.trim()) {
|
|
119
|
+
debug.log('release.tool', 'change-refused', { action, version });
|
|
120
|
+
return '서버에서 검증한 오너의 사람 채팅 문맥에서만 판올림을 바꿀 수 있습니다.';
|
|
121
|
+
}
|
|
122
|
+
const channels = context.confirmChannels;
|
|
123
|
+
if (!channels?.length) {
|
|
124
|
+
debug.log('release.tool', 'change-refused', { action, version });
|
|
125
|
+
return '원래 채팅의 확인 채널이 없어 판올림을 바꾸지 않았습니다.';
|
|
126
|
+
}
|
|
127
|
+
let prompt: string;
|
|
128
|
+
let apply: () => void;
|
|
129
|
+
if (action === 'add-item') {
|
|
130
|
+
const v = requiredString(args, 'version');
|
|
131
|
+
const id = requiredString(args, 'id');
|
|
132
|
+
const title = requiredString(args, 'title');
|
|
133
|
+
validateVersion(v);
|
|
134
|
+
const owner = args.owner === undefined ? undefined : requiredString(args, 'owner');
|
|
135
|
+
prompt = `${v} 에 칸 ${id} «${title}» 추가 — 진행할까요?`;
|
|
136
|
+
apply = () => { (deps.add ?? add)(v, { id, title, status: 'yellow', ...(owner ? { owner } : {}), updatedAt: new Date().toISOString(), updatedBy: ownerId }); };
|
|
137
|
+
} else if (action === 'move-item') {
|
|
138
|
+
const id = requiredString(args, 'id');
|
|
139
|
+
const from = requiredString(args, 'from');
|
|
140
|
+
const to = requiredString(args, 'to');
|
|
141
|
+
validateVersion(from); validateVersion(to);
|
|
142
|
+
if (from === to) throw new Error('같은 판으로 옮길 수 없습니다');
|
|
143
|
+
prompt = `${id} 칸을 ${from} 에서 ${to} 로 이동 — 진행할까요?`;
|
|
144
|
+
apply = () => { (deps.move ?? move)(id, from, to, ownerId); };
|
|
145
|
+
} else if (action === 'set-schedule') {
|
|
146
|
+
const v = requiredString(args, 'version');
|
|
147
|
+
validateVersion(v);
|
|
148
|
+
const cutAt = args.cutAt === undefined ? undefined : requiredString(args, 'cutAt');
|
|
149
|
+
const landBy = args.landBy === undefined ? undefined : requiredString(args, 'landBy');
|
|
150
|
+
if (!cutAt && !landBy) throw new Error('cutAt 또는 landBy 가 필요합니다');
|
|
151
|
+
// Validate offsets before asking for approval, not after.
|
|
152
|
+
for (const iso of [cutAt, landBy].filter((value): value is string => !!value)) {
|
|
153
|
+
if (!/^\d{4}-\d\d-\d\dT\d\d:\d\d(?::\d\d(?:\.\d{1,3})?)?(?:Z|[+-]\d\d:\d\d)$/.test(iso) || !Number.isFinite(Date.parse(iso))) throw new Error(`잘못된 ISO 시각: ${iso}`);
|
|
154
|
+
}
|
|
155
|
+
prompt = `${v} 판 일정 ${cutAt ? `컷 ${formatKst(cutAt)}` : ''}${landBy ? ` 착지 마감 ${formatKst(landBy)}` : ''} 변경 — 진행할까요?`;
|
|
156
|
+
apply = () => { (deps.setSchedule ?? setSchedule)(v, { ...(cutAt ? { cutAt } : {}), ...(landBy ? { landBy } : {}) }, ownerId); };
|
|
157
|
+
} else throw new Error('action 은 add-item, move-item, set-schedule 중 하나여야 합니다');
|
|
158
|
+
debug.log('release.tool', 'change-requested', { action, version });
|
|
159
|
+
const result = await (deps.confirm ?? requestConfirmation)({ prompt, onTimeout: () => false, failOpen: false,
|
|
160
|
+
channels });
|
|
161
|
+
if (!result.answer || !channels.some(channel => channel.name === result.channel)) {
|
|
162
|
+
debug.log('release.tool', 'change-refused', { action, version });
|
|
163
|
+
return `${prompt} — 승인되지 않아 변경하지 않았습니다.`;
|
|
164
|
+
}
|
|
165
|
+
apply();
|
|
166
|
+
debug.log('release.tool', 'change-confirmed', { action, version });
|
|
167
|
+
return `${prompt} — 변경했습니다.`;
|
|
168
|
+
}
|
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
// 발송 목적(kind) → 텔레그램 채널 역할 · 대상. 가벼운 모듈(telegram.ts 를 import 하지 않는다 — outbound-alert 순환 방지).
|
|
2
|
-
// 표에 없는 kind 는 undefined → 부르는 쪽이
|
|
2
|
+
// 표에 없는 kind 는 undefined → 부르는 쪽이 폴백을 판정한다(투자 알림은 옛 reportChannel 유지).
|
|
3
3
|
import type { UserConfig } from '../user-config.js';
|
|
4
|
+
import { resolveChannelBotToken } from '../channel-bot-token.js';
|
|
5
|
+
import { debug } from '../debug/log.js';
|
|
4
6
|
import { channelForRole, resolveTelegramChannels } from './telegram-channels.js';
|
|
5
7
|
|
|
6
8
|
export const DEFAULT_KIND_ROLES: Readonly<Record<string, string>> = {
|
|
7
9
|
intake: 'system', 'ops-report': 'system', 'ops-alert': 'system', 'ops-health': 'system',
|
|
10
|
+
regression: 'system', 'agent-mission': 'system',
|
|
8
11
|
};
|
|
9
12
|
|
|
13
|
+
/** No channel mapping for an operational kind may cross into a different bot. */
|
|
14
|
+
export function isOperationalKind(kind?: string): boolean {
|
|
15
|
+
return kind?.startsWith('ops-') ?? false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function mainHomeTarget(cfg: UserConfig): { botToken: string; chatId: number } | null {
|
|
19
|
+
const botToken = resolveChannelBotToken('telegram', cfg).token;
|
|
20
|
+
const chatId = cfg.telegram.homeChannel ?? cfg.telegram.allowedUsers[0];
|
|
21
|
+
return botToken && chatId != null && Number.isFinite(chatId) ? { botToken, chatId } : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function logKindRouteFallback(kind: string | undefined, to: 'report-channel' | 'main-home' | 'conatus-env' | 'none', sameBot: boolean): void {
|
|
25
|
+
try { debug.log('telegram.kind-route', 'fallback', { kind, to, sameBot }); } catch { /* logging must not prevent delivery */ }
|
|
26
|
+
}
|
|
27
|
+
|
|
10
28
|
export function roleForKind(cfg: UserConfig, kind?: string): string | undefined {
|
|
11
29
|
if (!kind) return undefined;
|
|
12
30
|
return cfg.telegram.kindRoles?.[kind] ?? DEFAULT_KIND_ROLES[kind];
|