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
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');
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface DashboardStatusLinesInput {
|
|
2
|
+
readonly provider?: string | null;
|
|
3
|
+
readonly model?: string | null;
|
|
4
|
+
readonly reasoning?: string | null;
|
|
5
|
+
readonly daemonAddress?: string | null;
|
|
6
|
+
readonly daemonConnected: boolean;
|
|
7
|
+
readonly accountName?: string | null;
|
|
8
|
+
readonly sessionSurfaceLines: readonly string[];
|
|
9
|
+
readonly view: string;
|
|
10
|
+
readonly focus: string;
|
|
11
|
+
readonly cwd: string;
|
|
12
|
+
readonly preview: string;
|
|
13
|
+
readonly starterClosed: string;
|
|
14
|
+
readonly chatOnly: string;
|
|
15
|
+
readonly acp: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function buildDashboardStatusLines(input: DashboardStatusLinesInput): string[] {
|
|
19
|
+
const known = (value?: string | null): string => value?.trim() || '모름';
|
|
20
|
+
// A remote URL can contain credentials or query parameters. Neither belongs in /status.
|
|
21
|
+
let address = '모름';
|
|
22
|
+
if (input.daemonAddress?.trim()) {
|
|
23
|
+
try {
|
|
24
|
+
const url = new URL(input.daemonAddress);
|
|
25
|
+
address = `${url.protocol}//${url.host}`;
|
|
26
|
+
} catch {
|
|
27
|
+
address = '모름';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return [
|
|
31
|
+
`모델: ${known(input.provider)}/${known(input.model)} (${known(input.reasoning)})`,
|
|
32
|
+
`연결: 데몬 ${address} ${input.daemonConnected ? '붙음' : '안 붙음'}`,
|
|
33
|
+
`계정: ${input.accountName?.trim() === 'default' ? '기본' : known(input.accountName)}`,
|
|
34
|
+
'',
|
|
35
|
+
'자세히',
|
|
36
|
+
...input.sessionSurfaceLines.map(line => ` ${line}`),
|
|
37
|
+
` view: ${input.view}`,
|
|
38
|
+
` focus: ${input.focus}`,
|
|
39
|
+
` cwd: ${input.cwd}`,
|
|
40
|
+
` preview: ${input.preview}`,
|
|
41
|
+
` starterClosed: ${input.starterClosed}`,
|
|
42
|
+
` chatOnly: ${input.chatOnly}`,
|
|
43
|
+
` acp: ${input.acp}`,
|
|
44
|
+
];
|
|
45
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface FirstScreenBandInput {
|
|
2
|
+
readonly width: number;
|
|
3
|
+
readonly daemon: boolean;
|
|
4
|
+
readonly pwa?: {
|
|
5
|
+
readonly tailnet?: string | null;
|
|
6
|
+
readonly lan?: string | null;
|
|
7
|
+
readonly loopback?: string | null;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** A startup transcript band; never includes credentials or a partial address. */
|
|
12
|
+
export function buildFirstScreenBand(input: FirstScreenBandInput): string[] {
|
|
13
|
+
const width = Number.isFinite(input.width) ? Math.max(0, Math.floor(input.width)) : 0;
|
|
14
|
+
const fit = (text: string): string => text.slice(0, width);
|
|
15
|
+
const address = [input.pwa?.tailnet, input.pwa?.lan, input.pwa?.loopback]
|
|
16
|
+
.map((candidate) => {
|
|
17
|
+
if (!candidate) return null;
|
|
18
|
+
try {
|
|
19
|
+
const url = new URL(candidate);
|
|
20
|
+
if (!['http:', 'https:'].includes(url.protocol) || !url.hostname) return null;
|
|
21
|
+
return `${url.origin}${url.pathname === '/app/' ? '/app/' : ''}`;
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
.find((candidate) => candidate !== null);
|
|
27
|
+
const fallback = input.pwa === undefined ? 'PWA: not checked' : 'PWA: unavailable';
|
|
28
|
+
const addressLine = address ? `PWA: ${address}` : fallback;
|
|
29
|
+
|
|
30
|
+
return [
|
|
31
|
+
fit(`elanous | daemon: ${input.daemon ? 'online' : 'offline'}`),
|
|
32
|
+
addressLine.length <= width ? addressLine : fit(address ? 'PWA: address available' : fallback),
|
|
33
|
+
fit('Type a message to begin | /help for commands'),
|
|
34
|
+
];
|
|
35
|
+
}
|
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
|
|
@@ -881,6 +881,10 @@ import { getModelFamily, getModelTier } from '../models/prompts.js';
|
|
|
881
881
|
import { buildPromptInjection, getPromptBankStore } from '../prompt-bank/index.js';
|
|
882
882
|
import { getInputHistoryStore, inputHistoryDbPath, inputHistoryJsonPath } from '../input-history.js';
|
|
883
883
|
import { inspectActiveProvider } from '../provider-summary.js';
|
|
884
|
+
import { activeCodexAccountView } from '../oauth/codex-account-store.js';
|
|
885
|
+
import { buildDashboardStatusLines } from './dashboard-status-lines.js';
|
|
886
|
+
import { buildFirstScreenBand } from './first-screen-band.js';
|
|
887
|
+
import { runNexusShow, type NexusShowResult } from '../cli/nexus-show.js';
|
|
884
888
|
import {
|
|
885
889
|
workingDirSegment, sessionCwdSegment, modelSegment, gitSegment,
|
|
886
890
|
ctxBarSegment, costSegment, speedSegment, elapsedSegment, cacheSegment,
|
|
@@ -2100,6 +2104,35 @@ export function createDashboardLogSearchOpener(deps: {
|
|
|
2100
2104
|
};
|
|
2101
2105
|
}
|
|
2102
2106
|
|
|
2107
|
+
export function renderDashboardFirstScreenBand(input: {
|
|
2108
|
+
readonly history: ReadonlyArray<{ role: string }>;
|
|
2109
|
+
readonly lines: string[];
|
|
2110
|
+
readonly width: number;
|
|
2111
|
+
readonly draw: () => void;
|
|
2112
|
+
readonly observe: (data: { daemon: boolean; hasPwa: boolean; hasTailnet: boolean }) => void;
|
|
2113
|
+
readonly show?: () => Promise<Pick<NexusShowResult, 'status' | 'urls'>>;
|
|
2114
|
+
}): Promise<void> {
|
|
2115
|
+
if (input.history.some((message) => message.role !== 'system')) return Promise.resolve();
|
|
2116
|
+
const band = buildFirstScreenBand({ width: input.width, daemon: false });
|
|
2117
|
+
input.lines.unshift(...band);
|
|
2118
|
+
const show = input.show ?? (() => runNexusShow({ format: 'json', out: { log: () => {}, error: () => {} } }));
|
|
2119
|
+
const observe = (data: { daemon: boolean; hasPwa: boolean; hasTailnet: boolean }): void => {
|
|
2120
|
+
try { input.observe(data); } catch { /* observation cannot interrupt the transcript */ }
|
|
2121
|
+
};
|
|
2122
|
+
return Promise.resolve().then(show).then((result) => {
|
|
2123
|
+
const daemon = result.status !== 'absent';
|
|
2124
|
+
const pwa = result.urls?.pwa;
|
|
2125
|
+
const hasPwa = !!pwa;
|
|
2126
|
+
const hasTailnet = !!pwa?.tailnet;
|
|
2127
|
+
observe({ daemon, hasPwa, hasTailnet });
|
|
2128
|
+
if (input.lines[1] !== band[1]) return;
|
|
2129
|
+
input.lines[1] = buildFirstScreenBand({ width: input.width, daemon, pwa })[1]!;
|
|
2130
|
+
try { input.draw(); } catch { /* screen may have closed */ }
|
|
2131
|
+
}).catch(() => {
|
|
2132
|
+
observe({ daemon: false, hasPwa: false, hasTailnet: false });
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2103
2136
|
export async function showDashboard(opts: ShowDashboardOptions = {}): Promise<DashboardAction> {
|
|
2104
2137
|
const dashboardQuitConfirmationState = createQuitConfirmationState();
|
|
2105
2138
|
const signalQuitConfirmationState = createQuitConfirmationState();
|
|
@@ -2623,6 +2656,41 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
2623
2656
|
}
|
|
2624
2657
|
chatScrollOffset = -1;
|
|
2625
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
|
+
};
|
|
2626
2694
|
const pushDebugLine = (line: string): void => {
|
|
2627
2695
|
debugLines.push(line);
|
|
2628
2696
|
const dropCount = trimLogBuffer(debugLines, DEBUG_LOG_BUFFER_SOFT_LIMIT);
|
|
@@ -6330,19 +6398,34 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
6330
6398
|
draw,
|
|
6331
6399
|
});
|
|
6332
6400
|
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
)
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6401
|
+
let daemonAttached = false;
|
|
6402
|
+
const getDashboardStatusLines = () => {
|
|
6403
|
+
const cfg = getUserConfig();
|
|
6404
|
+
const active = inspectActiveProvider(cfg);
|
|
6405
|
+
const reasoningProvider = (['openai-codex', 'openai', 'anthropic', 'gemini', 'grok', 'local', 'openrouter'] as const)
|
|
6406
|
+
.find(provider => active.provider === provider || active.provider === `auto:${provider}`);
|
|
6407
|
+
return buildDashboardStatusLines({
|
|
6408
|
+
provider: active.provider,
|
|
6409
|
+
model: active.model,
|
|
6410
|
+
reasoning: reasoningProvider
|
|
6411
|
+
? modelSupportsReasoning(reasoningProvider, active.model)
|
|
6412
|
+
? reasoningLevelLabel(effectiveReasoningLevel(cfg.llm, reasoningProvider, active.model))
|
|
6413
|
+
: '미지원'
|
|
6414
|
+
: undefined,
|
|
6415
|
+
daemonAddress: opts.remote?.url,
|
|
6416
|
+
daemonConnected: daemonAttached,
|
|
6417
|
+
accountName: active.provider === 'openai-codex' || active.provider === 'auto:openai-codex'
|
|
6418
|
+
? activeCodexAccountView().name : undefined,
|
|
6419
|
+
sessionSurfaceLines: buildSessionDashboardStatusLines(resolveSessionSurfaceStatus({ chatModeState })),
|
|
6420
|
+
view: String(workingDir.view),
|
|
6421
|
+
focus: workingDir.focus,
|
|
6422
|
+
cwd: workingDir.cwd,
|
|
6423
|
+
preview: dockedPreview.sourceMode,
|
|
6424
|
+
starterClosed: closedStarterPanes().length ? closedStarterPanes().map((pane) => paneLabel(pane)).join(', ') : 'none',
|
|
6425
|
+
chatOnly: `${effectiveChatOnlyMode() ? 'on' : 'off'}${chatOnlyMode ? ' (manual)' : ''}`,
|
|
6426
|
+
acp: dashboardAcpChat.isBusy() ? 'busy' : 'idle',
|
|
6427
|
+
});
|
|
6428
|
+
};
|
|
6346
6429
|
|
|
6347
6430
|
bootDashboardSlashExecutor({
|
|
6348
6431
|
initDashboardSlashExecutor,
|
|
@@ -6859,7 +6942,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
6859
6942
|
turnTypeaheadRef.state = r.state;
|
|
6860
6943
|
if (r.submissionDisposition) {
|
|
6861
6944
|
debug.log('dashboard.turn-typeahead', 'submission-classified', {
|
|
6862
|
-
|
|
6945
|
+
length: (r.immediateSubmission ?? turnTypeaheadRef.state.queuedSubmissions.at(-1))?.length ?? 0,
|
|
6863
6946
|
disposition: r.submissionDisposition,
|
|
6864
6947
|
});
|
|
6865
6948
|
// ⭐⭐⭐ `B3`(2026-08-19 · 대표 지시 ②) — FIFO 로 «턴 종료 후» 나가던 것을,
|
|
@@ -6886,6 +6969,11 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
6886
6969
|
// 배수된 만큼을 앞에서부터 떨어낸다(FIFO · 순서 보존).
|
|
6887
6970
|
const remaining = turnTypeaheadRef.state.queuedSubmissions.slice(drained.length);
|
|
6888
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();
|
|
6889
6977
|
debug.log('dashboard.turn-typeahead', 'drained-into-live-turn', {
|
|
6890
6978
|
drained: drained.length, remaining: remaining.length,
|
|
6891
6979
|
});
|
|
@@ -15397,6 +15485,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
15397
15485
|
...(opts.remote ? {
|
|
15398
15486
|
remote: opts.remote,
|
|
15399
15487
|
onRemoteDisconnect: () => {
|
|
15488
|
+
daemonAttached = false;
|
|
15400
15489
|
pushChatLine(C.warning(' ⚠ 데몬 연결이 끊겼다 · 입력 중인 글은 그대로다 · 다시 붙으려면 TUI 를 다시 띄워라'));
|
|
15401
15490
|
draw();
|
|
15402
15491
|
},
|
|
@@ -15610,6 +15699,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
15610
15699
|
},
|
|
15611
15700
|
});
|
|
15612
15701
|
const dashboardAcpSession: DashboardSession = dashboardAcpBootResult.session;
|
|
15702
|
+
daemonAttached = !!opts.remote && dashboardAcpBootResult.mode !== 'in-process';
|
|
15613
15703
|
// ⭐ `B3` — 턴 중 발화를 «도는 턴»에 넣을 때 쓰는 키.
|
|
15614
15704
|
try { acpSessionIdRef.current = dashboardAcpSession.currentSessionId ?? null; } catch { /* 없으면 넣지 않는다 */ }
|
|
15615
15705
|
pushDebugLine(C.muted('[acp-boot] dashboard ACP session ready'));
|
|
@@ -15701,7 +15791,20 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
15701
15791
|
chatLines.push(C.muted(` ${resume.turns} turns restored. Next turn continues this conversation.`));
|
|
15702
15792
|
}
|
|
15703
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
|
+
}
|
|
15704
15800
|
chatScrollOffset = -1;
|
|
15801
|
+
void renderDashboardFirstScreenBand({
|
|
15802
|
+
history: chat.history,
|
|
15803
|
+
lines: chatLines,
|
|
15804
|
+
width: Math.max(0, termSize().cols - 4),
|
|
15805
|
+
draw: () => draw(),
|
|
15806
|
+
observe: (data) => { try { debug.log('dashboard.first-screen-band', 'resolved', data); } catch { /* observation is best-effort */ } },
|
|
15807
|
+
});
|
|
15705
15808
|
|
|
15706
15809
|
while (true) {
|
|
15707
15810
|
draw();
|
|
@@ -16763,8 +16866,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
16763
16866
|
lastTurnFinalStatus = undefined;
|
|
16764
16867
|
turnTypeaheadRef.state = drained.state;
|
|
16765
16868
|
nextInitial = drained.nextInitial;
|
|
16766
|
-
const
|
|
16767
|
-
if (turnTypeaheadEcho) chatLines.push(C.muted(turnTypeaheadEcho));
|
|
16869
|
+
const queuedTurnEndText = drained.injectEnter ? drained.nextInitial : undefined;
|
|
16768
16870
|
if (interrupted && queuedBeforeDrain > 0) {
|
|
16769
16871
|
debug.log('dashboard.turn-typeahead', 'restored-after-interrupt', { count: queuedBeforeDrain });
|
|
16770
16872
|
}
|
|
@@ -18598,25 +18700,11 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
18598
18700
|
// pattern. Without this draw(), the echo sits in the chatLines
|
|
18599
18701
|
// array but doesn't paint until after the skill router's
|
|
18600
18702
|
// classifier LLM call returns (~1-2s of visual silence).
|
|
18601
|
-
|
|
18602
|
-
|
|
18603
|
-
|
|
18604
|
-
// with a `\u2026+N` marker by the log-pane renderer (which keeps a
|
|
18605
|
-
// strict 1-line-per-row invariant and only truncates lines that
|
|
18606
|
-
// weren't pre-wrapped). termCols-6 leaves room for the log
|
|
18607
|
-
// pane's 2-col left margin plus the 2-col hanging indent on
|
|
18608
|
-
// continuation rows; the \u276f marker stays on the first row only.
|
|
18609
|
-
const echoWrapCols = Math.max(20, termSize().cols - 6);
|
|
18610
|
-
const echoRows = wrapAnsiByWidth(`\u276f ${tok.text}`, echoWrapCols);
|
|
18611
|
-
if (echoRows.length === 0) {
|
|
18612
|
-
chatLines.push(C.accent('\u276f '));
|
|
18613
|
-
} else {
|
|
18614
|
-
echoRows.forEach((row, i) => {
|
|
18615
|
-
chatLines.push(C.accent(i === 0 ? row : ` ${row}`));
|
|
18616
|
-
});
|
|
18703
|
+
echoSubmittedUserText(tok.text);
|
|
18704
|
+
if (queuedTurnEndText !== undefined && input.text === queuedTurnEndText) {
|
|
18705
|
+
debug.log('dashboard.turn-typeahead', 'echoed', { path: 'turn-end', length: queuedTurnEndText.length });
|
|
18617
18706
|
}
|
|
18618
18707
|
renderAttachmentSummary(tok.added);
|
|
18619
|
-
chatScrollOffset = -1;
|
|
18620
18708
|
draw();
|
|
18621
18709
|
|
|
18622
18710
|
// Start the animated thinking indicator BEFORE the router so
|
|
@@ -18974,6 +19062,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
18974
19062
|
createTurnStreamRuntime: (initialAssistantStart) => {
|
|
18975
19063
|
latestTurnUsage = undefined;
|
|
18976
19064
|
createDashboardTurnStreamRuntimeEscBoundary(escAbortToolState);
|
|
19065
|
+
liveTypeaheadEchoes.length = 0;
|
|
18977
19066
|
const toolRendering = {
|
|
18978
19067
|
...getUserConfig().chat.rendering.tool,
|
|
18979
19068
|
expandHint: false,
|
|
@@ -19013,6 +19102,11 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
19013
19102
|
});
|
|
19014
19103
|
return {
|
|
19015
19104
|
...turnStreamRuntime,
|
|
19105
|
+
onText: (chunk, accumulated) => {
|
|
19106
|
+
turnStreamRuntime.onText(chunk, accumulated);
|
|
19107
|
+
restoreLiveTypeaheadEchoes();
|
|
19108
|
+
draw();
|
|
19109
|
+
},
|
|
19016
19110
|
onToolCall: (call) => {
|
|
19017
19111
|
const toolCall = call as DashboardTurnStreamCall;
|
|
19018
19112
|
trackEscAbortToolCall(escAbortToolState, toolCall);
|