elanous 0.2.17 → 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/J5H7BvqgSI8DUjtRRcA_S/_buildManifest.js +1 -0
- 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 -0
- package/apps/pwa/out/decisions/index.txt +30 -0
- 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 +2 -1
- package/samples/workflows/templates/approval-gated-deploy.yaml +30 -0
- package/samples/workflows/templates/chat-driven-research.yaml +35 -0
- package/samples/workflows/templates/discord-listen-and-respond.yaml +29 -0
- package/samples/workflows/templates/http-poll-then-alert.yaml +30 -0
- package/samples/workflows/templates/manual-quickstart.yaml +21 -0
- package/samples/workflows/templates/scheduled-daily-digest.yaml +30 -0
- package/samples/workflows/templates/webhook-deploy-hook.yaml +27 -0
- package/scripts/ci-public-export-import-gate.ts +7 -2
- package/scripts/ci-typecheck-changed.ts +21 -6
- package/scripts/coord-channel-watch.sh +2 -2
- package/scripts/discord-seat-probe.ts +6 -3
- package/scripts/docs/generate-cli-anatomy.ts +162 -0
- package/scripts/goals/authoring-metrics.ts +73 -0
- package/scripts/landing-heal/must-fix-match.ts +7 -7
- package/scripts/landing-heal/run-mission.ts +3 -2
- 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/public-export-test-run.ts +37 -2
- package/scripts/release-loop/checklist-gate-node.ts +13 -11
- package/scripts/release-loop/cut-branch.ts +2 -1
- package/scripts/release-loop/cutoff.ts +4 -2
- package/scripts/release-loop/docs-land-node.ts +8 -1
- package/scripts/release-loop/docs-node.ts +1 -1
- package/scripts/release-loop/gate-diff.ts +18 -0
- package/scripts/release-loop/gate-node.ts +436 -59
- package/scripts/release-loop/gate-partial.ts +106 -0
- package/scripts/release-loop/gate-timing.ts +120 -0
- package/scripts/release-loop/node-verdict.ts +2 -1
- package/scripts/release-loop/npm-publish-node.ts +11 -7
- package/scripts/release-loop/ops-upgrade-node.ts +8 -1
- package/scripts/release-loop/preflight.ts +246 -0
- package/scripts/release-loop/publish-node.ts +8 -1
- package/scripts/release-loop/release-readiness.ts +11 -6
- package/scripts/release-loop/release-version.ts +48 -0
- package/scripts/release-loop/resume-release.ts +174 -0
- package/scripts/release-loop/shard-plan.ts +65 -0
- package/scripts/release-loop/unattended-release.ts +45 -7
- package/scripts/release-loop/version-node.ts +145 -40
- package/scripts/rhythm/daily.ts +25 -4
- package/scripts/skills/public-check.ts +62 -12
- package/scripts/test-deterministic.ts +51 -33
- package/scripts/test-diet/effectiveness.ts +62 -1
- package/scripts/test-diet/lib.ts +1 -1
- package/scripts/test-diet/node.ts +8 -1
- package/scripts/video/scene-table.ts +159 -0
- package/src/acp/core-turn-bridge.ts +39 -2
- package/src/agent-mission/review-loop.ts +4 -3
- package/src/agent-substrate/pr-comment-meta.ts +20 -0
- package/src/autopilot/build/llm-conflict-merge.ts +28 -8
- package/src/away/presence.ts +40 -0
- package/src/away/release-watch.ts +255 -0
- package/src/briefing/brief-items.ts +86 -17
- package/src/chat/index.ts +2 -1
- package/src/chat/short-question.ts +3 -40
- package/src/cli/autopilot-cli.ts +30 -11
- package/src/cli/away-cli.ts +68 -0
- package/src/cli/brief-cli.ts +36 -2
- package/src/cli/freeze-cli.ts +3 -2
- package/src/cli/guardian-cli.ts +49 -0
- package/src/cli/intake-cli.ts +85 -7
- package/src/cli/log-event-names.ts +1 -0
- package/src/cli/notify-cli.ts +75 -0
- package/src/cli/pod-cli.ts +5 -1
- package/src/cli/provider-cli.ts +12 -2
- package/src/cli/pty-drive-cli.ts +14 -3
- package/src/cli/release-cli.ts +128 -19
- package/src/cli/setup-cli.ts +31 -1
- package/src/cli/tasks-cli.ts +403 -4
- package/src/context-bus/condense.ts +19 -3
- package/src/context-bus/context-day.ts +77 -24
- package/src/context-bus/context-first.ts +33 -0
- 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/discord-self-message.ts +20 -2
- package/src/discord-slash-wire.ts +1 -0
- package/src/domains/guardian-tick.ts +138 -0
- package/src/domains/outbound-alert.ts +244 -40
- package/src/domains/outbound-log-probe.ts +16 -1
- package/src/domains/release-tool.ts +2 -2
- 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/execution-loop/gates.ts +11 -2
- package/src/flow-loop/split.ts +8 -4
- package/src/git-fs/worktree.ts +23 -3
- package/src/graph-runner/runner.ts +25 -5
- package/src/harness/harness-cli-command.ts +12 -2
- package/src/harness/harness-queue.ts +388 -33
- package/src/harness/harness-salvage-cli.ts +78 -0
- package/src/harness/harness-worktree-add.ts +68 -0
- package/src/harness/helper-scan.ts +10 -3
- package/src/harness/ledger-write-guard.ts +6 -16
- package/src/hq/hq.ts +6 -1
- package/src/hq/seats.ts +369 -0
- package/src/index.ts +185 -18
- 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/adopt-gate.ts +49 -0
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/collect-github.ts +57 -25
- package/src/intake-plane/digest.ts +39 -15
- package/src/intake-plane/discord-seat-work.ts +8 -2
- 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/intake-plane/requirement-funnel.ts +316 -0
- package/src/knowledge/know-find.ts +10 -1
- package/src/loops/budget.ts +36 -0
- package/src/loops/orchestrator/degradation.ts +57 -0
- package/src/loops/orchestrator/finish-rate.ts +106 -4
- package/src/loops/orchestrator/grid-place.ts +11 -0
- package/src/loops/orchestrator/tick.ts +450 -54
- package/src/maturity/feature-maturity.ts +8 -5
- package/src/maturity/tui-slash-maturity.ts +1 -0
- package/src/mss/logging/log-store.ts +3 -0
- package/src/nexus/api/decisions-route.ts +11 -1
- package/src/nexus/api/http-server.ts +16 -5
- package/src/nexus/api/loop-edges.ts +352 -23
- package/src/nexus/api/ops-api.ts +96 -28
- package/src/nexus/api/ops-seats-sources.ts +2 -2
- package/src/nexus/api/workflow-templates.ts +3 -0
- package/src/nexus/index.ts +7 -0
- package/src/oauth/codex.ts +38 -0
- package/src/onboarding/first-chat-gate.ts +29 -0
- package/src/release-loop/checklist.ts +106 -6
- package/src/release-loop/feature-store.ts +64 -16
- package/src/release-loop/landing-freeze.ts +6 -4
- package/src/release-loop/light-rc.ts +173 -0
- package/src/release-loop/placement.ts +64 -20
- package/src/release-loop/rubric.ts +136 -0
- package/src/seat-dispatch/seat-ask.ts +79 -16
- package/src/seat-loop/seat-loop.ts +12 -6
- package/src/self-dev/ask-launch-flow.ts +24 -3
- package/src/self-dev/ask-path-candidates.ts +68 -0
- package/src/self-dev/dev-cli.ts +36 -0
- package/src/self-dev/dev-pipeline.ts +4 -0
- package/src/self-dev/draft-triage-rules.ts +1 -1
- package/src/self-dev/orchestrate-cli.ts +22 -0
- package/src/self-dev/orchestrate.ts +1 -0
- package/src/self-dev/pr-preview.ts +179 -0
- package/src/self-dev/round-pr-supersede.ts +244 -0
- package/src/self-dev/run-supervisor.ts +14 -0
- package/src/self-dev/shard-run.ts +23 -7
- package/src/self-implement/follow-up-goals.ts +2 -2
- package/src/self-implement/gate-cli.ts +29 -8
- package/src/self-implement/gate-remote.ts +427 -0
- package/src/self-implement/goal-author-clarification.ts +88 -13
- package/src/self-implement/goal-author.ts +8 -3
- package/src/self-implement/goal-clarification-escalation.ts +2 -0
- package/src/self-implement/l8-merge-queue.ts +29 -5
- package/src/self-implement/orchestrator.ts +61 -13
- package/src/self-implement/prior-draft-findings.ts +4 -3
- package/src/self-implement/salvage-branches.ts +254 -0
- package/src/skills/skill-feedback.ts +76 -0
- package/src/task-agent/actions.ts +101 -7
- 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 +49 -5
- package/src/task-agent/live-moves.ts +176 -0
- package/src/task-agent/mission.ts +449 -0
- package/src/task-agent/shadow.ts +258 -0
- package/src/task-agent/task-hand.ts +337 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +64 -4
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +10 -0
- 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 +88 -20
- package/src/telegram-away-command.ts +38 -0
- package/src/telegram-commands.ts +11 -0
- package/src/telegram-loops-command.ts +1 -1
- package/src/telegram-report-log-probe.ts +21 -0
- package/src/telegram-report.ts +27 -3
- package/src/telegram.ts +5 -20
- package/src/tui-client/dashboard-session.ts +1 -0
- package/src/user-config.ts +196 -4
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-aac7332576c4d6fc.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/2196-4700653d12a44b40.js +0 -1
- package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.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/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-3565eea1f1495ba7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4614-21403aac730d4586.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +0 -2
- package/apps/pwa/out/_next/static/chunks/6877-fe94e76a5841ee9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.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-4ce1c42c8d89903f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
- package/apps/pwa/out/_next/static/chunks/8949-ecb4128a0858b911.js +0 -1
- package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +0 -1
- 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-3143b07a817d1da9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-17ba086ef82d339b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-7eb661ff23af7da2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-6e5c3a867557298b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-7110f732f45bf944.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-9a41d65355fc25b9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-5e77cd1802567c89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-41afec0b255ed2bb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-4187666132fc98c4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-09328d499162bae4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-29ff3a8f84f03b6a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-80ec7401bc83f511.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-ab93b240f687d69a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-fc7d86467291ae38.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-de43a186e5ba5683.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-8b08cafad27759c9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-0e2d6985fb42cd2f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-13e4108999f58d13.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e8296ee284aa1293.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-043a9111e74f6b24.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-23a4601ceb7f452e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-10d958605a77b3bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-166745e51ac0c7b0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a04260b8ccc968d4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-7a1d85e121065cc8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-12916918988ac97e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d8a3347a9d0c3f4a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-5a64cb5208c6a8fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-1407ec83ea64c1f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-1d824510df025d18.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-6e878ebbe1737d00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-7f49fe645d5132f7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-81a4e7f8dbfe74cb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-27d71f131acd9320.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-424659bacfb8cdd7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-fc2df261f8a2c7df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-09461366af82ef13.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5d4fef9ccd7d406b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ea90a8ac73ce2749.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-539cde99554be20f.js +0 -1
- package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +0 -1
- package/apps/pwa/out/_next/static/yRA5ur6DcPl0YHSRheH2O/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{yRA5ur6DcPl0YHSRheH2O → J5H7BvqgSI8DUjtRRcA_S}/_ssgManifest.js +0 -0
|
@@ -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
|
+
}
|
|
@@ -4,6 +4,7 @@ import { dirname, join } from 'node:path';
|
|
|
4
4
|
import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
5
5
|
import { debug } from '../debug/log.js';
|
|
6
6
|
import { dateKey } from '../time/format.js';
|
|
7
|
+
import { readRequirementFunnel, type RequirementFunnelResult } from '../intake-plane/requirement-funnel.js';
|
|
7
8
|
|
|
8
9
|
export const BRIEF_DOMAINS = ['운영', '판', '흡수', '시장', '행정', '코나투스'] as const;
|
|
9
10
|
export const BRIEF_PRIORITIES = ['P0', 'P1', 'P2'] as const;
|
|
@@ -41,6 +42,7 @@ export interface BriefItemsOptions {
|
|
|
41
42
|
stateDir?: string;
|
|
42
43
|
now?: () => Date;
|
|
43
44
|
log?: (category: string, event: string, data?: unknown) => void;
|
|
45
|
+
requirementFunnel?: (now: Date) => RequirementFunnelResult;
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
export class BriefItemsInputError extends Error {
|
|
@@ -89,8 +91,15 @@ function needsDecision(item: BriefItem, today: string): boolean {
|
|
|
89
91
|
* duplicate claims dropped (same normalized sentence, most urgent kept).
|
|
90
92
|
*/
|
|
91
93
|
export function composeBriefMarkdown(items: readonly BriefItem[], slot: BriefSlot, now: Date): string {
|
|
94
|
+
return composeBriefPage(items, slot, now).markdown;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The ids are exactly the rows represented by the composed page, after deduplication. */
|
|
98
|
+
export function composeBriefPage(items: readonly BriefItem[], slot: BriefSlot, now: Date): { markdown: string; ids: number[] } {
|
|
92
99
|
const today = dateKey(now, { timeZone: KST });
|
|
93
|
-
|
|
100
|
+
// A duplicate left out of a delivered page stays unsent in the ledger, but must not reappear on a later page.
|
|
101
|
+
const deliveredClaims = new Set(items.filter(item => item.sent_at !== null).map(item => normalizeClaim(item.text)));
|
|
102
|
+
const eligible = items.filter(item => item.sent_at === null && beforeSlot(item.created_at, slot, now) && !deliveredClaims.has(normalizeClaim(item.text)));
|
|
94
103
|
// Duplicate claims keep the copy that needs a decision today (P0 or due today), then the higher priority, then the earliest (ACP must-fix).
|
|
95
104
|
const rank = (item: BriefItem) => [needsDecision(item, today) ? 0 : 1, BRIEF_PRIORITIES.indexOf(item.priority), item.id];
|
|
96
105
|
const better = (a: BriefItem, b: BriefItem) => { const x = rank(a), y = rank(b); return x[0]! - y[0]! || x[1]! - y[1]! || x[2]! - y[2]!; };
|
|
@@ -117,7 +126,7 @@ export function composeBriefMarkdown(items: readonly BriefItem[], slot: BriefSlo
|
|
|
117
126
|
for (const item of group.sort((a, b) => a.id - b.id)) lines.push(itemLine(item));
|
|
118
127
|
lines.push('');
|
|
119
128
|
}
|
|
120
|
-
return `${lines.join('\n').trimEnd()}\n
|
|
129
|
+
return { markdown: `${lines.join('\n').trimEnd()}\n`, ids: unique.map(item => item.id) };
|
|
121
130
|
}
|
|
122
131
|
|
|
123
132
|
function itemLine(item: BriefItem): string {
|
|
@@ -166,11 +175,13 @@ export class BriefItemsLedger {
|
|
|
166
175
|
readonly path: string;
|
|
167
176
|
private readonly now: () => Date;
|
|
168
177
|
private readonly log: (category: string, event: string, data?: unknown) => void;
|
|
178
|
+
private readonly requirementFunnel: (now: Date) => RequirementFunnelResult;
|
|
169
179
|
|
|
170
180
|
constructor(options: BriefItemsOptions = {}) {
|
|
171
181
|
this.path = join(options.stateDir ?? elanousStateRoot(), 'briefing', 'items.sqlite');
|
|
172
182
|
this.now = options.now ?? (() => new Date());
|
|
173
183
|
this.log = options.log ?? ((category, event, data) => debug.log(category, event, data));
|
|
184
|
+
this.requirementFunnel = options.requirementFunnel ?? ((now) => readRequirementFunnel({ stateDir: options.stateDir, now }));
|
|
174
185
|
}
|
|
175
186
|
|
|
176
187
|
private using<T>(work: (db: Database) => T): T {
|
|
@@ -246,15 +257,81 @@ export class BriefItemsLedger {
|
|
|
246
257
|
|
|
247
258
|
/**
|
|
248
259
|
* Markdown for one slot. Reads only — never marks, rewrites, or sends an item.
|
|
249
|
-
* Items created at or after the slot instant are left out.
|
|
260
|
+
* Items created at or after the slot instant are left out. At 08:30 the
|
|
261
|
+
* requirement funnel contributes ephemeral lines without adding ledger rows.
|
|
250
262
|
*/
|
|
251
263
|
compose(slot: string): string {
|
|
264
|
+
return this.composeWithIds(slot).markdown;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private itemsFromDb(db: Database): BriefItem[] {
|
|
268
|
+
return db.query('SELECT id, text, domain, priority, deadline, evidence, source, created_at, (SELECT MIN(sent_at) FROM sends WHERE sends.item_id = items.id) AS sent_at FROM items ORDER BY id').all() as BriefItem[];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private pageFromDb(db: Database, slot: BriefSlot): { markdown: string; ids: number[] } {
|
|
272
|
+
const items = this.itemsFromDb(db);
|
|
273
|
+
const now = this.now();
|
|
274
|
+
const morningCutoff = Date.parse(`${dateKey(now, { timeZone: KST })}T08:30:00+09:00`);
|
|
275
|
+
let funnelItems: BriefItem[] = [];
|
|
276
|
+
if (slot === '08:30' && now.getTime() >= morningCutoff) {
|
|
277
|
+
// A funnel failure must not take the morning brief down with it.
|
|
278
|
+
try {
|
|
279
|
+
const extra = this.requirementFunnel(new Date(morningCutoff - 1)).briefItems;
|
|
280
|
+
// Negative ids never collide with ledger rows and keep the funnel's own order (summary first).
|
|
281
|
+
funnelItems = extra.map((item, index): BriefItem => ({
|
|
282
|
+
id: index - extra.length, text: item.text, domain: item.domain, priority: item.priority, deadline: null,
|
|
283
|
+
evidence: item.evidence ?? null, source: item.source,
|
|
284
|
+
created_at: new Date(morningCutoff - 1).toISOString(), sent_at: null,
|
|
285
|
+
}));
|
|
286
|
+
} catch (error) {
|
|
287
|
+
this.log('briefing.items', 'requirement-funnel-failed', { slot, error: error instanceof Error ? error.message : String(error) });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const composed = composeBriefPage([...items, ...funnelItems], slot, now);
|
|
291
|
+
// Ephemeral funnel lines appear on the page but have no ledger row to mark sent.
|
|
292
|
+
const page = { markdown: composed.markdown, ids: composed.ids.filter(id => id > 0) };
|
|
293
|
+
const candidates = items.filter(item => item.sent_at === null && beforeSlot(item.created_at, slot, now)).length;
|
|
294
|
+
this.log('briefing.items', 'composed', { slot, candidates, bytes: Buffer.byteLength(page.markdown) });
|
|
295
|
+
return page;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Read-only page and the exact ledger item ids printed on it (after deduplication). */
|
|
299
|
+
composeWithIds(slot: string): { markdown: string; ids: number[] } {
|
|
252
300
|
if (!(BRIEF_SLOTS as readonly string[]).includes(slot)) throw new BriefItemsInputError('invalid slot');
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
301
|
+
return this.using(db => this.pageFromDb(db, slot as BriefSlot));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Hold the ledger's SQLite writer lock from selecting unsent rows through delivery and marking.
|
|
305
|
+
* A failed delivery rolls back and releases the lock; the next sender can retry.
|
|
306
|
+
*/
|
|
307
|
+
withSendLock<T>(slot: string, work: (page: { markdown: string; ids: number[] }, markSent: (ids: readonly number[], slot: BriefSlot) => number) => T): T {
|
|
308
|
+
if (!(BRIEF_SLOTS as readonly string[]).includes(slot)) throw new BriefItemsInputError('invalid slot');
|
|
309
|
+
return this.using(db => {
|
|
310
|
+
db.exec('BEGIN IMMEDIATE');
|
|
311
|
+
try {
|
|
312
|
+
const page = this.pageFromDb(db, slot as BriefSlot);
|
|
313
|
+
const result = work(page, (ids, sentSlot) => {
|
|
314
|
+
if (sentSlot !== slot || ids.length !== page.ids.length || ids.some((id, index) => id !== page.ids[index])) {
|
|
315
|
+
throw new BriefItemsInputError('marked ids must match composed page');
|
|
316
|
+
}
|
|
317
|
+
return this.markSentInDb(db, ids, sentSlot);
|
|
318
|
+
});
|
|
319
|
+
db.exec('COMMIT');
|
|
320
|
+
return result;
|
|
321
|
+
} catch (cause) {
|
|
322
|
+
db.exec('ROLLBACK');
|
|
323
|
+
throw cause;
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private markSentInDb(db: Database, ids: readonly number[], slot: BriefSlot): number {
|
|
329
|
+
const at = this.now().toISOString();
|
|
330
|
+
const insert = db.query('INSERT INTO sends (item_id, slot, sent_at) VALUES (?, ?, ?)');
|
|
331
|
+
let count = 0;
|
|
332
|
+
for (const id of ids) { insert.run(id, slot, at); count++; }
|
|
333
|
+
this.log('briefing.items', 'marked-sent', { slot, count });
|
|
334
|
+
return count;
|
|
258
335
|
}
|
|
259
336
|
|
|
260
337
|
/**
|
|
@@ -263,14 +340,6 @@ export class BriefItemsLedger {
|
|
|
263
340
|
*/
|
|
264
341
|
markSent(ids: readonly number[], slot: BriefSlot): number {
|
|
265
342
|
if (!(BRIEF_SLOTS as readonly string[]).includes(slot)) throw new BriefItemsInputError('invalid slot');
|
|
266
|
-
|
|
267
|
-
const count = this.using(db => {
|
|
268
|
-
const insert = db.query('INSERT INTO sends (item_id, slot, sent_at) VALUES (?, ?, ?)');
|
|
269
|
-
let n = 0;
|
|
270
|
-
db.transaction(() => { for (const id of ids) { insert.run(id, slot, at); n++; } })();
|
|
271
|
-
return n;
|
|
272
|
-
});
|
|
273
|
-
this.log('briefing.items', 'marked-sent', { slot, count });
|
|
274
|
-
return count;
|
|
343
|
+
return this.using(db => db.transaction(() => this.markSentInDb(db, ids, slot))());
|
|
275
344
|
}
|
|
276
345
|
}
|
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';
|
package/src/cli/autopilot-cli.ts
CHANGED
|
@@ -6,7 +6,12 @@ import { writeStdoutJson } from './stdout-json.js';
|
|
|
6
6
|
// runner. Spawns an ACP backend, opens a single session, runs the
|
|
7
7
|
// AutopilotLoopDriver against the mission, prints agent deltas on
|
|
8
8
|
// stdout + envelope/termination summary on stderr.
|
|
9
|
-
export
|
|
9
|
+
export interface AutopilotCliDeps {
|
|
10
|
+
/** logs.db 싱크 등록(시험 주입). 기본 = `registerStandaloneLogSink`. `task-agent-action` 이 부른다. */
|
|
11
|
+
registerSink?: (surface: string) => Promise<unknown>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function registerAutopilotCommands(program: Command, cliDeps: AutopilotCliDeps = {}): void {
|
|
10
15
|
// Explicit runtime entry for a selected action; the task chooser can invoke this entry.
|
|
11
16
|
const autopilotCmd = program
|
|
12
17
|
.command('autopilot')
|
|
@@ -14,18 +19,28 @@ export function registerAutopilotCommands(program: Command): void {
|
|
|
14
19
|
autopilotCmd.command('task-agent-action <actionJson>')
|
|
15
20
|
.description('Execute a selected task-agent action; defaults to shadow')
|
|
16
21
|
.action(async (actionJson: string) => {
|
|
22
|
+
// 독립 프로세스 — 싱크를 붙이지 않으면 `task-agent`/`action` 관측이 logs.db 에 안 닿는다(fail-open).
|
|
23
|
+
const { registerTaskAgentSink } = await import('./tasks-cli.js');
|
|
24
|
+
await registerTaskAgentSink(cliDeps.registerSink);
|
|
17
25
|
const { executeNextAction } = await import('../task-agent/actions.js');
|
|
18
26
|
const { getUserConfig } = await import('../user-config.js');
|
|
19
27
|
const { spawnSync } = await import('node:child_process');
|
|
20
|
-
const {
|
|
28
|
+
const { writeFileSync, mkdirSync } = await import('node:fs');
|
|
29
|
+
// 상태 파일은 «없음»(ENOENT)만 빈 상태다 — 손상·해석 실패면 덮어쓰지 않고 멈춘다(사후 리뷰 must-fix · #24482).
|
|
30
|
+
const { readTaskAgentState, TaskAgentStateError } = await import('../task-agent/task-hand.js');
|
|
21
31
|
const { join, dirname } = await import('node:path');
|
|
22
32
|
const { debug } = await import('../debug/log.js');
|
|
23
33
|
const { withFileLockSync } = await import('../storage/file-lock.js');
|
|
24
34
|
const config = getUserConfig() as ReturnType<typeof getUserConfig> & { taskAgent?: { mode?: string } };
|
|
25
35
|
const { effectiveInstanceRoot } = await import('../instance/resolve.js');
|
|
26
36
|
const statePath = join(effectiveInstanceRoot(), 'task-agent-actions.json');
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
type ActionState = { landingDay?: string; landingsToday?: number; failureCounts?: Record<string, number> };
|
|
38
|
+
let state: ActionState;
|
|
39
|
+
try { state = readTaskAgentState<ActionState>(statePath); } catch (error) {
|
|
40
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
29
44
|
const action = JSON.parse(actionJson) as import('../task-agent/actions.js').NextAction;
|
|
30
45
|
const deps: import('../task-agent/actions.js').ActionDeps = {
|
|
31
46
|
...state,
|
|
@@ -37,8 +52,7 @@ export function registerAutopilotCommands(program: Command): void {
|
|
|
37
52
|
mode: config.taskAgent?.mode,
|
|
38
53
|
// 하루 착지 칸은 상태 파일 잠금 안에서 확보·반환한다 — 두 CLI 프로세스가 같은 잔여 칸을 쓰지 않게.
|
|
39
54
|
reserveLanding: (day, limit) => withFileLockSync(`${statePath}.lock`, () => {
|
|
40
|
-
|
|
41
|
-
try { disk = JSON.parse(readFileSync(statePath, 'utf8')); } catch { /* first */ }
|
|
55
|
+
const disk = readTaskAgentState<ActionState>(statePath);
|
|
42
56
|
const used = disk.landingDay === day ? disk.landingsToday ?? 0 : 0;
|
|
43
57
|
if (used >= limit) return false;
|
|
44
58
|
mkdirSync(dirname(statePath), { recursive: true });
|
|
@@ -47,8 +61,7 @@ export function registerAutopilotCommands(program: Command): void {
|
|
|
47
61
|
return true;
|
|
48
62
|
}),
|
|
49
63
|
incrementFailure: (key) => withFileLockSync(`${statePath}.lock`, () => {
|
|
50
|
-
|
|
51
|
-
try { disk = JSON.parse(readFileSync(statePath, 'utf8')); } catch { /* first */ }
|
|
64
|
+
const disk = readTaskAgentState<ActionState>(statePath);
|
|
52
65
|
const counts = { ...(disk.failureCounts ?? {}) };
|
|
53
66
|
counts[key] = (counts[key] ?? 0) + 1;
|
|
54
67
|
mkdirSync(dirname(statePath), { recursive: true });
|
|
@@ -56,8 +69,7 @@ export function registerAutopilotCommands(program: Command): void {
|
|
|
56
69
|
return counts[key]!;
|
|
57
70
|
}),
|
|
58
71
|
releaseLanding: (day) => withFileLockSync(`${statePath}.lock`, () => {
|
|
59
|
-
|
|
60
|
-
try { disk = JSON.parse(readFileSync(statePath, 'utf8')); } catch { return; }
|
|
72
|
+
const disk = readTaskAgentState<ActionState>(statePath);
|
|
61
73
|
if (disk.landingDay !== day || !disk.landingsToday) return;
|
|
62
74
|
writeFileSync(statePath, JSON.stringify({ ...disk, landingsToday: disk.landingsToday - 1 }));
|
|
63
75
|
deps.landingsToday = disk.landingsToday - 1;
|
|
@@ -72,7 +84,14 @@ export function registerAutopilotCommands(program: Command): void {
|
|
|
72
84
|
return { status: p.status ?? 1, stdout: p.stdout ?? '', stderr: p.stderr ?? '' };
|
|
73
85
|
},
|
|
74
86
|
};
|
|
75
|
-
|
|
87
|
+
let result: string;
|
|
88
|
+
try { result = await executeNextAction(action, deps); } catch (error) {
|
|
89
|
+
// 잠금 안 읽기가 손상을 만나면 쓰지 않고 여기로 온다 — 실패로 끝낸다. 그 밖의 오류는 종전대로 올린다.
|
|
90
|
+
if (!(error instanceof TaskAgentStateError)) throw error;
|
|
91
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
92
|
+
process.exitCode = 1;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
76
95
|
if (config.taskAgent?.mode === 'live') deps.saveState?.();
|
|
77
96
|
process.stdout.write(`${result}\n`);
|
|
78
97
|
});
|
|
@@ -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
|
+
}
|