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,173 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { listCoordEvents } from '../../context-bus/coord-events.js';
|
|
4
|
+
import { CardStore, cardIndexPath, type TaskCard } from '../../task-cards/card-store.js';
|
|
5
|
+
import { loadRunLedger, resolveFederatedRunLedgerDirectories, type RunLedgerEntry } from '../../self-implement/run-ledger.js';
|
|
6
|
+
import { listLoops } from '../../loops/registry.js';
|
|
7
|
+
import { listSubSeatCharters } from './sub-seat-charters.js';
|
|
8
|
+
import { SEATS } from './ops-seats.js';
|
|
9
|
+
import { jsonResponse } from './json-response.js';
|
|
10
|
+
|
|
11
|
+
export interface LoopEdge {
|
|
12
|
+
at: string;
|
|
13
|
+
kind: 'request' | 'decision' | 'report' | 'card' | 'run';
|
|
14
|
+
from: string;
|
|
15
|
+
to: string;
|
|
16
|
+
ref: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface LoopEdgesDeps {
|
|
20
|
+
listCoord?: typeof listCoordEvents;
|
|
21
|
+
cardRoot?: string;
|
|
22
|
+
ledgerDir?: string;
|
|
23
|
+
listCards?: () => TaskCard[];
|
|
24
|
+
listRunStarts?: (since: string) => Array<{ runId: string; at: string; seat: string }>;
|
|
25
|
+
listLoopOwners?: () => Array<{ id: string; owner?: string | null }>;
|
|
26
|
+
seatIds?: () => string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const ID = /^[a-zA-Z0-9_-]{1,128}$/;
|
|
30
|
+
const SURFACES = new Set(['telegram', 'pwa', 'linear', 'tui', 'discord', 'api', 'cli', 'voice', 'folder', 'wish']);
|
|
31
|
+
const KINDS: Record<string, LoopEdge['kind']> = { '요청': 'request', '결정': 'decision', '보고': 'report' };
|
|
32
|
+
|
|
33
|
+
function seat(value: unknown, seats: ReadonlySet<string>): value is string {
|
|
34
|
+
return typeof value === 'string' && seats.has(value);
|
|
35
|
+
}
|
|
36
|
+
function surface(value: unknown): value is string {
|
|
37
|
+
return typeof value === 'string' && SURFACES.has(value);
|
|
38
|
+
}
|
|
39
|
+
function object(value: unknown): Record<string, unknown> | null {
|
|
40
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null;
|
|
41
|
+
}
|
|
42
|
+
function parse(content: string): Record<string, unknown> | null {
|
|
43
|
+
try { return object(JSON.parse(content) as unknown); } catch { return null; }
|
|
44
|
+
}
|
|
45
|
+
function inWindow(at: string, since: number, now: number): boolean {
|
|
46
|
+
const ms = Date.parse(at);
|
|
47
|
+
return Number.isFinite(ms) && ms >= since && ms <= now;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function cardEdges(card: TaskCard, since: number, now: number, seats: ReadonlySet<string>): LoopEdge[] {
|
|
51
|
+
if (!card.goalId.startsWith('wish:') || !ID.test(card.id)) return [];
|
|
52
|
+
const node = `card:${card.id}`;
|
|
53
|
+
const ref = card.id;
|
|
54
|
+
const edges: LoopEdge[] = [];
|
|
55
|
+
const intake = card.sections.find(s => s.key === 'intake:wish:0');
|
|
56
|
+
const intakeData = intake && parse(intake.content);
|
|
57
|
+
if (intake && surface(intakeData?.source) && inWindow(intake.createdAt, since, now)) {
|
|
58
|
+
edges.push({ at: intake.createdAt, kind: 'card', from: `surface:${intakeData.source}`, to: node, ref });
|
|
59
|
+
}
|
|
60
|
+
const split = card.sections.find(s => s.key === 'flow:split:0');
|
|
61
|
+
const splitData = split && parse(split.content);
|
|
62
|
+
const cells = Array.isArray(splitData?.cells) ? splitData.cells : [];
|
|
63
|
+
const owners = new Map<string, string>();
|
|
64
|
+
for (const item of cells) {
|
|
65
|
+
const cell = object(item);
|
|
66
|
+
if (typeof cell?.id === 'string' && ID.test(cell.id) && seat(cell.owner, seats)) owners.set(cell.id, cell.owner);
|
|
67
|
+
}
|
|
68
|
+
const placedOwners = new Set<string>();
|
|
69
|
+
for (const section of card.sections) {
|
|
70
|
+
if (!section.key.startsWith('flow:placed:')) continue;
|
|
71
|
+
const cellId = section.key.slice('flow:placed:'.length);
|
|
72
|
+
const saved = parse(section.content);
|
|
73
|
+
const placement = object(saved?.decision);
|
|
74
|
+
const owner = owners.get(cellId);
|
|
75
|
+
if (!owner || !placement || placement.id !== cellId || !ID.test(cellId)
|
|
76
|
+
|| typeof placement.version !== 'string' || !/^\d+\.\d+\.\d+$/.test(placement.version)) continue;
|
|
77
|
+
placedOwners.add(owner);
|
|
78
|
+
if (inWindow(section.createdAt, since, now)) {
|
|
79
|
+
const cellNode = `loop:${cellId}`;
|
|
80
|
+
edges.push({ at: section.createdAt, kind: 'card', from: node, to: cellNode, ref });
|
|
81
|
+
edges.push({ at: section.createdAt, kind: 'card', from: cellNode, to: owner, ref });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const reply = card.sections.find(s => s.key === 'intake:reply:0');
|
|
85
|
+
const replyData = reply && parse(reply.content);
|
|
86
|
+
// The destination section alone is not a send; flow:result records a completed card flow.
|
|
87
|
+
const result = card.sections.find(s => s.key === 'flow:result:0');
|
|
88
|
+
if (result && reply && surface(replyData?.surface) && inWindow(result.createdAt, since, now)) {
|
|
89
|
+
for (const owner of placedOwners) {
|
|
90
|
+
edges.push({ at: result.createdAt, kind: 'card', from: owner, to: `surface:${replyData.surface}`, ref });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return edges;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function runStarts(since: string, dir: string, owners: ReadonlyMap<string, string>, seats: ReadonlySet<string>): Array<{ runId: string; at: string; seat: string }> {
|
|
97
|
+
if (!existsSync(dir)) return [];
|
|
98
|
+
const result: Array<{ runId: string; at: string; seat: string }> = [];
|
|
99
|
+
let names: string[];
|
|
100
|
+
try { names = readdirSync(dir); } catch { return []; }
|
|
101
|
+
for (const name of names) {
|
|
102
|
+
if (!/^run-[a-zA-Z0-9-]+\.jsonl$/.test(name)) continue;
|
|
103
|
+
const path = join(dir, name);
|
|
104
|
+
try { if (!statSync(path).isFile() || statSync(path).size > 5 * 1024 * 1024) continue; }
|
|
105
|
+
catch { continue; }
|
|
106
|
+
const runId = name.slice(0, -6);
|
|
107
|
+
let entries: RunLedgerEntry[] | null;
|
|
108
|
+
try { entries = loadRunLedger(runId, dir); } catch { continue; }
|
|
109
|
+
const start = entries?.find(entry => entry.event === 'start' && entry.runId === runId);
|
|
110
|
+
const feature = typeof start?.data.feature === 'string' ? start.data.feature : '';
|
|
111
|
+
const launchSeat = start?.data.launchSeat ?? start?.data.seat ?? owners.get(feature)
|
|
112
|
+
?? start?.data.originAgent ?? start?.data.controller;
|
|
113
|
+
if (start?.timestamp && start.timestamp >= since && seat(launchSeat, seats)) {
|
|
114
|
+
result.push({ runId, at: start.timestamp, seat: launchSeat });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** GET only; project an allowlist of fields from the three ledgers. Never serialize ledger records. */
|
|
121
|
+
export function handleLoopEdgesGet(req: Request, deps: LoopEdgesDeps = {}): Response {
|
|
122
|
+
const params = new URL(req.url).searchParams;
|
|
123
|
+
const rawSince = params.get('since');
|
|
124
|
+
const since = rawSince === null ? Date.now() - 60 * 60 * 1000 : Date.parse(rawSince);
|
|
125
|
+
const rawLimit = params.get('limit');
|
|
126
|
+
const limit = rawLimit === null ? 100 : Number(rawLimit);
|
|
127
|
+
const now = Date.now();
|
|
128
|
+
if (rawSince !== null && !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$/.test(rawSince)
|
|
129
|
+
|| !Number.isFinite(since) || since > now || !Number.isSafeInteger(limit) || limit < 1 || limit > 500) {
|
|
130
|
+
return jsonResponse({ error: 'usage: GET /v1/loops/edges?since=<ISO>&limit=<1..500>' }, 400);
|
|
131
|
+
}
|
|
132
|
+
const sinceIso = new Date(since).toISOString();
|
|
133
|
+
const seats = new Set((deps.seatIds ?? (() => [...SEATS.map(s => s.seat), ...listSubSeatCharters().map(s => s.id)]))());
|
|
134
|
+
const edges: LoopEdge[] = [];
|
|
135
|
+
for (const event of (deps.listCoord ?? listCoordEvents)({ since: sinceIso, channelOnly: true })) {
|
|
136
|
+
if (!seat(event.refs.seat, seats) || !inWindow(event.at, since, now)) continue;
|
|
137
|
+
const kind = KINDS[event.refs.kind ?? ''];
|
|
138
|
+
if (!kind || !Array.isArray(event.refs.recipients)) continue;
|
|
139
|
+
const ref = typeof event.refs.url === 'string' && /^https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/(?:pull|issues)\/\d+#issuecomment-\d+$/.test(event.refs.url)
|
|
140
|
+
? event.refs.url : event.id;
|
|
141
|
+
if (!ID.test(ref) && !ref.startsWith('https://github.com/')) continue;
|
|
142
|
+
for (const recipient of new Set(event.refs.recipients)) {
|
|
143
|
+
if (seat(recipient, seats)) edges.push({ at: event.at, kind, from: event.refs.seat, to: recipient, ref });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (deps.listCards) {
|
|
147
|
+
for (const card of deps.listCards()) edges.push(...cardEdges(card, since, now, seats));
|
|
148
|
+
} else {
|
|
149
|
+
const root = deps.cardRoot;
|
|
150
|
+
if (existsSync(cardIndexPath(root))) {
|
|
151
|
+
const store = new CardStore(root, true);
|
|
152
|
+
try { for (const card of store.listCards()) edges.push(...cardEdges(card, since, now, seats)); }
|
|
153
|
+
finally { store.close(); }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const readStarts = deps.listRunStarts ?? ((from: string) => {
|
|
157
|
+
const owners = new Map<string, string>();
|
|
158
|
+
try {
|
|
159
|
+
for (const entry of (deps.listLoopOwners ?? (() => listLoops()))()) {
|
|
160
|
+
if (seat(entry.owner, seats)) owners.set(entry.id, entry.owner);
|
|
161
|
+
}
|
|
162
|
+
} catch { /* A registry outage does not erase explicit launch-seat evidence. */ }
|
|
163
|
+
return (deps.ledgerDir ? [deps.ledgerDir] : resolveFederatedRunLedgerDirectories({ includeTest: false }))
|
|
164
|
+
.flatMap(dir => runStarts(from, dir, owners, seats));
|
|
165
|
+
});
|
|
166
|
+
for (const run of readStarts(sinceIso)) {
|
|
167
|
+
if (seat(run.seat, seats) && ID.test(run.runId) && inWindow(run.at, since, now)) {
|
|
168
|
+
edges.push({ at: run.at, kind: 'run', from: run.seat, to: `loop:${run.runId}`, ref: run.runId });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
edges.sort((a, b) => b.at.localeCompare(a.at) || a.kind.localeCompare(b.kind) || a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
|
|
172
|
+
return jsonResponse({ edges: edges.slice(0, limit) });
|
|
173
|
+
}
|
|
@@ -53,7 +53,7 @@ export function startApprovalGate(input: ApprovalGateInput, deps: ApprovalGateDe
|
|
|
53
53
|
void Promise.resolve().then(() => (deps.runHostRegate ?? runHostRegate)({
|
|
54
54
|
prNumber: input.prNumber, headCommit: input.headSha, repoRoot: input.repoRoot, verifyOnly: true,
|
|
55
55
|
})).then(async (result) => {
|
|
56
|
-
let status = result.status ?? (result.passed ? 'passed' : 'failed');
|
|
56
|
+
let status: ApprovalGateStatus = result.status === 'frozen' ? 'unmeasured' : result.status ?? (result.passed ? 'passed' : 'failed');
|
|
57
57
|
let failures = result.failures.map(({ step, detail }) => `${step} — ${detail.replace(/\s+/g, ' ')}`.slice(0, 800));
|
|
58
58
|
if (status === 'passed') {
|
|
59
59
|
const current = await (deps.readPr ?? readPr)(input);
|
|
@@ -15,6 +15,7 @@ import { tasksDbPath } from '../../task-orchestrator/paths.js';
|
|
|
15
15
|
import { jsonResponse } from './http-server.js';
|
|
16
16
|
import { checkAuth, type MetaApiOpts } from './meta-api.js';
|
|
17
17
|
import { listScheduleRuns } from '../../domains/schedule-runs.js';
|
|
18
|
+
import { listLoops } from '../../loops/registry.js';
|
|
18
19
|
|
|
19
20
|
export interface SchedulesReadDeps {
|
|
20
21
|
rows?: () => ScheduleRow[];
|
|
@@ -24,6 +25,7 @@ export interface SchedulesReadDeps {
|
|
|
24
25
|
logsPath?: () => string;
|
|
25
26
|
/** 발화 이력 원천(`schedule_runs`) — 기본은 schedules.db 를 읽기 전용으로 연다. */
|
|
26
27
|
schedulesDbPath?: () => string;
|
|
28
|
+
loopOwners?: () => Array<{ id: string; title: string; owner?: string | null; enabled: boolean; lastRun: { at: string; status: string } | null; jobs: Array<{ id: string }> }>;
|
|
27
29
|
now?: () => Date;
|
|
28
30
|
}
|
|
29
31
|
|
|
@@ -193,10 +195,25 @@ function inventory(deps: SchedulesReadDeps): { cards: ScheduleCard[]; names: Map
|
|
|
193
195
|
return { cards, names };
|
|
194
196
|
}
|
|
195
197
|
|
|
198
|
+
export function readSchedulesInventory(deps: SchedulesReadDeps = {}): ScheduleCard[] {
|
|
199
|
+
return inventory(deps).cards;
|
|
200
|
+
}
|
|
201
|
+
|
|
196
202
|
export function handleSchedulesList(req: Request, opts: MetaApiOpts, deps: SchedulesReadDeps = {}): Response {
|
|
197
203
|
if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
198
|
-
const
|
|
204
|
+
const cards = readSchedulesInventory(deps);
|
|
199
205
|
const schedules = new URL(req.url).searchParams.get('includeOff') === '1' ? cards : cards.filter(c => c.state !== 'off');
|
|
206
|
+
if (new URL(req.url).searchParams.get('includeOwners') === '1') {
|
|
207
|
+
try {
|
|
208
|
+
const owners = (deps.loopOwners ?? (() => listLoops({ schedules: registryRows() })))().map(loop => ({ id: loop.id, title: redactSecretText(loop.title), owner: loop.owner ?? null,
|
|
209
|
+
enabled: loop.enabled, lastRun: loop.lastRun ? { at: loop.lastRun.at, status: loop.lastRun.status } : null,
|
|
210
|
+
jobs: loop.jobs.map(job => job.id) }));
|
|
211
|
+
return jsonResponse({ schedules, count: schedules.length, owners }, 200);
|
|
212
|
+
} catch {
|
|
213
|
+
// The ordinary inventory stays readable; an opted-in map must not mistake an outage for unowned loops.
|
|
214
|
+
return jsonResponse({ schedules, count: schedules.length, owners: null }, 200);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
200
217
|
return jsonResponse({ schedules, count: schedules.length }, 200);
|
|
201
218
|
}
|
|
202
219
|
|
|
@@ -6,6 +6,7 @@ import { resolveAttachmentPath } from '../../boot/attachment-store.js';
|
|
|
6
6
|
import { runGh } from '../../decisions/decision-cards.js';
|
|
7
7
|
import { openMsgStore, canonicalSeatId } from '../../msg/msg-store.js';
|
|
8
8
|
import { dispatchCeoTask, type CeoCommandDeps } from '../../seat-dispatch/ceo-commands.js';
|
|
9
|
+
import { appendSeatRequestRows, withSeatRequestLedgerLock } from '../../seat-dispatch/seat-request-ledger.js';
|
|
9
10
|
import { acknowledgeSeatAnswers, askSeat, deliverSeatAnswers, parseSeatAsk, type SeatAskDeps } from '../../seat-dispatch/seat-ask.js';
|
|
10
11
|
import { getUserConfig } from '../../user-config.js';
|
|
11
12
|
import { effectiveInstanceRoot } from '../../instance/resolve.js';
|
|
@@ -36,10 +37,12 @@ interface PendingRequest {
|
|
|
36
37
|
queuedAt: string;
|
|
37
38
|
ref: string;
|
|
38
39
|
attachments?: string[];
|
|
40
|
+
reason?: string;
|
|
41
|
+
closedAt?: string;
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
interface RejectedRequest extends Omit<PendingRequest, 'status'> {
|
|
42
|
-
status: 'rejected';
|
|
45
|
+
status: 'rejected' | 'done';
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
type RecordEntry = (SeatRequestItem & { key: string }) | PendingRequest | RejectedRequest;
|
|
@@ -73,19 +76,7 @@ function file(root: string): string {
|
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
function append(path: string, entry: RecordEntry): void {
|
|
76
|
-
|
|
77
|
-
// Atomically replace the journal so a failed write cannot leave a truncated JSON line.
|
|
78
|
-
const previous = existsSync(path) ? readFileSync(path, 'utf8') : '';
|
|
79
|
-
const temp = `${path}.${randomUUID()}.tmp`;
|
|
80
|
-
try {
|
|
81
|
-
writeFileSync(temp, `${previous}${JSON.stringify(entry)}\n`);
|
|
82
|
-
const fd = openSync(temp, 'r');
|
|
83
|
-
try { fsyncSync(fd); } finally { closeSync(fd); }
|
|
84
|
-
renameSync(temp, path);
|
|
85
|
-
} catch (error) {
|
|
86
|
-
try { unlinkSync(temp); } catch { /* no temp file to remove */ }
|
|
87
|
-
throw error;
|
|
88
|
-
}
|
|
79
|
+
appendSeatRequestRows(path, [entry]);
|
|
89
80
|
}
|
|
90
81
|
|
|
91
82
|
function records(path: string): Map<string, RecordEntry> {
|
|
@@ -100,7 +91,7 @@ function records(path: string): Map<string, RecordEntry> {
|
|
|
100
91
|
if (!line) continue;
|
|
101
92
|
const entry = JSON.parse(line) as RecordEntry;
|
|
102
93
|
if (typeof entry.key !== 'string' || typeof entry.seat !== 'string' || typeof entry.text !== 'string'
|
|
103
|
-
|| typeof entry.queuedAt !== 'string' || !['pending', 'queued', 'rejected'].includes(entry.status)
|
|
94
|
+
|| typeof entry.queuedAt !== 'string' || !['pending', 'queued', 'rejected', 'done'].includes(entry.status)
|
|
104
95
|
|| (entry.status === 'queued' && typeof entry.receiptId !== 'string')
|
|
105
96
|
|| (entry.status !== 'queued' && typeof entry.ref !== 'string')) {
|
|
106
97
|
throw new Error('invalid seat request journal');
|
|
@@ -121,7 +112,7 @@ function records(path: string): Map<string, RecordEntry> {
|
|
|
121
112
|
throw new Error('invalid seat request outcome');
|
|
122
113
|
}
|
|
123
114
|
const current = entries.get(item.key);
|
|
124
|
-
if (current?.status === 'queued') continue;
|
|
115
|
+
if (current?.status === 'queued' || current?.status === 'done' || current?.closedAt) continue;
|
|
125
116
|
if (current?.status === 'pending') {
|
|
126
117
|
if (item.status === 'rejected' && current.ref !== item.ref) continue;
|
|
127
118
|
if (item.status === 'queued' && current.queuedAt !== item.queuedAt) continue;
|
|
@@ -135,27 +126,32 @@ function outcomeFile(path: string, key: string): string {
|
|
|
135
126
|
return join(dirname(path), 'outcomes', `${key}.json`);
|
|
136
127
|
}
|
|
137
128
|
|
|
138
|
-
function persistOutcome(path: string, entry: RecordEntry, write: typeof append):
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (
|
|
143
|
-
} catch {
|
|
144
|
-
// The primary journal may be unavailable while the outcome is already committed
|
|
145
|
-
// to the intake queue. Keep a durable sidecar rather than a process-local receipt.
|
|
146
|
-
const target = outcomeFile(path, entry.key);
|
|
147
|
-
mkdirSync(dirname(target), { recursive: true });
|
|
148
|
-
const temp = `${target}.${randomUUID()}.tmp`;
|
|
129
|
+
function persistOutcome(path: string, entry: RecordEntry, write: typeof append): RecordEntry {
|
|
130
|
+
return withSeatRequestLedgerLock(path, () => {
|
|
131
|
+
const current = records(path).get(entry.key);
|
|
132
|
+
if (current && ('closedAt' in current || current.status === 'done')) return current;
|
|
133
|
+
if (current?.status === 'queued') return current;
|
|
149
134
|
try {
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
135
|
+
write(path, entry);
|
|
136
|
+
const sidecar = outcomeFile(path, entry.key);
|
|
137
|
+
if (existsSync(sidecar)) unlinkSync(sidecar);
|
|
138
|
+
} catch {
|
|
139
|
+
// A committed external delivery still needs a durable outcome if the journal write failed.
|
|
140
|
+
const target = outcomeFile(path, entry.key);
|
|
141
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
142
|
+
const temp = `${target}.${randomUUID()}.tmp`;
|
|
143
|
+
try {
|
|
144
|
+
writeFileSync(temp, JSON.stringify(entry));
|
|
145
|
+
const fd = openSync(temp, 'r');
|
|
146
|
+
try { fsyncSync(fd); } finally { closeSync(fd); }
|
|
147
|
+
renameSync(temp, target);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
try { unlinkSync(temp); } catch { /* no temp file */ }
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
157
152
|
}
|
|
158
|
-
|
|
153
|
+
return entry;
|
|
154
|
+
});
|
|
159
155
|
}
|
|
160
156
|
|
|
161
157
|
function receipt(item: SeatRequestItem): Response {
|
|
@@ -298,13 +294,18 @@ export async function handleSeatRequests(req: Request, deps: SeatRequestsDeps =
|
|
|
298
294
|
const gate = new Promise<void>((resolve) => { start = resolve; });
|
|
299
295
|
const task = (async (): Promise<Response> => {
|
|
300
296
|
await gate;
|
|
301
|
-
|
|
302
|
-
|
|
297
|
+
// A settled request answers from the journal before attachments are resolved — a retry
|
|
298
|
+
// must return its receipt even when the uploaded file is gone. The locked re-check below still guards races.
|
|
299
|
+
let settled: RecordEntry | undefined;
|
|
300
|
+
try { settled = records(path).get(key); }
|
|
303
301
|
catch { return unavailable(); }
|
|
304
|
-
if (
|
|
305
|
-
if (
|
|
306
|
-
|| JSON.stringify(
|
|
307
|
-
if (
|
|
302
|
+
if (settled) {
|
|
303
|
+
if (settled.seat !== seat.id || settled.text !== text
|
|
304
|
+
|| JSON.stringify(settled.attachments ?? []) !== JSON.stringify(attachmentIds)) return jsonResponse({ error: 'idempotency-conflict' }, 409);
|
|
305
|
+
if ('closedAt' in settled || settled.status === 'done') {
|
|
306
|
+
return jsonResponse({ error: 'request-closed', status: settled.status, reason: 'reason' in settled ? settled.reason : undefined }, 409);
|
|
307
|
+
}
|
|
308
|
+
if (settled.status === 'queued') return receipt(settled);
|
|
308
309
|
}
|
|
309
310
|
const attachments: Array<{ name: string; path: string }> = [];
|
|
310
311
|
for (const id of attachmentIds) {
|
|
@@ -316,18 +317,36 @@ export async function handleSeatRequests(req: Request, deps: SeatRequestsDeps =
|
|
|
316
317
|
const metadata = (input.attachments as Array<{ name?: unknown }>)[attachments.length];
|
|
317
318
|
attachments.push({ name: typeof metadata?.name === 'string' && metadata.name.trim() ? metadata.name : basename(resolved), path: resolved });
|
|
318
319
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
320
|
+
let initial: { intent?: PendingRequest; response?: Response };
|
|
321
|
+
try {
|
|
322
|
+
initial = withSeatRequestLedgerLock(path, () => {
|
|
323
|
+
const existing = records(path).get(key);
|
|
324
|
+
if (existing) {
|
|
325
|
+
if (existing.seat !== seat.id || existing.text !== text
|
|
326
|
+
|| JSON.stringify(existing.attachments ?? []) !== JSON.stringify(attachmentIds)) {
|
|
327
|
+
return { response: jsonResponse({ error: 'idempotency-conflict' }, 409) };
|
|
328
|
+
}
|
|
329
|
+
if ('closedAt' in existing || existing.status === 'done') {
|
|
330
|
+
return { response: jsonResponse({ error: 'request-closed', status: existing.status,
|
|
331
|
+
reason: 'reason' in existing ? existing.reason : undefined }, 409) };
|
|
332
|
+
}
|
|
333
|
+
if (existing.status === 'queued') return { response: receipt(existing) };
|
|
328
334
|
}
|
|
329
|
-
|
|
330
|
-
|
|
335
|
+
const intent: PendingRequest = existing?.status === 'pending'
|
|
336
|
+
? existing : { key, seat: seat.id, text, queuedAt: now, status: 'pending', ref: `pwa:${randomUUID()}`,
|
|
337
|
+
...(attachmentIds.length ? { attachments: attachmentIds } : {}) };
|
|
338
|
+
if (existing?.status !== 'pending') {
|
|
339
|
+
(deps.append ?? append)(path, intent);
|
|
340
|
+
if (existing?.status === 'rejected') {
|
|
341
|
+
const sidecar = outcomeFile(path, key);
|
|
342
|
+
if (existsSync(sidecar)) unlinkSync(sidecar);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return { intent };
|
|
346
|
+
});
|
|
347
|
+
} catch { return unavailable(); }
|
|
348
|
+
if (initial.response) return initial.response;
|
|
349
|
+
const intent = initial.intent!;
|
|
331
350
|
const ceoSeat = canonicalSeatId(seat.id);
|
|
332
351
|
if (['OP', 'TC', 'MK', 'UX'].includes(ceoSeat)) {
|
|
333
352
|
let result: Awaited<ReturnType<typeof dispatchCeoTask>>;
|
|
@@ -342,10 +361,12 @@ export async function handleSeatRequests(req: Request, deps: SeatRequestsDeps =
|
|
|
342
361
|
...(attachmentIds.length ? { attachments: attachmentIds } : {}), channel: result.channel,
|
|
343
362
|
...(result.channelError ? { channelError: result.channelError } : {}),
|
|
344
363
|
};
|
|
345
|
-
|
|
364
|
+
let persisted: RecordEntry;
|
|
365
|
+
try { persisted = persistOutcome(path, item, deps.append ?? append); }
|
|
346
366
|
catch { return unavailable(); }
|
|
367
|
+
if (persisted.status !== 'queued') return jsonResponse({ error: 'request-closed', status: persisted.status, reason: persisted.reason }, 409);
|
|
347
368
|
publishInsideEvent({ kind: 'seat', seat: item.seat, receiptId: item.receiptId, status: item.status, ts: item.queuedAt });
|
|
348
|
-
return receipt(
|
|
369
|
+
return receipt(persisted);
|
|
349
370
|
}
|
|
350
371
|
let result: Awaited<ReturnType<typeof submitIntakeWork>>;
|
|
351
372
|
try {
|
|
@@ -363,11 +384,13 @@ export async function handleSeatRequests(req: Request, deps: SeatRequestsDeps =
|
|
|
363
384
|
}
|
|
364
385
|
const item: RecordEntry & SeatRequestItem = { key, receiptId: result.acceptanceId, seat: seat.id, text, queuedAt: intent.queuedAt,
|
|
365
386
|
status: 'queued', ...(attachmentIds.length ? { attachments: attachmentIds } : {}) };
|
|
366
|
-
|
|
387
|
+
let persisted: RecordEntry;
|
|
388
|
+
try { persisted = persistOutcome(path, item, deps.append ?? append); }
|
|
367
389
|
catch { return unavailable(); }
|
|
390
|
+
if (persisted.status !== 'queued') return jsonResponse({ error: 'request-closed', status: persisted.status, reason: persisted.reason }, 409);
|
|
368
391
|
debug.log('seat-address.pwa', 'enqueued', { seat: seat.id, receiptId: item.receiptId });
|
|
369
392
|
publishInsideEvent({ kind: 'seat', seat: item.seat, receiptId: item.receiptId, status: item.status, ts: item.queuedAt });
|
|
370
|
-
return receipt(
|
|
393
|
+
return receipt(persisted);
|
|
371
394
|
})();
|
|
372
395
|
inFlight.set(lock, { payload, task });
|
|
373
396
|
start();
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { CardStore, type TaskCard } from '../../task-cards/card-store.js';
|
|
5
|
+
import { releaseLedgerRoot } from '../../instance/resolve.js';
|
|
2
6
|
import { createWishCard } from '../../intake-plane/wish-card.js';
|
|
3
7
|
import { jsonResponse } from './json-response.js';
|
|
4
8
|
|
|
@@ -7,19 +11,53 @@ export async function handleTaskCardsWishPost(req: Request, root?: string): Prom
|
|
|
7
11
|
try { body = await req.json(); }
|
|
8
12
|
catch { return jsonResponse({ error: 'bad_request' }, 400); }
|
|
9
13
|
if (!body || typeof body !== 'object' || Array.isArray(body)) return jsonResponse({ error: 'bad_request' }, 400);
|
|
10
|
-
const { text, ref } = body as Record<string, unknown>;
|
|
11
|
-
if (typeof text !== 'string' || !text.trim() || typeof ref !== 'string' || !ref.trim()
|
|
14
|
+
const { text, ref, sessionId } = body as Record<string, unknown>;
|
|
15
|
+
if (typeof text !== 'string' || !text.trim() || typeof ref !== 'string' || !ref.trim()
|
|
16
|
+
|| typeof sessionId !== 'string' || !sessionId.trim()) {
|
|
12
17
|
return jsonResponse({ error: 'bad_request' }, 400);
|
|
13
18
|
}
|
|
14
19
|
const store = new CardStore(root);
|
|
15
20
|
try {
|
|
16
|
-
const card = createWishCard({ text, source: 'pwa', ref }, store);
|
|
21
|
+
const card = createWishCard({ text, source: 'pwa', ref, replyTo: { surface: 'pwa', sessionId } }, store);
|
|
17
22
|
return jsonResponse(card, card.created ? 201 : 200);
|
|
18
23
|
} finally {
|
|
19
24
|
store.close();
|
|
20
25
|
}
|
|
21
26
|
}
|
|
22
27
|
|
|
28
|
+
export interface WishPlacement { cellId: string; cellTitle: string; version: string; status: 'green' | 'yellow' | 'red' | 'done' }
|
|
29
|
+
|
|
30
|
+
export function isSplitCell(cell: unknown): cell is { id: string } {
|
|
31
|
+
return cell !== null && typeof cell === 'object' && 'id' in cell
|
|
32
|
+
&& typeof cell.id === 'string' && cell.id.trim().length > 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Join the live checklist to the wish card by the split cell ID, never by title or card status. */
|
|
36
|
+
export function wishPlacements(card: TaskCard, root?: string): WishPlacement[] {
|
|
37
|
+
if (!card.goalId.startsWith('wish:')) return [];
|
|
38
|
+
const split = [...card.sections].reverse().find(section => section.key === 'orch:split');
|
|
39
|
+
if (!split) return [];
|
|
40
|
+
let cells: unknown;
|
|
41
|
+
try { cells = JSON.parse(split.content); } catch { return []; }
|
|
42
|
+
if (!Array.isArray(cells)) return [];
|
|
43
|
+
const ids = [...new Set(cells.filter(isSplitCell).map(cell => cell.id.trim()))];
|
|
44
|
+
if (!ids.length) return [];
|
|
45
|
+
const ledgerRoot = root ?? releaseLedgerRoot();
|
|
46
|
+
const path = join(ledgerRoot, 'release', 'features.sqlite');
|
|
47
|
+
if (!existsSync(path)) return [];
|
|
48
|
+
const db = new Database(path, { readonly: true, strict: true });
|
|
49
|
+
try {
|
|
50
|
+
const placements: WishPlacement[] = [];
|
|
51
|
+
for (const cellId of ids) {
|
|
52
|
+
const rows = db.query('SELECT a.version, a.status, COALESCE(a.title_override, f.title) AS title FROM assignments a JOIN features f ON f.id = a.feature_id WHERE a.feature_id = ? ORDER BY a.version').all(cellId) as Array<{ version: string; status: string; title: string }>;
|
|
53
|
+
for (const row of rows) if (['green', 'yellow', 'red', 'done'].includes(row.status)) {
|
|
54
|
+
placements.push({ cellId, cellTitle: row.title, version: row.version, status: row.status as WishPlacement['status'] });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return placements;
|
|
58
|
+
} finally { db.close(); }
|
|
59
|
+
}
|
|
60
|
+
|
|
23
61
|
/** Read-only card API; route registration and authorization belong to the HTTP server. */
|
|
24
62
|
export function handleTaskCardsGet(pathname: string, root?: string): Response {
|
|
25
63
|
if (pathname !== '/v1/task-cards' && !/^\/v1\/task-cards\/[^/]+$/.test(pathname)) {
|
|
@@ -46,7 +84,8 @@ export function handleTaskCardsGet(pathname: string, root?: string): Response {
|
|
|
46
84
|
const store = new CardStore(root);
|
|
47
85
|
try {
|
|
48
86
|
const card = store.getCard(id);
|
|
49
|
-
return card ? jsonResponse({ card
|
|
87
|
+
return card ? jsonResponse({ card, ...(card.goalId.startsWith('wish:') ? { placements: wishPlacements(card, root) } : {}) })
|
|
88
|
+
: jsonResponse({ error: 'not_found' }, 404);
|
|
50
89
|
} finally {
|
|
51
90
|
store.close();
|
|
52
91
|
}
|
|
@@ -302,6 +302,7 @@ export async function handleWorkflowPut(
|
|
|
302
302
|
}
|
|
303
303
|
const r = saveWorkflow(name, yamlText, { scope });
|
|
304
304
|
if (!r.ok) {
|
|
305
|
+
if (r.readonly) return jsonResponse({ error: 'readonly_workflow', reason: r.error }, 409);
|
|
305
306
|
return jsonResponse(
|
|
306
307
|
{
|
|
307
308
|
error: 'invalid_workflow',
|
|
@@ -323,7 +324,7 @@ export function handleWorkflowDelete(
|
|
|
323
324
|
const url = new URL(req.url);
|
|
324
325
|
const scope = url.searchParams.get('scope') === 'global' ? 'global' : 'project';
|
|
325
326
|
const r = deleteWorkflow(name, { scope });
|
|
326
|
-
if (!r.ok) return jsonResponse({ error: 'delete_failed', reason: r.error }, 404);
|
|
327
|
+
if (!r.ok) return jsonResponse({ error: r.readonly ? 'readonly_workflow' : 'delete_failed', reason: r.error }, r.readonly ? 409 : 404);
|
|
327
328
|
return jsonResponse({ ok: true, path: r.path, scope }, 200);
|
|
328
329
|
}
|
|
329
330
|
|
|
@@ -99,6 +99,8 @@ export interface EnhanceOpts {
|
|
|
99
99
|
reasoningEffort?: 'low' | 'medium' | 'high';
|
|
100
100
|
/** 산출물 유형 힌트(선택) — 예: 'PPT 발표덱', 'PLAN 문서'. 제약 문구 조정. */
|
|
101
101
|
deliverableHint?: string;
|
|
102
|
+
/** Optional usage observer for the caller's authoring phase; missing usage stays unknown. */
|
|
103
|
+
onUsage?: (usage: LLMUsage) => void;
|
|
102
104
|
/** 인핸싱 끄기(순수 verbatim). true 면 원문만 fenced 로 감싸 반환. */
|
|
103
105
|
disabled?: boolean;
|
|
104
106
|
/** Ask 원문 밖 스캐폴드에만 렌더할, 저작기가 관측한 디렉터리 실물 수. */
|
|
@@ -368,7 +370,10 @@ export async function enhancePrompt(raw: string, opts: EnhanceOpts = {}): Promis
|
|
|
368
370
|
const rawOut = await streamLLM(messages, () => {}, {
|
|
369
371
|
model,
|
|
370
372
|
reasoningEffort: opts.reasoningEffort ?? 'high',
|
|
371
|
-
onUsage: (usage) =>
|
|
373
|
+
onUsage: (usage) => {
|
|
374
|
+
logLlmUsage(model, usage);
|
|
375
|
+
try { opts.onUsage?.(usage); } catch { /* usage observation must not change enhancement */ }
|
|
376
|
+
},
|
|
372
377
|
});
|
|
373
378
|
const parsed = parseEnhanceJson(rawOut);
|
|
374
379
|
if (parsed) {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { releaseLedgerRoot } from '../instance/resolve.js';
|
|
2
|
+
import { releaseReadiness, type ReleaseReadiness } from '../../scripts/release-loop/release-readiness.js';
|
|
3
|
+
import { listSchedules, type ReleaseSchedule } from './release-schedule.js';
|
|
4
|
+
|
|
5
|
+
/** Cuts become eligible at cutAt and remain eligible through the end of the window. */
|
|
6
|
+
export function selectScheduledCut(schedules: readonly ReleaseSchedule[], now: Date, windowMinutes: number): ReleaseSchedule | null {
|
|
7
|
+
if (!Number.isFinite(windowMinutes) || windowMinutes <= 0) throw new Error('windowMinutes must be positive and finite');
|
|
8
|
+
const current = now.getTime();
|
|
9
|
+
if (!Number.isFinite(current)) throw new Error('now must be a valid date');
|
|
10
|
+
return schedules
|
|
11
|
+
.filter((schedule) => {
|
|
12
|
+
const cut = Date.parse(schedule.cutAt);
|
|
13
|
+
return Number.isFinite(cut) && cut <= current && current - cut <= windowMinutes * 60_000;
|
|
14
|
+
})
|
|
15
|
+
.sort((a, b) => Date.parse(a.cutAt) - Date.parse(b.cutAt) || a.version.localeCompare(b.version))[0] ?? null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type AutoStartDeferral = { deferred: true; reason: Exclude<ReleaseReadiness, { ready: true }>['reason'] | 'frozen' };
|
|
19
|
+
|
|
20
|
+
export type AutoStartResult =
|
|
21
|
+
| { status: 'no-cut' }
|
|
22
|
+
| { status: 'planned'; version: string; cutAt: string }
|
|
23
|
+
| { status: 'skipped'; version: string; reason: AutoStartDeferral['reason'] }
|
|
24
|
+
| { status: 'started'; version: string; cutAt: string };
|
|
25
|
+
|
|
26
|
+
export interface AutoStartOptions {
|
|
27
|
+
windowMinutes: number;
|
|
28
|
+
apply?: boolean;
|
|
29
|
+
now?: Date;
|
|
30
|
+
ledgerRoot?: string;
|
|
31
|
+
schedules?: (root: string) => ReleaseSchedule[];
|
|
32
|
+
readiness?: (version: string, ledgerRoot: string, now: Date) => ReleaseReadiness;
|
|
33
|
+
launch?: (version: string) => void | AutoStartDeferral | Promise<void | AutoStartDeferral>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Select from the release schedule; the existing `release run --if-ready` owns the launch-time lock and guards. */
|
|
37
|
+
export async function autoStartScheduledRelease(options: AutoStartOptions): Promise<AutoStartResult> {
|
|
38
|
+
const root = options.ledgerRoot ?? releaseLedgerRoot();
|
|
39
|
+
const now = options.now ?? new Date();
|
|
40
|
+
// Validate the window even when the ledger has no rows.
|
|
41
|
+
const schedule = selectScheduledCut((options.schedules ?? listSchedules)(root), now, options.windowMinutes);
|
|
42
|
+
if (!schedule) return { status: 'no-cut' };
|
|
43
|
+
const { version, cutAt } = schedule;
|
|
44
|
+
if (!options.apply) return { status: 'planned', version, cutAt };
|
|
45
|
+
const readiness = (options.readiness ?? ((v, ledgerRoot, at) => releaseReadiness(v, { ledgerRoot, now: () => at })))(version, root, now);
|
|
46
|
+
if (!readiness.ready) return { status: 'skipped', version, reason: readiness.reason };
|
|
47
|
+
if (!options.launch) throw new Error('auto-start apply requires a launch callback');
|
|
48
|
+
const launched = await options.launch(version);
|
|
49
|
+
if (launched?.deferred) return { status: 'skipped', version, reason: launched.reason };
|
|
50
|
+
return { status: 'started', version, cutAt };
|
|
51
|
+
}
|