elanous 0.2.9 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +1 -0
- package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8067-87ce253ec2f23697.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8195-6fde3d7a39859206.js} +5 -5
- package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-f972fc1c00a62e88.js → page-54b32bd27726588d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-f8116559c7424da7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-54e1f966ab60a4dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-973996d584f008c1.js → page-7ed95f296f5ee8e9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-8b0402a46a749a3d.js → page-b31610deda76337b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-037f83e2a1edc8e2.js → page-6cb0e1bbefe5fb3d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-0df30e0c7e50148a.js → page-e168fa1030af0548.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-4fddc90b23feb215.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-90670589b87abea8.js → page-26102fd7350f171e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-35bedde6d885f08f.js → page-139ba5c367bbf60a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +1 -0
- package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +14 -14
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -0
- package/apps/pwa/out/ops/seats/index.txt +30 -0
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -0
- package/apps/pwa/out/today/index.txt +33 -0
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/graphs/field/field-feed.yaml +2 -0
- package/graphs/implement-loop.yaml +1 -1
- package/package.json +1 -1
- package/plugins/card-followup/graphs/run-step.ts +48 -13
- package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
- package/plugins/doc-draft/graphs/run-step.ts +13 -6
- package/scripts/ci-pwa-build-gate.ts +160 -0
- package/scripts/coord-channel-watch.sh +42 -8
- package/scripts/coord-digest.ts +199 -0
- package/scripts/coord-post.sh +60 -3
- package/scripts/install.ps1 +68 -9
- package/scripts/install.sh +39 -12
- package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
- package/scripts/lib/tui-regress-checks.ts +146 -0
- package/scripts/memory-lifecycle-cycle.ts +2 -2
- package/scripts/ops-now-probe.ts +327 -0
- package/scripts/publish-internal-dist.ts +11 -1
- package/scripts/release-loop/cutoff.ts +19 -8
- package/scripts/release-loop/ops-upgrade-node.ts +72 -16
- package/scripts/release-loop/release-readiness.ts +9 -0
- package/scripts/release-loop/version-node.ts +58 -10
- package/scripts/repo-watch-cycle.ts +1 -1
- package/scripts/retro-cycle.ts +2 -2
- package/scripts/schedule-health-report.ts +1 -1
- package/scripts/storyboard/storyboard.ts +5 -161
- package/scripts/test-diet/lib.ts +30 -1
- package/scripts/test-diet/node.ts +82 -7
- package/scripts/test-timeout-audit.ts +114 -0
- package/scripts/tree-sync-alert.ts +1 -1
- package/scripts/tui-regress.ts +71 -0
- package/scripts/waitlist-daily-digest.ts +1 -1
- package/skills/explainer-video/SKILL.md +4 -0
- package/skills/explainer-video/engine/reel.mjs +18 -13
- package/skills/explainer-video/engine/reel.sh +24 -5
- package/skills/explainer-video/engine/stitch.mjs +145 -0
- package/skills/pitch-deck/SKILL.md +36 -0
- package/skills/pitch-deck/template.html +80 -0
- package/src/acp/codex-approval-adapter.ts +9 -3
- package/src/acp/core-turn-bridge.ts +117 -18
- package/src/acp/fact-question.ts +11 -0
- package/src/acp/server.ts +48 -7
- package/src/acp/tool-approval.ts +126 -0
- package/src/acp/turn-runner.ts +36 -0
- package/src/agent/monad-agent-turn.ts +8 -0
- package/src/ask-user-question/pending-questions.ts +25 -10
- package/src/ask-user-question/types.ts +9 -0
- package/src/auth/setup-link-tokens.ts +16 -5
- package/src/autopilot/build/integrity-gate.ts +12 -0
- package/src/boot/daemon-prompt-turn.ts +3 -0
- package/src/boot/daemon-tools/index.ts +8 -0
- package/src/boot/daemon-tools/types.ts +2 -0
- package/src/boot/daemon-tools/web-search.ts +1 -0
- package/src/chat/ctrl-c-in-input.ts +14 -0
- package/src/chat/index.ts +52 -0
- package/src/chat/pickers/state.ts +6 -2
- package/src/cli/agent-cli.ts +7 -0
- package/src/cli/decisions-cli.ts +25 -3
- package/src/cli/doctor-bundle-upload.ts +68 -0
- package/src/cli/doctor-cli.ts +38 -4
- package/src/cli/ops-cli.ts +20 -0
- package/src/cli/pod-cli.ts +49 -0
- package/src/cli/pr-cli.ts +31 -2
- package/src/cli/pwa-stop.ts +163 -13
- package/src/cli/pwa-test.ts +3 -0
- package/src/cli/release-cli.ts +63 -20
- package/src/cli/self-update.ts +14 -0
- package/src/cli/storyboard-cli.ts +37 -0
- package/src/cli/test-state-dir-flag.ts +11 -4
- package/src/connectors/linear.ts +146 -0
- package/src/context-bus/coord-events.ts +115 -0
- package/src/dashboard/dashboard-boot-fixture.ts +23 -0
- package/src/dashboard/index.ts +58 -21
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +7 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
- package/src/decisions/decision-cards.ts +49 -1
- package/src/decisions/decision-ledger.ts +75 -4
- package/src/decisions/decision-linear-projection.ts +157 -0
- package/src/decisions/web-push-decision-cards.ts +50 -0
- package/src/directive/interpretation-card.ts +36 -0
- package/src/discord.ts +2 -1
- package/src/domains/coo-admin-tool.ts +62 -0
- package/src/domains/core-tools.ts +11 -3
- package/src/domains/elanous-card.ts +29 -0
- package/src/domains/ops-facts-tool.ts +76 -0
- package/src/domains/outbound-alert.ts +41 -13
- package/src/domains/release-tool.ts +168 -0
- package/src/domains/telegram-kind-route.ts +19 -1
- package/src/exec-requests/planner.ts +98 -28
- package/src/exec-requests/runner.ts +107 -10
- package/src/exec-requests/store.ts +8 -2
- package/src/field/field-media.ts +10 -7
- package/src/field/field-reel.ts +14 -4
- package/src/git-fs/worktree.ts +45 -6
- package/src/graph-runner/graph-approval-card.ts +113 -0
- package/src/graph-runner/graph-cli.ts +129 -4
- package/src/graph-runner/graph-notify.ts +25 -3
- package/src/graph-runner/runner.ts +142 -16
- package/src/harness/browser-verify.ts +25 -2
- package/src/harness/harness-cli-command.ts +231 -35
- package/src/harness/harness-incidents-writer.fixture.ts +24 -0
- package/src/harness/harness-incidents.ts +129 -0
- package/src/harness/harness-stop.ts +53 -3
- package/src/hitl/clarification-policy.ts +3 -1
- package/src/index.ts +156 -37
- package/src/intake-plane/author-goals.ts +37 -4
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/discord-seat-work.ts +47 -2
- package/src/intake-plane/seat-doc-route.ts +170 -6
- package/src/intake-plane/telegram-seat-work.ts +44 -2
- package/src/intake-plane/telegram-work.ts +1 -0
- package/src/maturity/cli-maturity.ts +34 -0
- package/src/maturity/feature-maturity.ts +98 -1
- package/src/maturity/tui-slash-maturity.ts +31 -0
- package/src/memory.ts +32 -4
- package/src/nexus/api/exec-requests.ts +33 -3
- package/src/nexus/api/http-server.ts +53 -1
- package/src/nexus/api/meta-api.ts +11 -0
- package/src/nexus/api/ops-api.ts +10 -1
- package/src/nexus/api/ops-seats-sources.ts +37 -7
- package/src/nexus/api/ops-seats.ts +16 -6
- package/src/nexus/api/seat-requests.ts +88 -8
- package/src/nexus/api/sessions-store.ts +63 -0
- package/src/nexus/api/setup-codex-login.ts +160 -0
- package/src/nexus/api/usage-api.ts +102 -0
- package/src/nexus/index.ts +45 -4
- package/src/onboarding/first-run.ts +89 -0
- package/src/onboarding/lan-address.ts +45 -0
- package/src/onboarding/web-first.ts +119 -24
- package/src/onboarding.ts +4 -0
- package/src/ops-now/ops-now-refresh.ts +100 -0
- package/src/project/project-store.ts +115 -0
- package/src/release-loop/checklist.ts +78 -4
- package/src/release-loop/feature-store.ts +154 -21
- package/src/release-loop/release-schedule.ts +53 -0
- package/src/repl/index.ts +7 -0
- package/src/seat-dispatch/ceo-commands.ts +41 -8
- package/src/seat-dispatch/ceo-intent.ts +38 -0
- package/src/seat-loop/seat-loop.ts +231 -0
- package/src/seat-loop/seat-report.ts +64 -0
- package/src/self-dev/decompose.ts +66 -11
- package/src/self-dev/run-store.ts +10 -1
- package/src/self-implement/execution-clarification.ts +115 -0
- package/src/self-implement/gate-cli.ts +6 -3
- package/src/self-implement/orchestrator.ts +37 -3
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/run-status-mapping.ts +5 -3
- package/src/session/chat.ts +2 -0
- package/src/session/index.ts +9 -0
- package/src/shell-env-bootstrap.ts +17 -0
- package/src/steward/directive.ts +51 -2
- package/src/steward/launch.ts +250 -0
- package/src/steward/outcome-notify.ts +115 -0
- package/src/steward/steward-cards.ts +8 -2
- package/src/steward/triage-plan.ts +32 -4
- package/src/steward/triage.ts +93 -9
- package/src/steward/working-backwards.ts +64 -0
- package/src/storyboard/storyboard.ts +141 -0
- package/src/task-cards/card-store.ts +2 -2
- package/src/task-orchestrator/surfaces/pod-lease.ts +260 -0
- package/src/telegram-card-followup.ts +233 -0
- package/src/telegram-report.ts +15 -9
- package/src/telegram-run.ts +7 -0
- package/src/telegram.ts +29 -8
- package/src/tool-runtime/types.ts +4 -0
- package/src/tui-client/dashboard-session.ts +2 -0
- package/src/user-config.ts +118 -11
- package/src/version/packed-revision.json +1 -1
- package/src/web-push/notify-exec-request.ts +32 -0
- package/apps/pwa/out/_next/static/bFI5EEwg7T6aG88ibX4Zq/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8630-cdfc61e82c653ffc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8894-e263b332208e3957.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-139040d37c79dbc9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-fb7c2d458f32fcad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-35e2713597dd24d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-918b656f064577ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-5f30e00693527ebd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-7fbae173b3c103a7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-3b4edcce364062ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-c5794f484c251f44.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-1130d8eefcd87e76.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-016e2bda04eecb1f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-b4d6d9a3b56f2940.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-ec49a466121233ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-f72db356744ceade.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-a6fd9b50d1b85378.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-adfbd2ba9598e59a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-46fedff57d4bec67.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-0cf318e9dcee2bbf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-899b327cb8b6845c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-6c1e6ba743a29dfe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-1a8e4807292f6b17.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-28deba6bcb834916.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-de68e2205794037d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-c71c33a573230672.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4fc56790fe0d2682.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-59df2aebb38a7c1e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4647be5dbcbd0795.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e025892bda74b743.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a0955de73b89f078.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-369846d2921af584.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-72cbc1c8cdd0f65d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-77b28bfb0996d5ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-3220c4934f49c6c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-7df9243ed021a4cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-4498c3853f4f688d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
- package/apps/pwa/out/_next/static/css/f92f63a5df0ba229.css +0 -1
- package/src/dashboard/turn-typeahead-echo.ts +0 -23
- /package/apps/pwa/out/_next/static/{bFI5EEwg7T6aG88ibX4Zq → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
package/src/connectors/linear.ts
CHANGED
|
@@ -122,6 +122,152 @@ export async function fetchLinearIssues({ apiKey, teamKey, labelOrPrefix, since,
|
|
|
122
122
|
return events.sort((a, b) => PRIORITY_RANK[a.priority ?? 'none'] - PRIORITY_RANK[b.priority ?? 'none']);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
export interface LinearProjectIssue {
|
|
126
|
+
identifier: string;
|
|
127
|
+
title: string;
|
|
128
|
+
url: string;
|
|
129
|
+
state: { name: string; type: string };
|
|
130
|
+
dueDate: string | null;
|
|
131
|
+
priority: number;
|
|
132
|
+
assignee: { name: string } | null;
|
|
133
|
+
updatedAt: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
type LinearRequestOptions = { apiKey: string; fetch?: typeof fetch };
|
|
137
|
+
|
|
138
|
+
async function linearRequest<T>({ apiKey, fetch: fetchFn = fetch }: LinearRequestOptions, query: string, variables: Record<string, unknown>): Promise<T> {
|
|
139
|
+
let response: Response;
|
|
140
|
+
try {
|
|
141
|
+
response = await fetchFn('https://api.linear.app/graphql', {
|
|
142
|
+
method: 'POST', headers: { Authorization: apiKey, 'Content-Type': 'application/json' },
|
|
143
|
+
body: JSON.stringify({ query, variables }),
|
|
144
|
+
});
|
|
145
|
+
} catch (error) {
|
|
146
|
+
throw new Error(`Linear GraphQL request failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
147
|
+
}
|
|
148
|
+
if (!response.ok) throw new Error(`Linear GraphQL HTTP ${response.status}`);
|
|
149
|
+
let body: { data?: T; errors?: Array<{ message?: string }> };
|
|
150
|
+
try { body = await response.json() as typeof body; }
|
|
151
|
+
catch { throw new Error('Linear GraphQL invalid JSON response'); }
|
|
152
|
+
if (body.errors?.length) throw new Error(`Linear GraphQL: ${body.errors.map(e => e.message ?? 'unknown error').join('; ')}`);
|
|
153
|
+
if (!body.data) throw new Error('Linear GraphQL missing data');
|
|
154
|
+
return body.data;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Shared unique name → project id lookup for COO reads and decision writes. */
|
|
158
|
+
export async function resolveLinearProjectId({ apiKey, project, fetch: fetchFn = fetch }: LinearRequestOptions & { project: string }): Promise<string> {
|
|
159
|
+
const cursors = new Set<string>();
|
|
160
|
+
let projectId = project;
|
|
161
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(project)) {
|
|
162
|
+
const matches: string[] = [];
|
|
163
|
+
let after: string | null = null;
|
|
164
|
+
do {
|
|
165
|
+
const data: { projects?: { nodes?: Array<{ id: string; name: string }>; pageInfo?: { hasNextPage: boolean; endCursor: string | null } } } = await linearRequest({ apiKey, fetch: fetchFn },
|
|
166
|
+
`query CooProjects($after: String) { projects(first: 100, after: $after) { nodes { id name } pageInfo { hasNextPage endCursor } } }`, { after });
|
|
167
|
+
if (!Array.isArray(data.projects?.nodes)) throw new Error('Linear GraphQL missing projects');
|
|
168
|
+
for (const candidate of data.projects.nodes) if (candidate.name === project) matches.push(candidate.id);
|
|
169
|
+
const page = data.projects.pageInfo;
|
|
170
|
+
if (page?.hasNextPage && (!page.endCursor || cursors.has(page.endCursor))) throw new Error('Linear GraphQL pagination cursor missing or repeated');
|
|
171
|
+
after = page?.hasNextPage ? page.endCursor : null;
|
|
172
|
+
if (after) cursors.add(after);
|
|
173
|
+
} while (after);
|
|
174
|
+
if (matches.length > 1) throw new Error('프로젝트 이름이 여럿과 맞습니다');
|
|
175
|
+
if (!matches.length) throw new Error('프로젝트 이름과 맞는 항목이 없습니다');
|
|
176
|
+
projectId = matches[0]!;
|
|
177
|
+
}
|
|
178
|
+
return projectId;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Read-only project lookup. A name is resolved uniquely before querying its issues. */
|
|
182
|
+
export async function fetchLinearProjectIssues({ apiKey, project, includeDone = false, fetch: fetchFn = fetch }: {
|
|
183
|
+
apiKey: string; project: string; includeDone?: boolean; fetch?: typeof fetch;
|
|
184
|
+
}): Promise<LinearProjectIssue[] & { truncated: boolean }> {
|
|
185
|
+
const projectId = await resolveLinearProjectId({ apiKey, project, fetch: fetchFn });
|
|
186
|
+
const issues: LinearProjectIssue[] = [];
|
|
187
|
+
const cursors = new Set<string>();
|
|
188
|
+
let after: string | null = null;
|
|
189
|
+
do {
|
|
190
|
+
const stateFilter = includeDone ? '' : ', state: { type: { nin: ["completed", "canceled"] } }';
|
|
191
|
+
const data: { issues?: { nodes?: LinearProjectIssue[]; pageInfo?: { hasNextPage: boolean; endCursor: string | null } } } = await linearRequest({ apiKey, fetch: fetchFn },
|
|
192
|
+
`query CooProjectIssues($projectId: ID!, $after: String, $first: Int!) {
|
|
193
|
+
issues(filter: { project: { id: { eq: $projectId } }${stateFilter} }, first: $first, after: $after) {
|
|
194
|
+
nodes { identifier title url state { name type } dueDate priority assignee { name } updatedAt }
|
|
195
|
+
pageInfo { hasNextPage endCursor }
|
|
196
|
+
}
|
|
197
|
+
}`, { projectId, after, first: Math.min(100, 250 - issues.length) });
|
|
198
|
+
if (!Array.isArray(data.issues?.nodes)) throw new Error('Linear GraphQL missing issues');
|
|
199
|
+
for (const node of data.issues.nodes) {
|
|
200
|
+
if (!includeDone && (node.state?.type === 'completed' || node.state?.type === 'canceled')) continue;
|
|
201
|
+
if (issues.length < 250) issues.push(node);
|
|
202
|
+
}
|
|
203
|
+
const page = data.issues.pageInfo;
|
|
204
|
+
if (page?.hasNextPage && (!page.endCursor || cursors.has(page.endCursor))) throw new Error('Linear GraphQL pagination cursor missing or repeated');
|
|
205
|
+
after = page?.hasNextPage ? page.endCursor : null;
|
|
206
|
+
if (after) cursors.add(after);
|
|
207
|
+
} while (after && issues.length < 250);
|
|
208
|
+
Object.defineProperty(issues, 'truncated', { value: after !== null, enumerable: false });
|
|
209
|
+
return issues as LinearProjectIssue[] & { truncated: boolean };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Find a previously created decision issue after a crash between Linear creation and the local rename. */
|
|
213
|
+
export async function findLinearDecisionIssue({ apiKey, projectId, decisionId, fetch: fetchFn = fetch }: LinearRequestOptions & { projectId: string; decisionId: string }): Promise<string | null> {
|
|
214
|
+
const cursors = new Set<string>();
|
|
215
|
+
let after: string | null = null;
|
|
216
|
+
let match: string | null = null;
|
|
217
|
+
do {
|
|
218
|
+
const data: { issues?: { nodes?: Array<{ id: string; description: string | null }>; pageInfo?: { hasNextPage: boolean; endCursor: string | null } } } = await linearRequest({ apiKey, fetch: fetchFn },
|
|
219
|
+
'query DecisionIssue($projectId: ID!, $decisionId: String!, $after: String) { issues(filter: { project: { id: { eq: $projectId } }, description: { contains: $decisionId } }, first: 100, after: $after) { nodes { id description } pageInfo { hasNextPage endCursor } } }',
|
|
220
|
+
{ projectId, decisionId, after });
|
|
221
|
+
if (!Array.isArray(data.issues?.nodes)) throw new Error('Linear GraphQL missing issues');
|
|
222
|
+
for (const node of data.issues.nodes) {
|
|
223
|
+
if (!node.description?.split('\n').includes(`결정 id: ${decisionId}`)) continue;
|
|
224
|
+
if (match) throw new Error('Linear decision issue duplicated');
|
|
225
|
+
match = node.id;
|
|
226
|
+
}
|
|
227
|
+
const page = data.issues.pageInfo;
|
|
228
|
+
if (page?.hasNextPage && (!page.endCursor || cursors.has(page.endCursor))) throw new Error('Linear GraphQL pagination cursor missing or repeated');
|
|
229
|
+
after = page?.hasNextPage ? page.endCursor : null;
|
|
230
|
+
if (after) cursors.add(after);
|
|
231
|
+
} while (after);
|
|
232
|
+
return match;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Create in the COO project; Linear requires a team from that project for issueCreate. */
|
|
236
|
+
export async function createLinearIssue({ apiKey, projectId, title, description, dueDate, fetch: fetchFn = fetch }: LinearRequestOptions & {
|
|
237
|
+
projectId: string; title: string; description: string; dueDate?: string;
|
|
238
|
+
}): Promise<string> {
|
|
239
|
+
const data = await linearRequest<{ project?: { teams?: { nodes?: Array<{ id: string }> } } }>({ apiKey, fetch: fetchFn },
|
|
240
|
+
'query DecisionProjectTeam($id: String!) { project(id: $id) { teams { nodes { id } } } }', { id: projectId });
|
|
241
|
+
const teams = data.project?.teams?.nodes;
|
|
242
|
+
if (!teams?.[0]?.id) throw new Error('Linear project has no team');
|
|
243
|
+
const created = await linearRequest<{ issueCreate?: { success?: boolean; issue?: { id?: string } } }>({ apiKey, fetch: fetchFn },
|
|
244
|
+
'mutation DecisionIssueCreate($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id } } }',
|
|
245
|
+
{ input: { teamId: teams[0].id, projectId, title, description, ...(dueDate ? { dueDate } : {}) } });
|
|
246
|
+
const id = created.issueCreate?.issue?.id;
|
|
247
|
+
if (!created.issueCreate?.success || !id) throw new Error('Linear issueCreate did not return an issue id');
|
|
248
|
+
return id;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export async function commentLinearIssue({ apiKey, issueId, body, fetch: fetchFn = fetch }: LinearRequestOptions & { issueId: string; body: string }): Promise<void> {
|
|
252
|
+
const data = await linearRequest<{ commentCreate?: { success?: boolean; comment?: { id?: string } } }>({ apiKey, fetch: fetchFn },
|
|
253
|
+
'mutation DecisionComment($input: CommentCreateInput!) { commentCreate(input: $input) { success comment { id } } }',
|
|
254
|
+
{ input: { issueId, body } });
|
|
255
|
+
if (!data.commentCreate?.success || !data.commentCreate.comment?.id) throw new Error('Linear commentCreate failed');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export async function moveLinearIssueToStateType({ apiKey, issueId, type, fetch: fetchFn = fetch }: LinearRequestOptions & {
|
|
259
|
+
issueId: string; type: 'completed' | 'canceled';
|
|
260
|
+
}): Promise<void> {
|
|
261
|
+
const data = await linearRequest<{ issue?: { team?: { states?: { nodes?: Array<{ id: string; type: string }> } } } }>({ apiKey, fetch: fetchFn },
|
|
262
|
+
'query DecisionIssueStates($id: String!) { issue(id: $id) { team { states { nodes { id type } } } } }', { id: issueId });
|
|
263
|
+
const state = data.issue?.team?.states?.nodes?.find(node => node.type === type);
|
|
264
|
+
if (!state?.id) throw new Error(`Linear team workflow state not found: ${type}`);
|
|
265
|
+
const updated = await linearRequest<{ issueUpdate?: { success?: boolean } }>({ apiKey, fetch: fetchFn },
|
|
266
|
+
'mutation DecisionIssueClose($id: String!, $input: IssueUpdateInput!) { issueUpdate(id: $id, input: $input) { success } }',
|
|
267
|
+
{ id: issueId, input: { stateId: state.id } });
|
|
268
|
+
if (!updated.issueUpdate?.success) throw new Error('Linear issueUpdate failed');
|
|
269
|
+
}
|
|
270
|
+
|
|
125
271
|
export function toTaskRequest(event: ExternalTaskEvent): {
|
|
126
272
|
title: string; description: string; priority: 'high' | 'medium' | 'low';
|
|
127
273
|
external: { provider: 'linear'; ref: string; url?: string; team?: string };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { debug, redactSecretText } from '../debug/log.js';
|
|
5
|
+
import { openSurfaceEventsDb, recordEvent, surfaceEventsDbPath } from '../domains/surface-events.js';
|
|
6
|
+
|
|
7
|
+
export interface CoordEventInput {
|
|
8
|
+
seat: string;
|
|
9
|
+
recipients: string[];
|
|
10
|
+
all: boolean;
|
|
11
|
+
kind: string | null;
|
|
12
|
+
slot: string | null;
|
|
13
|
+
deadline: string | null;
|
|
14
|
+
url: string | null;
|
|
15
|
+
headline: string;
|
|
16
|
+
at?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CoordEvent {
|
|
20
|
+
id: string;
|
|
21
|
+
at: string;
|
|
22
|
+
text: string;
|
|
23
|
+
summary: string;
|
|
24
|
+
kind: string;
|
|
25
|
+
refs: Pick<CoordEventInput, 'seat' | 'recipients' | 'all' | 'kind' | 'slot' | 'deadline' | 'url'>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function seatIds(): Set<string> {
|
|
29
|
+
return new Set((JSON.parse(readFileSync(resolve(import.meta.dir, '../../scripts/coord-tracks.json'), 'utf8')) as
|
|
30
|
+
{ tracks: Array<{ id: string }> }).tracks.map((track) => track.id));
|
|
31
|
+
}
|
|
32
|
+
const kinds = new Set(['요청', '결정', '사고', '보고', '정정']);
|
|
33
|
+
|
|
34
|
+
/** Mirror the sender's ordered envelope fields; only the first line enters this parser. */
|
|
35
|
+
export function parseCoordHeader(seat: string, header: string): Omit<CoordEventInput, 'url' | 'at'> {
|
|
36
|
+
const first = header.split(/\r?\n/, 1)[0] ?? '';
|
|
37
|
+
if (!first.startsWith(`**[${seat}]**`)) throw new Error('조율 글 신원 접두 불일치');
|
|
38
|
+
const headline = first.slice(`**[${seat}]**`.length)
|
|
39
|
+
.replace(/^\s*(?:📌안내\s*)?\d{4}-\d\d-\d\d\s+\d\d:\d\d\s+KST\s*/, '').trim();
|
|
40
|
+
const headerBody = first.slice(`**[${seat}]**`.length);
|
|
41
|
+
const arrow = headerBody.indexOf('→');
|
|
42
|
+
const envelope = arrow >= 0 && !headerBody.slice(0, arrow).includes(' · ') ? headerBody.slice(arrow + 1).trim() : '';
|
|
43
|
+
const fields = envelope ? envelope.split(' · ') : [];
|
|
44
|
+
const ids = seatIds();
|
|
45
|
+
const recipients: string[] = [];
|
|
46
|
+
let all = false;
|
|
47
|
+
let i = 0;
|
|
48
|
+
for (; i < fields.length; i++) {
|
|
49
|
+
const field = fields[i]!.trim().split(' — ')[0]!;
|
|
50
|
+
const tokens = field.replaceAll(',', ' ').split(/\s+/).filter(Boolean);
|
|
51
|
+
if (!tokens.length || !tokens.every((t) => t === '전원' || ids.has(t))) break;
|
|
52
|
+
for (const token of tokens) {
|
|
53
|
+
if (token === '전원') all = true;
|
|
54
|
+
else recipients.push(token);
|
|
55
|
+
}
|
|
56
|
+
if (fields[i]!.includes(' — ')) { i++; break; }
|
|
57
|
+
}
|
|
58
|
+
const candidate = fields[i]?.trim() ?? '';
|
|
59
|
+
const kind = kinds.has(candidate) ? candidate : null;
|
|
60
|
+
const slot = fields[i + 1]?.trim() || null;
|
|
61
|
+
const deadline = fields.slice(i + 1).map((f) => f.trim()).find((f) => /^기한\s+\S/.test(f)) ?? null;
|
|
62
|
+
return { seat, recipients, all, kind, slot, deadline, headline };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Atomic lookup+insert makes retries safe, including URL-less posts in the same minute. */
|
|
66
|
+
export function recordCoordEvent(input: CoordEventInput, deps: { db?: Database } = {}): string {
|
|
67
|
+
const hasUrl = !!input.url;
|
|
68
|
+
let db: Database | undefined;
|
|
69
|
+
try {
|
|
70
|
+
db = deps.db ?? openSurfaceEventsDb();
|
|
71
|
+
const at = input.at ?? new Date().toISOString();
|
|
72
|
+
const first = input.headline.split(/\r?\n/, 1)[0] ?? '';
|
|
73
|
+
const withoutPrefix = first.replace(/^\*\*\[[A-Z]+\]\*\*\s*/, '')
|
|
74
|
+
.replace(/^(?:📌안내\s*)?\d{4}-\d\d-\d\d\s+\d\d:\d\d\s+KST\s*/, '');
|
|
75
|
+
const headline = redactSecretText(withoutPrefix).trim().slice(0, 120);
|
|
76
|
+
const safe = (value: string) => redactSecretText(value.split(/\r?\n/, 1)[0] ?? '');
|
|
77
|
+
const refs = { seat: safe(input.seat), recipients: input.recipients.map(safe), all: input.all,
|
|
78
|
+
kind: input.kind === null ? null : safe(input.kind), slot: input.slot === null ? null : safe(input.slot),
|
|
79
|
+
deadline: input.deadline === null ? null : safe(input.deadline), url: input.url ? safe(input.url) : null };
|
|
80
|
+
const result = db.transaction(() => {
|
|
81
|
+
const prior = (refs.url
|
|
82
|
+
? db!.prepare(`SELECT id FROM events WHERE surface='coord:channel' AND json_extract(refs,'$.url')=? LIMIT 1`).get(refs.url)
|
|
83
|
+
: db!.prepare(`SELECT id FROM events WHERE surface='coord:channel' AND json_extract(refs,'$.url') IS NULL
|
|
84
|
+
AND json_extract(refs,'$.seat')=? AND summary=? AND substr(ts,1,16)=? LIMIT 1`)
|
|
85
|
+
.get(refs.seat, headline, at.slice(0, 16))) as { id: string } | null;
|
|
86
|
+
if (prior) return { id: prior.id, dedup: true };
|
|
87
|
+
return { id: recordEvent(db!, { surface: 'coord:channel', direction: 'outbound', kind: refs.kind ?? 'unknown',
|
|
88
|
+
domain: 'elanous', category: 'coordination', text: headline, summary: headline,
|
|
89
|
+
refs: JSON.stringify(refs), ts: at }), dedup: false };
|
|
90
|
+
}).immediate();
|
|
91
|
+
try { debug.log('context.coord', result.dedup ? 'dedup' : 'recorded', { seat: refs.seat, kind: refs.kind, hasUrl }); }
|
|
92
|
+
catch { /* observation cannot turn a successful write into a failed send */ }
|
|
93
|
+
return result.id;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
try { debug.log('context.coord', 'failed', { seat: redactSecretText(input.seat), kind: input.kind && redactSecretText(input.kind), hasUrl }); }
|
|
96
|
+
catch { /* preserve the original write error */ }
|
|
97
|
+
throw error;
|
|
98
|
+
} finally { if (!deps.db) db?.close(); }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Read-only chronological list; a missing store is an empty ledger, not a newly created one. */
|
|
102
|
+
export function listCoordEvents(opts: { since: string; seat?: string }, deps: { db?: Database } = {}): CoordEvent[] {
|
|
103
|
+
if (!deps.db && !existsSync(surfaceEventsDbPath())) return [];
|
|
104
|
+
const db = deps.db ?? openSurfaceEventsDb();
|
|
105
|
+
try {
|
|
106
|
+
const rows = db.prepare(`SELECT id, ts, text, summary, kind, refs FROM events
|
|
107
|
+
WHERE surface='coord:channel' AND direction='outbound' AND ts>=?
|
|
108
|
+
${opts.seat ? "AND json_extract(refs,'$.seat')=?" : ''} ORDER BY ts ASC, rowid ASC`)
|
|
109
|
+
.all(...(opts.seat ? [opts.since, opts.seat] : [opts.since])) as Array<{
|
|
110
|
+
id: string; ts: string; text: string; summary: string; kind: string; refs: string;
|
|
111
|
+
}>;
|
|
112
|
+
return rows.map((r) => ({ id: r.id, at: r.ts, text: r.text, summary: r.summary, kind: r.kind,
|
|
113
|
+
refs: JSON.parse(r.refs) as CoordEvent['refs'] }));
|
|
114
|
+
} finally { if (!deps.db) db.close(); }
|
|
115
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { showDashboard } from './index.js';
|
|
2
|
+
|
|
3
|
+
const expected = process.env.DASHBOARD_BOOT_SUGGESTION;
|
|
4
|
+
if (!expected) throw new Error('DASHBOARD_BOOT_SUGGESTION is required');
|
|
5
|
+
|
|
6
|
+
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
7
|
+
let screen = '';
|
|
8
|
+
process.stdout.write = ((chunk: string | Uint8Array, ...args: unknown[]) => {
|
|
9
|
+
screen += typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8');
|
|
10
|
+
if (screen.includes(expected)) {
|
|
11
|
+
originalWrite(`FIRST_SCREEN_SUGGESTION=${expected}\n`);
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
14
|
+
return true;
|
|
15
|
+
}) as typeof process.stdout.write;
|
|
16
|
+
|
|
17
|
+
setTimeout(() => {
|
|
18
|
+
originalWrite(`FIRST_SCREEN_TIMEOUT=${screen.slice(-2000)}\n`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}, 20_000).unref();
|
|
21
|
+
|
|
22
|
+
await showDashboard();
|
|
23
|
+
throw new Error('dashboard exited before suggestion appeared');
|
package/src/dashboard/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type { CommandRunner } from '../ad-pipeline/higgsfield-backend.js';
|
|
|
17
17
|
import type { ShootRunOptions } from '../ad-pipeline/shoot-run.js';
|
|
18
18
|
import type { QcThresholds } from '../ad-pipeline/qc.js';
|
|
19
19
|
import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
20
|
+
import { suggestProjectForFolder } from '../project/project-store.js';
|
|
20
21
|
|
|
21
22
|
const dashboardDefaultShootRunOptions: Required<Pick<ShootRunOptions, 'pollIntervalMs' | 'maxPollsPerJob' | 'submitStaggerMs'>> = {
|
|
22
23
|
pollIntervalMs: 3_000,
|
|
@@ -425,7 +426,6 @@ import {
|
|
|
425
426
|
renderTurnTypeaheadEcho,
|
|
426
427
|
} from '../chat/turn-typeahead.js';
|
|
427
428
|
import { dispatchStreamingTurnTypeaheadSubmission } from './input/streaming-turn-typeahead.js';
|
|
428
|
-
import { wireDashboardTurnTypeaheadEcho } from './turn-typeahead-echo.js';
|
|
429
429
|
import { composeVertical, type LayoutZone } from '../layout/composer.js';
|
|
430
430
|
import type { CoreTurnDispatchTool } from '../core-turn/index.js';
|
|
431
431
|
// streamGrok kept as back-compat shim in chat.ts — not imported here
|
|
@@ -2656,6 +2656,41 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
2656
2656
|
}
|
|
2657
2657
|
chatScrollOffset = -1;
|
|
2658
2658
|
};
|
|
2659
|
+
const liveTypeaheadEchoes: { at: number; lines: string[] }[] = [];
|
|
2660
|
+
const restoreLiveTypeaheadEchoes = (): void => {
|
|
2661
|
+
let after = 0;
|
|
2662
|
+
for (const echo of liveTypeaheadEchoes) {
|
|
2663
|
+
let found = -1;
|
|
2664
|
+
for (let i = after; i <= chatLines.length - echo.lines.length; i++) {
|
|
2665
|
+
if (echo.lines.every((line, offset) => chatLines[i + offset] === line)) {
|
|
2666
|
+
found = i;
|
|
2667
|
+
break;
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
if (found < 0) {
|
|
2671
|
+
found = Math.max(after, Math.min(echo.at, chatLines.length));
|
|
2672
|
+
chatLines.splice(found, 0, ...echo.lines);
|
|
2673
|
+
}
|
|
2674
|
+
echo.at = found;
|
|
2675
|
+
after = found + echo.lines.length;
|
|
2676
|
+
}
|
|
2677
|
+
};
|
|
2678
|
+
const echoSubmittedUserText = (text: string, opts?: { live?: boolean }): void => {
|
|
2679
|
+
const echoStart = chatLines.length;
|
|
2680
|
+
chatLines.push('');
|
|
2681
|
+
if (opts?.live) chatLines.push(C.muted(' ↳ 이어서'));
|
|
2682
|
+
const echoWrapCols = Math.max(20, termSize().cols - 6);
|
|
2683
|
+
const echoRows = wrapAnsiByWidth(`❯ ${text}`, echoWrapCols);
|
|
2684
|
+
if (echoRows.length === 0) {
|
|
2685
|
+
chatLines.push(C.accent('❯ '));
|
|
2686
|
+
} else {
|
|
2687
|
+
echoRows.forEach((row, i) => {
|
|
2688
|
+
chatLines.push(C.accent(i === 0 ? row : ` ${row}`));
|
|
2689
|
+
});
|
|
2690
|
+
}
|
|
2691
|
+
if (opts?.live) liveTypeaheadEchoes.push({ at: echoStart, lines: chatLines.slice(echoStart) });
|
|
2692
|
+
chatScrollOffset = -1;
|
|
2693
|
+
};
|
|
2659
2694
|
const pushDebugLine = (line: string): void => {
|
|
2660
2695
|
debugLines.push(line);
|
|
2661
2696
|
const dropCount = trimLogBuffer(debugLines, DEBUG_LOG_BUFFER_SOFT_LIMIT);
|
|
@@ -6907,7 +6942,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
6907
6942
|
turnTypeaheadRef.state = r.state;
|
|
6908
6943
|
if (r.submissionDisposition) {
|
|
6909
6944
|
debug.log('dashboard.turn-typeahead', 'submission-classified', {
|
|
6910
|
-
|
|
6945
|
+
length: (r.immediateSubmission ?? turnTypeaheadRef.state.queuedSubmissions.at(-1))?.length ?? 0,
|
|
6911
6946
|
disposition: r.submissionDisposition,
|
|
6912
6947
|
});
|
|
6913
6948
|
// ⭐⭐⭐ `B3`(2026-08-19 · 대표 지시 ②) — FIFO 로 «턴 종료 후» 나가던 것을,
|
|
@@ -6934,6 +6969,11 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
6934
6969
|
// 배수된 만큼을 앞에서부터 떨어낸다(FIFO · 순서 보존).
|
|
6935
6970
|
const remaining = turnTypeaheadRef.state.queuedSubmissions.slice(drained.length);
|
|
6936
6971
|
turnTypeaheadRef.state = { ...turnTypeaheadRef.state, queuedSubmissions: remaining };
|
|
6972
|
+
for (const text of drained) {
|
|
6973
|
+
echoSubmittedUserText(text, { live: true });
|
|
6974
|
+
debug.log('dashboard.turn-typeahead', 'echoed', { path: 'live', length: text.length });
|
|
6975
|
+
}
|
|
6976
|
+
if (drained.length > 0) draw();
|
|
6937
6977
|
debug.log('dashboard.turn-typeahead', 'drained-into-live-turn', {
|
|
6938
6978
|
drained: drained.length, remaining: remaining.length,
|
|
6939
6979
|
});
|
|
@@ -15751,6 +15791,12 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
15751
15791
|
chatLines.push(C.muted(` ${resume.turns} turns restored. Next turn continues this conversation.`));
|
|
15752
15792
|
}
|
|
15753
15793
|
}
|
|
15794
|
+
if (dashboardAcpBootResult.mode !== 'resumed' && !chat.history.some((message) => message.role !== 'system')) {
|
|
15795
|
+
try {
|
|
15796
|
+
const project = suggestProjectForFolder(process.cwd());
|
|
15797
|
+
if (project) chatLines.push(C.muted(` Project suggestion: ${project.name} (${project.id})`));
|
|
15798
|
+
} catch { /* suggestions must not interrupt the dashboard conversation */ }
|
|
15799
|
+
}
|
|
15754
15800
|
chatScrollOffset = -1;
|
|
15755
15801
|
void renderDashboardFirstScreenBand({
|
|
15756
15802
|
history: chat.history,
|
|
@@ -16820,8 +16866,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
16820
16866
|
lastTurnFinalStatus = undefined;
|
|
16821
16867
|
turnTypeaheadRef.state = drained.state;
|
|
16822
16868
|
nextInitial = drained.nextInitial;
|
|
16823
|
-
const
|
|
16824
|
-
if (turnTypeaheadEcho) chatLines.push(C.muted(turnTypeaheadEcho));
|
|
16869
|
+
const queuedTurnEndText = drained.injectEnter ? drained.nextInitial : undefined;
|
|
16825
16870
|
if (interrupted && queuedBeforeDrain > 0) {
|
|
16826
16871
|
debug.log('dashboard.turn-typeahead', 'restored-after-interrupt', { count: queuedBeforeDrain });
|
|
16827
16872
|
}
|
|
@@ -18655,25 +18700,11 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
18655
18700
|
// pattern. Without this draw(), the echo sits in the chatLines
|
|
18656
18701
|
// array but doesn't paint until after the skill router's
|
|
18657
18702
|
// classifier LLM call returns (~1-2s of visual silence).
|
|
18658
|
-
|
|
18659
|
-
|
|
18660
|
-
|
|
18661
|
-
// with a `\u2026+N` marker by the log-pane renderer (which keeps a
|
|
18662
|
-
// strict 1-line-per-row invariant and only truncates lines that
|
|
18663
|
-
// weren't pre-wrapped). termCols-6 leaves room for the log
|
|
18664
|
-
// pane's 2-col left margin plus the 2-col hanging indent on
|
|
18665
|
-
// continuation rows; the \u276f marker stays on the first row only.
|
|
18666
|
-
const echoWrapCols = Math.max(20, termSize().cols - 6);
|
|
18667
|
-
const echoRows = wrapAnsiByWidth(`\u276f ${tok.text}`, echoWrapCols);
|
|
18668
|
-
if (echoRows.length === 0) {
|
|
18669
|
-
chatLines.push(C.accent('\u276f '));
|
|
18670
|
-
} else {
|
|
18671
|
-
echoRows.forEach((row, i) => {
|
|
18672
|
-
chatLines.push(C.accent(i === 0 ? row : ` ${row}`));
|
|
18673
|
-
});
|
|
18703
|
+
echoSubmittedUserText(tok.text);
|
|
18704
|
+
if (queuedTurnEndText !== undefined && input.text === queuedTurnEndText) {
|
|
18705
|
+
debug.log('dashboard.turn-typeahead', 'echoed', { path: 'turn-end', length: queuedTurnEndText.length });
|
|
18674
18706
|
}
|
|
18675
18707
|
renderAttachmentSummary(tok.added);
|
|
18676
|
-
chatScrollOffset = -1;
|
|
18677
18708
|
draw();
|
|
18678
18709
|
|
|
18679
18710
|
// Start the animated thinking indicator BEFORE the router so
|
|
@@ -19031,6 +19062,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
19031
19062
|
createTurnStreamRuntime: (initialAssistantStart) => {
|
|
19032
19063
|
latestTurnUsage = undefined;
|
|
19033
19064
|
createDashboardTurnStreamRuntimeEscBoundary(escAbortToolState);
|
|
19065
|
+
liveTypeaheadEchoes.length = 0;
|
|
19034
19066
|
const toolRendering = {
|
|
19035
19067
|
...getUserConfig().chat.rendering.tool,
|
|
19036
19068
|
expandHint: false,
|
|
@@ -19070,6 +19102,11 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
19070
19102
|
});
|
|
19071
19103
|
return {
|
|
19072
19104
|
...turnStreamRuntime,
|
|
19105
|
+
onText: (chunk, accumulated) => {
|
|
19106
|
+
turnStreamRuntime.onText(chunk, accumulated);
|
|
19107
|
+
restoreLiveTypeaheadEchoes();
|
|
19108
|
+
draw();
|
|
19109
|
+
},
|
|
19073
19110
|
onToolCall: (call) => {
|
|
19074
19111
|
const toolCall = call as DashboardTurnStreamCall;
|
|
19075
19112
|
trackEscAbortToolCall(escAbortToolState, toolCall);
|
|
@@ -3995,6 +3995,13 @@ export function buildDashboardSlashRegistry(): SlashCommandRegistry<DashboardSla
|
|
|
3995
3995
|
}
|
|
3996
3996
|
|
|
3997
3997
|
if (seSub === 'new' || seSub === 'clear') {
|
|
3998
|
+
if (seSub === 'new') {
|
|
3999
|
+
try {
|
|
4000
|
+
const { suggestProjectForFolder } = await import('../../project/project-store.js');
|
|
4001
|
+
const project = suggestProjectForFolder(process.cwd());
|
|
4002
|
+
if (project) ctx.chatLines.push(ctx.muted(` Project suggestion: ${project.name} (${project.id})`));
|
|
4003
|
+
} catch { /* suggestions must not interrupt a new conversation */ }
|
|
4004
|
+
}
|
|
3998
4005
|
// Clear the in-memory history and detach if attached. Doesn't
|
|
3999
4006
|
// delete any session files — those stay on disk for
|
|
4000
4007
|
// /session list to enumerate.
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { SlashCommand } from '../../chat/index.js';
|
|
2
|
+
import { readTuiSlashAudience, slashMaturity, slashVisibleFor } from '../../maturity/tui-slash-maturity.js';
|
|
3
|
+
import type { Role } from '../../maturity/feature-maturity.js';
|
|
2
4
|
import { visibleWidth } from '../../tui.js';
|
|
3
5
|
|
|
4
6
|
const ESSENTIAL_KEYS = [
|
|
@@ -34,46 +36,59 @@ export function buildEssentialHelpLines({
|
|
|
34
36
|
names,
|
|
35
37
|
descriptions,
|
|
36
38
|
width,
|
|
39
|
+
audience = readTuiSlashAudience(),
|
|
37
40
|
}: {
|
|
38
41
|
names: readonly string[];
|
|
39
42
|
descriptions: readonly Pick<SlashCommand, 'name' | 'aliases' | 'description'>[];
|
|
40
43
|
width: number;
|
|
44
|
+
audience?: { role: Role; showBeta: boolean };
|
|
41
45
|
}): string[] {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
const
|
|
59
|
-
const
|
|
46
|
+
const registered = new Set(names);
|
|
47
|
+
const entries = descriptions
|
|
48
|
+
.filter((command) => registered.has(command.name) &&
|
|
49
|
+
((audience.role === 'owner' && slashMaturity(command.name) === undefined) || slashVisibleFor(command.name, audience.role, { showBeta: audience.showBeta })))
|
|
50
|
+
.map((command) => {
|
|
51
|
+
const aliases = [...new Set(command.aliases ?? [])].filter((alias) => registered.has(alias) && alias !== command.name);
|
|
52
|
+
const label = `/${command.name}${aliases.length ? ` (${aliases.join(', ')})` : ''}`;
|
|
53
|
+
const description = command.description.replace(/\s+/g, ' ').trim()
|
|
54
|
+
.replace(/^(?:(?:H\d+\s+P\d+|CV-\d+|Sprint\s+\d+|Showroom\s+v2)(?:\s*[·—:]\s*|\s+))+/i, '')
|
|
55
|
+
.trim();
|
|
56
|
+
return { label, name: command.name, description };
|
|
57
|
+
})
|
|
58
|
+
.filter(({ label, description }) => description && !/[│╭╮╰╯┌┐└┘─]/.test(label))
|
|
59
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
60
|
+
// Reserve the longest label, 28 description characters, and an ellipsis
|
|
61
|
+
// in each cell. Never trade a missing label for a shorter modal.
|
|
62
|
+
const widestLabel = Math.max(0, ...entries.map(({ label }) => visibleWidth(label)));
|
|
63
|
+
const readableCellWidth = widestLabel + 2 + 28 + 1;
|
|
64
|
+
const columns = entries.length >= 40 && width >= 100
|
|
65
|
+
? Math.max(1, Math.floor(width / (readableCellWidth + 1)))
|
|
66
|
+
: 1;
|
|
60
67
|
const columnWidth = Math.floor(width / columns);
|
|
61
68
|
const commandLines: string[] = [];
|
|
62
69
|
for (let start = 0; start < entries.length; start += columns) {
|
|
63
|
-
const cells = entries.slice(start, start + columns).map(({
|
|
70
|
+
const cells = entries.slice(start, start + columns).map(({ label, description }) => {
|
|
64
71
|
const cellWidth = columnWidth - (columns > 1 ? 1 : 0);
|
|
65
|
-
|
|
66
|
-
return fitLine(`/${name} ${description}`, cellWidth);
|
|
72
|
+
return visibleWidth(`${label} `) > cellWidth ? '' : fitLine(`${label} ${description}`, cellWidth);
|
|
67
73
|
});
|
|
68
74
|
commandLines.push(cells.map((cell, index) =>
|
|
69
75
|
index === cells.length - 1 ? cell : cell + ' '.repeat(columnWidth - visibleWidth(cell)),
|
|
70
76
|
).join('').trimEnd());
|
|
71
77
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
''
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
const keyItems = ESSENTIAL_KEYS.map(([key, description]) => `${key} ${description}`);
|
|
79
|
+
const keyLines: string[] = [];
|
|
80
|
+
if (entries.length >= 40 && width >= 100) {
|
|
81
|
+
let keyRow = '';
|
|
82
|
+
for (const item of keyItems) {
|
|
83
|
+
if (keyRow && visibleWidth(keyRow) + 2 + visibleWidth(item) > width) {
|
|
84
|
+
keyLines.push(keyRow);
|
|
85
|
+
keyRow = '';
|
|
86
|
+
}
|
|
87
|
+
keyRow += (keyRow ? ' ' : '') + fitLine(item, width);
|
|
88
|
+
}
|
|
89
|
+
if (keyRow) keyLines.push(keyRow);
|
|
90
|
+
} else {
|
|
91
|
+
keyLines.push(...keyItems.map((item) => fitLine(item, width)));
|
|
92
|
+
}
|
|
93
|
+
return [fitLine('Commands', width), ...commandLines, '', fitLine('Keys', width), ...keyLines];
|
|
79
94
|
}
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { debug } from '../debug/log.js';
|
|
9
|
+
import { readPendingQuestions } from '../ask-user-question/pending-questions.js';
|
|
9
10
|
import { DecisionLedger, type DecisionEntry } from './decision-ledger.js';
|
|
10
11
|
|
|
11
|
-
export type CardPlatform = 'telegram' | 'discord';
|
|
12
|
+
export type CardPlatform = 'telegram' | 'discord' | 'webpush';
|
|
12
13
|
export interface CardButton { label: string; data: string }
|
|
13
14
|
export interface CardView { text: string; buttons: CardButton[][] }
|
|
14
15
|
export interface CardRef { chat: string; message: string }
|
|
@@ -99,6 +100,7 @@ export interface DecisionCardServiceOptions {
|
|
|
99
100
|
/** Owner user ids for this platform — taps from anyone else are refused and observed. */
|
|
100
101
|
ownerIds: readonly string[];
|
|
101
102
|
ledger?: DecisionLedger;
|
|
103
|
+
pendingQuestions?: typeof readPendingQuestions;
|
|
102
104
|
/** Where this platform keeps which card it sent (one file per platform — the two bots may run in different processes). */
|
|
103
105
|
statePath?: string;
|
|
104
106
|
now?: () => Date;
|
|
@@ -149,6 +151,52 @@ export class DecisionCardService {
|
|
|
149
151
|
}
|
|
150
152
|
let dirty = false;
|
|
151
153
|
if (!state.since) { state.since = this.now().toISOString(); dirty = true; debug.log('decisions.telegram', 'baseline', { platform, since: state.since }); }
|
|
154
|
+
// Same state root as the ledger — the H2 answer writer delivers under the ledger's stateDir, so the questions are read there too.
|
|
155
|
+
const pending = this.opts.pendingQuestions ? this.opts.pendingQuestions() : readPendingQuestions({ root: () => join(this.ledger.path, '..', '..') });
|
|
156
|
+
if (pending.ok) {
|
|
157
|
+
const seen = new Set(all.flatMap(entry => entry.resume ? [entry.resume.questionId] : []));
|
|
158
|
+
for (const record of pending.questions) {
|
|
159
|
+
for (const question of record.questions) {
|
|
160
|
+
const data = { questionId: record.id, runId: record.runId };
|
|
161
|
+
if (record.expiresAt && record.expiresAt <= this.now().toISOString()) {
|
|
162
|
+
debug.log('hitl.card-bridge', 'skipped', { ...data, reason: 'expired' });
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (question.impact !== 'high' && question.impact !== 'critical') {
|
|
166
|
+
debug.log('hitl.card-bridge', 'skipped', { ...data, reason: 'impact' });
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (record.questions.length !== 1 || question.options.length < 2 || question.options.length > 4) {
|
|
170
|
+
debug.log('hitl.card-bridge', 'skipped', { ...data, reason: 'options' });
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (seen.has(record.id)) {
|
|
174
|
+
debug.log('hitl.card-bridge', 'dedup', { ...data, decisionId: all.find(entry => entry.resume?.questionId === record.id)?.id, reason: 'already-raised' });
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const keys = ['a', 'b', 'c', 'd'];
|
|
179
|
+
const recommended = question.recommendedIndex;
|
|
180
|
+
const entry = this.ledger.raise({
|
|
181
|
+
title: question.question.split(/\r?\n/, 1)[0]!.slice(0, 120),
|
|
182
|
+
category: question.impact === 'critical' ? 'irreversible' : 'scope',
|
|
183
|
+
scqa: { s: question.question, c: question.question },
|
|
184
|
+
options: question.options.map((option, index) => ({ key: keys[index]!, label: option.label, consequence: option.description })),
|
|
185
|
+
recommendation: recommended !== undefined && Number.isInteger(recommended) && recommended >= 0 && recommended < question.options.length
|
|
186
|
+
? { option: keys[recommended]!, why: '런이 추천' } : { skipped: true, reason: '추천 없음' },
|
|
187
|
+
raisedBy: { agent: 'harness' },
|
|
188
|
+
resume: { questionId: record.id, ...(record.runId === undefined ? {} : { runId: record.runId }) },
|
|
189
|
+
...(record.expiresAt ? { dueAt: record.expiresAt } : {}),
|
|
190
|
+
});
|
|
191
|
+
seen.add(record.id);
|
|
192
|
+
all.unshift(entry);
|
|
193
|
+
debug.log('hitl.card-bridge', 'raised', { ...data, decisionId: entry.id });
|
|
194
|
+
} catch (error) {
|
|
195
|
+
debug.log('hitl.card-bridge', 'skipped', { ...data, reason: error instanceof Error ? error.name : 'unknown' });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} else debug.log('hitl.card-bridge', 'skipped', { reason: 'pending-unreadable' });
|
|
152
200
|
const chats = await this.opts.transport.ownerChats().catch(() => [] as string[]);
|
|
153
201
|
for (const e of all) {
|
|
154
202
|
const card = state.cards[e.id];
|