elanous 0.2.9 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +1 -0
- package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8067-87ce253ec2f23697.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8195-6fde3d7a39859206.js} +5 -5
- package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-f972fc1c00a62e88.js → page-54b32bd27726588d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-f8116559c7424da7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-54e1f966ab60a4dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-973996d584f008c1.js → page-7ed95f296f5ee8e9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-8b0402a46a749a3d.js → page-b31610deda76337b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-037f83e2a1edc8e2.js → page-6cb0e1bbefe5fb3d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-0df30e0c7e50148a.js → page-e168fa1030af0548.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-4fddc90b23feb215.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-90670589b87abea8.js → page-26102fd7350f171e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-35bedde6d885f08f.js → page-139ba5c367bbf60a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +1 -0
- package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +14 -14
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -0
- package/apps/pwa/out/ops/seats/index.txt +30 -0
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -0
- package/apps/pwa/out/today/index.txt +33 -0
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/graphs/field/field-feed.yaml +2 -0
- package/graphs/implement-loop.yaml +1 -1
- package/package.json +1 -1
- package/plugins/card-followup/graphs/run-step.ts +48 -13
- package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
- package/plugins/doc-draft/graphs/run-step.ts +13 -6
- package/scripts/ci-pwa-build-gate.ts +160 -0
- package/scripts/coord-channel-watch.sh +42 -8
- package/scripts/coord-digest.ts +199 -0
- package/scripts/coord-post.sh +60 -3
- package/scripts/install.ps1 +68 -9
- package/scripts/install.sh +39 -12
- package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
- package/scripts/lib/tui-regress-checks.ts +146 -0
- package/scripts/memory-lifecycle-cycle.ts +2 -2
- package/scripts/ops-now-probe.ts +327 -0
- package/scripts/publish-internal-dist.ts +11 -1
- package/scripts/release-loop/cutoff.ts +19 -8
- package/scripts/release-loop/ops-upgrade-node.ts +72 -16
- package/scripts/release-loop/release-readiness.ts +9 -0
- package/scripts/release-loop/version-node.ts +58 -10
- package/scripts/repo-watch-cycle.ts +1 -1
- package/scripts/retro-cycle.ts +2 -2
- package/scripts/schedule-health-report.ts +1 -1
- package/scripts/storyboard/storyboard.ts +5 -161
- package/scripts/test-diet/lib.ts +30 -1
- package/scripts/test-diet/node.ts +82 -7
- package/scripts/test-timeout-audit.ts +114 -0
- package/scripts/tree-sync-alert.ts +1 -1
- package/scripts/tui-regress.ts +71 -0
- package/scripts/waitlist-daily-digest.ts +1 -1
- package/skills/explainer-video/SKILL.md +4 -0
- package/skills/explainer-video/engine/reel.mjs +18 -13
- package/skills/explainer-video/engine/reel.sh +24 -5
- package/skills/explainer-video/engine/stitch.mjs +145 -0
- package/skills/pitch-deck/SKILL.md +36 -0
- package/skills/pitch-deck/template.html +80 -0
- package/src/acp/codex-approval-adapter.ts +9 -3
- package/src/acp/core-turn-bridge.ts +117 -18
- package/src/acp/fact-question.ts +11 -0
- package/src/acp/server.ts +48 -7
- package/src/acp/tool-approval.ts +126 -0
- package/src/acp/turn-runner.ts +36 -0
- package/src/agent/monad-agent-turn.ts +8 -0
- package/src/ask-user-question/pending-questions.ts +25 -10
- package/src/ask-user-question/types.ts +9 -0
- package/src/auth/setup-link-tokens.ts +16 -5
- package/src/autopilot/build/integrity-gate.ts +12 -0
- package/src/boot/daemon-prompt-turn.ts +3 -0
- package/src/boot/daemon-tools/index.ts +8 -0
- package/src/boot/daemon-tools/types.ts +2 -0
- package/src/boot/daemon-tools/web-search.ts +1 -0
- package/src/chat/ctrl-c-in-input.ts +14 -0
- package/src/chat/index.ts +52 -0
- package/src/chat/pickers/state.ts +6 -2
- package/src/cli/agent-cli.ts +7 -0
- package/src/cli/decisions-cli.ts +25 -3
- package/src/cli/doctor-bundle-upload.ts +68 -0
- package/src/cli/doctor-cli.ts +38 -4
- package/src/cli/ops-cli.ts +20 -0
- package/src/cli/pod-cli.ts +49 -0
- package/src/cli/pr-cli.ts +31 -2
- package/src/cli/pwa-stop.ts +163 -13
- package/src/cli/pwa-test.ts +3 -0
- package/src/cli/release-cli.ts +63 -20
- package/src/cli/self-update.ts +14 -0
- package/src/cli/storyboard-cli.ts +37 -0
- package/src/cli/test-state-dir-flag.ts +11 -4
- package/src/connectors/linear.ts +146 -0
- package/src/context-bus/coord-events.ts +115 -0
- package/src/dashboard/dashboard-boot-fixture.ts +23 -0
- package/src/dashboard/index.ts +58 -21
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +7 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
- package/src/decisions/decision-cards.ts +49 -1
- package/src/decisions/decision-ledger.ts +75 -4
- package/src/decisions/decision-linear-projection.ts +157 -0
- package/src/decisions/web-push-decision-cards.ts +50 -0
- package/src/directive/interpretation-card.ts +36 -0
- package/src/discord.ts +2 -1
- package/src/domains/coo-admin-tool.ts +62 -0
- package/src/domains/core-tools.ts +11 -3
- package/src/domains/elanous-card.ts +29 -0
- package/src/domains/ops-facts-tool.ts +76 -0
- package/src/domains/outbound-alert.ts +41 -13
- package/src/domains/release-tool.ts +168 -0
- package/src/domains/telegram-kind-route.ts +19 -1
- package/src/exec-requests/planner.ts +98 -28
- package/src/exec-requests/runner.ts +107 -10
- package/src/exec-requests/store.ts +8 -2
- package/src/field/field-media.ts +10 -7
- package/src/field/field-reel.ts +14 -4
- package/src/git-fs/worktree.ts +45 -6
- package/src/graph-runner/graph-approval-card.ts +113 -0
- package/src/graph-runner/graph-cli.ts +129 -4
- package/src/graph-runner/graph-notify.ts +25 -3
- package/src/graph-runner/runner.ts +142 -16
- package/src/harness/browser-verify.ts +25 -2
- package/src/harness/harness-cli-command.ts +231 -35
- package/src/harness/harness-incidents-writer.fixture.ts +24 -0
- package/src/harness/harness-incidents.ts +129 -0
- package/src/harness/harness-stop.ts +53 -3
- package/src/hitl/clarification-policy.ts +3 -1
- package/src/index.ts +156 -37
- package/src/intake-plane/author-goals.ts +37 -4
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/discord-seat-work.ts +47 -2
- package/src/intake-plane/seat-doc-route.ts +170 -6
- package/src/intake-plane/telegram-seat-work.ts +44 -2
- package/src/intake-plane/telegram-work.ts +1 -0
- package/src/maturity/cli-maturity.ts +34 -0
- package/src/maturity/feature-maturity.ts +98 -1
- package/src/maturity/tui-slash-maturity.ts +31 -0
- package/src/memory.ts +32 -4
- package/src/nexus/api/exec-requests.ts +33 -3
- package/src/nexus/api/http-server.ts +53 -1
- package/src/nexus/api/meta-api.ts +11 -0
- package/src/nexus/api/ops-api.ts +10 -1
- package/src/nexus/api/ops-seats-sources.ts +37 -7
- package/src/nexus/api/ops-seats.ts +16 -6
- package/src/nexus/api/seat-requests.ts +88 -8
- package/src/nexus/api/sessions-store.ts +63 -0
- package/src/nexus/api/setup-codex-login.ts +160 -0
- package/src/nexus/api/usage-api.ts +102 -0
- package/src/nexus/index.ts +45 -4
- package/src/onboarding/first-run.ts +89 -0
- package/src/onboarding/lan-address.ts +45 -0
- package/src/onboarding/web-first.ts +119 -24
- package/src/onboarding.ts +4 -0
- package/src/ops-now/ops-now-refresh.ts +100 -0
- package/src/project/project-store.ts +115 -0
- package/src/release-loop/checklist.ts +78 -4
- package/src/release-loop/feature-store.ts +154 -21
- package/src/release-loop/release-schedule.ts +53 -0
- package/src/repl/index.ts +7 -0
- package/src/seat-dispatch/ceo-commands.ts +41 -8
- package/src/seat-dispatch/ceo-intent.ts +38 -0
- package/src/seat-loop/seat-loop.ts +231 -0
- package/src/seat-loop/seat-report.ts +64 -0
- package/src/self-dev/decompose.ts +66 -11
- package/src/self-dev/run-store.ts +10 -1
- package/src/self-implement/execution-clarification.ts +115 -0
- package/src/self-implement/gate-cli.ts +6 -3
- package/src/self-implement/orchestrator.ts +37 -3
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/run-status-mapping.ts +5 -3
- package/src/session/chat.ts +2 -0
- package/src/session/index.ts +9 -0
- package/src/shell-env-bootstrap.ts +17 -0
- package/src/steward/directive.ts +51 -2
- package/src/steward/launch.ts +250 -0
- package/src/steward/outcome-notify.ts +115 -0
- package/src/steward/steward-cards.ts +8 -2
- package/src/steward/triage-plan.ts +32 -4
- package/src/steward/triage.ts +93 -9
- package/src/steward/working-backwards.ts +64 -0
- package/src/storyboard/storyboard.ts +141 -0
- package/src/task-cards/card-store.ts +2 -2
- package/src/task-orchestrator/surfaces/pod-lease.ts +260 -0
- package/src/telegram-card-followup.ts +233 -0
- package/src/telegram-report.ts +15 -9
- package/src/telegram-run.ts +7 -0
- package/src/telegram.ts +29 -8
- package/src/tool-runtime/types.ts +4 -0
- package/src/tui-client/dashboard-session.ts +2 -0
- package/src/user-config.ts +118 -11
- package/src/version/packed-revision.json +1 -1
- package/src/web-push/notify-exec-request.ts +32 -0
- package/apps/pwa/out/_next/static/bFI5EEwg7T6aG88ibX4Zq/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8630-cdfc61e82c653ffc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8894-e263b332208e3957.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-139040d37c79dbc9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-fb7c2d458f32fcad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-35e2713597dd24d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-918b656f064577ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-5f30e00693527ebd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-7fbae173b3c103a7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-3b4edcce364062ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-c5794f484c251f44.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-1130d8eefcd87e76.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-016e2bda04eecb1f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-b4d6d9a3b56f2940.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-ec49a466121233ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-f72db356744ceade.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-a6fd9b50d1b85378.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-adfbd2ba9598e59a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-46fedff57d4bec67.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-0cf318e9dcee2bbf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-899b327cb8b6845c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-6c1e6ba743a29dfe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-1a8e4807292f6b17.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-28deba6bcb834916.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-de68e2205794037d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-c71c33a573230672.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4fc56790fe0d2682.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-59df2aebb38a7c1e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4647be5dbcbd0795.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e025892bda74b743.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a0955de73b89f078.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-369846d2921af584.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-72cbc1c8cdd0f65d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-77b28bfb0996d5ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-3220c4934f49c6c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-7df9243ed021a4cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-4498c3853f4f688d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
- package/apps/pwa/out/_next/static/css/f92f63a5df0ba229.css +0 -1
- package/src/dashboard/turn-typeahead-echo.ts +0 -23
- /package/apps/pwa/out/_next/static/{bFI5EEwg7T6aG88ibX4Zq → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
package/scripts/install.sh
CHANGED
|
@@ -11,7 +11,7 @@ set -euo pipefail
|
|
|
11
11
|
|
|
12
12
|
usage() {
|
|
13
13
|
cat <<'EOF'
|
|
14
|
-
Usage: bash scripts/install.sh [--prefix PATH] [--source PATH.tgz|URL] [--no-modify-path] [--no-bootstrap-bun] [--no-setup] [--yes] [--allow-downgrade] [--help]
|
|
14
|
+
Usage: bash scripts/install.sh [--prefix PATH] [--source PATH.tgz|URL] [--no-modify-path] [--no-bootstrap-bun] [--no-install-deps] [--no-setup] [--yes] [--allow-downgrade] [--help]
|
|
15
15
|
curl -fsSL https://github.com/ElanvitalAI/elanous/releases/latest/download/install.sh | bash
|
|
16
16
|
|
|
17
17
|
Install elanous without contacting a package registry.
|
|
@@ -23,6 +23,7 @@ Install elanous without contacting a package registry.
|
|
|
23
23
|
(or set $ELANOUS_INSTALL_SOURCE; without either, fetch the verified latest release)
|
|
24
24
|
--no-modify-path do not append the elanous PATH block to a shell startup file
|
|
25
25
|
--no-bootstrap-bun fail instead of installing bun with its official installer when bun is missing
|
|
26
|
+
--no-install-deps do not install missing prerequisites with apt even when running as root
|
|
26
27
|
--no-setup do not start first-time setup in this window after installing (interactive terminals only)
|
|
27
28
|
--yes do not ask before updating an existing installation
|
|
28
29
|
--allow-downgrade allow installing a version older than the one already installed
|
|
@@ -38,6 +39,7 @@ PREFIX="${ELANOUS_INSTALL_PREFIX:-${XDG_DATA_HOME:-${HOME:?HOME is required}/.lo
|
|
|
38
39
|
SOURCE="${ELANOUS_INSTALL_SOURCE:-}"
|
|
39
40
|
MODIFY_PATH=1
|
|
40
41
|
BOOTSTRAP_BUN=1
|
|
42
|
+
INSTALL_DEPS=1
|
|
41
43
|
RUN_SETUP=1
|
|
42
44
|
ASSUME_YES=0
|
|
43
45
|
ALLOW_DOWNGRADE=0
|
|
@@ -53,6 +55,7 @@ while [ "$#" -gt 0 ]; do
|
|
|
53
55
|
SOURCE="$2"; shift 2 ;;
|
|
54
56
|
--no-modify-path) MODIFY_PATH=0; shift ;;
|
|
55
57
|
--no-bootstrap-bun) BOOTSTRAP_BUN=0; shift ;;
|
|
58
|
+
--no-install-deps) INSTALL_DEPS=0; shift ;;
|
|
56
59
|
--no-setup) RUN_SETUP=0; shift ;;
|
|
57
60
|
--yes|-y) ASSUME_YES=1; shift ;;
|
|
58
61
|
--allow-downgrade) ALLOW_DOWNGRADE=1; shift ;;
|
|
@@ -91,8 +94,8 @@ if ! command -v bun >/dev/null 2>&1 && [ -x "${BUN_INSTALL:-$HOME/.bun}/bin/bun"
|
|
|
91
94
|
export PATH="${BUN_INSTALL:-$HOME/.bun}/bin:$PATH"
|
|
92
95
|
fi
|
|
93
96
|
# Match doctor-distro.ts families when a required command is absent; do not source os-release as shell code.
|
|
94
|
-
|
|
95
|
-
local
|
|
97
|
+
required_command_family() {
|
|
98
|
+
local id='' id_like='' version='' key value family='unknown' like
|
|
96
99
|
if [ "$(uname -s)" = 'Darwin' ]; then
|
|
97
100
|
family='darwin'
|
|
98
101
|
elif [ "$(uname -s)" = 'Linux' ] && [ -r "${ELANOUS_INSTALL_OS_RELEASE_FILE:-/etc/os-release}" ]; then
|
|
@@ -121,6 +124,12 @@ required_command_hint() {
|
|
|
121
124
|
fi
|
|
122
125
|
fi
|
|
123
126
|
fi
|
|
127
|
+
printf '%s' "$family"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
required_command_hint() {
|
|
131
|
+
local package="$1" family
|
|
132
|
+
family="$(required_command_family)"
|
|
124
133
|
if [ "$package" = 'bun' ] && [ "$family" != 'unknown' ]; then
|
|
125
134
|
echo ' curl -fsSL https://bun.sh/install | bash' >&2
|
|
126
135
|
return
|
|
@@ -138,16 +147,33 @@ required_command_hint() {
|
|
|
138
147
|
SUDO='sudo '
|
|
139
148
|
[ "$(id -u 2>/dev/null)" = 0 ] && SUDO=''
|
|
140
149
|
|
|
141
|
-
# 빠진 선행 명령을 «한 번에» 모아 한 줄로 댄다.
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
150
|
+
# 빠진 선행 명령을 «한 번에» 모아 한 줄로 댄다. bun 은 여기서 세지 않는다(없으면 아래에서 공식 설치기로 깐다).
|
|
151
|
+
missing_prerequisites() {
|
|
152
|
+
MISSING=()
|
|
153
|
+
if ! command -v bun >/dev/null 2>&1 && [ "$BOOTSTRAP_BUN" -eq 1 ]; then
|
|
154
|
+
command -v curl >/dev/null 2>&1 || MISSING+=(curl)
|
|
155
|
+
command -v unzip >/dev/null 2>&1 || MISSING+=(unzip) # bun 공식 설치기가 쓴다
|
|
156
|
+
fi
|
|
157
|
+
command -v git >/dev/null 2>&1 || MISSING+=(git)
|
|
158
|
+
}
|
|
159
|
+
missing_prerequisites
|
|
160
|
+
INSTALLED_DEPS=''
|
|
161
|
+
DEPS_INSTALL_FAILED=0
|
|
162
|
+
if [ "${#MISSING[@]}" -gt 0 ] && [ "$INSTALL_DEPS" -eq 1 ] && [ "$SUDO" = '' ] &&
|
|
163
|
+
command -v apt-get >/dev/null 2>&1 && [ "$(required_command_family)" = debian ]; then
|
|
164
|
+
DEPS_TO_INSTALL=("${MISSING[@]}")
|
|
165
|
+
# Fresh container images ship with empty package lists — refresh once, then install without prompts.
|
|
166
|
+
if DEBIAN_FRONTEND=noninteractive apt-get update -qq >&2 &&
|
|
167
|
+
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "${DEPS_TO_INSTALL[@]}" >&2; then
|
|
168
|
+
INSTALLED_DEPS="${DEPS_TO_INSTALL[*]}"
|
|
169
|
+
echo "installed: $INSTALLED_DEPS" >&2
|
|
170
|
+
missing_prerequisites
|
|
171
|
+
else
|
|
172
|
+
DEPS_INSTALL_FAILED=1
|
|
173
|
+
echo '⛔ 자동 설치 실패 — 아래를 직접 실행하라 (automatic install failed — run the command below yourself):' >&2
|
|
174
|
+
fi
|
|
148
175
|
fi
|
|
149
|
-
|
|
150
|
-
if [ "${#MISSING[@]}" -gt 0 ]; then
|
|
176
|
+
if [ "${#MISSING[@]}" -gt 0 ] || [ "$DEPS_INSTALL_FAILED" -eq 1 ]; then
|
|
151
177
|
echo "⛔ required command missing: ${MISSING[*]}. Install it, then rerun this script:" >&2
|
|
152
178
|
required_command_hint "${MISSING[*]}"
|
|
153
179
|
exit 127
|
|
@@ -429,6 +455,7 @@ if [ "$MODIFY_PATH" -eq 1 ] && [ -n "${LOGIN_STARTUP:-}" ] && ! grep -Fqx "$MARK
|
|
|
429
455
|
fi
|
|
430
456
|
|
|
431
457
|
echo "$(t "Installed elanous $VERSION at $PREFIX/bin/elanous" "elanous $VERSION 을(를) 설치했습니다: $PREFIX/bin/elanous")"
|
|
458
|
+
[ -z "$INSTALLED_DEPS" ] || echo "installed: $INSTALLED_DEPS"
|
|
432
459
|
|
|
433
460
|
# ── 다음 걸음 (2026-09-23 · Phase 3 「사람 손」) ─────────────────────────
|
|
434
461
|
# ⛔ 설치가 끝나도 «무엇을 더 쳐야 하나»를 안 말하면, 빠뜨린 손이 나중에 «다른 원인의 얼굴»로 나타난다
|
|
@@ -79,6 +79,9 @@ export function createScenarios(): Scenario[] {
|
|
|
79
79
|
export function createScenariosWithReplay(readReplay: typeof readTerminalReplay): Scenario[] {
|
|
80
80
|
const marker = `pwa-scn-${randomBytes(8).toString('hex')}`;
|
|
81
81
|
const t1bMarker = `pwa-scn-${randomBytes(8).toString('hex')}`;
|
|
82
|
+
const t5bFirst = `ELANOUS_T5B_${randomBytes(8).toString('hex')}`;
|
|
83
|
+
const t5bSecond = `ELANOUS_T5B_${randomBytes(8).toString('hex')}`;
|
|
84
|
+
const screenContains = (text: string): string => `(() => { const host = document.querySelector('[data-xterm-host]'); const term = host?.__elanousTerm; if (!term || !host.getClientRects().length) return false; const buffer = term.buffer.active; for (let row = buffer.viewportY; row < buffer.viewportY + term.rows; row++) if (buffer.getLine(row)?.translateToString().includes(${JSON.stringify(text)})) return true; return false; })()`;
|
|
82
85
|
// C1 — the answer (n+1) never appears in the question, so finding it on the page means a model replied.
|
|
83
86
|
const c1Base = 1000 + (randomBytes(2).readUInt16BE(0) % 8000);
|
|
84
87
|
return [
|
|
@@ -149,6 +152,23 @@ export function createScenariosWithReplay(readReplay: typeof readTerminalReplay)
|
|
|
149
152
|
return body.terminals.filter((row) => row.producer === 'web-registration' && row.id === ids.terminalId).length === 1;
|
|
150
153
|
} }],
|
|
151
154
|
},
|
|
155
|
+
{
|
|
156
|
+
id: 'T5b', surface: 'terminal', path: '/app/term/', title: 'terminal shows output on open and after reload',
|
|
157
|
+
steps: [
|
|
158
|
+
{ kind: 'goto' }, { kind: 'waitFor', selector: terminal, timeoutMs: 20_000 },
|
|
159
|
+
{ kind: 'waitFor', jsPredicate: `Array.from(document.querySelectorAll(${JSON.stringify(connected)})).some(node => node.textContent?.includes('ACP: 연결됨'))`, timeoutMs: 20_000 },
|
|
160
|
+
{ kind: 'click', selector: terminal }, { kind: 'type', text: `echo ${t5bFirst}` }, { kind: 'press', key: 'Enter' },
|
|
161
|
+
{ kind: 'waitFor', jsPredicate: screenContains(t5bFirst), timeoutMs: 20_000 },
|
|
162
|
+
{ kind: 'goto' }, { kind: 'waitFor', selector: terminal, timeoutMs: 20_000 },
|
|
163
|
+
{ kind: 'waitFor', jsPredicate: screenContains(t5bFirst), timeoutMs: 20_000 },
|
|
164
|
+
{ kind: 'click', selector: terminal }, { kind: 'type', text: `echo ${t5bSecond}` }, { kind: 'press', key: 'Enter' },
|
|
165
|
+
{ kind: 'waitFor', jsPredicate: screenContains(t5bSecond), timeoutMs: 20_000 },
|
|
166
|
+
],
|
|
167
|
+
expect: [
|
|
168
|
+
{ js: screenContains(t5bFirst), expected: 'first marker still visible after reload' },
|
|
169
|
+
{ js: screenContains(t5bSecond), expected: 'new output visible after reload' },
|
|
170
|
+
],
|
|
171
|
+
},
|
|
152
172
|
{
|
|
153
173
|
id: 'C1', surface: 'chat', path: '/app/chat/', title: 'first message streams a model reply', costly: true,
|
|
154
174
|
steps: [{ kind: 'goto' }, { kind: 'waitFor', selector: chatInput, timeoutMs: 20_000 }, { kind: 'click', selector: chatInput }, { kind: 'type', text: `Reply with only the number that is one more than ${c1Base}.` }, { kind: 'press', key: 'Enter' }],
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// V1 — TUI regression checks that UX measured by hand on 10-02 (U1a · U1b · MAT1c · ONB1), as data.
|
|
2
|
+
// Each check says what to type and how to judge the rendered screen text. The verdicts are pure
|
|
3
|
+
// (screen string → ok/reason) so they are unit-tested without a PTY; scripts/tui-regress.ts drives the PTY.
|
|
4
|
+
|
|
5
|
+
export type Step =
|
|
6
|
+
| { kind: 'type'; text: string }
|
|
7
|
+
| { kind: 'key'; data: string }
|
|
8
|
+
| { kind: 'wait'; ms: number }
|
|
9
|
+
| { kind: 'snap'; as: string }
|
|
10
|
+
| { kind: 'erase'; count: number };
|
|
11
|
+
|
|
12
|
+
export interface Verdict { ok: boolean; reason: string }
|
|
13
|
+
|
|
14
|
+
export interface TuiCheck {
|
|
15
|
+
id: string;
|
|
16
|
+
title: string;
|
|
17
|
+
/** `tui.role` to set in the temp config before launch (owner when omitted). */
|
|
18
|
+
role?: 'owner' | 'contributor' | 'general';
|
|
19
|
+
steps: Step[];
|
|
20
|
+
judge: (snaps: Record<string, string>) => Verdict;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const CTRL_C = '\x03';
|
|
24
|
+
const BACKSPACE = '\x7f';
|
|
25
|
+
const HINT = /한 번 더 누르면 나갑니다/;
|
|
26
|
+
|
|
27
|
+
const ok = (reason: string): Verdict => ({ ok: true, reason });
|
|
28
|
+
const no = (reason: string): Verdict => ({ ok: false, reason });
|
|
29
|
+
|
|
30
|
+
/** The input row: the last screen line that starts with the prompt marker. */
|
|
31
|
+
export function inputRow(screen: string): string | undefined {
|
|
32
|
+
return screen.split('\n').filter((line) => line.trimStart().startsWith('❯')).at(-1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Palette entries visible after typing a slash prefix (lines other than the input row that show `/name`). */
|
|
36
|
+
export function paletteHas(screen: string, name: string): boolean {
|
|
37
|
+
const row = inputRow(screen);
|
|
38
|
+
return screen.split('\n').some((line) => line !== row && line.includes(`/${name}`));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function judgeFirstScreen(screen: string): Verdict {
|
|
42
|
+
if (/Step 1/.test(screen)) return no('«Step 1» 마법사가 떴다');
|
|
43
|
+
if (!inputRow(screen)) return no('입력칸 «❯» 가 없다');
|
|
44
|
+
return ok('입력칸이 보이고 «Step 1» 이 없다');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function judgeHintAppearsThenClears(armed: string, later: string): Verdict {
|
|
48
|
+
if (!HINT.test(armed)) return no('빈 입력 Ctrl+C 뒤 안내가 안 보인다');
|
|
49
|
+
if (HINT.test(later)) return no('2.6초 뒤에도 안내가 남아 있다');
|
|
50
|
+
return ok('안내가 떴다가 2초 뒤 사라졌다');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function judgeCtrlCClearsText(typed: string, after: string, text: string): Verdict {
|
|
54
|
+
if (!(inputRow(typed) ?? '').includes(text)) return no('친 글이 입력칸에 안 보인다(준비 실패)');
|
|
55
|
+
if ((inputRow(after) ?? '').includes(text)) return no('Ctrl+C 뒤에도 글이 남아 있다');
|
|
56
|
+
if (HINT.test(after)) return no('글을 지우는 Ctrl+C 에 나가기 안내가 떴다');
|
|
57
|
+
return ok('글이 지워지고 안내는 없다');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function judgePalette(screens: Record<string, string>, expectations: Record<string, boolean>): Verdict {
|
|
61
|
+
for (const [name, want] of Object.entries(expectations)) {
|
|
62
|
+
const screen = screens[name];
|
|
63
|
+
if (screen === undefined) return no(`/${name} 화면이 없다`);
|
|
64
|
+
if (paletteHas(screen, name) !== want) return no(`/${name} 가 팔레트에 ${want ? '없다' : '보인다'}`);
|
|
65
|
+
}
|
|
66
|
+
return ok(Object.entries(expectations).map(([n, w]) => `/${n} ${w ? '보임' : '숨김'}`).join(' · '));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** U1b — the help is drawn (key section ⊕ a run of command rows) and the prompt survives below it.
|
|
70
|
+
* Not «the whole grid fits»: an owner sees 60+ commands, which exceed a 50-row screen and scroll (PgUp). */
|
|
71
|
+
export function judgeHelpShown(screen: string): Verdict {
|
|
72
|
+
const lines = screen.split('\n');
|
|
73
|
+
const commandRows = lines.filter((line) => /^\s+\/[a-z][a-z-]*/.test(line)).length;
|
|
74
|
+
if (!lines.some((line) => /^\s*Keys\s*$/.test(line))) return no('/help 의 Keys 절이 안 보인다');
|
|
75
|
+
if (commandRows < 10) return no(`명령 줄이 ${commandRows}개뿐이다`);
|
|
76
|
+
if (!inputRow(screen)) return no('도움말을 연 뒤 입력칸이 화면 밖으로 밀렸다');
|
|
77
|
+
return ok(`도움말이 그려졌다(명령 줄 ${commandRows} · Keys 절 · 입력칸 유지)`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** U1c2 — lines typed while a reply streams show up as «my» lines, in order, each above its own answer. */
|
|
81
|
+
export function judgeQueuedEcho(screen: string): Verdict {
|
|
82
|
+
const lines = screen.split('\n');
|
|
83
|
+
const at = (re: RegExp, after = -1) => lines.findIndex((line, i) => i > after && re.test(line));
|
|
84
|
+
const u2 = at(/Q2MARK 라고만/);
|
|
85
|
+
if (u2 < 0) return no('줄 서 있던 «Q2MARK 라고만» 이 내 말로 안 보인다');
|
|
86
|
+
const a2 = lines.findIndex((line, i) => i > u2 && /Q2MARK/.test(line) && !/라고만/.test(line));
|
|
87
|
+
const u3 = at(/Q3MARK 라고만/, u2);
|
|
88
|
+
if (u3 < 0) return no('«Q3MARK 라고만» 이 내 말로 안 보인다(또는 순서가 뒤집혔다)');
|
|
89
|
+
const a3 = lines.findIndex((line, i) => i > u3 && /Q3MARK/.test(line) && !/라고만/.test(line));
|
|
90
|
+
if (!(a2 > u2 && u3 > a2 && a3 > u3)) return no(`순서가 내 말2(${u2}) → 답2(${a2}) → 내 말3(${u3}) → 답3(${a3}) 가 아니다`);
|
|
91
|
+
return ok('내 말2 → 답2 → 내 말3 → 답3');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const PALETTE_STEPS = (names: string[]): Step[] => names.flatMap((name) => [
|
|
95
|
+
{ kind: 'type', text: `/${name.slice(0, 3)}` } as Step,
|
|
96
|
+
{ kind: 'wait', ms: 800 } as Step,
|
|
97
|
+
{ kind: 'snap', as: name } as Step,
|
|
98
|
+
{ kind: 'erase', count: 4 } as Step,
|
|
99
|
+
{ kind: 'wait', ms: 300 } as Step,
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
export const TUI_REGRESS_CHECKS: TuiCheck[] = [
|
|
103
|
+
{
|
|
104
|
+
id: 'R1', title: '첫 화면 — 입력칸 · «Step 1» 0 (ONB1)',
|
|
105
|
+
steps: [{ kind: 'snap', as: 'boot' }],
|
|
106
|
+
judge: (s) => judgeFirstScreen(s.boot ?? ''),
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: 'R2', title: '빈 입력 Ctrl+C → 안내 → 2초 뒤 사라짐 (U1a · #22868)',
|
|
110
|
+
steps: [{ kind: 'key', data: CTRL_C }, { kind: 'wait', ms: 300 }, { kind: 'snap', as: 'armed' }, { kind: 'wait', ms: 2600 }, { kind: 'snap', as: 'later' }],
|
|
111
|
+
judge: (s) => judgeHintAppearsThenClears(s.armed ?? '', s.later ?? ''),
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 'R3', title: '글 친 뒤 Ctrl+C → 입력 지움 · 안내 없음 (U1a)',
|
|
115
|
+
steps: [{ kind: 'type', text: 'regress-abc' }, { kind: 'wait', ms: 400 }, { kind: 'snap', as: 'typed' }, { kind: 'key', data: CTRL_C }, { kind: 'wait', ms: 400 }, { kind: 'snap', as: 'after' }],
|
|
116
|
+
judge: (s) => judgeCtrlCClearsText(s.typed ?? '', s.after ?? '', 'regress-abc'),
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: 'R4', title: 'owner 팔레트 — /directive · /help 보임 (MAT1c)',
|
|
120
|
+
steps: PALETTE_STEPS(['directive', 'help']),
|
|
121
|
+
judge: (s) => judgePalette(s, { directive: true, help: true }),
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 'R5', title: 'general 팔레트 — /directive 숨김 · /help 보임 (MAT1c)',
|
|
125
|
+
role: 'general',
|
|
126
|
+
steps: PALETTE_STEPS(['directive', 'help']),
|
|
127
|
+
judge: (s) => judgePalette(s, { directive: false, help: true }),
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 'R6', title: '/help 가 그려지고 입력칸이 남는다 (U1b · #22802)',
|
|
131
|
+
steps: [{ kind: 'type', text: '/help' }, { kind: 'key', data: '\r' }, { kind: 'wait', ms: 1500 }, { kind: 'snap', as: 'help' }],
|
|
132
|
+
judge: (s) => judgeHelpShown(s.help ?? ''),
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: 'R7', title: '답 오는 중 쳐 둔 두 줄이 내 말로 차례로 (U1c2 · #23026 · 실제 LLM 턴)',
|
|
136
|
+
steps: [
|
|
137
|
+
{ kind: 'type', text: '1부터 25까지 숫자를 한 줄에 하나씩 써 줘. 도구는 쓰지 마.' }, { kind: 'key', data: '\r' }, { kind: 'wait', ms: 2500 },
|
|
138
|
+
{ kind: 'type', text: 'Q2MARK 라고만 답해' }, { kind: 'key', data: '\r' }, { kind: 'wait', ms: 500 },
|
|
139
|
+
{ kind: 'type', text: 'Q3MARK 라고만 답해' }, { kind: 'key', data: '\r' }, { kind: 'wait', ms: 60_000 },
|
|
140
|
+
{ kind: 'snap', as: 'after' },
|
|
141
|
+
],
|
|
142
|
+
judge: (s) => judgeQueuedEcho(s.after ?? ''),
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
export const ERASE = BACKSPACE;
|
|
@@ -79,7 +79,7 @@ async function main(): Promise<void> {
|
|
|
79
79
|
if (proposals.length) {
|
|
80
80
|
const body = proposals.map((p, i) => `${i + 1}. ${p.label} (강도 ${p.score.toFixed(2)})\n ${p.rationale}`).join('\n\n');
|
|
81
81
|
const msg = `💡 taste 제안 ${proposals.length}건 — 반복 관심에서 창발한 미션 후보입니다.\n(제안일 뿐 미션 생성 아님 · 승인/기각: \`/taste approve <테마>\` · \`/taste reject <테마>\`)\n\n${body}`;
|
|
82
|
-
try { sendOutbound(msg, 'report'); } catch (e) { log(`taste 제안 발송 오류: ${e instanceof Error ? e.message : String(e)}`); }
|
|
82
|
+
try { sendOutbound(msg, 'ops-report'); } catch (e) { log(`taste 제안 발송 오류: ${e instanceof Error ? e.message : String(e)}`); }
|
|
83
83
|
log(`taste 제안 표면화: ${proposals.length}건`);
|
|
84
84
|
}
|
|
85
85
|
}
|
|
@@ -101,7 +101,7 @@ async function main(): Promise<void> {
|
|
|
101
101
|
const changed = (report.recaps?.promoted ?? 0) + (report.consolidate?.consolidated ?? 0)
|
|
102
102
|
+ (report.archive?.archived ?? 0) + (report.prunedEvents ?? 0);
|
|
103
103
|
if (changed > 0) {
|
|
104
|
-
try { sendOutbound(`🧠 기억 회고(새벽) — ${summary}`, 'report'); } catch (e) { log(`발송 오류: ${e instanceof Error ? e.message : String(e)}`); }
|
|
104
|
+
try { sendOutbound(`🧠 기억 회고(새벽) — ${summary}`, 'ops-report'); } catch (e) { log(`발송 오류: ${e instanceof Error ? e.message : String(e)}`); }
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
// 자율행동 회상 로깅(autopilot P0.2) — 회고 루프도 자율행동.
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { join, dirname } from 'node:path';
|
|
5
|
+
import { debug, redactSecrets } from '../src/debug/log.js';
|
|
6
|
+
import { sendOutbound } from '../src/domains/outbound-alert.js';
|
|
7
|
+
import { DecisionLedger } from '../src/decisions/decision-ledger.js';
|
|
8
|
+
import { effectiveInstanceRoot, prodInstanceRoot, releaseLedgerRoot } from '../src/instance/resolve.js';
|
|
9
|
+
import { listMemories } from '../src/memory.js';
|
|
10
|
+
import { listSchedules, getSchedule } from '../src/release-loop/release-schedule.js';
|
|
11
|
+
import { listChecklist, summarizeChecklist } from '../src/release-loop/checklist.js';
|
|
12
|
+
import { getUserConfig } from '../src/user-config.js';
|
|
13
|
+
import { registerStandaloneLogSink } from '../src/domains/standalone-log-sink.js';
|
|
14
|
+
|
|
15
|
+
export type ProbeKind = 'cut' | 'green' | 'open';
|
|
16
|
+
export type ProbeVerdict = 'match' | 'mismatch' | 'pending' | 'unmeasured';
|
|
17
|
+
export interface ProbeRow {
|
|
18
|
+
ts: string;
|
|
19
|
+
question: string;
|
|
20
|
+
verdict: ProbeVerdict;
|
|
21
|
+
botValue: string | number | null;
|
|
22
|
+
ledgerValue: string | number | null;
|
|
23
|
+
opsNowUpdatedAt: string | null;
|
|
24
|
+
reason: string | null;
|
|
25
|
+
botReply: string | null;
|
|
26
|
+
}
|
|
27
|
+
export const STANDARD_QUESTIONS: ReadonlyArray<{ kind: ProbeKind; ask: (version: string) => string }> = [
|
|
28
|
+
{ kind: 'cut', ask: (version) => `${version} 컷 언제야?` },
|
|
29
|
+
{ kind: 'green', ask: (version) => `${version} 체크리스트 초록 몇 개야?` },
|
|
30
|
+
{ kind: 'open', ask: () => '지금 미결 결정 몇 개야?' },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export interface ProbeDeps {
|
|
34
|
+
schedules?: () => Promise<Array<{ version: string; cutAt: string }>> | Array<{ version: string; cutAt: string }>;
|
|
35
|
+
published?: (version: string) => Promise<boolean> | boolean;
|
|
36
|
+
cut?: (version: string) => Promise<string | null> | string | null;
|
|
37
|
+
green?: (version: string) => Promise<number> | number;
|
|
38
|
+
open?: () => Promise<number> | number;
|
|
39
|
+
ask?: (question: string, model?: string) => Promise<string>;
|
|
40
|
+
send?: typeof sendOutbound;
|
|
41
|
+
memoryUpdatedAt?: () => string | null;
|
|
42
|
+
model?: () => string | undefined;
|
|
43
|
+
root?: string;
|
|
44
|
+
now?: () => Date;
|
|
45
|
+
print?: (line: string) => void;
|
|
46
|
+
observe?: (question: string, verdict: ProbeVerdict, reason: string | null) => void;
|
|
47
|
+
}
|
|
48
|
+
export interface ProbeOptions { version?: string; dryRun?: boolean; json?: boolean }
|
|
49
|
+
|
|
50
|
+
function kstDate(date: Date): string {
|
|
51
|
+
return new Date(date.getTime() + 9 * 3_600_000).toISOString().slice(0, 10);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const CUT_ISO = /\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})(?!\d)/g;
|
|
55
|
+
const CUT_LOCAL = /(?:(\d{4})\s*[년./-]\s*)?(\d{1,2})\s*(?:월|[./-])\s*(\d{1,2})\s*(?:일)?(?:\s*\([^)]*\))?(?:\s*(?:오전|오후|AM|PM))?\s*(\d{1,2})\s*(?:시\s*(\d{1,2})\s*분?|:\s*(\d{2}))(?!\d)\s*(?:KST|한국시간)?/gi;
|
|
56
|
+
|
|
57
|
+
function cutValue(text: string): string | null {
|
|
58
|
+
const iso = new RegExp(CUT_ISO.source).exec(text)?.[0];
|
|
59
|
+
if (iso && Number.isFinite(Date.parse(iso))) return new Date(Date.parse(iso) + 9 * 3_600_000).toISOString().slice(0, 16);
|
|
60
|
+
const match = new RegExp(CUT_LOCAL.source, 'i').exec(text);
|
|
61
|
+
if (!match) return null;
|
|
62
|
+
const year = match[1] ? Number(match[1]) : null;
|
|
63
|
+
const month = Number(match[2]), day = Number(match[3]);
|
|
64
|
+
let hour = Number(match[4]);
|
|
65
|
+
const minute = Number(match[5] ?? match[6]);
|
|
66
|
+
if (/오후|PM/i.test(match[0]) && hour < 12) hour += 12;
|
|
67
|
+
if (/오전|AM/i.test(match[0]) && hour === 12) hour = 0;
|
|
68
|
+
if (month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || minute > 59) return null;
|
|
69
|
+
return `${year ?? '????'}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}T${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function normalizedCut(value: string, reference?: string): string | null {
|
|
73
|
+
const parsed = cutValue(value);
|
|
74
|
+
if (!parsed) return null;
|
|
75
|
+
if (parsed.startsWith('????-') && reference) return `${reference.slice(0, 4)}${parsed.slice(4)}`;
|
|
76
|
+
return parsed;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function ledgerCutValue(value: string): string | null {
|
|
80
|
+
const millis = Date.parse(value);
|
|
81
|
+
if (!Number.isFinite(millis)) return null;
|
|
82
|
+
return new Date(millis + 9 * 3_600_000).toISOString().slice(0, 16);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
type ParsedValue<T> = { value: T | null; ambiguous: boolean };
|
|
86
|
+
type Candidate<T> = { start: number; end: number; value: T };
|
|
87
|
+
|
|
88
|
+
function selectClaim<T>(reply: string, candidates: Candidate<T>[]): ParsedValue<T> {
|
|
89
|
+
if (candidates.length === 1) {
|
|
90
|
+
const after = reply.slice(candidates[0]!.end);
|
|
91
|
+
if (/^\s*(?:개|건|KST|한국시간)?\s*(?:가|이|은|는)?\s*(?:아닙니다|아니다|아니에요|아님|아닌|말고)/i.test(after)) {
|
|
92
|
+
return { value: null, ambiguous: true };
|
|
93
|
+
}
|
|
94
|
+
return { value: candidates[0]!.value, ambiguous: false };
|
|
95
|
+
}
|
|
96
|
+
if (candidates.length === 2) {
|
|
97
|
+
const between = reply.slice(candidates[0]!.end, candidates[1]!.start);
|
|
98
|
+
// «57개가 아니라 50개» · «3개, 아니 4개» — the later value is the asserted one.
|
|
99
|
+
if (/^(?:\s*(?:개|건|KST|한국시간))?\s*(?:가|이|은|는)?\s*(?:아니라|아닌|말고|[,,]?\s*아니(?:요|고)?\s*[,,]?)\s*$/i.test(between)) {
|
|
100
|
+
return { value: candidates[1]!.value, ambiguous: false };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return { value: null, ambiguous: candidates.length > 1 };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const NEGATED_AFTER = /^\s*(?:개|건)?\s*(?:가|이|은|는)?\s*(?:아닙니다|아니다|아니에요|아님|아닌|말고)/i;
|
|
107
|
+
// A number counts as the metric only when it carries a unit (개·건) or sits right after the label («초록은 57»).
|
|
108
|
+
// Anything else (a version «0.2.10판», a date) is not a claim about the count — unmeasured, never a false drift.
|
|
109
|
+
const DIRECT_AFTER_LABEL = /^\s*(?:은|는|이|가|도|:|=)?\s*(?:총|현재|지금|모두|전부)?\s*$/;
|
|
110
|
+
|
|
111
|
+
function countValue(reply: string, kind: 'green' | 'open'): ParsedValue<number> {
|
|
112
|
+
const label = kind === 'green' ? /(?:초록|녹색|green|🟢)/i : /(?:미결\s*결정|열린\s*결정|open\s*decisions?|미결)/i;
|
|
113
|
+
const found = label.exec(reply);
|
|
114
|
+
if (!found) {
|
|
115
|
+
const bare = /^\s*(\d[\d,]*)\s*(?:개|건)?[.。!\s]*$/.exec(reply);
|
|
116
|
+
if (!bare) return { value: null, ambiguous: false };
|
|
117
|
+
const value = Number(bare[1]!.replaceAll(',', ''));
|
|
118
|
+
return Number.isSafeInteger(value) && value >= 0 ? { value, ambiguous: false } : { value: null, ambiguous: false };
|
|
119
|
+
}
|
|
120
|
+
const answer = reply.slice(found.index + found[0].length);
|
|
121
|
+
const candidates: Candidate<number>[] = [];
|
|
122
|
+
for (const match of answer.matchAll(/(?<![\d.])\d[\d,]*(?![\d.])/g)) {
|
|
123
|
+
const value = Number(match[0].replaceAll(',', ''));
|
|
124
|
+
if (!Number.isSafeInteger(value) || value < 0) continue;
|
|
125
|
+
const end = match.index + match[0].length;
|
|
126
|
+
const unit = /^\s*(?:개|건)/.test(answer.slice(end));
|
|
127
|
+
const direct = DIRECT_AFTER_LABEL.test(answer.slice(0, match.index));
|
|
128
|
+
if (unit || direct) candidates.push({ start: match.index, end, value });
|
|
129
|
+
}
|
|
130
|
+
if (candidates.length) return selectClaim(answer, candidates);
|
|
131
|
+
// Number before the label («57개가 초록», «57개 초록이 아닙니다») — keep the negation that follows the label.
|
|
132
|
+
const reverse = new RegExp(`(?<![\\d.])(\\d[\\d,]*)(?![\\d.])\\s*(?:개|건)?\\s*(?:가|이|의)?\\s*${label.source}`, 'i').exec(reply);
|
|
133
|
+
if (!reverse) return { value: null, ambiguous: false };
|
|
134
|
+
const tail = reply.slice(reverse.index + reverse[0].length);
|
|
135
|
+
if (NEGATED_AFTER.test(tail)) return { value: null, ambiguous: true };
|
|
136
|
+
const value = Number(reverse[1]!.replaceAll(',', ''));
|
|
137
|
+
return Number.isSafeInteger(value) && value >= 0 ? { value, ambiguous: false } : { value: null, ambiguous: false };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function replyValue(kind: ProbeKind, reply: string, ledger: string | number): ParsedValue<string | number> {
|
|
141
|
+
if (kind !== 'cut') return countValue(reply, kind);
|
|
142
|
+
const candidates: Candidate<string>[] = [];
|
|
143
|
+
for (const match of reply.matchAll(CUT_ISO)) {
|
|
144
|
+
const value = normalizedCut(match[0], String(ledger));
|
|
145
|
+
if (value) candidates.push({ start: match.index, end: match.index + match[0].length, value });
|
|
146
|
+
}
|
|
147
|
+
for (const match of reply.matchAll(CUT_LOCAL)) {
|
|
148
|
+
if (candidates.some((candidate) => match.index < candidate.end && match.index + match[0].length > candidate.start)) continue;
|
|
149
|
+
let value = normalizedCut(match[0], String(ledger));
|
|
150
|
+
const zone = /^\s*(UTC|GMT|Z\b|[A-Z]{2,4}T\b|[+-]\d{1,2}(?::?\d{2})?\b)\s*([+-]\d{1,2}(?::?\d{2})?)?/i.exec(reply.slice(match.index + match[0].length));
|
|
151
|
+
if (value && zone && !/KST/i.test(match[0])) {
|
|
152
|
+
// A written zone other than KST: UTC/GMT converts (+9h); any other zone or offset is not guessed — unmeasured.
|
|
153
|
+
if (/^(?:UTC|GMT|Z)$/i.test(zone[1]!) && !zone[2] && !value.startsWith('????')) {
|
|
154
|
+
const millis = Date.parse(`${value}:00Z`);
|
|
155
|
+
value = Number.isFinite(millis) ? new Date(millis + 9 * 3_600_000).toISOString().slice(0, 16) : null;
|
|
156
|
+
} else value = null;
|
|
157
|
+
}
|
|
158
|
+
if (value) candidates.push({ start: match.index, end: match.index + match[0].length + (zone && !/KST/i.test(match[0]) ? zone[0].length : 0), value });
|
|
159
|
+
}
|
|
160
|
+
candidates.sort((a, b) => a.start - b.start);
|
|
161
|
+
const valid = candidates.filter(({ value: parsed }) => {
|
|
162
|
+
const millis = Date.parse(`${parsed}:00Z`);
|
|
163
|
+
return Number.isFinite(millis) && new Date(millis).toISOString().slice(0, 16) === parsed;
|
|
164
|
+
});
|
|
165
|
+
return selectClaim(reply, valid);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function askBot(question: string, model?: string): Promise<string> {
|
|
169
|
+
const root = effectiveInstanceRoot();
|
|
170
|
+
return new Promise((resolve, reject) => {
|
|
171
|
+
const args = ['bin/elanous.mjs', ...(root === prodInstanceRoot() ? [] : [`--test=${root}`]), 'ask', question, '--json'];
|
|
172
|
+
const child = spawn('bun', args, {
|
|
173
|
+
cwd: join(import.meta.dir, '..'),
|
|
174
|
+
env: { ...process.env, ...(model ? { ELANOUS_LLM_MODEL: model } : {}) },
|
|
175
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
176
|
+
});
|
|
177
|
+
let out = '';
|
|
178
|
+
let tooLong = false;
|
|
179
|
+
let timedOut = false;
|
|
180
|
+
const timer = setTimeout(() => { timedOut = true; child.kill(); reject(new Error('timeout')); }, 90_000);
|
|
181
|
+
child.stdout.on('data', (chunk: Buffer) => {
|
|
182
|
+
out += chunk.toString();
|
|
183
|
+
if (out.length > 1024 * 1024) { tooLong = true; child.kill(); }
|
|
184
|
+
});
|
|
185
|
+
child.on('error', (error) => { clearTimeout(timer); reject(error); });
|
|
186
|
+
child.on('close', (code) => {
|
|
187
|
+
clearTimeout(timer);
|
|
188
|
+
if (timedOut) return reject(new Error('timeout'));
|
|
189
|
+
if (tooLong) return reject(new Error('bot-output-too-large'));
|
|
190
|
+
if (code !== 0) return reject(new Error('bot-error'));
|
|
191
|
+
try {
|
|
192
|
+
const result = JSON.parse(out) as { reply?: unknown; error?: unknown };
|
|
193
|
+
if (typeof result.reply !== 'string') throw new Error('bot-no-reply');
|
|
194
|
+
resolve(result.reply);
|
|
195
|
+
} catch { reject(new Error('bot-no-reply')); }
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function isPublished(version: string, releaseRoot: string): boolean {
|
|
201
|
+
try {
|
|
202
|
+
const record = JSON.parse(readFileSync(join(releaseRoot, 'release', version, 'release.json'), 'utf8')) as { publishedAt?: unknown; version?: unknown };
|
|
203
|
+
return record.version === version && typeof record.publishedAt === 'string';
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false;
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function ledgerOpen(): number { return new DecisionLedger().list({ status: 'open' }).length; }
|
|
211
|
+
function memoryUpdatedAt(): string | null { return listMemories().find((entry) => entry.name === 'ops-now')?.updatedAt ?? null; }
|
|
212
|
+
function probeModel(): string | undefined {
|
|
213
|
+
const config = getUserConfig().raw.opsNowProbe;
|
|
214
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) return undefined;
|
|
215
|
+
const model = (config as Record<string, unknown>).model;
|
|
216
|
+
return typeof model === 'string' && model.trim() ? model.trim() : undefined;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export async function runOpsNowProbe(options: ProbeOptions = {}, deps: ProbeDeps = {}): Promise<{ rows: ProbeRow[]; exitCode: number; alert: string | null }> {
|
|
220
|
+
const now = (deps.now ?? (() => new Date()))();
|
|
221
|
+
const root = deps.root ?? effectiveInstanceRoot();
|
|
222
|
+
const releaseRoot = releaseLedgerRoot();
|
|
223
|
+
let version = options.version;
|
|
224
|
+
if (!version) {
|
|
225
|
+
try {
|
|
226
|
+
if (!deps.schedules && !existsSync(join(releaseRoot, 'release', 'features.sqlite'))) throw new Error('ledger-unreadable');
|
|
227
|
+
const schedules = await (deps.schedules ?? (() => listSchedules(releaseRoot)))();
|
|
228
|
+
const ordered = schedules.filter((row) => Number.isFinite(Date.parse(row.cutAt)))
|
|
229
|
+
.sort((a, b) => Date.parse(a.cutAt) - Date.parse(b.cutAt));
|
|
230
|
+
for (const row of ordered) {
|
|
231
|
+
if (!await (deps.published ?? ((v) => isPublished(v, releaseRoot)))(row.version)) {
|
|
232
|
+
version = row.version;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
} catch { /* A missing version makes the two release questions unmeasured. */ }
|
|
237
|
+
}
|
|
238
|
+
const updatedAt = (() => { try { return (deps.memoryUpdatedAt ?? memoryUpdatedAt)(); } catch { return null; } })();
|
|
239
|
+
let model: string | undefined;
|
|
240
|
+
try { model = deps.model ? deps.model() : probeModel(); } catch { /* Use the ask path's configured model. */ }
|
|
241
|
+
const rows: ProbeRow[] = [];
|
|
242
|
+
for (const item of STANDARD_QUESTIONS) {
|
|
243
|
+
const question = item.ask(version ?? '(판 미상)');
|
|
244
|
+
let ledgerValue: string | number | null = null;
|
|
245
|
+
let reason: string | null = null;
|
|
246
|
+
try {
|
|
247
|
+
if (item.kind !== 'open' && !version) throw new Error('ledger-unreadable');
|
|
248
|
+
if (item.kind === 'green' && !deps.green && !existsSync(join(releaseRoot, 'release', 'features.sqlite')) && !existsSync(join(releaseRoot, 'release', version!, 'checklist.json'))) throw new Error('ledger-unreadable');
|
|
249
|
+
if (item.kind === 'open' && !deps.open && !existsSync(new DecisionLedger().path)) throw new Error('ledger-unreadable');
|
|
250
|
+
const value = item.kind === 'cut' ? await (deps.cut ?? ((v) => getSchedule(v, releaseRoot)?.cutAt ?? null))(version!)
|
|
251
|
+
: item.kind === 'green' ? await (deps.green ?? ((v) => summarizeChecklist(listChecklist(v)).green))(version!) : await (deps.open ?? ledgerOpen)();
|
|
252
|
+
if (item.kind === 'cut') ledgerValue = typeof value === 'string' ? ledgerCutValue(value) : null;
|
|
253
|
+
else ledgerValue = typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
254
|
+
if (ledgerValue === null) reason = 'ledger-unreadable';
|
|
255
|
+
} catch { reason = 'ledger-unreadable'; }
|
|
256
|
+
let reply: string | null = null;
|
|
257
|
+
let botValue: string | number | null = null;
|
|
258
|
+
try { reply = await (deps.ask ?? askBot)(question, model); }
|
|
259
|
+
catch (error) { if (!reason) reason = (error as Error).message === 'timeout' ? 'timeout' : 'bot-unresponsive'; }
|
|
260
|
+
let verdict: ProbeVerdict = 'unmeasured';
|
|
261
|
+
if (!reason) {
|
|
262
|
+
if (!reply?.trim()) reason = 'bot-unresponsive';
|
|
263
|
+
else if (/확인\s*(?:중|해\s*보겠|해\s*볼게|해\s*보도록|이\s*필요)|조회\s*중|살펴\s*보겠/.test(reply)) { verdict = 'pending'; reason = 'bot-pending'; }
|
|
264
|
+
else {
|
|
265
|
+
const parsed = replyValue(item.kind, reply, ledgerValue!);
|
|
266
|
+
botValue = parsed.value;
|
|
267
|
+
if (botValue === null) reason = parsed.ambiguous ? 'value-ambiguous' : 'value-unparseable';
|
|
268
|
+
else verdict = botValue === ledgerValue ? 'match' : 'mismatch';
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const row: ProbeRow = { ts: now.toISOString(), question, verdict, botValue, ledgerValue, opsNowUpdatedAt: updatedAt,
|
|
272
|
+
reason, botReply: reply === null ? null : redactSecrets({ reply }).reply.slice(0, 200) };
|
|
273
|
+
rows.push(row);
|
|
274
|
+
debug.log('ops-now.probe', 'verdict', { question, verdict, reason });
|
|
275
|
+
deps.observe?.(question, verdict, reason);
|
|
276
|
+
}
|
|
277
|
+
const path = join(root, 'ops-now-probe', `${kstDate(now)}.jsonl`);
|
|
278
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
279
|
+
appendFileSync(path, rows.map((row) => JSON.stringify(row)).join('\n') + '\n', { mode: 0o600 });
|
|
280
|
+
const mismatches = rows.filter((row) => row.verdict === 'mismatch');
|
|
281
|
+
const alert = mismatches.length ? `🚨 ops-now 봇 답 불일치 — ${mismatches.map((row) => `${row.question} 봇 ${row.botValue} / 원장 ${row.ledgerValue}`).join(' · ')} · ops-now updatedAt ${updatedAt ?? '미상'}` : null;
|
|
282
|
+
let alertDelivery: 'sent' | 'failed' | 'dry-run' | null = alert ? (options.dryRun ? 'dry-run' : 'failed') : null;
|
|
283
|
+
if (alert && !options.dryRun) {
|
|
284
|
+
try {
|
|
285
|
+
if (await (deps.send ?? sendOutbound)(alert, 'ops-alert')) alertDelivery = 'sent';
|
|
286
|
+
} catch {
|
|
287
|
+
// A throwing sender has the same undelivered outcome as a false return.
|
|
288
|
+
}
|
|
289
|
+
if (alertDelivery === 'failed') debug.log('ops-now.probe', 'alert-failed', { kind: 'ops-alert' });
|
|
290
|
+
}
|
|
291
|
+
const print = deps.print ?? console.log;
|
|
292
|
+
const warning = [
|
|
293
|
+
...(rows.some((row) => row.verdict === 'unmeasured') ? ['⚠ 측정하지 못한 질문이 있습니다.'] : []),
|
|
294
|
+
...(alertDelivery === 'failed' ? ['⚠ ops-alert 전송 실패'] : []),
|
|
295
|
+
].join(' ') || null;
|
|
296
|
+
if (options.json) print(JSON.stringify({ version: version ?? null, rows, alert, alertDelivery, warning, wouldSend: Boolean(alert && options.dryRun), exitCode: mismatches.length ? 1 : 0 }));
|
|
297
|
+
else {
|
|
298
|
+
for (const row of rows) print(`${row.verdict} · ${row.question} · 봇 ${row.botValue ?? '-'} · 원장 ${row.ledgerValue ?? '-'}${row.reason ? ` · ${row.reason}` : ''}`);
|
|
299
|
+
if (warning) print(warning);
|
|
300
|
+
if (alert) {
|
|
301
|
+
if (alertDelivery === 'dry-run') print(`보냈을 것: ${alert}`);
|
|
302
|
+
else if (alertDelivery === 'sent') print(alert);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return { rows, exitCode: mismatches.length ? 1 : 0, alert };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export async function main(args: string[] = process.argv.slice(2)): Promise<number> {
|
|
309
|
+
let version: string | undefined;
|
|
310
|
+
let dryRun = false, json = false, once = false;
|
|
311
|
+
for (let i = 0; i < args.length; i++) {
|
|
312
|
+
const arg = args[i];
|
|
313
|
+
if (arg === '--once') once = true;
|
|
314
|
+
else if (arg === '--dry-run') dryRun = true;
|
|
315
|
+
else if (arg === '--json') json = true;
|
|
316
|
+
else if (arg === '--version' && args[i + 1] && !args[i + 1]!.startsWith('--')) version = args[++i];
|
|
317
|
+
else throw new Error(`알 수 없는 옵션: ${arg}`);
|
|
318
|
+
}
|
|
319
|
+
if (!once) throw new Error('--once 필요');
|
|
320
|
+
await registerStandaloneLogSink('ops-now-probe');
|
|
321
|
+
return (await runOpsNowProbe({ version, dryRun, json })).exitCode;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (import.meta.main) {
|
|
325
|
+
try { process.exitCode = await main(); }
|
|
326
|
+
catch (error) { console.error(`ops-now-probe: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 2; }
|
|
327
|
+
}
|