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
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// Pre-render field cards once; render only the photo composition on subsequent runs.
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { join, dirname, resolve } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
|
|
9
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const W = 1080, H = 1920, FPS = 30, CARD_VERSION = 1;
|
|
11
|
+
const cacheRoot = () => process.env.EXPLAINER_CACHE || join(homedir(), '.cache', 'elanous-explainer');
|
|
12
|
+
const valid = (path) => { try { const stat = statSync(path); return stat.isFile() && stat.size > 0; } catch { return false; } };
|
|
13
|
+
function run(bin, args, cwd) {
|
|
14
|
+
const result = spawnSync(bin, args, { cwd, encoding: 'utf8', maxBuffer: 4 * 1024 * 1024 });
|
|
15
|
+
if (result.error || result.status !== 0) throw new Error(`${bin}: ${result.error?.message ?? result.stderr?.slice(-2000) ?? `exit ${result.status}`}`);
|
|
16
|
+
return result.stdout;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function renderCards({ title, sub, intro, end, cacheDir }) {
|
|
20
|
+
for (const [part, output] of [['intro', intro], ['end', end]]) {
|
|
21
|
+
if (valid(output)) continue;
|
|
22
|
+
const project = join(cacheDir, `project-${part}`);
|
|
23
|
+
mkdirSync(project, { recursive: true });
|
|
24
|
+
run('node', [join(here, 'reel.mjs'), project, '--part', part, '--title', title, '--sub', sub]);
|
|
25
|
+
run('npx', ['-y', `hyperframes@${process.env.HYPERFRAMES_VERSION || '0.8.95'}`, 'render', '--fps', String(FPS), '--workers', '4', '--output', output], join(project, 'reel', 'hf'));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const normalizeCardTitle = (title) => !title || /^[a-z0-9]+(?:-[a-z0-9]+)*$/i.test(title) ? '현장 스케치' : title;
|
|
30
|
+
|
|
31
|
+
function ownerGone(lock) {
|
|
32
|
+
let pid;
|
|
33
|
+
try { pid = JSON.parse(readFileSync(join(lock, 'owner.json'), 'utf8')).pid; }
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error.code !== 'ENOENT' && !(error instanceof SyntaxError)) throw error;
|
|
36
|
+
// A new owner may still be writing its identity. Never reclaim that window.
|
|
37
|
+
try { return Date.now() - statSync(lock).mtimeMs > 5_000; } catch { return false; }
|
|
38
|
+
}
|
|
39
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return false;
|
|
40
|
+
try { process.kill(pid, 0); return false; }
|
|
41
|
+
catch (error) { return error.code === 'ESRCH'; }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// A single reaper checks identity again under this claim before moving an orphan.
|
|
45
|
+
function recoverLock(lock) {
|
|
46
|
+
const recovery = `${lock}.recovery`;
|
|
47
|
+
try { mkdirSync(recovery); }
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (error.code !== 'EEXIST') throw error;
|
|
50
|
+
if (ownerGone(recovery)) rmSync(recovery, { recursive: true, force: true });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
writeFileSync(join(recovery, 'owner.json'), JSON.stringify({ pid: process.pid }));
|
|
55
|
+
if (!ownerGone(lock)) return;
|
|
56
|
+
const abandoned = `${lock}.abandoned-${randomUUID()}`;
|
|
57
|
+
try { renameSync(lock, abandoned); }
|
|
58
|
+
catch (error) { if (error.code !== 'ENOENT') throw error; return; }
|
|
59
|
+
rmSync(abandoned, { recursive: true, force: true });
|
|
60
|
+
} finally { rmSync(recovery, { recursive: true, force: true }); }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function ensureCards({ title, sub, cacheDir = join(cacheRoot(), 'cards'), render = renderCards }) {
|
|
64
|
+
title = normalizeCardTitle(title);
|
|
65
|
+
const key = createHash('sha256').update(JSON.stringify([title, sub, W, H, CARD_VERSION])).digest('hex').slice(0, 16);
|
|
66
|
+
const dir = join(cacheDir, key);
|
|
67
|
+
const intro = join(dir, 'intro.mp4'), end = join(dir, 'end.mp4');
|
|
68
|
+
const ready = () => valid(intro) && valid(end);
|
|
69
|
+
if (ready()) return { intro, end, cardsCache: 'hit' };
|
|
70
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
71
|
+
const lock = `${dir}.lock`;
|
|
72
|
+
const deadline = Date.now() + 10 * 60_000;
|
|
73
|
+
for (;;) {
|
|
74
|
+
try {
|
|
75
|
+
mkdirSync(lock);
|
|
76
|
+
try { writeFileSync(join(lock, 'owner.json'), JSON.stringify({ pid: process.pid })); }
|
|
77
|
+
catch (error) { rmSync(lock, { recursive: true, force: true }); throw error; }
|
|
78
|
+
break;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (error.code !== 'EEXIST') throw error;
|
|
81
|
+
if (ready()) return { intro, end, cardsCache: 'hit' };
|
|
82
|
+
recoverLock(lock);
|
|
83
|
+
if (Date.now() >= deadline) throw new Error(`timed out waiting for card render: ${lock}`);
|
|
84
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
let staging;
|
|
88
|
+
try {
|
|
89
|
+
if (ready()) return { intro, end, cardsCache: 'hit' };
|
|
90
|
+
staging = mkdtempSync(join(cacheDir, `.${key}-`));
|
|
91
|
+
await render({ title, sub, intro: join(staging, 'intro.mp4'), end: join(staging, 'end.mp4'), cacheDir: staging });
|
|
92
|
+
if (!valid(join(staging, 'intro.mp4')) || !valid(join(staging, 'end.mp4'))) throw new Error(`card renderer did not produce both cards: ${dir}`);
|
|
93
|
+
rmSync(dir, { recursive: true, force: true });
|
|
94
|
+
renameSync(staging, dir);
|
|
95
|
+
return { intro, end, cardsCache: 'miss' };
|
|
96
|
+
} finally {
|
|
97
|
+
if (staging) rmSync(staging, { recursive: true, force: true });
|
|
98
|
+
rmSync(lock, { recursive: true, force: true });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function duration(file) {
|
|
103
|
+
const seconds = Number(run('ffprobe', ['-v', 'error', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', file]).trim());
|
|
104
|
+
if (!Number.isFinite(seconds) || seconds <= 0) throw new Error(`invalid duration: ${file}`);
|
|
105
|
+
return seconds;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function stitch({ intro, body, end, bgm, out }) {
|
|
109
|
+
const first = duration(intro), middle = duration(body), last = duration(end);
|
|
110
|
+
if (Math.min(first, middle, last) <= 0.3) throw new Error('xfade requires clips longer than 0.3s');
|
|
111
|
+
const total = first + middle + last - 0.6;
|
|
112
|
+
const args = ['-v', 'error', '-y', '-i', intro, '-i', body, '-i', end];
|
|
113
|
+
const music = bgm && existsSync(bgm);
|
|
114
|
+
if (music) args.push('-stream_loop', '-1', '-i', bgm);
|
|
115
|
+
const filters = [
|
|
116
|
+
...[0, 1, 2].map((i) => `[${i}:v]fps=${FPS},scale=${W}:${H},setsar=1,format=yuv420p,settb=AVTB,setpts=PTS-STARTPTS[v${i}]`),
|
|
117
|
+
`[v0][v1]xfade=transition=fade:duration=0.3:offset=${(first - 0.3).toFixed(3)}[v01]`,
|
|
118
|
+
`[v01][v2]xfade=transition=fade:duration=0.3:offset=${(first + middle - 0.6).toFixed(3)}[v]`,
|
|
119
|
+
];
|
|
120
|
+
if (music) filters.push(`[3:a]atrim=duration=${total.toFixed(3)},asetpts=PTS-STARTPTS,afade=t=in:d=0.4,afade=t=out:st=${Math.max(0, total - 1.6).toFixed(3)}:d=1.6,loudnorm=I=-16:TP=-1.5:LRA=9[a]`);
|
|
121
|
+
else filters.push(`anullsrc=r=48000:cl=stereo,atrim=duration=${total.toFixed(3)}[a]`);
|
|
122
|
+
mkdirSync(dirname(out), { recursive: true });
|
|
123
|
+
run('ffmpeg', [...args, '-filter_complex', filters.join(';'), '-map', '[v]', '-map', '[a]', '-t', total.toFixed(3), '-r', String(FPS), '-c:v', 'libx264', '-preset', 'veryfast', '-pix_fmt', 'yuv420p', '-c:a', 'aac', '-ar', '48000', '-ac', '2', '-movflags', '+faststart', out]);
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
128
|
+
const args = process.argv.slice(2);
|
|
129
|
+
const value = (name) => { const i = args.indexOf(name); return i < 0 ? undefined : args[i + 1]; };
|
|
130
|
+
const folder = args[1] && resolve(args[1]);
|
|
131
|
+
if (args[0] === '--warm-cards') {
|
|
132
|
+
const title = value('--title'), sub = value('--sub');
|
|
133
|
+
if (title === undefined || sub === undefined) throw new Error('--warm-cards requires --title and --sub');
|
|
134
|
+
const cards = await ensureCards({ title, sub });
|
|
135
|
+
console.log(`reel: cards ${cards.cardsCache} · ${dirname(cards.intro)}`);
|
|
136
|
+
} else if (args[0] === '--instant' && folder) {
|
|
137
|
+
const timelineFile = join(folder, 'reel', 'timeline.json');
|
|
138
|
+
const timeline = JSON.parse(readFileSync(timelineFile, 'utf8'));
|
|
139
|
+
const cards = await ensureCards({ title: timeline.title, sub: timeline.sub });
|
|
140
|
+
const bgm = process.env.FIELD_REEL_BGM || join(cacheRoot(), 'bgm', 'field.wav');
|
|
141
|
+
stitch({ intro: cards.intro, body: join(folder, 'reel', 'body.mp4'), end: cards.end, bgm, out: join(folder, 'reel', 'reel-9x16.mp4') });
|
|
142
|
+
writeFileSync(timelineFile, JSON.stringify({ ...timeline, cardsCache: cards.cardsCache, music: existsSync(bgm) }, null, 2));
|
|
143
|
+
console.log(`reel: cards ${cards.cardsCache}`);
|
|
144
|
+
} else throw new Error('usage: stitch.mjs --warm-cards --title … --sub … | --instant <folder>');
|
|
145
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pitch-deck
|
|
3
|
+
description: "회사나 제품의 한국어 피치덱·투자 소개 슬라이드를 사실 출처와 함께 만들 때 사용한다. 피치덱, 발표 덱, pitch deck 요청에 대응한다."
|
|
4
|
+
category: knowledge
|
|
5
|
+
sideEffects: write
|
|
6
|
+
triggers:
|
|
7
|
+
- 피치덱
|
|
8
|
+
- pitch deck
|
|
9
|
+
requires: []
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Pitch deck — 근거를 지닌 짧은 발표
|
|
13
|
+
|
|
14
|
+
## 언제 / 입력
|
|
15
|
+
|
|
16
|
+
회사 또는 제품을 5~10분에 소개할 때 쓴다. 입력은 **회사 한 줄 정의**, 저장소의 **사실 원천 파일들**(읽은 판본과 줄 위치), **발표 길이**다. 독자·구매자·공개 가능 범위를 먼저 정한다. 확인되지 않은 값은 매출·고객·시장 실적처럼 말하지 않는다.
|
|
17
|
+
|
|
18
|
+
## 장 구성
|
|
19
|
+
|
|
20
|
+
기본 뼈대 **일곱**: **정의**(한 줄로 무엇을 하나) → **문제**(누가 왜 멈추나) → **시장**(TAM·SAM·SOM, 출처 없으면 가정) → **기술/BM**(해결법·경쟁력·BM·누가 지불하나) → **성과**(검증된 사례·측정 또는 «아직 검증 전») → **성장 전략**(어떤 순서로 누구에게 닿나) → **팀**(누가 실행하나). **계획**은 기본 일곱과 *별도 마지막 장*: 제목·표지에 «계획 · 순서와 시기는 바뀔 수 있습니다»를 쓰고 월 단위 약속은 쓰지 않는다. 발표 길이에 맞게 기본 장 하나를 둘로 나누거나 오프닝을 더해 최종 8~12장으로 만든다.
|
|
21
|
+
|
|
22
|
+
첫 장은 **3초 안에 뜻이 전해지는 쉬운 한 줄**, 전문 용어 0. 다음 장에는 **실물 증거**를 둔다: 실제 화면의 자리와 시연할 행동을 명시하고, 이미지가 없으면 «화면 자리»라고 표기한다. 화면·시연은 검증 전이면 «계획»으로 구분하며 이미 된 일로 그리지 않는다. 사이트와 데모 대본의 메시지 순서(약속 → 증거 → 작동 원리 → 다음 행동)를 따른다. 설득 문장은 빠르고 쉬워도 기술적 아우라는 검증 가능한 실물로 뒷받침한다. 남의 이름·제휴 암시·«곧»·미측정 성과는 넣지 않는다.
|
|
23
|
+
|
|
24
|
+
각 장: **한 장 한 메시지**, 제목은 명사 나열이 아니라 **주장 문장**. 본문·제목을 합쳐 **공백 기준 45낱말 이하**(출처 ID·진행표시·접근성 라벨 제외). 넘치면 장을 나누고 핵심을 고른다. 수치·날짜·비율은 읽은 파일의 **파일:줄** 또는 공개 링크를 SOURCES의 개별 주장과 짝짓는다. 출처가 없는 시장 규모·추정 TAM/SAM/SOM은 장에 **«가정»** 표시하고 SOURCES의 가정 목록에도 적는다. 근거가 없으면 수치를 만들지 말고 측정 방법을 제시한다. 고객·매출·계약·기술 성숙도를 과장하지 않는다.
|
|
25
|
+
|
|
26
|
+
### 누구에게 파나 — ECP / ICP
|
|
27
|
+
|
|
28
|
+
ECP(early customer profile)의 **시급한 문제·사건/구매 트리거·현재 우회 비용·도입 준비**를 묻고, ICP(ideal customer profile)의 **조직/업종·규모·구매자·사용자·의사결정 권한·도입 제약**을 적는다. 시장 장에서는 ECP → ICP → 접근 가능한 구매자(SOM) 순서로 좁힌다. 기술/BM 장에는 구매자와 사용자, 값이 생기는 순간, 과금 가설을 서로 구분한다. 인터뷰 전인 칸은 «가정»이라고 쓰며 공개되지 않은 연락처·고객사 내부 수치를 옮기지 않는다.
|
|
29
|
+
|
|
30
|
+
## 작성 · 출력
|
|
31
|
+
|
|
32
|
+
1. 참고본은 `.eml`의 본문·첨부를 파싱해 **슬라이드 구조만**, docx/xlsx에서는 **칸 이름만** 참고한다. 참고본의 사람 이름·연락처·주소·내부 수치·원문 문구는 전재하지 않는다. 저장소의 브랜드 정본과 공개 가능한 기능 근거를 읽고 계획을 현재 성과로 바꾸지 않는다.
|
|
33
|
+
2. `template.html`의 CSS·JS를 각 덱 HTML에 **인라인 복사**하고 `<section class="slide" data-claim="…">`마다 하나의 메시지를 넣는다. 파일 밖 폰트·이미지·스크립트 의존성 없이 로컬 파일로 직접 연다. 제목의 주장 ID마다 SOURCES 표에 **같은 ID**, 출처와 그 줄에서 확인할 내용, 상태(사실/가정/계획)를 기록한다. 숫자 하나라도 별도 근거가 필요하면 같은 행에서 항목별로 대응한다.
|
|
34
|
+
3. 출력은 **한 파일 HTML 덱**, 키보드 방향키/스페이스로 넘김(화면 버튼도 제공), 16:9, 인쇄하면 한 쪽 한 장. 버튼·입력 요소에 초점이 있을 때는 슬라이드 단축키를 건너뛰어 기본 키보드 조작(«이전» 버튼에서 스페이스)을 보존한다. 인쇄 미리보기·첫/끝 넘김·숫자/개인 연락처를 확인한다. 디자인 시스템을 새로 만들지 않고 `내부 문서 `BRAND-CANON-elanvital-and-elanous-2026-09-30``의 색·서체 사용 기준을 따른다.
|
|
35
|
+
|
|
36
|
+
기존 연결: `src/skills/index.ts`의 `buildSkillIndex` → `listSkillNames`/`parseSkillMd`가 이 폴더의 SKILL.md를 발견한다. 덱은 스킬이 작성해 직접 여는 **독립 문서**이며 HTML을 위한 신규 런타임 라우트는 만들지 않는다.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="ko">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>피치덱 — 회사명</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root { color-scheme: dark; font-family: Geist, "G sans", "Noto Sans KR", system-ui, sans-serif; background: #0c0c0e; color: #f2f1ee; }
|
|
9
|
+
* { box-sizing: border-box; }
|
|
10
|
+
body { margin: 0; }
|
|
11
|
+
main { min-height: 100svh; display: grid; place-items: center; padding: 4rem 1rem; }
|
|
12
|
+
.slide { display: none; width: min(100%, calc((100svh - 8rem) * 16 / 9)); aspect-ratio: 16 / 9; padding: clamp(1.4rem, 5vw, 5rem); background: #0c0c0e; border: 1px solid #f2f1ee; flex-direction: column; justify-content: space-between; overflow: hidden; }
|
|
13
|
+
.slide.active { display: flex; }
|
|
14
|
+
.eyebrow, .marker { color: #e95047; font-size: clamp(.8rem, 1.3vw, 1rem); font-weight: 650; letter-spacing: .08em; }
|
|
15
|
+
h1, h2 { max-width: 20ch; font-size: clamp(2rem, 4.4vw, 4.9rem); line-height: 1.15; letter-spacing: -.045em; margin: .4em 0; text-wrap: balance; }
|
|
16
|
+
p, li { max-width: 54ch; font-size: clamp(1rem, 1.7vw, 1.6rem); line-height: 1.55; }
|
|
17
|
+
ul { padding-left: 1.2em; }
|
|
18
|
+
.accent { color: #e95047; }
|
|
19
|
+
.screen-slot { border: 1px dashed #e95047; padding: .6rem 1rem; color: #f2f1ee; font-size: clamp(.9rem, 1.4vw, 1.2rem); }
|
|
20
|
+
.foot { display: flex; justify-content: space-between; align-items: end; gap: 1rem; color: #f2f1ee; font-size: .85rem; }
|
|
21
|
+
nav { position: fixed; bottom: 1rem; right: 1rem; display: flex; align-items: center; gap: .7rem; background: #0c0c0e; padding: .4rem; }
|
|
22
|
+
button { background: #0c0c0e; color: #f2f1ee; border: 1px solid #e95047; padding: .6rem .9rem; font: inherit; cursor: pointer; }
|
|
23
|
+
button:focus-visible { outline: 3px solid #e95047; outline-offset: 3px; }
|
|
24
|
+
button:disabled { opacity: .42; cursor: not-allowed; }
|
|
25
|
+
@media print {
|
|
26
|
+
@page { size: 16in 9in; margin: 0; }
|
|
27
|
+
body { width: 16in; }
|
|
28
|
+
:root { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
|
|
29
|
+
main { display: block; padding: 0; min-height: 0; }
|
|
30
|
+
.slide, .slide.active { display: flex; width: 16in; height: 9in; aspect-ratio: auto; border: 0; break-after: page; page-break-after: always; }
|
|
31
|
+
.slide:last-child { break-after: auto; page-break-after: auto; }
|
|
32
|
+
nav { display: none; }
|
|
33
|
+
}
|
|
34
|
+
</style>
|
|
35
|
+
</head>
|
|
36
|
+
<body>
|
|
37
|
+
<main id="deck" aria-label="피치덱">
|
|
38
|
+
<section class="slide active" data-claim="T1" aria-label="정의" aria-hidden="false">
|
|
39
|
+
<span class="eyebrow">브랜드 · 정의</span>
|
|
40
|
+
<div><h1>한 줄로 무엇을 하는지 말합니다.</h1><p>검증 가능한 회사 한 줄 정의를 여기에 넣습니다.</p></div>
|
|
41
|
+
<div class="foot"><span>출처: SOURCES · T1</span><span class="marker">회사명</span></div>
|
|
42
|
+
</section>
|
|
43
|
+
<section class="slide" data-claim="T2" aria-label="계획" aria-hidden="true">
|
|
44
|
+
<span class="eyebrow">계획 · 순서와 시기는 바뀔 수 있습니다</span>
|
|
45
|
+
<div><h2>다음 걸음은 검증하며 정합니다.</h2><p>이 칸은 현재 제공 기능이 아니라 앞으로의 계획입니다.</p></div>
|
|
46
|
+
<div class="foot"><span>출처: SOURCES · T2</span><span class="marker">계획</span></div>
|
|
47
|
+
</section>
|
|
48
|
+
</main>
|
|
49
|
+
<nav aria-label="슬라이드 이동"><button id="prev" type="button" aria-label="이전 장">← 이전</button><output id="position" aria-live="polite"></output><button id="next" type="button" aria-label="다음 장">다음 →</button></nav>
|
|
50
|
+
<script>
|
|
51
|
+
(() => {
|
|
52
|
+
const slides = [...document.querySelectorAll('section.slide')];
|
|
53
|
+
const prev = document.getElementById('prev');
|
|
54
|
+
const next = document.getElementById('next');
|
|
55
|
+
const position = document.getElementById('position');
|
|
56
|
+
let current = 0;
|
|
57
|
+
function show(index) {
|
|
58
|
+
current = Math.max(0, Math.min(slides.length - 1, index));
|
|
59
|
+
slides.forEach((slide, i) => {
|
|
60
|
+
slide.classList.toggle('active', i === current);
|
|
61
|
+
slide.setAttribute('aria-hidden', String(i !== current));
|
|
62
|
+
});
|
|
63
|
+
prev.disabled = current === 0;
|
|
64
|
+
next.disabled = current === slides.length - 1;
|
|
65
|
+
position.textContent = `${current + 1} / ${slides.length}`;
|
|
66
|
+
}
|
|
67
|
+
prev.addEventListener('click', () => show(current - 1));
|
|
68
|
+
next.addEventListener('click', () => show(current + 1));
|
|
69
|
+
document.addEventListener('keydown', event => {
|
|
70
|
+
if (event.target instanceof Element && event.target.closest('button, a, input, textarea, select, [contenteditable="true"]')) return;
|
|
71
|
+
if (['ArrowRight', 'ArrowDown', ' ', 'PageDown'].includes(event.key)) { event.preventDefault(); show(current + 1); }
|
|
72
|
+
if (['ArrowLeft', 'ArrowUp', 'PageUp'].includes(event.key)) { event.preventDefault(); show(current - 1); }
|
|
73
|
+
if (event.key === 'Home') show(0);
|
|
74
|
+
if (event.key === 'End') show(slides.length - 1);
|
|
75
|
+
});
|
|
76
|
+
show(0);
|
|
77
|
+
})();
|
|
78
|
+
</script>
|
|
79
|
+
</body>
|
|
80
|
+
</html>
|
|
@@ -103,6 +103,12 @@ export interface CodexApprovalAdapter {
|
|
|
103
103
|
onMcpToolCall(req: CodexMcpToolCallRequest): Promise<CodexApprovalDecision>;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
/** The 3-way approval question's id and labels — exported so a surface that
|
|
107
|
+
* answers approvals by yes/no (an ACP turn's client) can recognise it. */
|
|
108
|
+
export const CODEX_APPROVAL_QUESTION_ID = 'codex_approval';
|
|
109
|
+
export const CODEX_APPROVAL_OPT_ONCE = 'Allow once';
|
|
110
|
+
export const CODEX_APPROVAL_OPT_REJECT = 'Reject';
|
|
111
|
+
|
|
106
112
|
/** Build a CodexApprovalAdapter. Intentionally minimal in Phase 3.A —
|
|
107
113
|
* the handlers degrade gracefully when no approver is attached, and
|
|
108
114
|
* the Phase 3.B app-server client will hook these methods up with real
|
|
@@ -131,10 +137,10 @@ export function createCodexApprovalAdapter(
|
|
|
131
137
|
// multi-option question path is used. "Allow for session" maps to codex
|
|
132
138
|
// `acceptForSession` so the sub-agent stops re-prompting for the rest of
|
|
133
139
|
// the run.
|
|
134
|
-
const OPT_ONCE =
|
|
140
|
+
const OPT_ONCE = CODEX_APPROVAL_OPT_ONCE;
|
|
135
141
|
const OPT_SESSION = 'Allow for session';
|
|
136
|
-
const OPT_REJECT =
|
|
137
|
-
const APPROVAL_QID =
|
|
142
|
+
const OPT_REJECT = CODEX_APPROVAL_OPT_REJECT;
|
|
143
|
+
const APPROVAL_QID = CODEX_APPROVAL_QUESTION_ID;
|
|
138
144
|
|
|
139
145
|
/** Ask the user via the MULTI-OPTION question channel: Allow once /
|
|
140
146
|
* Allow for session / Reject. Returns the scoped decision. Only called
|
|
@@ -46,7 +46,11 @@ import { getUserConfig } from '../user-config.js';
|
|
|
46
46
|
import { getModelFamily } from '../models/prompts.js';
|
|
47
47
|
import { resolveModelContextWindow } from '../models/context-window.js';
|
|
48
48
|
import type { AcpServerOptions, AcpTurnContext } from './server.js';
|
|
49
|
+
import { acpToolDenialResult } from './tool-approval.js';
|
|
49
50
|
import { readOriginSessionMeta } from './origin-session-meta.js';
|
|
51
|
+
import { isFactQuestion } from './fact-question.js';
|
|
52
|
+
|
|
53
|
+
const FACT_QUESTION_INSTRUCTION = '사실·최신 정보 질문이다. WebSearch 를 1~2회 쓰고 검색 결과(제목·스니펫·URL)로 답하라. 출처 링크를 붙여라. 셸·브라우저·파일 도구는 검색 결과로 부족할 때만. 도구 예산에 닿으면 지금까지 근거로 답하고 끝에 «더 찾아볼까요?»를 붙여라.';
|
|
50
54
|
|
|
51
55
|
export interface CoreTurnBridgeDeps {
|
|
52
56
|
/** Build the seed message list for this ACP prompt. Implementations
|
|
@@ -112,6 +116,28 @@ export interface CoreTurnBridgeDeps {
|
|
|
112
116
|
* pegging the event loop. */
|
|
113
117
|
export const DEFAULT_BRIDGE_ABORT_POLL_MS = 50;
|
|
114
118
|
|
|
119
|
+
/** PCH-2b — ask the ACP client before a write/shell tool runs. A denied,
|
|
120
|
+
* cancelled or timed-out tool is not run; the LLM gets a «not run» result. */
|
|
121
|
+
export function acpApprovalGatedDispatch(
|
|
122
|
+
turnCtx: AcpTurnContext,
|
|
123
|
+
dispatchTool: CoreTurnDispatchTool,
|
|
124
|
+
signal: AbortSignal,
|
|
125
|
+
): CoreTurnDispatchTool {
|
|
126
|
+
const approveTool = turnCtx.approveTool;
|
|
127
|
+
if (!approveTool) return dispatchTool;
|
|
128
|
+
return async (name, args, dispatchCtx) => {
|
|
129
|
+
if (signal.aborted || turnCtx.isAborted()) return acpToolDenialResult(name, 'cancelled');
|
|
130
|
+
const approval = await approveTool({
|
|
131
|
+
toolCallId: dispatchCtx?.callId ?? `${name}-${Date.now().toString(36)}`,
|
|
132
|
+
toolName: name,
|
|
133
|
+
toolArgs: args,
|
|
134
|
+
});
|
|
135
|
+
if (!approval.allowed) return acpToolDenialResult(name, approval.reason);
|
|
136
|
+
if (signal.aborted || turnCtx.isAborted()) return acpToolDenialResult(name, 'cancelled');
|
|
137
|
+
return dispatchTool(name, args, dispatchCtx);
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
115
141
|
/** Extract the trailing assistant message text from runCoreTurn's
|
|
116
142
|
* `newMessages` array. emitTurnComplete (src/llm.ts:3835) appends
|
|
117
143
|
* `{role:'assistant', content:[{type:'text', text:finalAssistantText}]}`
|
|
@@ -173,7 +199,23 @@ export function bridgeCoreTurnToAcp(
|
|
|
173
199
|
}),
|
|
174
200
|
);
|
|
175
201
|
const modelOverride = deps.resolveModel?.({ sessionId: turnCtx.sessionId, userText: turnCtx.userText });
|
|
176
|
-
const
|
|
202
|
+
const factQuestion = isFactQuestion(turnCtx.userText);
|
|
203
|
+
const explicitMaxToolTurns = deps.resolveMaxToolTurns?.({ sessionId: turnCtx.sessionId });
|
|
204
|
+
const maxToolTurns = explicitMaxToolTurns ?? (factQuestion ? getUserConfig().chat.factQuestion.maxToolTurns : undefined);
|
|
205
|
+
const turnMessages: LLMMessage[] = factQuestion
|
|
206
|
+
? [{ role: 'system', content: FACT_QUESTION_INSTRUCTION }, ...messages]
|
|
207
|
+
: messages;
|
|
208
|
+
let toolCallCount = 0;
|
|
209
|
+
const toolKinds = new Set<string>();
|
|
210
|
+
const observeToolBudget = () => {
|
|
211
|
+
try {
|
|
212
|
+
debug.log('chat.turn', 'tool-budget', {
|
|
213
|
+
factQuestion, maxToolTurns: maxToolTurns ?? null,
|
|
214
|
+
count: toolCallCount, kinds: [...toolKinds],
|
|
215
|
+
});
|
|
216
|
+
} catch { /* observation cannot interrupt a turn */ }
|
|
217
|
+
};
|
|
218
|
+
observeToolBudget();
|
|
177
219
|
|
|
178
220
|
// 2026-05-03 PM++ — Track streamed text so onTurnComplete can
|
|
179
221
|
// detect runtime-injected synthesis (W5-E/W5-F/W5-G) and surface
|
|
@@ -235,17 +277,29 @@ export function bridgeCoreTurnToAcp(
|
|
|
235
277
|
} catch {
|
|
236
278
|
// Observability must not prevent the turn from reaching its dispatcher.
|
|
237
279
|
}
|
|
280
|
+
const approvedDispatch = acpApprovalGatedDispatch(turnCtx, deps.dispatchTool, ctrl.signal);
|
|
281
|
+
const dispatchTool: CoreTurnDispatchTool = factQuestion && maxToolTurns !== undefined && maxToolTurns > 0
|
|
282
|
+
? async (name, args, dispatchCtx) => {
|
|
283
|
+
if (toolCallCount >= maxToolTurns) {
|
|
284
|
+
return { error: 'Tool budget reached. Answer using the evidence already collected.' };
|
|
285
|
+
}
|
|
286
|
+
toolCallCount += 1;
|
|
287
|
+
toolKinds.add(name);
|
|
288
|
+
observeToolBudget();
|
|
289
|
+
return approvedDispatch(name, args, dispatchCtx);
|
|
290
|
+
}
|
|
291
|
+
: approvedDispatch;
|
|
238
292
|
await runTurnDispatch({
|
|
239
293
|
sessionId: turnCtx.sessionId,
|
|
240
294
|
...(originSessionId ? { originSessionId } : {}),
|
|
241
295
|
...(turnCtx.userText ? { userText: turnCtx.userText } : {}),
|
|
242
|
-
messages,
|
|
296
|
+
messages: turnMessages,
|
|
243
297
|
tools,
|
|
244
|
-
dispatchTool
|
|
298
|
+
dispatchTool,
|
|
245
299
|
signal: ctrl.signal,
|
|
246
300
|
...(modelOverride !== undefined ? { modelOverride } : {}),
|
|
247
301
|
...(maxToolTurns !== undefined ? { maxToolTurns } : {}),
|
|
248
|
-
...(deps.budgetGrant !== undefined ? { budgetGrant: deps.budgetGrant } : {}),
|
|
302
|
+
...((!factQuestion || explicitMaxToolTurns !== undefined) && deps.budgetGrant !== undefined ? { budgetGrant: deps.budgetGrant } : {}),
|
|
249
303
|
callbacks: {
|
|
250
304
|
onText: (delta) => {
|
|
251
305
|
// Fire-and-forget: ACP `push` is async but the core-turn
|
|
@@ -262,7 +316,14 @@ export function bridgeCoreTurnToAcp(
|
|
|
262
316
|
// callers see today. Fire-and-forget for the same reason
|
|
263
317
|
// onText is: callbacks are synchronous, `turnCtx.push*` is
|
|
264
318
|
// async, ordering is preserved by the SDK.
|
|
265
|
-
onToolCall: (call) => {
|
|
319
|
+
onToolCall: (call) => {
|
|
320
|
+
if (!factQuestion || maxToolTurns === undefined || maxToolTurns <= 0) {
|
|
321
|
+
toolCallCount += 1;
|
|
322
|
+
toolKinds.add(call.name);
|
|
323
|
+
observeToolBudget();
|
|
324
|
+
}
|
|
325
|
+
void turnCtx.pushToolCall(call);
|
|
326
|
+
},
|
|
266
327
|
onToolResult: (call) => { void turnCtx.pushToolResult(call); },
|
|
267
328
|
onUsage: (usage) => { void turnCtx.pushUsage(usage); },
|
|
268
329
|
// Codex Responses API reasoning summary stream. Currently
|
|
@@ -301,26 +362,64 @@ export function bridgeCoreTurnToAcp(
|
|
|
301
362
|
// re-emit would cause the "displayed then deleted then
|
|
302
363
|
// re-displayed" flicker the user reported.
|
|
303
364
|
const finalAssistantText = extractLastAssistantText(newMessages);
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
365
|
+
const budgetReached = factQuestion && maxToolTurns !== undefined
|
|
366
|
+
&& maxToolTurns > 0 && toolCallCount >= maxToolTurns;
|
|
367
|
+
const followup = '더 찾아볼까요?';
|
|
368
|
+
const needsFollowup = budgetReached && finalAssistantText !== null
|
|
369
|
+
&& finalAssistantText.trim().length > 0
|
|
370
|
+
&& !finalAssistantText.trimEnd().endsWith(followup);
|
|
371
|
+
let lastAssistantIndex = -1;
|
|
372
|
+
for (let i = newMessages.length - 1; i >= 0; i--) {
|
|
373
|
+
if (newMessages[i]?.role === 'assistant') { lastAssistantIndex = i; break; }
|
|
374
|
+
}
|
|
375
|
+
const followupSuffix = `\n${followup}`;
|
|
376
|
+
const completedMessages: LLMMessage[] = needsFollowup
|
|
377
|
+
? newMessages.map((message, index) => {
|
|
378
|
+
if (index !== lastAssistantIndex) return message;
|
|
379
|
+
if (typeof message.content === 'string') {
|
|
380
|
+
return { ...message, content: message.content + followupSuffix };
|
|
381
|
+
}
|
|
382
|
+
if (Array.isArray(message.content)) {
|
|
383
|
+
let lastTextIndex = -1;
|
|
384
|
+
for (let blockIndex = message.content.length - 1; blockIndex >= 0; blockIndex--) {
|
|
385
|
+
if (message.content[blockIndex]?.type === 'text') { lastTextIndex = blockIndex; break; }
|
|
386
|
+
}
|
|
387
|
+
if (lastTextIndex >= 0) {
|
|
388
|
+
return { ...message, content: message.content.map((block, blockIndex) =>
|
|
389
|
+
blockIndex === lastTextIndex && block.type === 'text'
|
|
390
|
+
? { ...block, text: block.text + followupSuffix }
|
|
391
|
+
: block) };
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return message;
|
|
395
|
+
})
|
|
396
|
+
: newMessages;
|
|
397
|
+
const visibleFinalText = needsFollowup ? extractLastAssistantText(completedMessages) : finalAssistantText;
|
|
398
|
+
const originalFinalStreamed = needsFollowup && finalAssistantText !== null
|
|
307
399
|
&& streamedTextSum.endsWith(finalAssistantText);
|
|
400
|
+
const synthesisAlreadyStreamed =
|
|
401
|
+
visibleFinalText !== null
|
|
402
|
+
&& visibleFinalText.length > 0
|
|
403
|
+
&& streamedTextSum.endsWith(visibleFinalText);
|
|
308
404
|
const needsReSurface =
|
|
309
|
-
|
|
310
|
-
&&
|
|
311
|
-
&& !synthesisAlreadyStreamed
|
|
312
|
-
|
|
405
|
+
visibleFinalText !== null
|
|
406
|
+
&& visibleFinalText !== streamedTextSum
|
|
407
|
+
&& !synthesisAlreadyStreamed
|
|
408
|
+
&& !originalFinalStreamed;
|
|
409
|
+
if (debug.enabled && visibleFinalText !== null) {
|
|
313
410
|
debug.log('llm.synthesis-surface', 'acp-bridge.surface-final-text', {
|
|
314
411
|
streamedChars: streamedTextSum.length,
|
|
315
|
-
finalChars:
|
|
316
|
-
delta:
|
|
412
|
+
finalChars: visibleFinalText.length,
|
|
413
|
+
delta: visibleFinalText.length - streamedTextSum.length,
|
|
317
414
|
startsWithStreamed: streamedTextSum.length > 0
|
|
318
|
-
&&
|
|
415
|
+
&& visibleFinalText.startsWith(streamedTextSum),
|
|
319
416
|
endsWithStreamedTail: synthesisAlreadyStreamed,
|
|
320
417
|
needsReSurface,
|
|
321
418
|
});
|
|
322
419
|
}
|
|
323
|
-
if (
|
|
420
|
+
if (originalFinalStreamed) {
|
|
421
|
+
void turnCtx.push(followupSuffix);
|
|
422
|
+
} else if (needsReSurface) {
|
|
324
423
|
// Emit a clear-then-replace pair so clients that follow
|
|
325
424
|
// the empty-chunk-as-clear convention (dashboard plain
|
|
326
425
|
// chat) reset their per-round buffer before receiving
|
|
@@ -329,12 +428,12 @@ export function bridgeCoreTurnToAcp(
|
|
|
329
428
|
// would only have the new finalAssistantText delta to
|
|
330
429
|
// work with anyway).
|
|
331
430
|
void turnCtx.push('');
|
|
332
|
-
void turnCtx.push(
|
|
431
|
+
void turnCtx.push(visibleFinalText!);
|
|
333
432
|
}
|
|
334
433
|
if (deps.onTurnComplete) {
|
|
335
434
|
await deps.onTurnComplete({
|
|
336
435
|
sessionId: turnCtx.sessionId,
|
|
337
|
-
newMessages,
|
|
436
|
+
newMessages: completedMessages,
|
|
338
437
|
});
|
|
339
438
|
}
|
|
340
439
|
},
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function isFactQuestion(userText: string): boolean {
|
|
2
|
+
const text = userText.trim();
|
|
3
|
+
if (!text || text.length > 120 || /[\r\n]/.test(text)) return false;
|
|
4
|
+
if (/`|\bsrc\/|(?:^|[\s/])[^\s/]+\.(?:ts|tsx|js|jsx|json|md|py|sh|yaml|yml)\b|고쳐|구현|실행|만들어|수정|디버그|파일|읽어|열어|저장|작성|삭제|편집|검색해|찾아줘|보여줘|\/cc\b|delegate_code_agent/i.test(text)) return false;
|
|
5
|
+
// A request («…해줘?», «재시작해», «restart …») is work, not a fact question — even with a «?» or «지금»(round 3: «지금 서버를 재시작해줘?»).
|
|
6
|
+
const request = text.replace(/알려\s*(?:줘|주세요|줄래|주실래요)[??]?$/, '');
|
|
7
|
+
if (/(?:해|해\s*줘|해\s*주세요|하세요|해라|하자|줘|주세요|줄래|주실래요|봐|봐줘)[??]?$/.test(request)) return false;
|
|
8
|
+
if (/재시작|시작|중지|멈춰|꺼|켜|설치|배포|돌려|보내|올려|지워|바꿔|추가|업데이트|재부팅|재실행|정리|등록|\b(?:restart|start|stop|run|install|deploy|delete|remove|create|update|fix|kill|reboot)\b/i.test(text)) return false;
|
|
9
|
+
if (!/[??]$|(?:뭐야|무엇인가요|인가요|나요|입니까|얼마야|언제야|누구야|알려줘|알려주세요)$/.test(text)) return false;
|
|
10
|
+
return /최근|최신|지금|현재|몇|언제|누가|누구|얼마|가격|버전|출시|최저시급|\b(?:latest|current|when|who|how many|how much|price|version|release)\b/i.test(text);
|
|
11
|
+
}
|