elanous 0.2.18 → 0.2.19
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/{P6YTVgzuZz5oTZhY4Din_ → J5H7BvqgSI8DUjtRRcA_S}/_buildManifest.js +1 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1157-cba0e8553d35a980.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{726.83532d69452ee24b.js → 1470.98a6ab61ccb4131c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/171-23a877ed11720de9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2336-e830c777eb6bce04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9247-269e4464cc4b491c.js → 2375-e061bb24cd2b5cde.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{211-9c9090c59e2c5907.js → 2977-e38442e86289f1d3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{f6b7c9ba.64a895b8b2fedbdc.js → 2ea96958.e8c7e112de50b067.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3094.67339318c7f72a3a.js +2 -0
- package/apps/pwa/out/_next/static/chunks/338-ceb956612dcfaa86.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3395-835e4ceaa6335ba4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +5 -0
- package/apps/pwa/out/_next/static/chunks/3441-be82e3595aef3a20.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3689-89905039bf3e7257.js +1 -0
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3822-6f268bc26c6a84ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1268.0840ff91e083fcbd.js → 4323.c5dfb6f503304775.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4457-d7df81c90f9d0268.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4551-77dadae76f6cbace.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6110.1297d4093fdfbb0d.js → 4847.56f5bc454fbcc827.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/4978.6fd93b974441e87f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5012-f746f6549b869f91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7327-2d69fb321ba73389.js → 6062-73e68343a9814f6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/62-5bfed1e23790b745.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6320-9bc8561fde416e07.js +1 -0
- package/apps/pwa/out/_next/static/chunks/649-ca96d1c5d09e02b8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6726-85a7db59b288c53f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6735-33267c4630d0d963.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7633-141682015873beb6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{935.4ddaa99fc1d1126c.js → 7647.2fcd30d894d5ee30.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{f6231284-45726550aba08fca.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8067-143791231c99bd02.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8430-a10cfba8ef282d4e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8870-9614a213b990b10b.js +25 -0
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-12c2f30dea62291c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/976-4a9b4ebdca7c739a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9989-15cdf568bafa9fc8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/ae787e4c-e5b1ef00b2dbceca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-deef5ee191830f14.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-b193da485f9a14b2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-b6e1a79df0e214f4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-e84df4d907f703c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a5fb9a5db89ae4ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1499f481d7fc52d6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-c7cf753aeb648790.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-4118469e514e10fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-e968f7630654332a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-505915052f947022.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-c2f962fb4a3b06c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7b5ed0bd8a589ec4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-cf8193250dd42dd4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-cf3f5dfa35dea8e9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-5b29d9b06ceadade.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a15e7fcc283e9ca7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-d2915e7b70ff3d0e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-b70cd005d9169e25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-2595f0eba29f57e1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-c776322454156e36.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-77bbd17f4435aa10.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-049689e611ecc936.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-85954a4fb09c44eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-e773e552e1068ee6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5633c4bfa7684b4a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d829b92bd01b4994.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-061c317fc771becf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a45ece8865ed4b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-0be990618c0d56bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-bfea4865dc244b73.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d6ba8e337741484.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-76a88b55d816482e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-bba1542df59f61ce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4185c95689bb372e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b699e1a8d5d34b8b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-c225e338c31bf274.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-449353f70507ca1f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-90b889714102476d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-f1ee7fbeb7f96ddc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-d59a95e1434dfaa0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-71491a23414d1a47.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-de7112bdb65fa4fe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-feb4d0d88079660d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f17d887b40dd6b1d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-8da77be2192bcd5d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-72c3b9a5e9582b29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3afe4c8d0c48c39.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-b9308f2d8a931531.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-6f35f9d892fa630b.js +1 -0
- package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
- package/apps/pwa/out/_next/static/css/85f53c6f35d11382.css +1 -0
- package/apps/pwa/out/_next/static/css/d3427996da11e480.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +23 -23
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +23 -23
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +23 -23
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +22 -22
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +22 -22
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +23 -23
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +22 -22
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +23 -23
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +23 -23
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +22 -22
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +25 -24
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +23 -23
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +23 -23
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +22 -22
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +24 -23
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +23 -23
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +23 -23
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +28 -27
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +22 -22
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +22 -22
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +23 -23
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +23 -23
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +22 -22
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +22 -22
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +22 -22
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +23 -23
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +23 -23
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +22 -22
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +23 -23
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +23 -23
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +23 -23
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +23 -23
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +23 -23
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +22 -22
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +23 -23
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +23 -23
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +23 -23
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +23 -23
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +23 -23
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +23 -23
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +23 -23
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +23 -23
- package/graphs/ops/guardian.yaml +26 -0
- package/graphs/ops/recipes.yaml +3 -0
- package/package.json +1 -1
- package/scripts/coord-channel-watch.sh +2 -2
- package/scripts/discord-seat-probe.ts +3 -1
- package/scripts/lib/pch-regress-map.ts +23 -0
- package/scripts/morning-report.ts +18 -4
- package/scripts/pch-regress.ts +88 -0
- package/scripts/release-loop/gate-diff.ts +18 -0
- package/scripts/release-loop/gate-node.ts +338 -46
- package/scripts/release-loop/gate-partial.ts +106 -0
- package/scripts/release-loop/gate-timing.ts +120 -0
- package/scripts/release-loop/resume-release.ts +64 -5
- package/scripts/release-loop/shard-plan.ts +65 -0
- package/scripts/release-loop/unattended-release.ts +2 -0
- package/scripts/video/scene-table.ts +159 -0
- package/src/acp/core-turn-bridge.ts +39 -2
- package/src/away/presence.ts +40 -0
- package/src/away/release-watch.ts +255 -0
- package/src/chat/index.ts +2 -1
- package/src/chat/short-question.ts +3 -40
- package/src/cli/away-cli.ts +68 -0
- package/src/cli/guardian-cli.ts +49 -0
- package/src/cli/intake-cli.ts +13 -7
- package/src/cli/log-event-names.ts +1 -0
- package/src/cli/pod-cli.ts +5 -1
- package/src/cli/pty-drive-cli.ts +14 -3
- package/src/cli/release-cli.ts +32 -7
- package/src/cli/setup-cli.ts +31 -1
- package/src/cli/tasks-cli.ts +104 -11
- package/src/context-bus/context-now-public.ts +1 -1
- package/src/context-bus/context-now-surfaces.ts +16 -6
- package/src/context-bus/context-now.ts +97 -3
- package/src/core-turn/short-question.ts +100 -0
- package/src/dashboard/dashboard-status-lines.ts +24 -4
- package/src/dashboard/index.ts +50 -5
- package/src/dashboard/input/slash-executor.ts +6 -2
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +63 -36
- package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +21 -8
- package/src/dashboard/slash-runtime/help-groups.ts +44 -0
- package/src/dashboard/slash-runtime/model-choices.ts +125 -0
- package/src/domains/guardian-tick.ts +138 -0
- package/src/domains/outbound-alert.ts +44 -13
- package/src/domains/schedule-registry.ts +23 -0
- package/src/domains/telegram-kind-route.ts +39 -0
- package/src/domains/watch-default-scheduler.ts +95 -0
- package/src/esc-abort-gate.ts +44 -0
- package/src/graph-runner/runner.ts +25 -5
- package/src/harness/harness-cli-command.ts +2 -0
- package/src/harness/harness-salvage-cli.ts +78 -0
- package/src/harness/harness-worktree-add.ts +68 -0
- package/src/harness/ledger-write-guard.ts +6 -16
- package/src/hq/hq.ts +6 -1
- package/src/index.ts +52 -3
- package/src/instance/test-write-guard-spy-first.fixture.ts +13 -0
- package/src/instance/test-write-guard.ts +200 -0
- package/src/intake-plane/digest.ts +39 -15
- package/src/intake-plane/harness-target.ts +4 -3
- package/src/intake-plane/intake-sources.ts +1 -0
- package/src/intake-plane/items.ts +1 -0
- package/src/loops/orchestrator/degradation.ts +57 -0
- package/src/loops/orchestrator/finish-rate.ts +33 -3
- package/src/loops/orchestrator/tick.ts +166 -45
- package/src/maturity/feature-maturity.ts +6 -4
- package/src/maturity/tui-slash-maturity.ts +1 -0
- package/src/mss/logging/log-store.ts +3 -0
- package/src/nexus/api/http-server.ts +16 -5
- package/src/nexus/api/loop-edges.ts +290 -17
- package/src/nexus/api/ops-api.ts +96 -28
- package/src/nexus/index.ts +7 -0
- package/src/release-loop/checklist.ts +2 -0
- package/src/release-loop/feature-store.ts +54 -7
- package/src/release-loop/light-rc.ts +173 -0
- package/src/release-loop/placement.ts +51 -17
- package/src/release-loop/rubric.ts +57 -1
- package/src/self-dev/dev-cli.ts +36 -0
- package/src/self-dev/orchestrate.ts +1 -0
- package/src/self-dev/pr-preview.ts +179 -0
- package/src/self-dev/shard-run.ts +23 -7
- package/src/self-implement/gate-cli.ts +29 -8
- package/src/self-implement/goal-author-clarification.ts +88 -13
- package/src/self-implement/goal-clarification-escalation.ts +2 -0
- package/src/self-implement/orchestrator.ts +2 -1
- package/src/self-implement/salvage-branches.ts +254 -0
- package/src/task-agent/actions.ts +63 -2
- package/src/task-agent/board.ts +132 -0
- package/src/task-agent/card-evidence.ts +203 -0
- package/src/task-agent/completion-evidence.ts +126 -0
- package/src/task-agent/judge.ts +18 -0
- package/src/task-agent/live-moves.ts +176 -0
- package/src/task-agent/mission.ts +32 -6
- package/src/task-agent/shadow.ts +183 -15
- package/src/task-agent/task-hand.ts +117 -15
- package/src/task-orchestrator/surfaces/pod-command-job.ts +64 -4
- package/src/task-orchestrator/surfaces/pod-lease.ts +142 -13
- package/src/task-orchestrator/surfaces/pod-pool.ts +13 -5
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +2 -2
- package/src/telegram-away-command.ts +38 -0
- package/src/telegram-commands.ts +11 -0
- package/src/telegram-report-log-probe.ts +21 -0
- package/src/telegram-report.ts +27 -3
- package/src/tui-client/dashboard-session.ts +1 -0
- package/src/user-config.ts +134 -2
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-30dff22c9e1f3073.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
- package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +0 -1
- package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6158.af2e93b7adff709b.js +0 -2
- package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
- package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +0 -1
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d92f7ee3994e84c7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-4733c967b56a4277.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-d22fd849cc22f104.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-bb742f995c6769e7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-02b073a2ac793b83.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-8a9b3594bc23289b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-a0eb02638b1e6f31.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-308111abadf35670.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-5d97b2901e0d1ea6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c39554c321576982.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-8c3d8e63ae2e1c98.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-9b72a07d424701c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a572f58cd3ca5864.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-be5ea965a07fbc41.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-c130b63862437814.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-fa244bd146a00ae8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-44d56d6391965a6a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-83a700d4c754d802.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-7b3170f374f77b9d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-a3c58f5c72a5f3f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-e8e1ecaea7cd71d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-1467047104141297.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-102310930791197a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-6ed0b73bae8351d8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-44d5f767491692bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-43336055451f6e37.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-3cc47dca0922ab9a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a77ee355a2304ff0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-b17cd46cf0e9b8cf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-61553851aa606146.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-a1f585e154e61c59.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-7bf54c9bb9274af8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-5150ef17b5383a57.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-134da468538baf48.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-c2782b330447ffb6.js +0 -1
- package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +0 -1
- /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → J5H7BvqgSI8DUjtRRcA_S}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
4
|
+
|
|
5
|
+
/** AWAY-MODE-1 — 대표가 자리를 비웠나. 한 파일(`<instance>/presence.json`)이 정본이고 CLI·텔레그램 /away 가 같이 쓴다. */
|
|
6
|
+
export interface Presence {
|
|
7
|
+
away: boolean;
|
|
8
|
+
/** 마지막으로 바꾼 시각(ISO). 한 번도 안 바꿨으면 없음. */
|
|
9
|
+
since?: string;
|
|
10
|
+
/** 누가 바꿨나 — cli · telegram · pwa. */
|
|
11
|
+
by?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function presencePath(root: string = effectiveInstanceRoot()): string {
|
|
15
|
+
return join(root, 'presence.json');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** 못 읽으면 «자리에 있음»으로 읽는다 — 외출 알림은 켠 사람만 받는다. */
|
|
19
|
+
export function readPresence(path: string = presencePath()): Presence {
|
|
20
|
+
try {
|
|
21
|
+
const value: unknown = JSON.parse(readFileSync(path, 'utf8'));
|
|
22
|
+
if (!value || typeof value !== 'object') return { away: false };
|
|
23
|
+
const rec = value as Partial<Presence>;
|
|
24
|
+
return {
|
|
25
|
+
away: rec.away === true,
|
|
26
|
+
...(typeof rec.since === 'string' ? { since: rec.since } : {}),
|
|
27
|
+
...(typeof rec.by === 'string' ? { by: rec.by } : {}),
|
|
28
|
+
};
|
|
29
|
+
} catch { return { away: false }; }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function writePresence(away: boolean, by: string, opts: { path?: string; now?: Date } = {}): Presence {
|
|
33
|
+
const path = opts.path ?? presencePath();
|
|
34
|
+
const presence: Presence = { away, since: (opts.now ?? new Date()).toISOString(), by };
|
|
35
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
36
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
37
|
+
writeFileSync(tmp, `${JSON.stringify(presence)}\n`);
|
|
38
|
+
renameSync(tmp, path);
|
|
39
|
+
return presence;
|
|
40
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { debug } from '../debug/log.js';
|
|
4
|
+
import { deliver, inQuietHours, sendOutbound } from '../domains/outbound-alert.js';
|
|
5
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
6
|
+
import { readReleaseRuns, type ReleaseRunNodeView, type ReleaseRunView } from '../nexus/api/ops-api.js';
|
|
7
|
+
import { readPresence, type Presence } from './presence.js';
|
|
8
|
+
|
|
9
|
+
/** AWAY-MODE-1 — 외출 중 발행 현황 공백 0. 발행 런 원장(`/v1/ops/release/runs` 와 같은 판정)을 직전에 본 것과 견주어
|
|
10
|
+
* «새로 생긴 전이»만 텔레그램 한 통으로 보낸다. 같은 전이는 두 번 안 보낸다(보낸 뒤에만 기준을 옮긴다). */
|
|
11
|
+
|
|
12
|
+
export const RELEASE_SCREEN_URL = 'https://op.elanous.ai/app/ops/release';
|
|
13
|
+
/** 발행 중 이 간격 동안 새 전이가 없으면 «생존» 한 줄. */
|
|
14
|
+
export const ALIVE_EVERY_MS = 60 * 60_000;
|
|
15
|
+
/** 이보다 오래된 런은 따라가지 않는다 — 외출을 켠 순간 지난 판이 쏟아지지 않게. */
|
|
16
|
+
export const FOLLOW_WINDOW_MS = 36 * 3600_000;
|
|
17
|
+
|
|
18
|
+
export interface RunMark {
|
|
19
|
+
status: string;
|
|
20
|
+
/** 끝난(ok 가 정해진) 노드 수. */
|
|
21
|
+
ended: number;
|
|
22
|
+
/** 지금 도는 노드(있으면). */
|
|
23
|
+
running?: string;
|
|
24
|
+
/** 이 런에 대해 마지막으로 보낸 시각(ISO) — 생존 줄 간격의 기준. */
|
|
25
|
+
sentAt: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface WatchState { runs: Record<string, RunMark> }
|
|
29
|
+
|
|
30
|
+
export interface WatchMessage { text: string; urgent: boolean }
|
|
31
|
+
|
|
32
|
+
const BLOCKED = new Set(['failed', 'budget-exceeded', 'awaiting-approval']);
|
|
33
|
+
|
|
34
|
+
const clean = (text: string, max: number): string => {
|
|
35
|
+
const one = text.replace(/[\r\n\t]+/g, ' ').replace(/\s+/g, ' ').trim();
|
|
36
|
+
return one.length > max ? `${one.slice(0, max - 1)}…` : one;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const label = (run: ReleaseRunView): string => run.version ?? run.runId.slice(0, 8);
|
|
40
|
+
|
|
41
|
+
/** 그래프의 종결 자리표 — 노드가 아니라 끝을 표시한다(런 상태로 따로 말한다). */
|
|
42
|
+
const TERMINAL = new Set(['done', 'failed']);
|
|
43
|
+
|
|
44
|
+
function endedNodes(run: ReleaseRunView): ReleaseRunNodeView[] {
|
|
45
|
+
return run.nodes.filter((node) => node.ok !== null && !TERMINAL.has(node.nodeId));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function runningNode(run: ReleaseRunView): ReleaseRunNodeView | undefined {
|
|
49
|
+
return run.status === 'running' ? run.nodes.find((node) => node.ok === null) : undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function minutesSince(iso: string | undefined, now: number): number | null {
|
|
53
|
+
if (!iso) return null;
|
|
54
|
+
const at = Date.parse(iso);
|
|
55
|
+
return Number.isFinite(at) ? Math.max(0, Math.round((now - at) / 60_000)) : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 노드 하나가 끝났을 때의 한 줄. 게이트는 도입 수·면제를 꼭 말한다(«통과»만으로는 면제를 못 읽는다). */
|
|
59
|
+
export function nodeLine(node: ReleaseRunNodeView): string {
|
|
60
|
+
const mark = node.ok ? '✅' : '❌';
|
|
61
|
+
const facts = node.facts ?? {};
|
|
62
|
+
const parts: string[] = [`${mark} ${node.nodeId}`];
|
|
63
|
+
if (facts.introduced !== undefined) parts.push(`도입 ${facts.introduced}${facts.preexisting !== undefined ? ` · 기존 ${facts.preexisting}` : ''}`);
|
|
64
|
+
if (node.summary) parts.push(clean(node.summary, 120));
|
|
65
|
+
else if (facts.verdict) parts.push(facts.verdict);
|
|
66
|
+
let line = parts.join(' · ');
|
|
67
|
+
if (facts.waiver) line += `\n ⚠️ 면제 적용: ${clean(facts.waiver, 140)}`;
|
|
68
|
+
return line;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function blockedLine(run: ReleaseRunView): string {
|
|
72
|
+
const last = endedNodes(run).at(-1);
|
|
73
|
+
const where = last ? `${last.nodeId}${last.ok === false ? ' 실패' : ''}` : '노드 기록 없음';
|
|
74
|
+
const why = last?.summary ? ` — ${clean(last.summary, 140)}` : '';
|
|
75
|
+
if (run.status === 'awaiting-approval') {
|
|
76
|
+
return `⏸ ${label(run)} 발행 승인 대기 · ${where}${why}\n 다음 수: 승인 권한자(OP·대표)가 승인해야 다음 노드로 간다`;
|
|
77
|
+
}
|
|
78
|
+
const kind = run.status === 'budget-exceeded' ? '예산 초과로 멈춤' : '막힘';
|
|
79
|
+
return `⛔ ${label(run)} 발행 ${kind} · ${where}${why}\n 다음 수: 담당 자리(TC·OP)가 재개 또는 면제를 판단한다 — 결정이 오면 이 채널로 알린다`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** 직전 표지와 지금 런을 견주어 보낼 줄을 만든다. 순수 함수 — 시험이 이것을 문다. */
|
|
83
|
+
export function diffRun(prev: RunMark | undefined, run: ReleaseRunView, now: number): { lines: string[]; urgent: boolean; changed: boolean } {
|
|
84
|
+
const ended = endedNodes(run);
|
|
85
|
+
const running = runningNode(run);
|
|
86
|
+
const lines: string[] = [];
|
|
87
|
+
let urgent = false;
|
|
88
|
+
if (!prev) {
|
|
89
|
+
// 이미 끝난 판은 조용히 기준만 잡는다 — 외출을 켤 때 지난 판이 쏟아지지 않게.
|
|
90
|
+
if (run.status === 'done') return { lines, urgent, changed: true };
|
|
91
|
+
// 처음 보는 런 — 지난 노드를 다시 쏟지 않고 «지금 어디인가» 한 줄로 시작한다.
|
|
92
|
+
const total = run.path.filter((step) => !TERMINAL.has(step)).length || run.nodes.length;
|
|
93
|
+
const where = run.status === 'running'
|
|
94
|
+
? `지금 ${running?.nodeId ?? '다음 노드 준비'}${running?.startedAt ? `(${minutesSince(running.startedAt, now)}분째)` : ''}`
|
|
95
|
+
: `상태 ${run.status}`;
|
|
96
|
+
lines.push(`📦 ${label(run)} 발행 따라가기 시작 · 끝난 노드 ${ended.length}${total ? `/${total}` : ''} · ${where}`);
|
|
97
|
+
if (BLOCKED.has(run.status)) { lines.push(blockedLine(run)); urgent = true; }
|
|
98
|
+
return { lines, urgent, changed: true };
|
|
99
|
+
}
|
|
100
|
+
for (const node of ended.slice(prev.ended)) {
|
|
101
|
+
lines.push(nodeLine(node));
|
|
102
|
+
if (node.ok === false) urgent = true;
|
|
103
|
+
}
|
|
104
|
+
if (running && running.nodeId !== prev.running && running.nodeId === 'gate') {
|
|
105
|
+
lines.push(`▶ gate 시작${running.startedAt ? ` · ${new Date(running.startedAt).toLocaleTimeString('ko-KR', { timeZone: 'Asia/Seoul', hour: '2-digit', minute: '2-digit', hour12: false })} KST` : ''}`);
|
|
106
|
+
}
|
|
107
|
+
if (run.status !== prev.status) {
|
|
108
|
+
if (run.status === 'done') {
|
|
109
|
+
const took = minutesSince(run.startedAt, now);
|
|
110
|
+
lines.push(`🎉 ${label(run)} 발행 완료 · 노드 ${ended.length}${took !== null ? ` · ${Math.floor(took / 60)}시간 ${took % 60}분` : ''}`);
|
|
111
|
+
} else if (BLOCKED.has(run.status)) {
|
|
112
|
+
lines.push(blockedLine(run));
|
|
113
|
+
urgent = true;
|
|
114
|
+
} else if (run.status === 'running' && BLOCKED.has(prev.status)) {
|
|
115
|
+
lines.push(`▶ ${label(run)} 발행 재개`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (!lines.length && run.status === 'running' && now - Date.parse(prev.sentAt) >= ALIVE_EVERY_MS) {
|
|
119
|
+
const mins = minutesSince(running?.startedAt, now);
|
|
120
|
+
lines.push(`💓 ${label(run)} 발행 진행 중 · 지금 ${running?.nodeId ?? '다음 노드 준비'}${mins !== null ? `(${mins}분째)` : ''} · 끝난 노드 ${ended.length}/${run.path.filter((step) => !TERMINAL.has(step)).length || ended.length}`);
|
|
121
|
+
}
|
|
122
|
+
const changed = lines.length > 0 || prev.ended !== ended.length || prev.status !== run.status || prev.running !== running?.nodeId;
|
|
123
|
+
return { lines, urgent, changed };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function markOf(run: ReleaseRunView, sentAt: string): RunMark {
|
|
127
|
+
const running = runningNode(run);
|
|
128
|
+
return { status: run.status, ended: endedNodes(run).length, ...(running ? { running: running.nodeId } : {}), sentAt };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** 따라갈 런 — 최근 36시간 안에 시작한 것만. 원장엔 «running» 인 채 버려진 옛 런이 있어 상태로 고르면 매시간 생존 줄이 샌다. */
|
|
132
|
+
export function followedRuns(runs: ReleaseRunView[], now: number): ReleaseRunView[] {
|
|
133
|
+
return runs.filter((run) => now - Date.parse(run.startedAt) <= FOLLOW_WINDOW_MS);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function composeMessage(lines: string[]): string {
|
|
137
|
+
return [...lines, `↗ ${RELEASE_SCREEN_URL}`].join('\n');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function watchStatePath(root: string = effectiveInstanceRoot()): string {
|
|
141
|
+
return join(root, 'away', 'release-watch.json');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function readWatchState(path: string = watchStatePath()): WatchState {
|
|
145
|
+
try {
|
|
146
|
+
const value: unknown = JSON.parse(readFileSync(path, 'utf8'));
|
|
147
|
+
const runs = value && typeof value === 'object' ? (value as { runs?: unknown }).runs : undefined;
|
|
148
|
+
return { runs: runs && typeof runs === 'object' ? runs as Record<string, RunMark> : {} };
|
|
149
|
+
} catch { return { runs: {} }; }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function writeWatchState(state: WatchState, path: string = watchStatePath()): void {
|
|
153
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
154
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
155
|
+
writeFileSync(tmp, `${JSON.stringify(state)}\n`);
|
|
156
|
+
renameSync(tmp, path);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface TickDeps {
|
|
160
|
+
presence?: () => Presence;
|
|
161
|
+
runs?: () => ReleaseRunView[] | 'unavailable';
|
|
162
|
+
readState?: () => WatchState;
|
|
163
|
+
writeState?: (state: WatchState) => void;
|
|
164
|
+
/** 보통 줄 — 야간 무음이면 아침까지 보류된다. */
|
|
165
|
+
send?: (text: string) => boolean;
|
|
166
|
+
/** 막힘·실패 — 야간 무음을 뚫는다. */
|
|
167
|
+
sendUrgent?: (text: string) => boolean;
|
|
168
|
+
now?: () => number;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export type TickOutcome =
|
|
172
|
+
| { outcome: 'present' }
|
|
173
|
+
| { outcome: 'unavailable' }
|
|
174
|
+
| { outcome: 'quiet'; followed: number }
|
|
175
|
+
| { outcome: 'sent'; followed: number; lines: number; urgent: boolean }
|
|
176
|
+
| { outcome: 'send-failed'; followed: number; lines: number; urgent: boolean };
|
|
177
|
+
|
|
178
|
+
export interface UrgentIo {
|
|
179
|
+
quiet: () => boolean;
|
|
180
|
+
outbound: (text: string, kind: string) => boolean;
|
|
181
|
+
deliver: (text: string, kind: string) => 'daemon' | 'direct' | false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** 막힘·실패 차선 — 야간 무음이면 보류 큐를 건너뛰고 바로 보낸다(`deliver`), 아니면 평소 길(`sendOutbound`). */
|
|
185
|
+
export function makeUrgentSender(io: UrgentIo = { quiet: () => inQuietHours(), outbound: sendOutbound, deliver: (t, k) => deliver(t, k) }): (text: string) => boolean {
|
|
186
|
+
return (text) => io.quiet() ? io.deliver(text, 'ops-alert') !== false : io.outbound(text, 'ops-alert');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const defaultSendUrgent = makeUrgentSender();
|
|
190
|
+
|
|
191
|
+
/** 한 번 본다. 자리에 있으면 아무것도 안 한다(기준도 지운다 — 다음 외출이 지난 일을 다시 보내지 않게). */
|
|
192
|
+
export function releaseWatchTick(deps: TickDeps = {}): TickOutcome {
|
|
193
|
+
const presence = (deps.presence ?? readPresence)();
|
|
194
|
+
const writeState = deps.writeState ?? ((state: WatchState) => writeWatchState(state));
|
|
195
|
+
if (!presence.away) {
|
|
196
|
+
const state = (deps.readState ?? readWatchState)();
|
|
197
|
+
if (Object.keys(state.runs).length) writeState({ runs: {} });
|
|
198
|
+
return { outcome: 'present' };
|
|
199
|
+
}
|
|
200
|
+
const all = (deps.runs ?? (() => readReleaseRuns(null, undefined, { facts: true })))();
|
|
201
|
+
if (all === 'unavailable') {
|
|
202
|
+
try { debug.log('away.release-watch', 'runs-unavailable', {}); } catch { /* fail-open */ }
|
|
203
|
+
return { outcome: 'unavailable' };
|
|
204
|
+
}
|
|
205
|
+
const now = (deps.now ?? Date.now)();
|
|
206
|
+
const nowIso = new Date(now).toISOString();
|
|
207
|
+
const state = (deps.readState ?? readWatchState)();
|
|
208
|
+
const runs = followedRuns(all, now);
|
|
209
|
+
const lines: string[] = [];
|
|
210
|
+
let urgent = false;
|
|
211
|
+
const next: WatchState = { runs: {} };
|
|
212
|
+
let changed = false;
|
|
213
|
+
for (const run of [...runs].reverse()) {
|
|
214
|
+
const prev = state.runs[run.runId];
|
|
215
|
+
const diff = diffRun(prev, run, now);
|
|
216
|
+
lines.push(...diff.lines);
|
|
217
|
+
urgent ||= diff.urgent;
|
|
218
|
+
changed ||= diff.changed;
|
|
219
|
+
next.runs[run.runId] = diff.lines.length ? markOf(run, nowIso) : { ...markOf(run, prev?.sentAt ?? nowIso) };
|
|
220
|
+
}
|
|
221
|
+
if (Object.keys(state.runs).some((id) => !next.runs[id])) changed = true;
|
|
222
|
+
if (!lines.length) {
|
|
223
|
+
if (changed) writeState(next);
|
|
224
|
+
return { outcome: 'quiet', followed: runs.length };
|
|
225
|
+
}
|
|
226
|
+
const text = composeMessage(lines);
|
|
227
|
+
const ok = urgent ? (deps.sendUrgent ?? defaultSendUrgent)(text) : (deps.send ?? ((t: string) => sendOutbound(t, 'op-report')))(text);
|
|
228
|
+
try { debug.log('away.release-watch', ok ? 'sent' : 'send-failed', { followed: runs.length, lines: lines.length, urgent }); } catch { /* fail-open */ }
|
|
229
|
+
// 못 보냈으면 기준을 옮기지 않는다 — 다음 틱이 같은 줄을 다시 시도한다(중복 대신 누락을 막는 쪽).
|
|
230
|
+
if (ok) writeState(next);
|
|
231
|
+
return { outcome: ok ? 'sent' : 'send-failed', followed: runs.length, lines: lines.length, urgent };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** /release · away status 가 쓰는 «지금» 요약 — 가장 최근 런 하나. */
|
|
235
|
+
export function releaseNowText(runs: ReleaseRunView[] | 'unavailable', now: number = Date.now()): string {
|
|
236
|
+
if (runs === 'unavailable') return '발행 런 원장을 못 읽었다(권한·디스크) — 「없다」가 아니다';
|
|
237
|
+
const run = runs[0];
|
|
238
|
+
if (!run) return `발행 런 기록 없음\n↗ ${RELEASE_SCREEN_URL}`;
|
|
239
|
+
const ended = endedNodes(run);
|
|
240
|
+
const running = runningNode(run);
|
|
241
|
+
const total = run.path.filter((step) => !TERMINAL.has(step)).length || run.nodes.length;
|
|
242
|
+
const head = `📦 ${label(run)} · ${run.status} · 끝난 노드 ${ended.length}${total ? `/${total}` : ''}`;
|
|
243
|
+
const lines = [head];
|
|
244
|
+
if (running) {
|
|
245
|
+
const mins = minutesSince(running.startedAt, now);
|
|
246
|
+
lines.push(`지금: ${running.nodeId}${mins !== null ? ` (${mins}분째)` : ''}`);
|
|
247
|
+
}
|
|
248
|
+
const last = ended.at(-1);
|
|
249
|
+
if (last) lines.push(`마지막: ${nodeLine(last)}`);
|
|
250
|
+
if (BLOCKED.has(run.status)) lines.push(blockedLine(run));
|
|
251
|
+
const started = minutesSince(run.startedAt, now);
|
|
252
|
+
if (started !== null) lines.push(`시작 ${Math.floor(started / 60)}시간 ${started % 60}분 전`);
|
|
253
|
+
lines.push(`↗ ${RELEASE_SCREEN_URL}`);
|
|
254
|
+
return lines.join('\n');
|
|
255
|
+
}
|
package/src/chat/index.ts
CHANGED
|
@@ -434,7 +434,7 @@ export interface SlashCommand {
|
|
|
434
434
|
export const SLASH_COMMANDS: SlashCommand[] = [
|
|
435
435
|
{ name: 'help', aliases: ['?'], description: '명령과 키 보기' },
|
|
436
436
|
{ name: 'resume', aliases: [], description: '지난 대화 이어 가기 · /resume <id 앞자리>' },
|
|
437
|
-
{ name: 'model', aliases: ['m'], description: '모델 바꾸기 — /model
|
|
437
|
+
{ name: 'model', aliases: ['m'], description: '모델 바꾸기 — /model 빠름|보통|깊음 (코드명도 됨)', subcommands: ['빠름', '보통', '깊음', 'codex', 'terra', 'sol', 'luna', 'opus', 'sonnet', 'grok'] },
|
|
438
438
|
{ name: 'clear', aliases: ['cls'], description: '대화 기록 지우기' },
|
|
439
439
|
{ name: 'status', aliases: ['st'], description: '지금 상태 — 모델 · 연결 · 계정' },
|
|
440
440
|
{ name: 'now', aliases: [], description: '현재 맥락과 출처 표 — /now [주제]' },
|
|
@@ -448,6 +448,7 @@ export const SLASH_COMMANDS: SlashCommand[] = [
|
|
|
448
448
|
// and the PWA `/design-check` panel. Listed (not baselined as hidden) on
|
|
449
449
|
// purpose: a surface nobody can discover is not a surface.
|
|
450
450
|
{ name: 'design', aliases: ['design-check'], description: 'Design rulebooks this repository declares · /design [--declared] · /design pick [number|id] to choose a design direction', subcommands: ['--declared', 'pick'] },
|
|
451
|
+
{ name: 'decide', aliases: ['dec'], description: '열린 결정 카드 보기·답하기 — /decide [번호] [선택지 키] [확인]' },
|
|
451
452
|
{ name: 'provider', aliases: ['p'], description: 'LLM providers — /provider (list) · next (cycle, also Alt+M / pill click) · use <name> · pick (visual picker) · reset', subcommands: ['next', 'use', 'pick', 'picker', 'menu', 'reset', 'list'] },
|
|
452
453
|
{ name: 'reasoning', aliases: ['r', 'think'], description: 'Reasoning level (codex effort + summary, anthropic extended-thinking budget) — /reasoning [off|low|medium|high|xhigh] (cycle when no arg · xhigh 는 모델 상한이 xhigh 이상일 때만 wire 에 실린다, 아니면 high 로 깎인다)', subcommands: ['off', 'low', 'medium', 'high', 'xhigh'] },
|
|
453
454
|
{ name: 'local', aliases: ['ll'], description: 'Local OpenAI-compatible LLM — ping / models / test / use', subcommands: ['ping', 'models', 'test', 'use', 'status'] },
|
|
@@ -1,40 +1,3 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
// 거부 목록으로 변형을 다 막을 수는 없다 — 오분류 비용은 «확인해 볼까요?» 한 줄로 막힌다(도구가 안 실릴 뿐 대화는 이어진다).
|
|
5
|
-
// 판정 표본 = src/chat/short-question.test.ts 의 고정 표 20개(⊕ 리뷰가 든 변형).
|
|
6
|
-
|
|
7
|
-
export interface ShortQuestionContext {
|
|
8
|
-
hasAttachments?: boolean;
|
|
9
|
-
}
|
|
10
|
-
export interface ShortQuestionVerdict {
|
|
11
|
-
fast: boolean;
|
|
12
|
-
reason: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const GREETING = /^(?:안녕(?:하세요|하십니까)?|반가워(?:요)?|고마워(?:요)?|감사합니다|하이|hi|hello|hey|thanks|thank you)[!?.~\s]*$/i;
|
|
16
|
-
// «한 줄로» 같은 짧은 꼬리를 떼고 본다.
|
|
17
|
-
const TAIL = /\s*(?:한\s*줄로|짧게|간단히|briefly|in one line)[.!]?\s*$/i;
|
|
18
|
-
const KO_QUESTION_END = /(?:\?|까|니|나요|냐|야|어때|뭐|몇|언제|어디|누구|왜|인가|일까)[?.!~]*$/;
|
|
19
|
-
const EN_QUESTION = /^(?:what|why|how|who|where|when|which|is|are|does|do)\b.*\?$/i;
|
|
20
|
-
const REQUEST_KO = /(?:해\s*줘|해\s*주세요|줘(?:요)?(?=[?.!\s]|$)|주세요|줄래|할래|볼래|줄\s*수\s*있|돌려|실행|고쳐|만들어\s*줘|찾아|검색|확인해|열어|보내|올려|지워|써\s*줘|작성해|요약해|정리해|분석해)/;
|
|
21
|
-
const REQUEST_EN = /\b(?:please|can you|could you|would you|run|test|tests|fix|build|open|search|check|send|create|write|deploy|install|summarize)\b/i;
|
|
22
|
-
const TASK = /(?:작업|파일|폴더|디렉터리|레포|저장소|커밋|브랜치|\bPR\b|코드|스크립트|테스트|빌드|배포|설치|설정|명령어|로그|버그|이슈)/i;
|
|
23
|
-
const PATH_OR_CODE = /(?:https?:\/\/|www\.|[\w.-]+\/[\w./-]+|\.(?:ts|tsx|js|py|md|json|yaml|swift|kt)\b|[`{};=<>])/i;
|
|
24
|
-
const LIVE = /(?:오늘\s*(?:날짜|며칠|무슨\s*요일)|지금\s*몇\s*시|최신|뉴스|속보|주가|시세|환율|날씨|today'?s|latest|news|stock|weather|right now)/i;
|
|
25
|
-
|
|
26
|
-
export function classifyShortQuestion(text: string, ctx: ShortQuestionContext = {}): ShortQuestionVerdict {
|
|
27
|
-
const q = text.trim();
|
|
28
|
-
if (!q) return { fast: false, reason: 'empty' };
|
|
29
|
-
if (ctx.hasAttachments) return { fast: false, reason: 'attachment' };
|
|
30
|
-
if (/[\r\n\u2028\u2029]/.test(q)) return { fast: false, reason: 'multiline' };
|
|
31
|
-
if ([...q].length > 80) return { fast: false, reason: 'long' };
|
|
32
|
-
if (PATH_OR_CODE.test(q)) return { fast: false, reason: 'path-or-code' };
|
|
33
|
-
if (LIVE.test(q)) return { fast: false, reason: 'live' };
|
|
34
|
-
if (REQUEST_KO.test(q) || REQUEST_EN.test(q)) return { fast: false, reason: 'request' };
|
|
35
|
-
if (TASK.test(q)) return { fast: false, reason: 'task' };
|
|
36
|
-
if (GREETING.test(q)) return { fast: true, reason: 'greeting' };
|
|
37
|
-
const core = q.replace(TAIL, '').trim();
|
|
38
|
-
if (KO_QUESTION_END.test(core) || EN_QUESTION.test(core)) return { fast: true, reason: 'question' };
|
|
39
|
-
return { fast: false, reason: 'not-a-question' };
|
|
40
|
-
}
|
|
1
|
+
// The classifier lives in core-turn so the headless ACP path (core-turn-bridge) can use it without importing
|
|
2
|
+
// chat code (tui-client-headless-guard). Existing chat callers keep this path.
|
|
3
|
+
export * from '../core-turn/short-question.js';
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import { readPresence, writePresence, type Presence } from '../away/presence.js';
|
|
3
|
+
import { releaseNowText, releaseWatchTick, type TickDeps, type TickOutcome } from '../away/release-watch.js';
|
|
4
|
+
import { sendOutbound } from '../domains/outbound-alert.js';
|
|
5
|
+
import { readReleaseRuns } from '../nexus/api/ops-api.js';
|
|
6
|
+
|
|
7
|
+
export interface AwayCliDeps {
|
|
8
|
+
read?: () => Presence;
|
|
9
|
+
write?: (away: boolean, by: string) => Presence;
|
|
10
|
+
tick?: (deps?: TickDeps) => TickOutcome;
|
|
11
|
+
now?: () => string;
|
|
12
|
+
send?: (text: string) => boolean;
|
|
13
|
+
output?: (line: string) => void;
|
|
14
|
+
setExitCode?: (code: number) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** AWAY-MODE-1 — `elanous away on|off|status|tick`. 켜고 끄는 순간 텔레그램에 한 줄을 보내 «받는 길이 산다»를 바로 본다. */
|
|
18
|
+
export function registerAwayCommand(program: Command, deps: AwayCliDeps = {}): void {
|
|
19
|
+
const read = deps.read ?? (() => readPresence());
|
|
20
|
+
const write = deps.write ?? ((away: boolean, by: string) => writePresence(away, by));
|
|
21
|
+
const tick = deps.tick ?? releaseWatchTick;
|
|
22
|
+
const now = deps.now ?? (() => releaseNowText(readReleaseRuns(null, undefined, { facts: true })));
|
|
23
|
+
const send = deps.send ?? ((text: string) => sendOutbound(text, 'op-report'));
|
|
24
|
+
const output = deps.output ?? ((line: string) => console.log(line));
|
|
25
|
+
const setExitCode = deps.setExitCode ?? ((code: number) => { process.exitCode = code; });
|
|
26
|
+
|
|
27
|
+
const away = program.command('away').description('외출 모드 — 켜면 발행 현황을 텔레그램으로 따라간다(노드 전이 · 막힘 즉시 · 60분 생존 줄)');
|
|
28
|
+
|
|
29
|
+
away.command('on')
|
|
30
|
+
.description('외출 모드 켜기 — 지금 발행 현황 한 통을 보내고 그 뒤 전이를 따라간다')
|
|
31
|
+
.action(() => {
|
|
32
|
+
write(true, 'cli');
|
|
33
|
+
const ok = send(`🚶 외출 모드 켬 — 발행 현황을 여기로 보낸다(끄기: /away off)\n${now()}`);
|
|
34
|
+
const result = tick();
|
|
35
|
+
output(`외출 모드 켬 · 확인 한 통 ${ok ? '보냄' : '못 보냄'} · 첫 확인 ${result.outcome}`);
|
|
36
|
+
if (!ok) output('⚠️ 텔레그램 발송 실패 — elanous logs --category outbound.send 로 확인');
|
|
37
|
+
setExitCode(ok ? 0 : 1);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
away.command('off')
|
|
41
|
+
.description('외출 모드 끄기')
|
|
42
|
+
.action(() => {
|
|
43
|
+
write(false, 'cli');
|
|
44
|
+
tick();
|
|
45
|
+
const ok = send('🏠 외출 모드 끔 — 발행 알림은 평소대로');
|
|
46
|
+
output(`외출 모드 끔 · 확인 한 통 ${ok ? '보냄' : '못 보냄'}`);
|
|
47
|
+
setExitCode(ok ? 0 : 1);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
away.command('status')
|
|
51
|
+
.description('지금 외출 모드인가 ⊕ 발행 현황')
|
|
52
|
+
.option('--json', 'JSON 한 줄')
|
|
53
|
+
.action((opts: { json?: boolean }) => {
|
|
54
|
+
const presence = read();
|
|
55
|
+
if (opts.json) { output(JSON.stringify(presence)); return; }
|
|
56
|
+
output(`${presence.away ? '🚶 외출 중' : '🏠 자리에 있음'}${presence.since ? ` · ${presence.since}` : ''}${presence.by ? ` · ${presence.by}` : ''}`);
|
|
57
|
+
output(now());
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
away.command('tick')
|
|
61
|
+
.description('발행 현황을 한 번 보고 새 전이만 보낸다(주기 실행용 · 자리에 있으면 아무것도 안 함)')
|
|
62
|
+
.option('--json', 'JSON 한 줄')
|
|
63
|
+
.action((opts: { json?: boolean }) => {
|
|
64
|
+
const result = tick();
|
|
65
|
+
output(opts.json ? JSON.stringify(result) : `away tick: ${result.outcome}`);
|
|
66
|
+
setExitCode(result.outcome === 'send-failed' || result.outcome === 'unavailable' ? 1 : 0);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import type { Database } from 'bun:sqlite';
|
|
3
|
+
import type { GuardianPlan } from '../domains/guardian-tick.js';
|
|
4
|
+
import { getUserConfig } from '../user-config.js';
|
|
5
|
+
import { writeStdoutJson } from './stdout-json.js';
|
|
6
|
+
|
|
7
|
+
// Domain modules (schedules.db · schedule-runner → node-cron) load lazily inside the actions,
|
|
8
|
+
// so registering `guardian` adds no static dependency to the CLI boot path.
|
|
9
|
+
async function guardianPlan(): Promise<{ db: Database; plan: GuardianPlan }> {
|
|
10
|
+
const { listSchedules, openSchedulesDb } = await import('../domains/schedule-registry.js');
|
|
11
|
+
const db = openSchedulesDb();
|
|
12
|
+
try {
|
|
13
|
+
return { db, plan: { jobs: listSchedules(db) } };
|
|
14
|
+
} catch (error) {
|
|
15
|
+
db.close();
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function registerGuardianCommands(program: Command): void {
|
|
21
|
+
const guardian = program.command('guardian').description('크론 수호자 그림자 관측(기존 크론·알림 무변경)');
|
|
22
|
+
guardian.command('tick').description('현재 시각에 발화했을 잡을 관측(잡은 실행하지 않음)')
|
|
23
|
+
.action(async () => {
|
|
24
|
+
const { guardianTick } = await import('../domains/guardian-tick.js');
|
|
25
|
+
const { db, plan } = await guardianPlan();
|
|
26
|
+
try {
|
|
27
|
+
const cfg = getUserConfig();
|
|
28
|
+
// Live activation (and crontab retirement) is a separate operational change.
|
|
29
|
+
if (cfg.guardian?.mode === 'live') throw new Error('guardian live activation is outside GUARD-ONE-a');
|
|
30
|
+
const result = await guardianTick(new Date(), plan, { config: cfg });
|
|
31
|
+
await writeStdoutJson(JSON.stringify(result, null, 2) + '\n');
|
|
32
|
+
} finally { db.close(); }
|
|
33
|
+
});
|
|
34
|
+
guardian.command('compare').description('하루의 예정 발화와 기록된 실제 발화 비교')
|
|
35
|
+
.requiredOption('--day <YYYY-MM-DD>', '비교할 달력 날짜')
|
|
36
|
+
.option('--json', 'JSON 출력')
|
|
37
|
+
.action(async (opts: { day: string; json?: boolean }) => {
|
|
38
|
+
const { guardianShadowDay } = await import('../domains/guardian-tick.js');
|
|
39
|
+
const { db, plan } = await guardianPlan();
|
|
40
|
+
try {
|
|
41
|
+
const result = guardianShadowDay(opts.day, { db, plan });
|
|
42
|
+
if (opts.json) await writeStdoutJson(JSON.stringify(result, null, 2) + '\n');
|
|
43
|
+
else {
|
|
44
|
+
console.log(`guardian ${result.day}: ${result.same ? 'same' : 'diff'} (현재 레지스트리 cron·활성 기준 재계산 · alertsActual=null: 발송 수 미측정)`);
|
|
45
|
+
for (const row of result.jobs) console.log(` ${row.job}: would=${row.would} actual=${row.actual} alertsActual=${row.alertsActual ?? '미측정'}`);
|
|
46
|
+
}
|
|
47
|
+
} finally { db.close(); }
|
|
48
|
+
});
|
|
49
|
+
}
|
package/src/cli/intake-cli.ts
CHANGED
|
@@ -46,7 +46,8 @@ export async function runIntakeCandidatesCli(
|
|
|
46
46
|
const limit = opts.limit === undefined ? undefined : parseCandidatesLimit(opts.limit);
|
|
47
47
|
const directives = deps.directives ?? (async () => {
|
|
48
48
|
const [{ Database }, { existsSync }, { directiveDbPath }] = await Promise.all([
|
|
49
|
-
|
|
49
|
+
// The directive index is excluded from the public export; load it at run time only (proact-meter · know-find pattern).
|
|
50
|
+
import('bun:sqlite'), import('node:fs'), import(['..', 'directives', 'directive-index.js'].join('/')) as Promise<{ directiveDbPath: () => string }>,
|
|
50
51
|
]);
|
|
51
52
|
const path = directiveDbPath();
|
|
52
53
|
if (!existsSync(path)) return [];
|
|
@@ -144,14 +145,15 @@ export async function runIntakeToTasksCli(
|
|
|
144
145
|
|
|
145
146
|
/** `--telegram` 다이제스트. 렌즈는 digest 를 만들기 전에 돈다. 렌즈 실패는 보고를 막지 않는다. */
|
|
146
147
|
export async function runIntakeDigestCli(
|
|
147
|
-
opts: { day?: string; json?: boolean; telegram?: boolean; vault?: string; note?: string },
|
|
148
|
+
opts: { day?: string; json?: boolean; telegram?: boolean; vault?: string; note?: string; seat?: string },
|
|
148
149
|
deps: IntakeDigestCliDeps = {},
|
|
149
150
|
): Promise<{ sent?: boolean; empty?: boolean }> {
|
|
150
|
-
const { buildIntakeDigest, renderDigestMarkdown, renderDigestTelegram } = await import('../intake-plane/digest.js');
|
|
151
|
+
const { buildIntakeDigest, recordSeatBriefed, renderDigestMarkdown, renderDigestTelegram } = await import('../intake-plane/digest.js');
|
|
151
152
|
const { effectiveInstanceRoot } = await import('../instance/resolve.js');
|
|
152
153
|
const root = deps.root ?? effectiveInstanceRoot();
|
|
153
154
|
const day = opts.day ?? new Date(Date.now() + 9 * 3600_000).toISOString().slice(0, 10);
|
|
154
|
-
|
|
155
|
+
// A seat briefing ignores lens rows (digest drops them), so it does not pay for a lens pass over other seats' items.
|
|
156
|
+
if (opts.telegram && opts.seat === undefined) {
|
|
155
157
|
const annotate = deps.annotateLens ?? (async (lensRoot, lensDay) => {
|
|
156
158
|
const { annotateIntakeLens } = await import('../intake-plane/lens.js');
|
|
157
159
|
return annotateIntakeLens(lensRoot, lensDay);
|
|
@@ -159,9 +161,10 @@ export async function runIntakeDigestCli(
|
|
|
159
161
|
try { await annotate(root, day); }
|
|
160
162
|
catch (error) { debug.log('intake.lens', 'annotate-failed', { day, reason: error instanceof Error ? error.message : String(error) }); }
|
|
161
163
|
}
|
|
162
|
-
const
|
|
164
|
+
const seat = opts.seat === undefined ? undefined : (await import('../intake-plane/intake-sources.js')).canonicalSeat(opts.seat);
|
|
165
|
+
const d = (deps.buildDigest ?? buildIntakeDigest)(root, day, undefined, undefined, seat);
|
|
163
166
|
if (opts.telegram) {
|
|
164
|
-
if (!d.absorbed.length && !d.news?.length) { console.log(`텔레그램: ${day} 흡수 0 — 보내지 않음`); return { empty: true }; }
|
|
167
|
+
if (!seat && !d.absorbed.length && !d.news?.length) { console.log(`텔레그램: ${day} 흡수 0 — 보내지 않음`); return { empty: true }; }
|
|
165
168
|
const text = renderDigestTelegram(d, { ...(opts.vault ? { vaultRoot: opts.vault } : {}), ...(opts.note ? { notePath: opts.note } : {}) });
|
|
166
169
|
const sent = deps.sendTelegram
|
|
167
170
|
? await deps.sendTelegram(text)
|
|
@@ -171,6 +174,8 @@ export async function runIntakeDigestCli(
|
|
|
171
174
|
return sendTelegramReport(getUserConfig(), text, { markdown: true, kind: 'intake' });
|
|
172
175
|
})();
|
|
173
176
|
debug.log('intake.digest', 'telegram', { day, absorbed: d.absorbed.length, goals: d.goals.length, sent });
|
|
177
|
+
if (seat && sent) recordSeatBriefed(root, seat, day, d.absorbed.map((entry) => entry.id));
|
|
178
|
+
if (seat === 'user') debug.log('watch.default', 'brief', { topics: (await import('../intake-plane/intake-sources.js')).listSources({ seat }, root).length, items: d.absorbed.length, sent });
|
|
174
179
|
console.log(sent ? `텔레그램 보고 채널로 보냈다 (${day} · 흡수 ${d.absorbed.length} · 골 후보 ${d.goals.length})` : '텔레그램 보고 채널 설정이 없다(telegram.reportChannel) — 보내지 않음');
|
|
175
180
|
if (!sent) process.exitCode = 3;
|
|
176
181
|
return { sent };
|
|
@@ -397,9 +402,10 @@ export function registerIntakeCommands(program: Command, candidateDeps: IntakeCa
|
|
|
397
402
|
.option('--day <YYYY-MM-DD>', 'KST 날짜 (기본 오늘)')
|
|
398
403
|
.option('--json', '구조화 출력')
|
|
399
404
|
.option('--telegram', '텔레그램 보고 채널(telegram.reportChannel)로 짧은 판을 보낸다')
|
|
405
|
+
.option('--seat <id>', '자리 id (user 아침 브리핑)')
|
|
400
406
|
.option('--vault <root>', '옵시디언 볼트 뿌리 — 텔레그램 판에 노트 열기 주소를 싣는다')
|
|
401
407
|
.option('--note <path>', '열기 주소가 가리킬 노트(그날 트렌드 노트)')
|
|
402
|
-
.action(async (opts: { day?: string; json?: boolean; telegram?: boolean; vault?: string; note?: string }) => {
|
|
408
|
+
.action(async (opts: { day?: string; json?: boolean; telegram?: boolean; vault?: string; note?: string; seat?: string }) => {
|
|
403
409
|
await runIntakeDigestCli(opts);
|
|
404
410
|
});
|
|
405
411
|
|
package/src/cli/pod-cli.ts
CHANGED
|
@@ -71,7 +71,7 @@ export function registerPodCommands(program: Command, deps: PodCliDeps = {}): vo
|
|
|
71
71
|
const current = spec ? null : kubectl(['config', 'current-context']);
|
|
72
72
|
const context = spec ?? (current?.status === 0 ? current.stdout.trim() : '');
|
|
73
73
|
const members = context ? parsePodPool(context) : [];
|
|
74
|
-
const measure: PoolLeaseMeasure = members.length ? measurePoolLease(members, { kubectl, dns: (context) => (deps.dns ?? probePoolDns)(context, kubectl) }) : { members: [] };
|
|
74
|
+
const measure: PoolLeaseMeasure = members.length ? measurePoolLease(members, { kubectl, dns: (context) => (deps.dns ?? probePoolDns)(context, kubectl), measureUsage: true }) : { members: [] };
|
|
75
75
|
let accounts: number | null = null;
|
|
76
76
|
let accountReason: string | null = null;
|
|
77
77
|
try { accounts = (deps.accounts ?? (() => listCodexAccountsInStore().length))(); }
|
|
@@ -96,6 +96,10 @@ export function registerPodCommands(program: Command, deps: PodCliDeps = {}): vo
|
|
|
96
96
|
}
|
|
97
97
|
io.log(`실측 점유: ${measure.members.map((m) => `${m.context} ${m.running === null || m.pending === null ? '못 쟀다' : m.running + m.pending}/${m.capacity}`).join(' · ') || '?'}`);
|
|
98
98
|
io.log(`권장 지금 ${decision.recommended ?? '?'} 개 더 (limitedBy=${decision.limitedBy ?? 'unknown'})${decision.reason ? ` · ${decision.reason}` : ''}`);
|
|
99
|
+
if (decision.admission) {
|
|
100
|
+
const g = (v: number | null) => v === null ? '?' : `${(v / 1024 ** 3).toFixed(1)}Gi`;
|
|
101
|
+
io.log(`골당 입장 ${g(decision.admission.admitBytes)} (실사용 표본 ${decision.admission.samples} · p95 ${g(decision.admission.p95Bytes)}${decision.admission.fallback ? ` · 폴백: ${decision.admission.fallback}` : ''})`);
|
|
102
|
+
}
|
|
99
103
|
io.log(`예약(저작 중) ${host.reserved} · 대기 Job ${waitingJobs ?? '?'} · 실행 ${decision.running ?? '?'}`);
|
|
100
104
|
io.log(`임대 없는 실행 ${decision.unleasedRunning ?? '?'}`);
|
|
101
105
|
io.log(`capacity: ${decision.capacitySlots ?? '?'} 칸 (상한 ${members.reduce((sum, m) => sum + m.capacity, 0)} − Running ${decision.running ?? '?'} − Pending ${decision.pending ?? '?'}; 권장 수에서 건강한 멤버의 임대 없는 실행 차감)`);
|
package/src/cli/pty-drive-cli.ts
CHANGED
|
@@ -51,6 +51,18 @@ export interface TuiWorkdirDecision {
|
|
|
51
51
|
readonly message?: string;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/** 「명시 격리」 판정 — `--test`(명시 플래그 층) 이거나 `--isolated-root` 를 줬을 때만 참.
|
|
55
|
+
*
|
|
56
|
+
* ⭐ 이 식은 «한 벌»이다 — `runPtyDriveInner`(거부)와 `src/index.ts` dev 의 자동 워크트리
|
|
57
|
+
* 판정(`decideAutoWorktree`)이 같이 쓴다. 둘이 갈리면 「자동으로 만들었어야 하는데 거부」나
|
|
58
|
+
* 「거부했어야 하는데 자동」이 조용히 생긴다. 3층(트리 파생)은 «명시»가 아니라서 빠진다. */
|
|
59
|
+
export function isExplicitlyIsolated(
|
|
60
|
+
resolution: { readonly kind: string; readonly layer: string },
|
|
61
|
+
isolatedRoot?: string,
|
|
62
|
+
): boolean {
|
|
63
|
+
return (resolution.kind === 'test' && resolution.layer === 'explicit-flag') || Boolean(isolatedRoot);
|
|
64
|
+
}
|
|
65
|
+
|
|
54
66
|
/** Reject the unsafe combination before a TUI child can inherit the caller's tree. */
|
|
55
67
|
export function decideTuiWorkdir(isolated: boolean, cwd: string | undefined): TuiWorkdirDecision {
|
|
56
68
|
const workdirProvided = Boolean(cwd?.trim());
|
|
@@ -62,7 +74,7 @@ export function decideTuiWorkdir(isolated: boolean, cwd: string | undefined): Tu
|
|
|
62
74
|
// ⛔⭐ 거부 문면은 **무엇을 주면 되는지**까지 말한다(교차 세션 제안 · 2026-08-02).
|
|
63
75
|
// 상대가 헤맨 이유가 *"어디에 config 를 쓰나"* 였다 — 실효 자리를 확인하는 한 줄을 함께 준다.
|
|
64
76
|
...(rejected
|
|
65
|
-
? { message: '격리 우주에서는 작업 디렉토리를 명시해야 한다 — `--cwd <worktree-path>` 를 주십시오. '
|
|
77
|
+
? { message: '격리 우주에서는 작업 디렉토리를 명시해야 한다 — `--worktree` 를 주거나 `--cwd <worktree-path>` 를 주십시오. '
|
|
66
78
|
+ '(안 주면 명령을 친 트리가 자식의 쓰기 허용 구역이 됩니다.) '
|
|
67
79
|
+ '지금 어느 우주인지는 `elanous where` 로 확인할 수 있습니다.' }
|
|
68
80
|
: {}),
|
|
@@ -499,8 +511,7 @@ async function runPtyDriveInner(opts: PtyDriveOpts): Promise<{ exitCode: number
|
|
|
499
511
|
const { effectiveInstanceRoot } = await import('../instance/resolve.js');
|
|
500
512
|
const scopeRoot = effectiveInstanceRoot();
|
|
501
513
|
const resolution = resolveCurrentInstance();
|
|
502
|
-
const explicitlyIsolated = (resolution
|
|
503
|
-
|| Boolean(opts.isolatedRoot);
|
|
514
|
+
const explicitlyIsolated = isExplicitlyIsolated(resolution, opts.isolatedRoot);
|
|
504
515
|
const workdirDecision = decideTuiWorkdir(explicitlyIsolated, opts.cwd);
|
|
505
516
|
debug.log('pty.drive', 'tui-workdir-decision', workdirDecision);
|
|
506
517
|
if (workdirDecision.rejected) throw new Error(workdirDecision.message);
|