elanous 0.2.12 → 0.2.14
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/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-6e7e0377c1db0bdc.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/4562-071d8bac44a01069.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-2df489622a52a33a.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/{9906-482c6619b3fdda20.js → 8060-cc008cc6fedfa818.js} +1 -1
- 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/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-74383f890f075511.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-7b2b4f378354869a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-1abfe3aec3be6bdc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-1512d91c7491ed62.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-a627db94ec6904a0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-e6b84ab07d3567d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-fed69f9759d248d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-a67e11ba962337b5.js → page-15374909d8f1aebd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-012a742f81218b01.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-fd7e200d4d83fbd7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-99effd9d893a43dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-938de57ae0315e46.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-160089123a40dd91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-629e1ede482ec6eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-793fa536b3a6ef37.js → page-2ce5471926761fdd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-27e99b4e54a849d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-32cde713be47cddb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e7e10cd3386222e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-e64caff084f7ce21.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-db07eaffba0df7e3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e1e47d8d940aba3d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-86f91034bd5cabe5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.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-b3759cc14da80908.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-ce4d8a1215ce53a3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-58043db78ba519ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-357750dfb91bf960.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-e297866051595d81.js → page-274703bc41b71333.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-3b7e821dc492bdfd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-f8da5dc6f3752276.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2f810b18d89ecba6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.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/2e33713c444d5b39.css +1 -0
- package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +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/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/examples/teams/personas/backend.yaml +4 -0
- package/examples/teams/personas/pm.yaml +4 -0
- package/examples/teams/personas/qa.yaml +4 -0
- package/examples/teams/small-product.yaml +20 -0
- package/graphs/lecture/lecture-note.yaml +28 -0
- package/graphs/lecture/recipes.yaml +17 -0
- package/graphs/orchestrator/orchestrator.yaml +59 -0
- package/graphs/orchestrator/recipes.yaml +18 -0
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +4 -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 +2 -1
- package/scripts/audit-test-state-writes.ts +72 -17
- package/scripts/brand/check.ts +186 -0
- package/scripts/context-hooks/emit.ts +36 -14
- package/scripts/coord-channel-watch.sh +1 -0
- package/scripts/coord-post.sh +11 -1
- package/scripts/discord-seat-probe.ts +144 -0
- package/scripts/docs-cli-check.ts +23 -6
- package/scripts/expose-rubric-check.ts +118 -0
- package/scripts/expose-rubric-suggest.ts +163 -0
- package/scripts/heal/run-role.ts +14 -0
- package/scripts/hq/hq-standby.sh +65 -0
- package/scripts/hq/standby-promote.ts +173 -0
- package/scripts/hq/standby-role.ts +59 -0
- package/scripts/hq/standby-snapshot.ts +226 -0
- package/scripts/hq/standby-verify.ts +223 -0
- package/scripts/lecture-note/node.ts +256 -0
- package/scripts/lessons/handbook-page.ts +85 -0
- package/scripts/release-loop/gate-node.ts +62 -15
- package/scripts/release-loop/ops-upgrade-node.ts +17 -5
- 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 +196 -0
- package/scripts/release-story/draft.ts +172 -0
- package/scripts/rhythm/daily.ts +383 -0
- package/scripts/rhythm/weekly.ts +294 -0
- package/scripts/skills/public-check.ts +169 -0
- package/scripts/test-deterministic.ts +57 -11
- 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/omni-crawl/SKILL.md +16 -16
- package/skills/omni-crawl/package-lock.json +1 -544
- package/skills/omni-crawl/package.json +3 -4
- package/skills/omni-crawl/scripts/main.ts +13 -13
- package/skills/omni-crawl/scripts/monitor.ts +8 -8
- package/skills/omni-crawl/src/apify.ts +13 -2
- package/skills/omni-crawl/src/capture.ts +6 -0
- package/skills/omni-crawl/src/registry.ts +2 -2
- package/skills/omni-crawl/src/stdout-json.ts +4 -0
- package/skills/omni-digest/SKILL.md +10 -11
- package/skills/omni-digest/package-lock.json +0 -545
- package/skills/omni-digest/package.json +2 -5
- package/skills/omni-digest/scripts/main.ts +3 -3
- package/skills/omni-digest/src/delegate.ts +5 -5
- package/skills/omni-digest/src/fetch.ts +2 -2
- package/skills/omni-digest/src/media.ts +25 -11
- package/skills/omni-digest/src/visual.ts +3 -2
- package/skills/omni-market/scripts/main.ts +1 -1
- package/skills/omni-market/scripts/stdout-json.ts +4 -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/identity-env.ts +11 -0
- package/src/agent-mission/driver.ts +15 -10
- package/src/agent-mission/duo.ts +94 -0
- package/src/agent-mission/pty-decision.ts +1 -1
- package/src/autopilot/build/llm-conflict-merge.ts +45 -8
- package/src/autopilot/build/merge-intent.ts +53 -0
- package/src/autopilot/track-agent-shadow.ts +2 -1
- package/src/autopilot/track-agent.ts +4 -1
- package/src/boot/daemon-prompt-request.ts +3 -0
- package/src/boot/daemon-prompt-turn.ts +6 -2
- package/src/boot/daemon-runtime.ts +48 -16
- package/src/chat/index.ts +2 -0
- package/src/claims/claims-ledger.ts +232 -0
- package/src/claims/claims-render.ts +157 -0
- package/src/cli/claims-cli.ts +94 -0
- package/src/cli/decisions-cli.ts +84 -20
- package/src/cli/docs-cli.ts +2 -0
- package/src/cli/flow-cli.ts +11 -0
- package/src/cli/freeze-cli.ts +45 -0
- 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/outputs-cli.ts +92 -0
- package/src/cli/persona-cli.ts +29 -0
- package/src/cli/pod-cli.ts +10 -3
- package/src/cli/pr-cli.ts +44 -2
- package/src/cli/private-ledger-commands.ts +54 -4
- package/src/cli/pty-drive-verdict.ts +11 -2
- package/src/cli/release-cli.ts +248 -45
- package/src/cli/rfc-status.ts +91 -0
- package/src/connectors/linear.ts +5 -3
- package/src/context-bus/condense.ts +189 -0
- package/src/context-bus/context-day.ts +82 -0
- package/src/context-bus/context-now-public.ts +86 -0
- package/src/context-bus/context-now-surfaces.ts +70 -4
- package/src/context-bus/context-now.ts +4 -3
- package/src/context-bus/long-term-memory.ts +201 -24
- package/src/context-bus/nightly-condense.ts +1 -1
- 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 +13 -7
- package/src/decisions/decision-ledger.ts +83 -18
- 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/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-kinds/registry.ts +2 -2
- package/src/graph-runner/graph-approval-card.ts +2 -2
- package/src/graph-runner/graph-grow.ts +64 -11
- package/src/graph-runner/graph-tick.ts +13 -2
- package/src/graph-runner/runner.ts +111 -22
- package/src/harness/harness-cli-command.ts +178 -27
- package/src/harness/harness-incidents.ts +43 -12
- package/src/harness/harness-pod-dispatch.ts +2 -0
- package/src/harness/harness-queue-child.ts +37 -0
- package/src/harness/harness-queue.ts +494 -0
- package/src/harness/helper-scan.ts +139 -0
- package/src/harness/model-input-observation.ts +37 -0
- package/src/hooks/github-shadow.ts +72 -2
- package/src/hooks/receiver.ts +4 -2
- package/src/hq/hq.ts +318 -0
- package/src/hq/lease.ts +210 -0
- package/src/hq/ledger-write-target.ts +41 -0
- package/src/hq/promote.ts +301 -0
- package/src/index.ts +281 -27
- package/src/intake-plane/discord-seat-work.ts +13 -6
- package/src/intake-plane/linear-wish.ts +72 -0
- package/src/intake-plane/persona-answer.ts +5 -0
- package/src/intake-plane/telegram-seat-work.ts +13 -6
- 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/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 +32 -0
- package/src/loops/checker.ts +180 -0
- package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
- package/src/loops/loop-cli.ts +72 -6
- package/src/loops/neighbors.ts +40 -0
- package/src/loops/observe.ts +59 -0
- package/src/loops/orchestrator/author-depth.ts +164 -0
- package/src/loops/orchestrator/author-input-check.ts +77 -0
- package/src/loops/orchestrator/author-ledger.ts +230 -0
- package/src/loops/orchestrator/finish-rate.ts +175 -0
- package/src/loops/orchestrator/overlap.ts +180 -0
- package/src/loops/orchestrator/resync-shadow.ts +227 -0
- package/src/loops/orchestrator/tick.ts +302 -0
- package/src/loops/orchestrator/traffic.ts +165 -0
- package/src/loops/package/verify.ts +125 -0
- package/src/loops/registry.ts +454 -9
- package/src/loops/status-rows.ts +118 -0
- package/src/loops/verdict.ts +34 -0
- package/src/maturity/bot-slash-catalog.ts +66 -0
- package/src/maturity/feature-maturity.ts +8 -5
- package/src/memory.ts +37 -4
- package/src/nexus/api/card-followup-route.ts +118 -0
- package/src/nexus/api/dashboard.ts +6 -1
- package/src/nexus/api/decisions-route.ts +68 -0
- package/src/nexus/api/http-server.ts +65 -2
- package/src/nexus/api/merge-approvals-gate.ts +1 -1
- package/src/nexus/api/personas.ts +2 -1
- package/src/nexus/api/schedules-read.ts +5 -1
- package/src/nexus/api/skill-exec-route.ts +97 -0
- package/src/nexus/api/task-cards-api.ts +44 -5
- package/src/nexus/api/workflows.ts +2 -1
- package/src/persona/loader.ts +18 -0
- package/src/persona/persona-todo.ts +14 -9
- package/src/persona/presets.ts +2 -1
- package/src/persona/team.ts +105 -0
- package/src/persona/types.ts +4 -0
- package/src/pod-lease/host-lease.ts +130 -20
- package/src/release-loop/auto-start.ts +48 -0
- package/src/release-loop/checklist.ts +13 -4
- package/src/release-loop/feature-store.ts +28 -16
- package/src/release-loop/landed-but-yellow.ts +12 -0
- package/src/release-loop/landing-freeze.ts +121 -0
- package/src/release-loop/placement.ts +242 -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 +73 -15
- package/src/seat-dispatch/seat-questions.ts +58 -0
- package/src/seat-loop/seat-loop.ts +495 -46
- package/src/seat-loop/seat-pack.ts +127 -0
- package/src/self-dev/ask-launch-flow.ts +3 -0
- package/src/self-dev/dev-pipeline.ts +6 -2
- package/src/self-dev/draft-sweep.ts +76 -0
- package/src/self-dev/run-store.ts +73 -1
- package/src/self-dev/run-supervisor.ts +8 -0
- 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 +294 -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-lookup.ts +147 -0
- package/src/self-implement/heal-intake.ts +79 -0
- package/src/self-implement/host-regate.ts +25 -2
- package/src/self-implement/l8-merge-queue.ts +157 -1
- package/src/self-implement/orchestrator.ts +114 -10
- package/src/self-implement/revert-guard.ts +62 -0
- package/src/self-implement/run-ledger.ts +1 -1
- package/src/self-implement/seams.ts +28 -8
- package/src/session/index.ts +2 -0
- package/src/steward/launch.ts +5 -1
- package/src/steward/readiness.ts +125 -0
- package/src/steward/rescue.ts +75 -0
- package/src/steward/triage.ts +48 -9
- package/src/task-cards/card-cli.ts +86 -0
- package/src/task-cards/card-store.ts +10 -6
- package/src/task-orchestrator/surfaces/pod-lease.ts +23 -8
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
- package/src/task-orchestrator/surfaces/pod-pool.ts +27 -10
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +112 -18
- package/src/telegram-commands.ts +27 -3
- package/src/telegram-dispatch-forward.ts +1 -1
- package/src/telegram-fabric-plan.ts +90 -0
- package/src/telegram-loops-command.ts +44 -0
- package/src/telegram-project-command.ts +10 -4
- package/src/telegram.ts +5 -0
- package/src/tool-runtime/elanous-control-runtimes.ts +2 -1
- package/src/user-config.ts +204 -16
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/executor.ts +4 -0
- package/src/workflow-runtime/node-catalog.ts +13 -0
- package/src/workflow-runtime/nodes/knowledge.ts +38 -0
- package/src/workflow-runtime/schema.ts +17 -1
- package/src/workflow-runtime/storage.ts +31 -13
- package/src/workflow-runtime/types.ts +13 -1
- package/apps/pwa/out/_next/static/-6XrAoD3eU7NyMHAE4xPS/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1147-466ec15afacc1a65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1394-ee3795b112907122.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-2e522a8ac5f2afde.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/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/6751-bb3595948b2e311b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7277-0b2fc7c16ba2d64b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7758-0b018a9e4fc56200.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-0259994816604c49.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/9920-5cfdc5e8fd209825.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-cb169620945d1952.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-cefe8169e36cf62a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-d2e35228bc497119.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-5c05b0da4bc1392c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e0ccdb576812b523.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-813c4a52c9d05ca8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-4731f2c215358937.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-a022b257950f39e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-a2ffc717e04b8b0a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-b59ee4e70fc18fcf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-c172dabd796eb7a3.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-1efd0cb9e0ed9d13.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-d5dc7ff69a37b4cf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-e71c84189e8c683e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-1d6a533ce5a6efcb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-dfca9b7d2de0b135.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-6dd3bfbbde2a5c2c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-490653e8d4cc8852.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-77447356baad75d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-29e961191f6057df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-c822f55b884d9104.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fa4ccfc432424e52.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-6d604227e1664b71.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-9c7e8f744a765daf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-4048543b4b7ad5e0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4c3a3126abed551a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-64b0aecb1ca75e75.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4292d0149020e8a7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-db14d0be7f1c460c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-e5d8072cd7cfef73.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-9547b5cec7cf76bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e6dcb83265530636.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-45248f08d6aecae5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-2cddeb9e2aeedc55.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-baba2e047872fb0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-e895da4ec9cac345.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-4344faf20216233b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-698bb29f11864b89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-bd6a6dc1ddc273f2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2e9e2fb01c9da1c9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-d62ceb13411ebba4.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/{-6XrAoD3eU7NyMHAE4xPS → x6mjI6vqOkVVu0jFuDkz1}/_ssgManifest.js +0 -0
|
@@ -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
|
+
}
|
|
@@ -35,21 +35,27 @@ export function isIrreversible(entry: Pick<DecisionEntry, 'category'>): boolean
|
|
|
35
35
|
|
|
36
36
|
const kst = (at: string) => new Intl.DateTimeFormat('ko-KR', { timeZone: 'Asia/Seoul', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false }).format(new Date(at));
|
|
37
37
|
|
|
38
|
-
/** The card
|
|
38
|
+
/** The card opens with a short pitch; the full question and choice buttons remain below it. */
|
|
39
39
|
export function renderCardText(e: DecisionEntry, extra: { note?: string; confirm?: string } = {}): string {
|
|
40
|
+
const recommendedKey = 'option' in e.recommendation ? e.recommendation.option : undefined;
|
|
41
|
+
const recommended = e.options.find(o => o.key === recommendedKey);
|
|
42
|
+
const alternative = e.options.find(o => o.key === e.alternative);
|
|
40
43
|
const lines = [
|
|
41
44
|
`🗳 결정 요청 ${e.id}${isIrreversible(e) ? ' · ⚠️ 되돌릴 수 없음' : ''}`,
|
|
42
45
|
`«${e.title}» · 올린 자리: ${e.raisedBy.agent}${e.raisedBy.track ? `(${e.raisedBy.track})` : ''}`,
|
|
43
46
|
'',
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
`S: ${e.scqa.s.replace(/\s+/g, ' ')}`,
|
|
48
|
+
`C: ${e.scqa.c.replace(/\s+/g, ' ')}`,
|
|
49
|
+
`Q: ${(e.scqa.q ?? '(비움)').replace(/\s+/g, ' ')}`,
|
|
50
|
+
`A: ${(e.scqa.a ?? '(비움)').replace(/\s+/g, ' ')}`,
|
|
51
|
+
'skipped' in e.recommendation ? `권고 없음: ${e.recommendation.reason}` : `권고: ${recommended?.label ?? e.recommendation.option} — ${e.recommendation.why}`,
|
|
52
|
+
...(alternative ? [`대안: ${alternative.label} — ${alternative.consequence}`] : []),
|
|
53
|
+
e.crossCheck?.length ? `교차 확인: ${e.crossCheck.map(check => `${check.seat} ✓ ${check.note.replace(/\s+/g, ' ')}`).join(' · ')}` : `교차 확인 없음(${e.crossCheckSkipped ?? '미기재'})`,
|
|
54
|
+
...(e.dissent ? [`이견: ${e.dissent}`] : []),
|
|
55
|
+
...(e.dueAt ? [`기한: ${kst(e.dueAt)} KST`] : []),
|
|
46
56
|
...(e.pendingQuestion ? ['', `대기 질문 전문:\n${e.pendingQuestion}`] : []),
|
|
47
|
-
...(e.scqa.q ? [`질문: ${e.scqa.q}`] : []),
|
|
48
|
-
...(e.scqa.a ? [`제안: ${e.scqa.a}`] : []),
|
|
49
57
|
'',
|
|
50
58
|
...e.options.map((o) => `${o.key.toUpperCase()}) ${o.label} — ${o.consequence}`),
|
|
51
|
-
'skipped' in e.recommendation ? `권고 없음: ${e.recommendation.reason}` : `권고: ${e.recommendation.option.toUpperCase()} — ${e.recommendation.why}`,
|
|
52
|
-
...(e.dueAt ? [`기한: ${kst(e.dueAt)}`] : []),
|
|
53
59
|
...(extra.note ? ['', `📝 메모: ${extra.note}`] : []),
|
|
54
60
|
];
|
|
55
61
|
if (extra.confirm) {
|
|
@@ -22,12 +22,24 @@ export interface DecisionEntry {
|
|
|
22
22
|
pendingQuestion?: string;
|
|
23
23
|
/** DEC-TG — optional deadline (UTC). Cards remind the owner two hours before it. */
|
|
24
24
|
dueAt?: string;
|
|
25
|
+
crossCheck?: Array<{ seat: string; at: string; note: string }>;
|
|
26
|
+
crossCheckSkipped?: string;
|
|
27
|
+
alternative?: string;
|
|
28
|
+
dissent?: string;
|
|
25
29
|
decidedAt?: string; decidedBy?: DecisionActor; choice?: string; note?: string; versionAtDecision?: Versions;
|
|
26
30
|
withdrawnAt?: string; withdrawReason?: string;
|
|
27
31
|
history: Array<{ type: 'raised' | 'options-added' | 'decided' | 'withdrawn'; at?: string; by: string; version?: Versions; choice?: string; reason?: string }>;
|
|
28
32
|
}
|
|
29
|
-
export type RaiseInput = Pick<DecisionEntry, 'title' | 'category' | 'scqa' | 'options' | 'recommendation' | 'raisedBy' | 'refs' | 'dueAt' | 'resume' | 'pendingQuestion'> & { raisedAt?: string };
|
|
30
|
-
type
|
|
33
|
+
export type RaiseInput = Pick<DecisionEntry, 'title' | 'category' | 'scqa' | 'options' | 'recommendation' | 'raisedBy' | 'refs' | 'dueAt' | 'resume' | 'pendingQuestion' | 'crossCheck' | 'crossCheckSkipped' | 'alternative' | 'dissent'> & { raisedAt?: string };
|
|
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 };
|
|
31
43
|
export interface DecisionLedgerOptions extends VersionOptions { stateDir?: string; now?: () => Date; resolveVersion?: (at: string) => Versions; writeAnswer?: typeof writePendingQuestionAnswer }
|
|
32
44
|
|
|
33
45
|
/** null = the decision resumes no run; otherwise whether the answer file was written (or already matched). */
|
|
@@ -89,13 +101,27 @@ function validate(input: RaiseInput, historical = false): RaiseInput {
|
|
|
89
101
|
? { skipped: true as const, reason: safe(single(input.recommendation.reason, 'skip reason')) }
|
|
90
102
|
: { option: input.recommendation.option, why: safe(single(input.recommendation.why, 'recommendation why')) };
|
|
91
103
|
if ('option' in recommendation && !options.some(o => o.key === recommendation.option)) throw new Error('recommended option not found');
|
|
104
|
+
const alternative = input.alternative === undefined ? undefined : single(input.alternative, 'alternative');
|
|
105
|
+
if (alternative && !options.some(o => o.key === alternative)) throw new Error('alternative option not found');
|
|
106
|
+
if (alternative && 'option' in recommendation && alternative === recommendation.option) throw new Error('alternative must differ from recommendation');
|
|
107
|
+
if (input.crossCheck?.length && input.crossCheckSkipped !== undefined) throw new Error('cross-check and skipped reason cannot coexist');
|
|
108
|
+
if (input.crossCheck !== undefined && !Array.isArray(input.crossCheck)) throw new Error('cross-check must be a list');
|
|
109
|
+
const crossCheck = input.crossCheck?.map(check => {
|
|
110
|
+
const seat = single(check.seat, 'cross-check seat');
|
|
111
|
+
if (!['OP', 'MK', 'TC', 'UX'].includes(seat)) throw new Error('invalid cross-check seat');
|
|
112
|
+
return { seat, at: utc(check.at), note: safe(single(check.note, 'cross-check note')) };
|
|
113
|
+
});
|
|
114
|
+
const crossCheckSkipped = input.crossCheckSkipped === undefined ? undefined : safe(single(input.crossCheckSkipped, 'cross-check skipped reason'));
|
|
115
|
+
const dissent = input.dissent === undefined ? undefined : safe(single(input.dissent, 'dissent'));
|
|
92
116
|
if (input.resume && !/^(?:auq:[0-9a-z]+:[0-9a-z]{5}|execution:[A-Za-z0-9_-]+:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?![\s\S])/.test(input.resume.questionId)) throw new Error('invalid resume questionId');
|
|
93
117
|
const resume = input.resume ? { questionId: input.resume.questionId,
|
|
94
118
|
...(input.resume.runId === undefined ? {} : { runId: single(input.resume.runId, 'resume runId') }) } : undefined;
|
|
95
119
|
if (input.pendingQuestion !== undefined) required(input.pendingQuestion, 'pending question');
|
|
96
120
|
return { ...input, title, scqa: { s, c, ...(q ? { q } : {}), ...(a ? { a } : {}) }, options, recommendation,
|
|
97
121
|
...(input.pendingQuestion === undefined ? {} : { pendingQuestion: safe(input.pendingQuestion) }),
|
|
98
|
-
...(resume ? { resume } : {}),
|
|
122
|
+
...(resume ? { resume } : {}), ...(crossCheck ? { crossCheck } : {}),
|
|
123
|
+
...(crossCheckSkipped !== undefined ? { crossCheckSkipped } : {}),
|
|
124
|
+
...(alternative !== undefined ? { alternative } : {}), ...(dissent !== undefined ? { dissent } : {}),
|
|
99
125
|
raisedBy: { agent: safe(single(input.raisedBy.agent, 'agent')), ...(input.raisedBy.track ? { track: input.raisedBy.track } : {}), ...(input.raisedBy.session ? { session: safe(single(input.raisedBy.session, 'session')) } : {}) },
|
|
100
126
|
...(input.refs ? { refs: input.refs.map(r => safe(single(r, 'ref'))) } : {}),
|
|
101
127
|
...(input.dueAt ? { dueAt: utc(input.dueAt) } : {}) };
|
|
@@ -178,6 +204,7 @@ export class DecisionLedger {
|
|
|
178
204
|
if (filters.category && !CATEGORIES.includes(filters.category)) throw new Error('invalid category');
|
|
179
205
|
const map = new Map<string, DecisionEntry>();
|
|
180
206
|
for (const event of this.events()) {
|
|
207
|
+
if (event.type === 'seat-recorded') continue;
|
|
181
208
|
if (event.type === 'raised') {
|
|
182
209
|
if (map.has(event.entry.id)) throw new Error(`duplicate decision: ${event.entry.id}`);
|
|
183
210
|
map.set(event.entry.id, event.entry);
|
|
@@ -203,25 +230,63 @@ export class DecisionLedger {
|
|
|
203
230
|
&& (!filters.decidedBy || e.decidedBy?.kind === filters.decidedBy)
|
|
204
231
|
&& (!filters.category || e.category === filters.category)).sort((a, b) => (b.raisedAt ?? b.importedAt ?? '').localeCompare(a.raisedAt ?? a.importedAt ?? '') || a.id.localeCompare(b.id));
|
|
205
232
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const
|
|
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);
|
|
209
242
|
return this.locked(() => {
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const entry: DecisionEntry = { ...clean, id, raisedAt: at, version, status: 'open', history: [{ type: 'raised', at, by: clean.raisedBy.agent, version }] };
|
|
220
|
-
this.append({ type: 'raised', entry });
|
|
221
|
-
debug.log('decisions', 'raised', { id, category: entry.category, by: entry.raisedBy.agent });
|
|
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 });
|
|
222
252
|
return entry;
|
|
223
253
|
});
|
|
224
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
|
+
}
|
|
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; }
|
|
264
|
+
raise(input: RaiseInput): DecisionEntry {
|
|
265
|
+
const clean = validate(input);
|
|
266
|
+
return this.locked(() => this.raiseLocked(clean));
|
|
267
|
+
}
|
|
268
|
+
/** Reuse a card with the same source reference, including across competing raisers. */
|
|
269
|
+
raiseOnce(input: RaiseInput, ref: string): DecisionEntry {
|
|
270
|
+
const clean = validate(input);
|
|
271
|
+
if (!clean.refs?.includes(ref)) throw new Error('decision source reference required');
|
|
272
|
+
return this.locked(() => this.list({ status: 'all' }).find(entry => entry.refs?.includes(ref)) ?? this.raiseLocked(clean));
|
|
273
|
+
}
|
|
274
|
+
private raiseLocked(clean: RaiseInput): DecisionEntry {
|
|
275
|
+
const at = utc(clean.raisedAt ?? this.now().toISOString());
|
|
276
|
+
const day = at.slice(0, 10).replaceAll('-', '');
|
|
277
|
+
const prefix = `D-${day}-`;
|
|
278
|
+
let next = 1;
|
|
279
|
+
for (const entry of this.list({ status: 'all' })) {
|
|
280
|
+
if (entry.id.startsWith(prefix)) next = Math.max(next, Number(entry.id.slice(prefix.length)) + 1);
|
|
281
|
+
}
|
|
282
|
+
const id = `${prefix}${String(next).padStart(2, '0')}`;
|
|
283
|
+
const version = this.version(at);
|
|
284
|
+
const entry: DecisionEntry = { ...clean, id, raisedAt: at, version, status: 'open', history: [{ type: 'raised', at, by: clean.raisedBy.agent, version }] };
|
|
285
|
+
this.append({ type: 'raised', entry });
|
|
286
|
+
debug.log('decisions', 'raised', { id, category: entry.category, by: entry.raisedBy.agent });
|
|
287
|
+
debug.log('decisions', 'raised-crosscheck', { id, seats: entry.crossCheck?.length ?? 0, skipped: entry.crossCheckSkipped ?? null });
|
|
288
|
+
return entry;
|
|
289
|
+
}
|
|
225
290
|
/** Record the decision and say whether its answer reached the waiting run (null = the decision resumes nothing). */
|
|
226
291
|
decideWithDelivery(id: string, choice: string, by: DecisionActor, note?: string, decidedAt?: string): { entry: DecisionEntry; delivery: AnswerDelivery } {
|
|
227
292
|
return this.decideLocked(id, choice, by, note, decidedAt);
|
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
|
|
23
23
|
import { debug } from '../debug/log.js';
|
|
24
24
|
import { botAudienceFor, gateDiscordSchemas } from '../maturity/bot-command-maturity.js';
|
|
25
|
+
import { buildBotSlashCatalog } from '../maturity/bot-slash-catalog.js';
|
|
26
|
+
import { FEATURE_MATURITY } from '../maturity/feature-maturity.js';
|
|
27
|
+
import { SLASH_COMMANDS } from '../chat/index.js';
|
|
25
28
|
import type { DiscordBot } from '../discord.js';
|
|
26
29
|
import {
|
|
27
30
|
loadLayeredPersonaDirs,
|
|
@@ -195,6 +198,19 @@ export async function wireSprint21Runtime(opts: WireSprint21Opts): Promise<Sprin
|
|
|
195
198
|
router.bind(statusCommand);
|
|
196
199
|
router.bind(pollCommand);
|
|
197
200
|
for (const command of botCommands) router.bind(command);
|
|
201
|
+
const catalog = buildBotSlashCatalog({
|
|
202
|
+
surface: 'discord', coreCommands: SLASH_COMMANDS, maturity: FEATURE_MATURITY,
|
|
203
|
+
handledCommands: router.schemas(),
|
|
204
|
+
});
|
|
205
|
+
for (const entry of catalog.commands) {
|
|
206
|
+
if (entry.supported) continue;
|
|
207
|
+
const { name, description } = entry;
|
|
208
|
+
router.bind({ schema: { name, description }, handler: async () => ({
|
|
209
|
+
type: RESPONSE_CHANNEL_MESSAGE_WITH_SOURCE,
|
|
210
|
+
content: catalog.unsupportedReply(entry.name)!,
|
|
211
|
+
ephemeral: true,
|
|
212
|
+
}) });
|
|
213
|
+
}
|
|
198
214
|
|
|
199
215
|
// 5. Wire bot callbacks — INTERACTION_CREATE + reactions
|
|
200
216
|
// (Bot must be created with onInteraction/onReaction in opts;
|
|
@@ -64,6 +64,8 @@ import { getUserConfig } from './user-config.js';
|
|
|
64
64
|
import { detectCard, runCardFollowup, NotACardError } from './card-followup/core.js';
|
|
65
65
|
import { localOcrText } from './telegram-card-followup.js';
|
|
66
66
|
import type { runGraph } from './graph-runner/runner.js';
|
|
67
|
+
import { readSlashContextNow, renderTelegramNow } from './context-bus/context-now-surfaces.js';
|
|
68
|
+
import type { ContextNowDeps } from './context-bus/context-now.js';
|
|
67
69
|
|
|
68
70
|
const SLASH_FOCUS_TURNS_DEFAULT = 8;
|
|
69
71
|
|
|
@@ -139,6 +141,7 @@ export interface DiscordSelfMessageDeps {
|
|
|
139
141
|
questionChannelFor?: (channelId: string) => import('./hitl/question.js').QuestionChannel;
|
|
140
142
|
log?: (msg: string) => void;
|
|
141
143
|
cardFollowupDeps?: { ocrText?: (path: string) => Promise<string | null>; runGraph?: typeof runGraph; rootDir?: () => string };
|
|
144
|
+
nowDeps?: ContextNowDeps;
|
|
142
145
|
}
|
|
143
146
|
|
|
144
147
|
/** Compose the full discord self+interweave onMessage handler. */
|
|
@@ -356,6 +359,11 @@ export function buildDiscordSelfOnMessage(deps: DiscordSelfMessageDeps): DcMessa
|
|
|
356
359
|
// Voice text commands pass the guild gate for the voice adapter's
|
|
357
360
|
// sake — never route them into the LLM turn (legacy behavior).
|
|
358
361
|
if (/^\/voice-(join|leave|status)\b/.test(ctx.text.trim())) return undefined;
|
|
362
|
+
const nowCommand = /^[/!]now(?:\s+([\s\S]*))?$/.exec(ctx.text.trim());
|
|
363
|
+
if (nowCommand) {
|
|
364
|
+
const args = nowCommand[1]?.trim().split(/\s+/).filter(Boolean) ?? [];
|
|
365
|
+
return renderTelegramNow(readSlashContextNow(args, deps.nowDeps));
|
|
366
|
+
}
|
|
359
367
|
if (!ctx.text.trim() && ctx.attachments.length === 1 && ctx.attachments[0]!.contentType?.startsWith('image/')) {
|
|
360
368
|
const photo = ctx.attachments[0]!;
|
|
361
369
|
if (detectCard({ width: photo.width, height: photo.height, ocrText: null }).decision !== 'skip') {
|
|
@@ -16,12 +16,15 @@
|
|
|
16
16
|
// 우회해 도착하므로 여기서 allowlist를 직접 강제한다.
|
|
17
17
|
|
|
18
18
|
import type { UserConfig } from './user-config.js';
|
|
19
|
+
import { SLASH_COMMANDS } from './chat/index.js';
|
|
19
20
|
import type { DiscordBot, DcIncoming, DcMessageStreamer } from './discord.js';
|
|
20
21
|
import { makeCommandRest } from './discord/slash-registry.js';
|
|
21
22
|
import { normalizeInteractionPayload } from './discord/slash-router.js';
|
|
22
23
|
import type { SlashCommandSchema } from './discord/slash-types.js';
|
|
23
24
|
import { debug } from './debug/log.js';
|
|
24
25
|
import { gateDiscordSchemas, readBotAudience } from './maturity/bot-command-maturity.js';
|
|
26
|
+
import { buildBotSlashCatalog } from './maturity/bot-slash-catalog.js';
|
|
27
|
+
import { FEATURE_MATURITY } from './maturity/feature-maturity.js';
|
|
25
28
|
import { resolveChannelBotToken } from './channel-bot-token.js';
|
|
26
29
|
|
|
27
30
|
const REST_BASE = 'https://discord.com/api/v10';
|
|
@@ -42,6 +45,9 @@ export const ELANOUS_SLASH_COMMANDS: readonly SlashCommandSchema[] = [
|
|
|
42
45
|
{ name: 'file', description: '첨부 (이미지/문서 — 백엔드가 봄)', type: 11, required: false },
|
|
43
46
|
] },
|
|
44
47
|
{ name: 'brain', description: '브레인 모드로 복귀 (위임 해제)' },
|
|
48
|
+
{ name: 'now', description: '지금 판·칸·결정·자리·최근 맥락 보기', options: [
|
|
49
|
+
{ name: 'topic', description: '좁혀 볼 주제 (선택)', type: 3, required: false },
|
|
50
|
+
] },
|
|
45
51
|
{ name: 'sessions', description: '바인딩된 세션 목록' },
|
|
46
52
|
{ name: 'new', description: '새 세션으로 시작 (이전 대화 보존)' },
|
|
47
53
|
{ name: 'attach', description: '이 채널을 다른 세션에 연결', options: [
|
|
@@ -58,6 +64,15 @@ export const ELANOUS_SLASH_COMMANDS: readonly SlashCommandSchema[] = [
|
|
|
58
64
|
{ name: 'voice-status', description: '보이스 연결 상태' },
|
|
59
65
|
] as const;
|
|
60
66
|
|
|
67
|
+
/** The native wire keeps its original schemas (including options); only new catalog
|
|
68
|
+
* entries use the core registry's description and the unsupported response. */
|
|
69
|
+
export function discordWireCatalog() {
|
|
70
|
+
return buildBotSlashCatalog({
|
|
71
|
+
surface: 'discord', coreCommands: SLASH_COMMANDS, maturity: FEATURE_MATURITY,
|
|
72
|
+
handledCommands: ELANOUS_SLASH_COMMANDS,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
61
76
|
/** SlashInteraction → 기존 텍스트 명령 문장 합성. */
|
|
62
77
|
export function synthesizeCommandText(
|
|
63
78
|
commandName: string,
|
|
@@ -79,6 +94,10 @@ export function synthesizeCommandText(
|
|
|
79
94
|
case 'voice-leave': return '!voice-leave';
|
|
80
95
|
case 'voice-status': return '!voice-status';
|
|
81
96
|
case 'sessions': return '!sessions';
|
|
97
|
+
case 'now': {
|
|
98
|
+
const topic = options.get('topic');
|
|
99
|
+
return typeof topic === 'string' && topic.trim() ? `!now ${topic.trim()}` : '!now';
|
|
100
|
+
}
|
|
82
101
|
case 'new': return '!new';
|
|
83
102
|
case 'attach': return `!attach ${String(options.get('prefix') ?? '').trim()}`;
|
|
84
103
|
case 'brain': return '!brain';
|
|
@@ -123,8 +142,16 @@ export function buildDiscordSlashWire(deps: DiscordSlashWireDeps): DiscordSlashW
|
|
|
123
142
|
const guilds = await (await fetchImpl(`${REST_BASE}/users/@me/guilds`, auth)).json() as Array<{ id: string }>;
|
|
124
143
|
const rest = makeCommandRest({ token, ...(deps.__fetchImpl ? { fetchImpl: deps.__fetchImpl } : {}) });
|
|
125
144
|
const audience = readBotAudience((deps.userConfig.raw?.discord as { commandAudience?: unknown } | undefined)?.commandAudience);
|
|
126
|
-
const
|
|
127
|
-
|
|
145
|
+
const catalog = discordWireCatalog();
|
|
146
|
+
const handled = new Map(ELANOUS_SLASH_COMMANDS.map((schema) => [schema.name, schema]));
|
|
147
|
+
const allSchemas: SlashCommandSchema[] = catalog.commands.map((entry) => {
|
|
148
|
+
const original = handled.get(entry.name);
|
|
149
|
+
if (original) return original;
|
|
150
|
+
const { name, description } = entry;
|
|
151
|
+
return { name, description };
|
|
152
|
+
});
|
|
153
|
+
const { schemas, hidden } = gateDiscordSchemas(allSchemas, audience);
|
|
154
|
+
debug.log('discord.command', 'menu-filtered', { total: allSchemas.length, shown: schemas.length - hidden, ...audience });
|
|
128
155
|
for (const g of Array.isArray(guilds) ? guilds : []) {
|
|
129
156
|
const registered = await rest.bulkOverwriteGuild(app.id, g.id, schemas);
|
|
130
157
|
log(`[slash] registered ${registered.length} commands in guild ${g.id}`);
|
|
@@ -180,6 +207,8 @@ export function buildDiscordSlashWire(deps: DiscordSlashWireDeps): DiscordSlashW
|
|
|
180
207
|
await ack(it.id, it.token, `이 봇은 <#${deps.channelScope}> 채널 전용입니다.`);
|
|
181
208
|
return;
|
|
182
209
|
}
|
|
210
|
+
const unsupported = discordWireCatalog().unsupportedReply(it.commandName);
|
|
211
|
+
if (unsupported) { await ack(it.id, it.token, unsupported); return; }
|
|
183
212
|
const text = synthesizeCommandText(it.commandName, it.options);
|
|
184
213
|
const attachments = resolvedAttachments(raw);
|
|
185
214
|
await ack(it.id, it.token, `▶ \`${text}\`${attachments.length ? ` (+첨부 ${attachments.length})` : ''}`);
|
|
@@ -448,7 +448,7 @@ function reportUndeliverable(kind: string, daemonPath: DaemonPathClass | 'not-fo
|
|
|
448
448
|
try { debug.log('outbound.send', 'undeliverable', { kind: safeObservationLabel(kind), daemonPath, universe, root }); } catch { /* fail-soft */ }
|
|
449
449
|
const hint = universe === 'prod'
|
|
450
450
|
? '운영 데몬이 떠 있는지 확인: elanous nexus show'
|
|
451
|
-
: '운영으로 보내려면 설치본 elanous 로 실행하거나 ELANOUS_STATE_DIR=~/.elanous
|
|
451
|
+
: '운영으로 보내려면 설치본 elanous 로 실행하거나 ELANOUS_STATE_DIR=~/.elanous 와 --config-dir ~/.elanous 를 준다';
|
|
452
452
|
try { console.error(`[outbound] ⛔ 못 보냄(${kind}) — 데몬 ${daemonPath} · 직접 발송도 실패(토큰 없음 또는 전송 실패) · 이 프로세스의 우주 ${universe} (${root}) · ${hint}`); } catch { /* fail-soft */ }
|
|
453
453
|
}
|
|
454
454
|
|
|
@@ -15,18 +15,41 @@
|
|
|
15
15
|
// Resolution order:
|
|
16
16
|
// 1. Programmatic override set via `setElanousConfigDir(dir)` (incl.
|
|
17
17
|
// from the `--config-dir` CLI flag).
|
|
18
|
-
// 2.
|
|
18
|
+
// 2. State-dir via `effectiveInstanceRoot()` (including isolated source trees).
|
|
19
|
+
// 3. `~/.elanous` for the installed daily-driver root.
|
|
19
20
|
|
|
20
21
|
import { homedir } from 'node:os';
|
|
21
|
-
import {
|
|
22
|
+
import { resolve } from 'node:path';
|
|
22
23
|
|
|
23
24
|
let override: string | undefined;
|
|
25
|
+
let checkedIgnoredEnv = false;
|
|
26
|
+
|
|
27
|
+
function normalizedDir(dir: string): string {
|
|
28
|
+
const trimmed = dir.trim();
|
|
29
|
+
const expanded = trimmed === '~' ? homedir() : trimmed.startsWith('~/') ? `${homedir()}${trimmed.slice(1)}` : trimmed;
|
|
30
|
+
return resolve(expanded);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function warnIfIgnoredEnv(resolved: string, source: 'flag' | 'state-dir' | 'default'): void {
|
|
34
|
+
if (checkedIgnoredEnv) return;
|
|
35
|
+
checkedIgnoredEnv = true;
|
|
36
|
+
const env = process.env.ELANOUS_CONFIG_DIR;
|
|
37
|
+
if (env === undefined || normalizedDir(env) === normalizedDir(resolved)) return;
|
|
38
|
+
console.error(`ELANOUS_CONFIG_DIR 는 읽지 않습니다 — 설정 폴더 = ${resolved} · 그 폴더를 쓰려면 --config-dir ${normalizedDir(env)}`);
|
|
39
|
+
try {
|
|
40
|
+
const { debug } = require('./debug/log.js') as typeof import('./debug/log.js');
|
|
41
|
+
debug.log('config.dir', 'env-ignored', { env, resolved, source });
|
|
42
|
+
} catch { /* logging must not change config resolution */ }
|
|
43
|
+
}
|
|
24
44
|
|
|
25
45
|
/** Resolve the current elanous config / daemon root directory. Caller
|
|
26
46
|
* decides what subpath to append (`workflows/`, `config.json`,
|
|
27
47
|
* `acp-token`, ...). */
|
|
28
48
|
export function getElanousConfigDir(): string {
|
|
29
|
-
if (override !== undefined)
|
|
49
|
+
if (override !== undefined) {
|
|
50
|
+
warnIfIgnoredEnv(override, 'flag');
|
|
51
|
+
return override;
|
|
52
|
+
}
|
|
30
53
|
// ★ §4d (P3 · 2026-07-26) — **config-dir 은 state-dir 을 따라간다.**
|
|
31
54
|
//
|
|
32
55
|
// `ELANOUS_STATE_DIR` 은 env 라 전 자손에 자동 전파되는데 config-dir 은 argv 재부착 **7곳**
|
|
@@ -41,7 +64,9 @@ export function getElanousConfigDir(): string {
|
|
|
41
64
|
// (`configDirFollowingStateDir` 은 §4d 계약을 문서화·테스트하는 순수 함수로 남는다 —
|
|
42
65
|
// 실제 해석 경로는 이 리졸버 하나다.)
|
|
43
66
|
const { effectiveInstanceRoot } = require('./instance/resolve.js') as typeof import('./instance/resolve.js');
|
|
44
|
-
|
|
67
|
+
const resolved = effectiveInstanceRoot();
|
|
68
|
+
warnIfIgnoredEnv(resolved, process.env.ELANOUS_STATE_DIR?.trim() ? 'state-dir' : 'default');
|
|
69
|
+
return resolved;
|
|
45
70
|
}
|
|
46
71
|
|
|
47
72
|
/** Programmatic override. Trims, rejects empty/blank. Child processes
|