elanous 0.2.13 → 0.2.15
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/LZcstaO9_NXypPFmujolv/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-33ffcc49ab45123b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9656-c1540088f5859c80.js → 1844-9ec4b1d12f6e80eb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{390.c373e31b3b9d0c41.js → 1948.15f356b15a625597.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
- package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4685-c3c1c625b71accd1.js → 4614-21403aac730d4586.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{8039-df92e08fede5de8f.js → 4795-f372490b178583fc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1452.93700483b9d4fc61.js → 6110.1297d4093fdfbb0d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +2 -0
- package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +1 -0
- package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1829-b453a1b22d3999bb.js → 7327-2d69fb321ba73389.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +25 -0
- package/apps/pwa/out/_next/static/chunks/{8421-1c4acfb1395a0edf.js → 8949-ecb4128a0858b911.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9103-7a3ae7f60af3b99f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3869-3cd46526879f454c.js → 9247-29d889b809288e65.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{79f8be9e-957d68913b590b08.js → a9f2b54d-7865aba0f4d2ee49.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-0e2c57fe6add4b2a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e05a77df7e005b04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b8c49f6b4fa2df91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-976813c91d7394ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-28b9448091776c06.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1d4901eb7c2d8822.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-72d3e7ff35a8c954.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-60c7a8e1a4fe62ce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-2a52b316cb143d9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-c3ce483c8d5db370.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-13c7b53e0c2519a9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-96f074f6538e8a2c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-e97be5334d83a9f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-955ec2da3be4c560.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-c1343f9499d86f3f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-8739a2e0ae793130.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-d23b12bb92242558.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c7fea475488201a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-e2ee2896811e98d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-5efbf81124c474ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f1a1fc0a82738c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-da6c08aae0f92768.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-06ee266953d9c879.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-153c2b3bcc89e8de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-87a11e6a05f15199.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-7d4182964407c09c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-0d6d23b40263a7ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-63d14a4d8fe1e1c1.js → page-f5597e894c02cd33.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-73735b64bfe1ae67.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-8fcec4c9ea2e7f70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f7e631e9b4daf66e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-9817c7f9007466cc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-97f9f43172a3a90e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5cd7e3a9-404fd6b8d0ecd44c.js → f6231284-45726550aba08fca.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{f48170cc-d05d28bf87a43bf4.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +1 -0
- package/apps/pwa/out/_next/static/css/1f7c958c41ffa3b9.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 -0
- package/apps/pwa/out/ceo/index.txt +33 -0
- 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/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 +23 -23
- 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 +22 -22
- 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 -0
- package/apps/pwa/out/loops/index.txt +30 -0
- 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/heal/heal-loop.yaml +1 -1
- package/graphs/implement-loop.yaml +1 -1
- package/graphs/orchestrator/orchestrator.yaml +59 -0
- package/graphs/orchestrator/recipes.yaml +18 -0
- package/graphs/release/recipes.yaml +6 -0
- package/graphs/release/release-loop.yaml +6 -1
- package/graphs/rhythm/daily.yaml +38 -0
- package/graphs/rhythm/recipes.yaml +18 -0
- package/graphs/rhythm/weekly.yaml +39 -0
- package/graphs/video/recipes.yaml +3 -0
- package/graphs/video/youtube-summary.yaml +17 -0
- package/package.json +1 -1
- package/scripts/audit-test-state-writes.ts +72 -17
- package/scripts/brand/check.ts +188 -0
- package/scripts/ci-typecheck-changed.ts +50 -18
- package/scripts/coord-post.sh +11 -1
- package/scripts/docs/generate-graph-anatomy.ts +315 -0
- package/scripts/docs-cli-check.ts +23 -6
- package/scripts/expose-rubric-check.ts +118 -0
- package/scripts/expose-rubric-suggest.ts +169 -0
- package/scripts/heal/run-role.ts +14 -0
- package/scripts/hq/standby-verify.ts +11 -7
- package/scripts/lecture-note/node.ts +31 -11
- package/scripts/lessons/handbook-page.ts +112 -0
- package/scripts/marketing/generate-ax-brief.ts +140 -0
- package/scripts/marketing/generate-feature-map.ts +118 -0
- package/scripts/release-loop/checklist-gate-node.ts +53 -14
- package/scripts/release-loop/gate-node.ts +89 -14
- package/scripts/release-loop/publish-node.ts +14 -1
- package/scripts/release-loop/unattended-release.ts +23 -1
- package/scripts/release-loop/vault-note-node.ts +428 -0
- package/scripts/release-story/draft.ts +220 -0
- package/scripts/rhythm/daily.ts +570 -0
- package/scripts/rhythm/weekly.ts +315 -0
- package/scripts/skills/public-check.ts +169 -0
- package/scripts/smart-merge/resync-shadow.ts +147 -0
- package/scripts/test-deterministic.ts +4 -8
- package/scripts/test-diet/effectiveness.ts +195 -0
- package/scripts/test-diet/lib.ts +16 -8
- package/scripts/test-diet/node.ts +77 -9
- package/scripts/ux/demo-rehearsal.ts +2 -2
- package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
- package/scripts/video/youtube-summary.ts +133 -0
- package/skills/diagram-master/SKILL.md +42 -49
- package/skills/diagram-master/references/pyproject.toml +4 -1
- package/skills/diagram-master/references/routing-guide.md +4 -2
- package/skills/grill-me/SKILL.md +1 -0
- package/skills/youtube-master/SKILL.md +13 -13
- package/skills/youtube-master/package-lock.json +0 -545
- package/skills/youtube-master/package.json +2 -5
- package/skills/youtube-master/scripts/main.ts +2 -2
- package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
- package/skills/youtube-master/src/util.ts +30 -7
- package/src/agent-env/profile.ts +161 -0
- package/src/agent-mission/driver.ts +3 -3
- package/src/agent-substrate/pr-reviewer.ts +2 -0
- package/src/ask-user-question/pending-questions.ts +5 -0
- package/src/auth/github-app-token.ts +112 -13
- package/src/autopilot/build/llm-conflict-merge.ts +73 -10
- package/src/autopilot/build/merge-intent.ts +53 -0
- package/src/briefing/brief-items.ts +121 -0
- package/src/chat/index.ts +2 -0
- package/src/claims/claims-ledger.ts +232 -0
- package/src/claims/claims-render.ts +166 -0
- package/src/cli/agent-env-cli.ts +21 -0
- package/src/cli/claims-cli.ts +94 -0
- package/src/cli/decisions-cli.ts +44 -3
- package/src/cli/docs-cli.ts +2 -0
- package/src/cli/doctor-cli.ts +23 -1
- package/src/cli/doctor-node-modules.ts +147 -0
- package/src/cli/flow-cli.ts +11 -0
- package/src/cli/freeze-cli.ts +45 -0
- package/src/cli/intake-cli.ts +46 -17
- package/src/cli/lesson-cli.ts +108 -0
- package/src/cli/logs-cli.ts +104 -2
- package/src/cli/logs-model-input.ts +131 -0
- package/src/cli/pod-cli.ts +2 -0
- package/src/cli/pr-cli.ts +29 -1
- package/src/cli/private-ledger-commands.ts +54 -4
- package/src/cli/pty-drive-verdict.ts +11 -2
- package/src/cli/release-cli.ts +190 -33
- package/src/cli/rfc-status.ts +91 -0
- package/src/cli/seat-requests-cli.ts +52 -0
- package/src/connectors/linear.ts +5 -3
- package/src/context-bus/condense.ts +15 -20
- package/src/context-bus/context-now-public.ts +96 -0
- package/src/context-bus/context-now-surfaces.ts +55 -2
- package/src/context-bus/context-now.ts +6 -3
- package/src/context-bus/coord-events.ts +6 -4
- package/src/context-bus/long-term-memory.ts +92 -11
- package/src/dashboard/index.ts +75 -22
- package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
- package/src/dashboard/slash-runtime/loops-table.ts +77 -0
- package/src/decisions/decision-cards.ts +33 -7
- package/src/decisions/decision-ledger.ts +40 -1
- package/src/decisions/proact-meter.ts +286 -0
- package/src/decisions/proact1-lite.ts +86 -0
- package/src/decisions/web-push-decision-cards.ts +1 -1
- package/src/discord/sprint21-runtime.ts +16 -0
- package/src/discord-self-message.ts +8 -0
- package/src/discord-slash-wire.ts +31 -2
- package/src/domains/alert-audit.ts +271 -0
- package/src/domains/core-tools.ts +4 -3
- package/src/domains/ops-facts-tool.ts +31 -1
- package/src/domains/outbound-alert.ts +1 -1
- package/src/elanous-config-dir.ts +29 -4
- package/src/execution-loop/launch-gate.ts +29 -14
- package/src/flow/cards.ts +239 -0
- package/src/graph-runner/graph-tick.ts +4 -0
- package/src/graph-runner/runner.ts +30 -5
- package/src/harness/harness-cli-command.ts +259 -38
- package/src/harness/harness-incidents.ts +45 -12
- package/src/harness/harness-pod-dispatch.ts +102 -11
- package/src/harness/harness-queue-child.ts +3 -3
- package/src/harness/harness-queue.ts +583 -65
- package/src/harness/helper-repair.ts +87 -0
- package/src/harness/helper-scan.ts +151 -0
- package/src/harness/launch-stamp.ts +236 -0
- package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
- package/src/harness/ledger-write-guard.ts +35 -0
- package/src/harness/model-input-observation.ts +37 -0
- package/src/harness/pod-memory-policy.ts +15 -0
- package/src/harness/repository-name.ts +55 -0
- package/src/hq/fence-audit.ts +90 -0
- package/src/hq/hq.ts +63 -5
- package/src/hq/lease.ts +4 -4
- package/src/hq/move-plan.ts +126 -0
- package/src/hq/promote.ts +332 -0
- package/src/index.ts +159 -27
- package/src/intake-plane/check.ts +63 -0
- package/src/intake-plane/digest.ts +37 -15
- package/src/intake-plane/front-classifier.ts +6 -0
- package/src/intake-plane/items.ts +3 -2
- package/src/intake-plane/lens.ts +177 -0
- package/src/intake-plane/linear-wish.ts +39 -11
- package/src/intake-plane/route.ts +145 -1
- package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
- package/src/intake-plane/wish-card.ts +53 -2
- package/src/intake-plane/wish-folder.ts +5 -2
- package/src/intake-plane/wish-reply.ts +72 -0
- package/src/launch-head/seat-goal-queue-lock.fixture.ts +15 -0
- package/src/launch-head/seat-goal-queue.ts +198 -0
- package/src/lessons/lesson-import.ts +106 -0
- package/src/lessons/lesson-ledger.ts +206 -0
- package/src/loops/activity.ts +203 -0
- package/src/loops/budget.ts +63 -0
- package/src/loops/checker.ts +5 -3
- package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
- package/src/loops/loop-cli.ts +36 -4
- package/src/loops/neighbors.ts +40 -0
- package/src/loops/observe.ts +10 -0
- package/src/loops/orchestrator/author-depth.ts +220 -0
- package/src/loops/orchestrator/author-input-check.ts +82 -0
- package/src/loops/orchestrator/author-ledger.ts +232 -0
- package/src/loops/orchestrator/finish-rate.ts +175 -0
- package/src/loops/orchestrator/overlap.ts +51 -12
- package/src/loops/orchestrator/resync-shadow.ts +227 -0
- package/src/loops/orchestrator/seat-share.ts +56 -0
- package/src/loops/orchestrator/tick.ts +309 -0
- package/src/loops/orchestrator/traffic.ts +134 -13
- package/src/loops/registry.ts +535 -9
- package/src/loops/status-rows.ts +118 -0
- package/src/loops/verdict.ts +17 -5
- package/src/maturity/bot-slash-catalog.ts +66 -0
- package/src/maturity/feature-maturity.ts +9 -5
- package/src/maturity/tui-slash-availability.ts +31 -0
- package/src/mss/logging/log-store.ts +35 -4
- package/src/nexus/api/dashboard.ts +6 -1
- package/src/nexus/api/http-server.ts +33 -3
- package/src/nexus/api/loop-edges.ts +173 -0
- package/src/nexus/api/merge-approvals-gate.ts +1 -1
- package/src/nexus/api/schedules-read.ts +18 -1
- package/src/nexus/api/seat-requests.ts +79 -56
- package/src/nexus/api/task-cards-api.ts +44 -5
- package/src/nexus/api/workflows.ts +2 -1
- package/src/prompt-enhance/enhance.ts +6 -1
- package/src/release-loop/auto-start.ts +51 -0
- package/src/release-loop/checklist.ts +22 -4
- package/src/release-loop/feature-store.ts +71 -32
- package/src/release-loop/landing-freeze.ts +121 -0
- package/src/release-loop/merged-pr-checklist.ts +125 -0
- package/src/release-loop/placement.ts +302 -0
- package/src/release-loop/release-schedule.ts +3 -1
- package/src/seat-dispatch/ceo-commands.ts +2 -2
- package/src/seat-dispatch/seat-ask.ts +16 -3
- package/src/seat-dispatch/seat-questions.ts +58 -0
- package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
- package/src/seat-dispatch/seat-request-ledger.ts +133 -0
- package/src/seat-loop/seat-loop.ts +531 -40
- package/src/self-dev/dev-pipeline.ts +14 -6
- package/src/self-dev/draft-sweep.ts +162 -6
- package/src/self-dev/draft-triage-rules.ts +25 -8
- package/src/self-dev/orchestrate.ts +15 -1
- package/src/self-dev/review-provider-fallback.ts +6 -1
- package/src/self-dev/run-store.ts +73 -1
- package/src/self-dev/run-supervisor.ts +57 -2
- package/src/self-dev/self-orchestrate-runtime.ts +5 -1
- package/src/self-implement/budget-gate.ts +29 -0
- package/src/self-implement/expose-gate.ts +47 -0
- package/src/self-implement/frozen-merges.ts +295 -0
- package/src/self-implement/gate-cli.ts +10 -4
- package/src/self-implement/gate-scope.ts +46 -9
- package/src/self-implement/goal-archive.ts +222 -0
- package/src/self-implement/goal-author.ts +43 -5
- package/src/self-implement/goal-clarification-escalation.ts +5 -0
- package/src/self-implement/goal-lookup.ts +147 -0
- package/src/self-implement/heal-intake.ts +197 -0
- package/src/self-implement/host-regate.ts +32 -2
- package/src/self-implement/l8-merge-queue.ts +132 -0
- package/src/self-implement/orchestrator.ts +299 -48
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/revert-guard.ts +97 -0
- package/src/self-implement/run-ledger.ts +3 -1
- package/src/self-implement/run-status-mapping.ts +2 -0
- package/src/self-implement/seams.ts +29 -9
- package/src/self-implement/self-implement-cli.ts +1 -0
- package/src/self-implement/speed-claims.ts +338 -0
- package/src/session/index.ts +2 -0
- package/src/steward/triage.ts +74 -3
- package/src/steward/working-backwards.ts +4 -1
- package/src/task-cards/card-cli.ts +10 -2
- package/src/task-cards/card-store.ts +10 -6
- package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
- package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
- package/src/task-orchestrator/surfaces/pod-pool.ts +60 -8
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +224 -49
- package/src/task-orchestrator/surfaces/self-implement.ts +5 -1
- package/src/telegram-commands.ts +27 -3
- package/src/telegram-loops-command.ts +44 -0
- package/src/telegram.ts +25 -4
- package/src/user-config.ts +146 -18
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/nodes/bash.ts +9 -1
- package/src/workflow-runtime/storage.ts +31 -13
- package/apps/pwa/out/_next/static/D51l8jUBplJrIpWvENsDK/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1394-931c9827067b2276.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3992-fe5c06d6b31f8b25.js +0 -25
- package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
- package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8605-9da4678f8a310d18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e49b9b76c565a0b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-f14987d5850a31e0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-4c1dbb21861beab2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-f6bc66b919b2ea7d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-60603c1e056c9b25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-a0ea8f5a2341ee6b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-31012264da8a341f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-50309aeb0bdd554a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-0696ee3fac9fe56c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66c27608f651b866.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-6ddd2109b7fc3a80.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-7a2bf830038a2264.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1f327f18694fabca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e5cef2fe6e4028df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cf596128d2b420ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-f672536f14f0969f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-29a709616df9b050.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-fee842d7c2151129.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-78b40a344ed22f24.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-a011dd5b0bff29c5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-d92e32d51ab5157e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-5f21aed6932ef375.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-1e1a53946804debf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-a1833cd43d832601.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-2a511d320f762e06.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-1c3e03f88dac1e21.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c4d5289c2ea97a89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b91f8952deaeaf27.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-71b43a2588489987.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
- package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
- /package/apps/pwa/out/_next/static/{D51l8jUBplJrIpWvENsDK → LZcstaO9_NXypPFmujolv}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { awaitGlobalPersonaLoad, getGlobalPersonaRegistry } from '../../persona/global-registry.js';
|
|
2
|
+
import { resolvePersonaAddress } from '../../intake-plane/persona-answer.js';
|
|
3
|
+
import { loadSession, updateSessionMeta } from '../../session/index.js';
|
|
4
|
+
import { assemblePersonaPrompt } from '../../persona/prompt-assembler.js';
|
|
5
|
+
import type { LLMMessage } from '../../llm.js';
|
|
6
|
+
|
|
7
|
+
/** Unattached conversations use a dashboard-local id; attached sessions use persisted metadata. */
|
|
8
|
+
export class TuiChatPersonaSelection {
|
|
9
|
+
private readonly selected = new Map<string, string>();
|
|
10
|
+
|
|
11
|
+
get(sessionId: string): string | undefined {
|
|
12
|
+
return loadSession(sessionId)?.meta.chatPersonaId ?? this.selected.get(sessionId);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
set(sessionId: string, personaId: string | undefined): void {
|
|
16
|
+
const meta = updateSessionMeta(sessionId, (m) => {
|
|
17
|
+
if (personaId) m.chatPersonaId = personaId;
|
|
18
|
+
else delete m.chatPersonaId;
|
|
19
|
+
});
|
|
20
|
+
if (!meta) {
|
|
21
|
+
if (personaId) this.selected.set(sessionId, personaId);
|
|
22
|
+
else this.selected.delete(sessionId);
|
|
23
|
+
} else {
|
|
24
|
+
this.selected.delete(sessionId);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function handleTuiChatPersona(
|
|
30
|
+
args: readonly string[], sessionId: string,
|
|
31
|
+
selection: TuiChatPersonaSelection,
|
|
32
|
+
): Promise<string> {
|
|
33
|
+
if (args[0] === '-') {
|
|
34
|
+
if (args.length !== 1) return '쓰는 법: /persona [이름|-]';
|
|
35
|
+
selection.set(sessionId, undefined);
|
|
36
|
+
return '이 대화의 페르소나를 뺐습니다';
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
await awaitGlobalPersonaLoad();
|
|
40
|
+
const registry = getGlobalPersonaRegistry();
|
|
41
|
+
if (!args.length) {
|
|
42
|
+
const selected = selection.get(sessionId);
|
|
43
|
+
const personas = registry.list().sort((a, b) => a.displayName.localeCompare(b.displayName));
|
|
44
|
+
const current = personas.find(p => p.personaId === selected);
|
|
45
|
+
return `이 대화의 페르소나: ${current ? `${current.displayName} (${current.personaId})` : '없음'}\n고를 수 있는 페르소나: ${personas.map(p => `${p.displayName} (${p.personaId})${p.personaId === selected ? ' (지금)' : ''}`).join(' · ') || '없음'}\n고르기: /persona <이름> · 빼기: /persona -`;
|
|
46
|
+
}
|
|
47
|
+
const name = args.join(' ');
|
|
48
|
+
const persona = resolvePersonaAddress(name, registry);
|
|
49
|
+
if (!persona) return `그런 페르소나가 없습니다: ${name}`;
|
|
50
|
+
selection.set(sessionId, persona.personaId);
|
|
51
|
+
return `${persona.displayName} 페르소나를 이 대화에 골랐습니다`;
|
|
52
|
+
} catch {
|
|
53
|
+
return '페르소나 목록을 읽지 못했습니다';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function tuiChatPersonaMessages(
|
|
58
|
+
sessionId: string, selection: TuiChatPersonaSelection,
|
|
59
|
+
): LLMMessage[] {
|
|
60
|
+
const id = selection.get(sessionId);
|
|
61
|
+
if (!id) return [];
|
|
62
|
+
const persona = getGlobalPersonaRegistry().get(id);
|
|
63
|
+
if (!persona) return [];
|
|
64
|
+
const prompt = assemblePersonaPrompt(persona, '').systemPrompt;
|
|
65
|
+
return prompt ? [{ role: 'system', content: prompt }] : [];
|
|
66
|
+
}
|
|
@@ -273,6 +273,7 @@
|
|
|
273
273
|
// of circular dependency on dashboard/index.ts.
|
|
274
274
|
|
|
275
275
|
import { tuiNowSlash } from '../../context-bus/context-now-surfaces.js';
|
|
276
|
+
import { handleTuiChatPersona, type TuiChatPersonaSelection } from './chat-persona.js';
|
|
276
277
|
import { createWishCard } from '../../intake-plane/wish-card.js';
|
|
277
278
|
import { CardStore } from '../../task-cards/card-store.js';
|
|
278
279
|
import type { ContextNowDeps } from '../../context-bus/context-now.js';
|
|
@@ -390,6 +391,8 @@ import { resolveTerminalMoveDestination } from '../../terminal-matrix/mobility.j
|
|
|
390
391
|
import { loadPersistedSessions } from '../../terminal/session-persistence.js';
|
|
391
392
|
import { dispatchTerminalModalObserve } from '../../skills/tools/terminal-modal.js';
|
|
392
393
|
import { SlashCommandRegistry } from './registry.js';
|
|
394
|
+
import { cronSecondDueAt, filterLoopRows, formatLoopsTable, localLoopRows, type LoopsTableRow } from './loops-table.js';
|
|
395
|
+
import { loopCronVerdict } from '../../loops/verdict.js';
|
|
393
396
|
import { executeImmediateDashboardSlash } from '../input/slash-executor.js';
|
|
394
397
|
import {
|
|
395
398
|
buildContextSlashCommand,
|
|
@@ -1029,6 +1032,7 @@ export interface DashboardSlashContext {
|
|
|
1029
1032
|
/** Currently-attached session id (TUI's pointer to a JSONL on disk). */
|
|
1030
1033
|
getAttachedSessionId(): string | null;
|
|
1031
1034
|
setAttachedSessionId(id: string | null): void;
|
|
1035
|
+
chatPersona?: { sessionId(): string; selection: TuiChatPersonaSelection; newConversation?(): void };
|
|
1032
1036
|
/** Telegram chat id paired with the attached session, when handed off. */
|
|
1033
1037
|
getAttachedChatId(): number | null;
|
|
1034
1038
|
setAttachedChatId(id: number | null): void;
|
|
@@ -1038,6 +1042,9 @@ export interface DashboardSlashContext {
|
|
|
1038
1042
|
send: (cfg: ReturnType<typeof getUserConfig>, text: string) => Promise<boolean>;
|
|
1039
1043
|
};
|
|
1040
1044
|
|
|
1045
|
+
/** Test seam for the local read-only loop and cron registry. */
|
|
1046
|
+
loopsLocalRows?: (now: Date) => LoopsTableRow[];
|
|
1047
|
+
|
|
1041
1048
|
sessionSlash: {
|
|
1042
1049
|
/** opts.remote — when the dashboard is attached to a remote daemon. */
|
|
1043
1050
|
remoteDaemon(): { url: string; token: string | undefined } | null;
|
|
@@ -1364,6 +1371,28 @@ function emitReplyToChat(ctx: Pick<DashboardSlashContext, 'pushChatLine'>, line:
|
|
|
1364
1371
|
ctx.pushChatLine(line);
|
|
1365
1372
|
}
|
|
1366
1373
|
|
|
1374
|
+
async function fetchLoopsDaemonJson<T>(url: string, headers: Record<string, string>): Promise<T> {
|
|
1375
|
+
const controller = new AbortController();
|
|
1376
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
1377
|
+
try {
|
|
1378
|
+
return await Promise.race([
|
|
1379
|
+
(async () => {
|
|
1380
|
+
const response = await fetch(url, { headers, signal: controller.signal });
|
|
1381
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
1382
|
+
return await response.json() as T;
|
|
1383
|
+
})(),
|
|
1384
|
+
new Promise<never>((_, reject) => {
|
|
1385
|
+
timeout = setTimeout(() => {
|
|
1386
|
+
controller.abort();
|
|
1387
|
+
reject(new Error('request timed out'));
|
|
1388
|
+
}, 1_000);
|
|
1389
|
+
}),
|
|
1390
|
+
]);
|
|
1391
|
+
} finally {
|
|
1392
|
+
if (timeout) clearTimeout(timeout);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1367
1396
|
export function buildDashboardSlashRegistry(nowDeps?: ContextNowDeps): SlashCommandRegistry<DashboardSlashContext, DashboardSlashReturn> {
|
|
1368
1397
|
const registry = new SlashCommandRegistry<DashboardSlashContext, DashboardSlashReturn>();
|
|
1369
1398
|
|
|
@@ -1371,6 +1400,61 @@ export function buildDashboardSlashRegistry(nowDeps?: ContextNowDeps): SlashComm
|
|
|
1371
1400
|
for (const line of tuiNowSlash(args, nowDeps)) ctx.pushChatLine(line);
|
|
1372
1401
|
});
|
|
1373
1402
|
|
|
1403
|
+
registry.register('loops', async (args, ctx) => {
|
|
1404
|
+
if (args.length > 1) {
|
|
1405
|
+
ctx.pushChatLine(ctx.warning(' usage: /loops [owner]'));
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
const now = new Date(Date.now());
|
|
1409
|
+
let rows: LoopsTableRow[] = [];
|
|
1410
|
+
const remote = ctx.sessionSlash.remoteDaemon();
|
|
1411
|
+
if (remote) {
|
|
1412
|
+
const base = deriveDaemonHttpBase(remote.url);
|
|
1413
|
+
if (base) {
|
|
1414
|
+
try {
|
|
1415
|
+
const headers: Record<string, string> = remote.token ? { authorization: `Bearer ${remote.token}` } : {};
|
|
1416
|
+
const body = await fetchLoopsDaemonJson<{ schedules?: Array<{ name: string; domain?: string | null; runVia?: string | null;
|
|
1417
|
+
source: string; cron?: string | null; state: string; lastRun?: { at: string; status: string | null; exit: number | null } | null;
|
|
1418
|
+
intervalMs?: number | null }> }>(`${base}/v1/schedules?includeOff=1`, headers);
|
|
1419
|
+
rows = (body.schedules ?? []).map(schedule => ({
|
|
1420
|
+
layer: 'daemon cron', name: schedule.name, owner: schedule.domain ?? '—', mode: schedule.runVia ?? schedule.source,
|
|
1421
|
+
lastRun: schedule.lastRun?.at ?? null,
|
|
1422
|
+
verdict: loopCronVerdict({ enabled: schedule.state !== 'off', lastRunAt: schedule.lastRun?.at ?? null,
|
|
1423
|
+
lastStatus: schedule.lastRun?.exit != null && schedule.lastRun.exit !== 0 ? 'failed' : schedule.lastRun?.status ?? null,
|
|
1424
|
+
intervalMs: schedule.intervalMs ?? null,
|
|
1425
|
+
secondDueAt: cronSecondDueAt(schedule.cron ?? null, schedule.lastRun?.at ?? null) }, now.getTime()),
|
|
1426
|
+
}));
|
|
1427
|
+
} catch (err) {
|
|
1428
|
+
ctx.pushChatLine(ctx.warning(` daemon cron unavailable: ${err instanceof Error ? err.message : String(err)}`));
|
|
1429
|
+
}
|
|
1430
|
+
try {
|
|
1431
|
+
const headers: Record<string, string> = remote.token ? { authorization: `Bearer ${remote.token}` } : {};
|
|
1432
|
+
const body = await fetchLoopsDaemonJson<{ loops?: { loops?: Array<{
|
|
1433
|
+
name: string; label: string; armed: boolean | null; category?: string;
|
|
1434
|
+
last: { at: string; status: string } | null;
|
|
1435
|
+
}> } | null }>(`${base}/v1/dashboard/loops`, headers);
|
|
1436
|
+
// dashboardLoops returns LoopStatus (no owner field). Its category is
|
|
1437
|
+
// exec/reflect, not an owner; keep ownership unknown rather than
|
|
1438
|
+
// attributing every daemon loop to a guessed domain.
|
|
1439
|
+
rows.push(...(body.loops?.loops ?? []).map(loop => ({
|
|
1440
|
+
layer: 'daemon loop', name: loop.label || loop.name, owner: '—', mode: loop.category ?? 'loop',
|
|
1441
|
+
lastRun: loop.last?.at ?? null,
|
|
1442
|
+
verdict: loopCronVerdict({ enabled: loop.armed !== false, lastRunAt: loop.last?.at ?? null,
|
|
1443
|
+
lastStatus: loop.last?.status ?? null, intervalMs: null }, now.getTime()),
|
|
1444
|
+
})));
|
|
1445
|
+
} catch (err) {
|
|
1446
|
+
ctx.pushChatLine(ctx.warning(` daemon loops unavailable: ${err instanceof Error ? err.message : String(err)}`));
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
try { rows.push(...(ctx.loopsLocalRows ?? localLoopRows)(now)); }
|
|
1451
|
+
catch (err) { ctx.pushChatLine(ctx.warning(` local loops unavailable: ${err instanceof Error ? err.message : String(err)}`)); }
|
|
1452
|
+
const filtered = filterLoopRows(rows, args[0]);
|
|
1453
|
+
for (const line of formatLoopsTable(filtered, text => ctx.error(text))) ctx.pushChatLine(` ${line}`);
|
|
1454
|
+
if (!filtered.length) ctx.pushChatLine(ctx.muted(' No loops or cron jobs found.'));
|
|
1455
|
+
ctx.setChatScrollOffset(-1);
|
|
1456
|
+
});
|
|
1457
|
+
|
|
1374
1458
|
registry.register('wish', (args, ctx) => {
|
|
1375
1459
|
const text = args.join(' ').trim();
|
|
1376
1460
|
if (!text) {
|
|
@@ -1427,6 +1511,16 @@ export function buildDashboardSlashRegistry(nowDeps?: ContextNowDeps): SlashComm
|
|
|
1427
1511
|
}
|
|
1428
1512
|
});
|
|
1429
1513
|
|
|
1514
|
+
registry.register('persona', async (args, ctx) => {
|
|
1515
|
+
if (!ctx.chatPersona) {
|
|
1516
|
+
ctx.pushChatLine(ctx.warning(' 페르소나를 사용할 수 없습니다'));
|
|
1517
|
+
} else {
|
|
1518
|
+
const text = await handleTuiChatPersona(args, ctx.chatPersona.sessionId(), ctx.chatPersona.selection);
|
|
1519
|
+
for (const line of text.split('\n')) ctx.pushChatLine(ctx.muted(` ${line}`));
|
|
1520
|
+
}
|
|
1521
|
+
ctx.setChatScrollOffset(-1);
|
|
1522
|
+
});
|
|
1523
|
+
|
|
1430
1524
|
registry.register('directive', async (args, ctx) => {
|
|
1431
1525
|
try {
|
|
1432
1526
|
const { addDirective } = await import('../../steward/directive.js');
|
|
@@ -4042,6 +4136,7 @@ export function buildDashboardSlashRegistry(nowDeps?: ContextNowDeps): SlashComm
|
|
|
4042
4136
|
const prev = attached;
|
|
4043
4137
|
ctx.setAttachedSessionId(null);
|
|
4044
4138
|
ctx.setAttachedChatId(null);
|
|
4139
|
+
ctx.chatPersona?.newConversation?.();
|
|
4045
4140
|
const chatHistory = ctx.compactSlash.chatHistory;
|
|
4046
4141
|
const sysFromDash = chatHistory.find(m => m.role === 'system');
|
|
4047
4142
|
chatHistory.length = 0;
|
|
@@ -4151,6 +4246,10 @@ export function buildDashboardSlashRegistry(nowDeps?: ContextNowDeps): SlashComm
|
|
|
4151
4246
|
sessionAppendMessage(meta.id, { role: m.role, content, ts: nowTs });
|
|
4152
4247
|
}
|
|
4153
4248
|
sessionAttachTelegram(meta.id, chatTarget);
|
|
4249
|
+
if (ctx.chatPersona) {
|
|
4250
|
+
const selected = ctx.chatPersona.selection.get(ctx.chatPersona.sessionId());
|
|
4251
|
+
if (selected) ctx.chatPersona.selection.set(meta.id, selected);
|
|
4252
|
+
}
|
|
4154
4253
|
ctx.setAttachedSessionId(meta.id);
|
|
4155
4254
|
ctx.setAttachedChatId(chatTarget);
|
|
4156
4255
|
ctx.chatLines.push(ctx.success(` ✓ attached session ${meta.id.slice(0, 8)} → chat ${chatTarget} (${chatHistory.length} turns mirrored)`));
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { listLoops, type LoopEntry } from '../../loops/registry.js';
|
|
4
|
+
import { loopCronVerdict, type LoopCronVerdict } from '../../loops/verdict.js';
|
|
5
|
+
import { listSchedules, schedulesDbPath, type ScheduleRow } from '../../domains/schedule-registry.js';
|
|
6
|
+
import { nextRuns } from '../../domains/schedule-state.js';
|
|
7
|
+
|
|
8
|
+
export interface LoopsTableRow {
|
|
9
|
+
layer: string;
|
|
10
|
+
name: string;
|
|
11
|
+
owner: string;
|
|
12
|
+
mode: string;
|
|
13
|
+
lastRun: string | null;
|
|
14
|
+
verdict: LoopCronVerdict;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Read the already-inventoried local registry without opening/migrating a writable store. */
|
|
18
|
+
export function localLoopRows(now: Date): LoopsTableRow[] {
|
|
19
|
+
let schedules: ScheduleRow[] = [];
|
|
20
|
+
if (existsSync(schedulesDbPath())) {
|
|
21
|
+
const db = new Database(schedulesDbPath(), { readonly: true });
|
|
22
|
+
try { schedules = listSchedules(db); } finally { db.close(); }
|
|
23
|
+
}
|
|
24
|
+
const loops = listLoops({ schedules, now });
|
|
25
|
+
const rows: LoopsTableRow[] = loops.map(loop => graphLoopRow(loop, schedules, now));
|
|
26
|
+
const graphJobIds = new Set(loops.flatMap(loop => loop.jobs.map(job => job.id)));
|
|
27
|
+
for (const schedule of schedules) {
|
|
28
|
+
if (graphJobIds.has(schedule.id)) continue;
|
|
29
|
+
rows.push(scheduleRow(schedule, 'local cron', now));
|
|
30
|
+
}
|
|
31
|
+
return rows;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function cronSecondDueAt(cron: string | null, lastRun: string | null): string | null {
|
|
35
|
+
if (!cron || !lastRun) return null;
|
|
36
|
+
const last = new Date(lastRun);
|
|
37
|
+
if (!Number.isFinite(last.getTime())) return null;
|
|
38
|
+
return nextRuns(cron, last, 2)[1] ?? null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function graphLoopRow(loop: LoopEntry, schedules: ScheduleRow[], now: Date): LoopsTableRow {
|
|
42
|
+
const active = loop.jobs.map(job => schedules.find(row => row.id === job.id)).filter((r): r is ScheduleRow => !!r);
|
|
43
|
+
const cron = loop.trigger.cron ?? active[0]?.cron ?? null;
|
|
44
|
+
const interval = active.find(r => r.interval_ms && r.interval_ms > 0)?.interval_ms ?? null;
|
|
45
|
+
const lastRun = loop.lastRun?.at ?? active.map(r => r.last_run).filter((at): at is string => !!at).sort().at(-1) ?? null;
|
|
46
|
+
const status = loop.lastRun?.status ?? active.find(r => r.last_run === lastRun)?.last_status ?? null;
|
|
47
|
+
return {
|
|
48
|
+
layer: 'local loop', name: loop.title, owner: loop.owner ?? '—', mode: loop.mode ?? (loop.trigger.cron ? 'cron' : 'event'), lastRun,
|
|
49
|
+
verdict: loopCronVerdict({ enabled: loop.enabled, lastRunAt: lastRun, lastStatus: status, intervalMs: interval,
|
|
50
|
+
secondDueAt: cronSecondDueAt(cron, lastRun) }, now.getTime()),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function scheduleRow(row: ScheduleRow, layer: string, now: Date): LoopsTableRow {
|
|
55
|
+
return {
|
|
56
|
+
layer, name: row.name, owner: row.domain ?? '—', mode: row.run_via || row.source,
|
|
57
|
+
lastRun: row.last_run,
|
|
58
|
+
verdict: loopCronVerdict({ enabled: !!row.enabled && row.disabled_reason !== 'vanished', lastRunAt: row.last_run,
|
|
59
|
+
lastStatus: row.last_exit !== null && row.last_exit !== undefined && row.last_exit !== 0 ? 'failed' : row.last_status ?? null,
|
|
60
|
+
intervalMs: row.interval_ms, secondDueAt: cronSecondDueAt(row.cron, row.last_run) }, now.getTime()),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function filterLoopRows(rows: LoopsTableRow[], owner?: string): LoopsTableRow[] {
|
|
65
|
+
return owner ? rows.filter(row => row.owner !== '—' && row.owner.toLowerCase() === owner.toLowerCase()) : rows;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function formatLoopsTable(rows: LoopsTableRow[], verdictColor: (text: string) => string): string[] {
|
|
69
|
+
const columns = ['LAYER', 'NAME', 'OWNER', 'MODE', 'LAST RUN', 'VERDICT'];
|
|
70
|
+
const data = rows.map(r => [r.layer, r.name, r.owner, r.mode, r.lastRun ?? '—', r.verdict]);
|
|
71
|
+
const widths = columns.map((col, i) => Math.max(col.length, ...data.map(cells => cells[i]!.length)));
|
|
72
|
+
const format = (cells: string[]) => cells.map((cell, i) => cell.padEnd(widths[i]!)).join(' ');
|
|
73
|
+
return [format(columns), ...rows.map((row, i) => {
|
|
74
|
+
const cells = data[i]!;
|
|
75
|
+
return `${format(cells.slice(0, 5))} ${row.verdict === 'late' || row.verdict === 'failed' ? verdictColor(cells[5]!) : cells[5]}`;
|
|
76
|
+
})];
|
|
77
|
+
}
|
|
@@ -28,6 +28,7 @@ export interface CardTransport {
|
|
|
28
28
|
const IRREVERSIBLE = new Set(['money', 'publish', 'security', 'irreversible', 'secret']);
|
|
29
29
|
export const DECISION_DATA_PREFIX = 'dec:';
|
|
30
30
|
const REMIND_BEFORE_MS = 2 * 3600_000;
|
|
31
|
+
const WEBPUSH_MAX_SEND_ATTEMPTS = 3;
|
|
31
32
|
|
|
32
33
|
export function isIrreversible(entry: Pick<DecisionEntry, 'category'>): boolean {
|
|
33
34
|
return IRREVERSIBLE.has(entry.category);
|
|
@@ -99,7 +100,8 @@ interface CardState {
|
|
|
99
100
|
/** First start of this platform's service — decisions raised before it are not pushed (no burst of old items at
|
|
100
101
|
* deploy); they stay reachable through `/decisions`. */
|
|
101
102
|
since?: string;
|
|
102
|
-
cards: Record<string, { refs: CardRef[]; closed?: boolean; remindedAt?: string; note?: string }>;
|
|
103
|
+
cards: Record<string, { refs: CardRef[]; closed?: boolean; remindedAt?: string; note?: string; attempts?: number; noSubscribers?: boolean }>;
|
|
104
|
+
testOriginQuestions?: Record<string, true>;
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
export interface DecisionCardServiceOptions {
|
|
@@ -165,16 +167,27 @@ export class DecisionCardService {
|
|
|
165
167
|
for (const record of pending.questions) {
|
|
166
168
|
for (const question of record.questions) {
|
|
167
169
|
const data = { questionId: record.id, runId: record.runId };
|
|
170
|
+
const logSkip = (reason: string): void => debug.log('hitl.card-bridge', 'skipped', { ...data, reason });
|
|
171
|
+
// Path is not provenance: operational goals can live under the OS temporary directory too.
|
|
172
|
+
if (record.testOrigin === true) {
|
|
173
|
+
state.testOriginQuestions ??= {};
|
|
174
|
+
if (!state.testOriginQuestions[record.id]) {
|
|
175
|
+
state.testOriginQuestions[record.id] = true;
|
|
176
|
+
dirty = true;
|
|
177
|
+
debug.log('hitl.card-bridge', 'skipped', { ...data, reason: 'test-origin' });
|
|
178
|
+
}
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
168
181
|
if (record.expiresAt && record.expiresAt <= this.now().toISOString()) {
|
|
169
|
-
|
|
182
|
+
logSkip('expired');
|
|
170
183
|
continue;
|
|
171
184
|
}
|
|
172
185
|
if (question.impact !== 'high' && question.impact !== 'critical') {
|
|
173
|
-
|
|
186
|
+
logSkip('impact');
|
|
174
187
|
continue;
|
|
175
188
|
}
|
|
176
189
|
if (record.questions.length !== 1 || question.options.length < 2 || question.options.length > 4) {
|
|
177
|
-
|
|
190
|
+
logSkip('options');
|
|
178
191
|
continue;
|
|
179
192
|
}
|
|
180
193
|
if (seen.has(record.id)) {
|
|
@@ -200,7 +213,7 @@ export class DecisionCardService {
|
|
|
200
213
|
all.unshift(entry);
|
|
201
214
|
debug.log('hitl.card-bridge', 'raised', { ...data, decisionId: entry.id });
|
|
202
215
|
} catch (error) {
|
|
203
|
-
|
|
216
|
+
logSkip(error instanceof Error ? error.name : 'unknown');
|
|
204
217
|
}
|
|
205
218
|
}
|
|
206
219
|
}
|
|
@@ -208,13 +221,26 @@ export class DecisionCardService {
|
|
|
208
221
|
const chats = await this.opts.transport.ownerChats().catch(() => [] as string[]);
|
|
209
222
|
for (const e of all) {
|
|
210
223
|
const card = state.cards[e.id];
|
|
211
|
-
if (e.status === 'open' && !card
|
|
224
|
+
if (e.status === 'open' && (!card || (platform === 'webpush' && !card.refs.length && (card.attempts ?? 0) < WEBPUSH_MAX_SEND_ATTEMPTS))
|
|
225
|
+
&& e.options.length >= 2 && e.raisedAt !== undefined && e.raisedAt >= state.since) {
|
|
226
|
+
if (platform === 'webpush' && !chats.length) {
|
|
227
|
+
if (!card?.noSubscribers) {
|
|
228
|
+
state.cards[e.id] = { ...(card ?? { refs: [] }), noSubscribers: true };
|
|
229
|
+
dirty = true;
|
|
230
|
+
debug.log('decisions.webpush', 'no-subscribers', { id: e.id });
|
|
231
|
+
}
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
212
234
|
const refs: CardRef[] = [];
|
|
213
235
|
for (const chat of chats) {
|
|
214
236
|
const ref = await this.opts.transport.send(chat, renderCard(e)).catch(() => null);
|
|
215
237
|
if (ref) refs.push(ref);
|
|
216
238
|
}
|
|
217
|
-
if (
|
|
239
|
+
if (platform === 'webpush') {
|
|
240
|
+
state.cards[e.id] = { ...(card ?? { refs: [] }), refs, attempts: (card?.attempts ?? 0) + 1 };
|
|
241
|
+
dirty = true;
|
|
242
|
+
} else if (refs.length) state.cards[e.id] = { refs };
|
|
243
|
+
if (refs.length) sent++;
|
|
218
244
|
debug.log('decisions.telegram', 'card-sent', { platform, id: e.id, category: e.category, chats: chats.length, delivered: refs.length });
|
|
219
245
|
continue;
|
|
220
246
|
}
|
|
@@ -31,7 +31,15 @@ export interface DecisionEntry {
|
|
|
31
31
|
history: Array<{ type: 'raised' | 'options-added' | 'decided' | 'withdrawn'; at?: string; by: string; version?: Versions; choice?: string; reason?: string }>;
|
|
32
32
|
}
|
|
33
33
|
export type RaiseInput = Pick<DecisionEntry, 'title' | 'category' | 'scqa' | 'options' | 'recommendation' | 'raisedBy' | 'refs' | 'dueAt' | 'resume' | 'pendingQuestion' | 'crossCheck' | 'crossCheckSkipped' | 'alternative' | 'dissent'> & { raisedAt?: string };
|
|
34
|
-
type
|
|
34
|
+
export type Seat = 'OP' | 'MK' | 'TC' | 'UX';
|
|
35
|
+
export interface SeatDecisionRecord {
|
|
36
|
+
id: string; title: string; decision: string; seat: Seat; delegation: string;
|
|
37
|
+
reporting: 'posthoc'; recordedAt: string;
|
|
38
|
+
/** Present only when the actual decision time is known. */
|
|
39
|
+
decidedAt?: string; versionAtDecision?: Versions; refs?: string[];
|
|
40
|
+
}
|
|
41
|
+
export type SeatDecisionInput = Pick<SeatDecisionRecord, 'title' | 'decision' | 'seat' | 'delegation' | 'refs'> & { decidedAt?: string };
|
|
42
|
+
type Event = { type: 'raised'; entry: DecisionEntry } | { type: 'seat-recorded'; entry: SeatDecisionRecord } | { type: 'options-added'; id: string; at: string; options: DecisionOption[]; by: string } | { type: 'decided'; id: string; at?: string; by: DecisionActor; choice?: string; version?: Versions; note?: string } | { type: 'withdrawn'; id: string; at: string; reason: string; version: Versions };
|
|
35
43
|
export interface DecisionLedgerOptions extends VersionOptions { stateDir?: string; now?: () => Date; resolveVersion?: (at: string) => Versions; writeAnswer?: typeof writePendingQuestionAnswer }
|
|
36
44
|
|
|
37
45
|
/** null = the decision resumes no run; otherwise whether the answer file was written (or already matched). */
|
|
@@ -196,6 +204,7 @@ export class DecisionLedger {
|
|
|
196
204
|
if (filters.category && !CATEGORIES.includes(filters.category)) throw new Error('invalid category');
|
|
197
205
|
const map = new Map<string, DecisionEntry>();
|
|
198
206
|
for (const event of this.events()) {
|
|
207
|
+
if (event.type === 'seat-recorded') continue;
|
|
199
208
|
if (event.type === 'raised') {
|
|
200
209
|
if (map.has(event.entry.id)) throw new Error(`duplicate decision: ${event.entry.id}`);
|
|
201
210
|
map.set(event.entry.id, event.entry);
|
|
@@ -221,6 +230,36 @@ export class DecisionLedger {
|
|
|
221
230
|
&& (!filters.decidedBy || e.decidedBy?.kind === filters.decidedBy)
|
|
222
231
|
&& (!filters.category || e.category === filters.category)).sort((a, b) => (b.raisedAt ?? b.importedAt ?? '').localeCompare(a.raisedAt ?? a.importedAt ?? '') || a.id.localeCompare(b.id));
|
|
223
232
|
}
|
|
233
|
+
/** Delegated decisions are already made: they share the append-only ledger, never the owner's open-card state machine. */
|
|
234
|
+
recordSeatDecision(input: SeatDecisionInput): SeatDecisionRecord {
|
|
235
|
+
const seat = input.seat;
|
|
236
|
+
if (!['OP', 'MK', 'TC', 'UX'].includes(seat)) throw new Error('invalid seat');
|
|
237
|
+
const title = safe(single(input.title, 'title'));
|
|
238
|
+
const decision = safe(single(input.decision, 'decision'));
|
|
239
|
+
const delegation = safe(single(input.delegation, 'delegation'));
|
|
240
|
+
const refs = input.refs?.map(ref => safe(single(ref, 'ref')));
|
|
241
|
+
const decidedAt = input.decidedAt === undefined ? undefined : utc(input.decidedAt);
|
|
242
|
+
return this.locked(() => {
|
|
243
|
+
const existing = this.seatReport();
|
|
244
|
+
if (refs?.some(ref => existing.some(entry => entry.seat === seat && entry.title === title && entry.decision === decision && entry.refs?.includes(ref)))) throw new Error('seat decision source already recorded');
|
|
245
|
+
const recordedAt = utc(this.now().toISOString());
|
|
246
|
+
const prefix = `SD-${recordedAt.slice(0, 10).replaceAll('-', '')}-`;
|
|
247
|
+
const next = existing.reduce((n, entry) => entry.id.startsWith(prefix) ? Math.max(n, Number(entry.id.slice(prefix.length)) + 1) : n, 1);
|
|
248
|
+
const entry: SeatDecisionRecord = { id: `${prefix}${String(next).padStart(2, '0')}`, title, decision, seat, delegation,
|
|
249
|
+
reporting: 'posthoc', recordedAt, ...(decidedAt ? { decidedAt, versionAtDecision: this.version(decidedAt) } : {}), ...(refs?.length ? { refs } : {}) };
|
|
250
|
+
this.append({ type: 'seat-recorded', entry });
|
|
251
|
+
debug.log('decisions', 'seat-recorded', { id: entry.id, seat });
|
|
252
|
+
return entry;
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
seatReport(filters: { since?: string; seat?: Seat } = {}): SeatDecisionRecord[] {
|
|
256
|
+
const since = filters.since === undefined ? undefined : utc(filters.since);
|
|
257
|
+
if (filters.seat && !['OP', 'MK', 'TC', 'UX'].includes(filters.seat)) throw new Error('invalid seat');
|
|
258
|
+
return this.events().filter((event): event is Extract<Event, { type: 'seat-recorded' }> => event.type === 'seat-recorded')
|
|
259
|
+
.map(event => event.entry)
|
|
260
|
+
.filter(entry => (!since || (entry.recordedAt ?? entry.decidedAt ?? '') >= since) && (!filters.seat || entry.seat === filters.seat))
|
|
261
|
+
.sort((a, b) => (b.recordedAt ?? b.decidedAt ?? '').localeCompare(a.recordedAt ?? a.decidedAt ?? '') || a.id.localeCompare(b.id));
|
|
262
|
+
}
|
|
224
263
|
show(id: string): DecisionEntry { const entry = this.list({ status: 'all' }).find(e => e.id === id); if (!entry) throw new Error(`decision not found: ${id}`); return entry; }
|
|
225
264
|
raise(input: RaiseInput): DecisionEntry {
|
|
226
265
|
const clean = validate(input);
|