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
|
@@ -30,19 +30,32 @@ export function activeRunsForGoal(goalId: string, deps: ActiveRunsForGoalDeps =
|
|
|
30
30
|
if (observation.completeness !== 'complete') unknownReasons.add(`ledger observation ${observation.completeness}`);
|
|
31
31
|
for (const root of observation.pty.unreadable) unknownReasons.add(`unreadable PTY: ${root}`);
|
|
32
32
|
const active = new Set<string>();
|
|
33
|
+
let skippedTerminal = 0;
|
|
33
34
|
for (const run of observation.entries) {
|
|
34
|
-
if (run.status === 'ended-unclosed') continue;
|
|
35
|
-
|
|
35
|
+
if (run.status === 'ended-unclosed') { skippedTerminal += 1; continue; }
|
|
36
|
+
const hasObservedPty = run.ptyRefs.length > 0;
|
|
37
|
+
const live = run.status === 'running' && hasObservedPty;
|
|
38
|
+
if (run.ledgerDirectories.length === 0) {
|
|
39
|
+
if (hasObservedPty) { unknown = true; unknownReasons.add(`${run.runId}: ledger directory absent`); }
|
|
40
|
+
else skippedTerminal += 1;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
36
43
|
let identified = false;
|
|
37
44
|
for (const directory of run.ledgerDirectories) {
|
|
38
45
|
try {
|
|
39
46
|
const ledger = loadLedger(run.runId, directory);
|
|
40
|
-
if (ledger === null) {
|
|
47
|
+
if (ledger === null) {
|
|
48
|
+
if (hasObservedPty) { unknown = true; unknownReasons.add(`${run.runId}: ledger unavailable`); }
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
41
51
|
const ids = new Set(ledger.flatMap((entry) => entry.goalId ? [entry.goalId] : []));
|
|
42
|
-
if (ids.size !== 1) {
|
|
52
|
+
if (ids.size !== 1) {
|
|
53
|
+
if (hasObservedPty) { unknown = true; unknownReasons.add(`${run.runId}: expected one goalId, found ${ids.size}`); }
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
43
56
|
identified = true;
|
|
44
57
|
let matches = ids.has(goalId);
|
|
45
|
-
if (!matches && goalId.startsWith('request-') &&
|
|
58
|
+
if (!matches && goalId.startsWith('request-') && live) {
|
|
46
59
|
const document = ledger.find((entry) => entry.event === 'start')?.data.goalFile;
|
|
47
60
|
if (typeof document === 'string') {
|
|
48
61
|
try {
|
|
@@ -52,20 +65,22 @@ export function activeRunsForGoal(goalId: string, deps: ActiveRunsForGoalDeps =
|
|
|
52
65
|
}
|
|
53
66
|
}
|
|
54
67
|
if (matches) {
|
|
55
|
-
if (
|
|
68
|
+
if (live) active.add(run.runId);
|
|
56
69
|
else { unknown = true; unknownReasons.add(`${run.runId}: matching ledger without live PTY`); }
|
|
57
70
|
}
|
|
58
|
-
} catch {
|
|
71
|
+
} catch {
|
|
72
|
+
if (hasObservedPty) { unknown = true; unknownReasons.add(`${run.runId}: ledger read failed`); }
|
|
73
|
+
}
|
|
59
74
|
}
|
|
60
|
-
if (!identified)
|
|
75
|
+
if (!identified && !hasObservedPty) skippedTerminal += 1;
|
|
61
76
|
}
|
|
62
77
|
// Positive evidence wins even if a different process or store is unreadable.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
return
|
|
78
|
+
const result: ActiveRunsForGoal = active.size > 0 ? [...active].sort() : unknown ? 'unknown' : [];
|
|
79
|
+
try {
|
|
80
|
+
debug.log('execution-loop.launch-gate', 'active-runs-result', { goalId, result, skippedTerminal });
|
|
81
|
+
if (result === 'unknown') debug.log('execution-loop.launch-gate', 'active-runs-unknown', { goalId, reasons: [...unknownReasons], skippedTerminal });
|
|
82
|
+
} catch { /* observation is fail-soft */ }
|
|
83
|
+
return result;
|
|
69
84
|
} catch (error) {
|
|
70
85
|
try { debug.log('execution-loop.launch-gate', 'active-runs-unknown', { goalId, reasons: [error instanceof Error ? error.message : String(error)] }); } catch { /* observation is fail-soft */ }
|
|
71
86
|
return 'unknown';
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { openSync, closeSync, readFileSync, statSync, unlinkSync, writeSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { debug } from '../debug/log.js';
|
|
4
|
+
import { judge } from '../llm/judge-layer.js';
|
|
5
|
+
import { CardStore, type TaskCard } from '../task-cards/card-store.js';
|
|
6
|
+
import { CliUserError } from '../cli/cli-user-error.js';
|
|
7
|
+
import { DecisionLedger, type DecisionCategory, type RaiseInput } from '../decisions/decision-ledger.js';
|
|
8
|
+
import { listChecklist, type Checklist } from '../release-loop/checklist.js';
|
|
9
|
+
import { placeCell, type PlacementCell, type PlacementDecision, type PlacementDeps } from '../release-loop/placement.js';
|
|
10
|
+
|
|
11
|
+
export type FlowCell = PlacementCell & { falsifier: string };
|
|
12
|
+
export type FlowGate = { title: string; text: string; category: 'money' | 'publish' | 'security' | 'irreversible' };
|
|
13
|
+
export type FlowSplit = { cells: FlowCell[]; gated: FlowGate[]; fallback: boolean };
|
|
14
|
+
export type FlowResult = { cardId: string; cells: number; placements: PlacementDecision[]; unplaced: Array<{ id: string; reason: string }>; decisions: string[]; text: string; duplicate?: boolean };
|
|
15
|
+
export interface FlowDeps {
|
|
16
|
+
store?: CardStore;
|
|
17
|
+
judge?: typeof judge;
|
|
18
|
+
place?: typeof placeCell;
|
|
19
|
+
placement?: PlacementDeps;
|
|
20
|
+
raise?: (input: RaiseInput, ref: string) => string;
|
|
21
|
+
reply?: (cardId: string, text: string) => Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function log(event: 'split' | 'placed' | 'gated' | 'fallback', data: Record<string, unknown>): void {
|
|
25
|
+
debug.log('flow', event, data);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const categories: Array<[RegExp, FlowGate['category']]> = [
|
|
29
|
+
[/결제|구매|송금|지불|payment|purchase|charge|pay\b|transfer money/i, 'money'],
|
|
30
|
+
[/외부\s*게시|공개\s*게시|발행|배포|publish|post publicly/i, 'publish'],
|
|
31
|
+
[/보안|인증|권한|비밀|secret|credential|security/i, 'security'],
|
|
32
|
+
[/되돌릴 수 없|비가역|삭제|폐기|irreversib|permanent delet/i, 'irreversible'],
|
|
33
|
+
];
|
|
34
|
+
function unsafe(text: string): FlowGate['category'] | undefined {
|
|
35
|
+
return categories.find(([pattern]) => pattern.test(text))?.[1];
|
|
36
|
+
}
|
|
37
|
+
function portions(text: string): string[] {
|
|
38
|
+
return text.split(/(?:\r?\n|[.!?。!?]\s+|\s+(?:and|then|그리고|및)\s+)/i).map(s => s.trim()).filter(Boolean);
|
|
39
|
+
}
|
|
40
|
+
function gate(text: string, category: FlowGate['category']): FlowGate {
|
|
41
|
+
return { title: text.split(/\r?\n/, 1)[0]!.slice(0, 100), text, category };
|
|
42
|
+
}
|
|
43
|
+
function valid(value: unknown): { cells: Array<{ id: string; title: string; owner: string; falsifier: string; predecessors: string[]; priority?: PlacementCell['priority']; deadlineVersion?: string }>; gated: FlowGate[] } | null {
|
|
44
|
+
if (!value || typeof value !== 'object') return null;
|
|
45
|
+
const v = value as Record<string, unknown>;
|
|
46
|
+
if (!Array.isArray(v.cells) || !Array.isArray(v.gated) || v.cells.length > 4 || v.cells.length + v.gated.length === 0) return null;
|
|
47
|
+
const cells = v.cells as Array<Record<string, unknown>>;
|
|
48
|
+
if (cells.some(c => !/^[a-zA-Z0-9_-]{1,32}$/.test(String(c.id ?? '')) || typeof c.title !== 'string' || !c.title.trim()
|
|
49
|
+
|| !['OP', 'TC', 'MK', 'UX'].includes(String(c.owner)) || typeof c.falsifier !== 'string' || !c.falsifier.trim()
|
|
50
|
+
|| !Array.isArray(c.predecessors) || c.predecessors.some(p => typeof p !== 'string')
|
|
51
|
+
|| c.priority !== undefined && !['P0', 'P1', 'P2'].includes(String(c.priority))
|
|
52
|
+
|| c.deadlineVersion !== undefined && typeof c.deadlineVersion !== 'string')) return null;
|
|
53
|
+
const ids = cells.map(c => c.id as string);
|
|
54
|
+
if (new Set(ids).size !== ids.length || cells.some((c, index) => (c.predecessors as string[]).some(p => !ids.slice(0, index).includes(p)))) return null;
|
|
55
|
+
const gated = v.gated as Array<Record<string, unknown>>;
|
|
56
|
+
if (gated.some(g => typeof g.title !== 'string' || !g.title.trim() || typeof g.text !== 'string' || !g.text.trim() || !categories.some(([, category]) => category === g.category))) return null;
|
|
57
|
+
return v as ReturnType<typeof valid> & { cells: typeof cells; gated: FlowGate[] };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function wish(card: TaskCard): { title: string; text: string; source: string; priority: PlacementCell['priority']; deadlineVersion?: string } {
|
|
61
|
+
if (!card.goalId.startsWith('wish:')) throw new CliUserError(`Wish 카드가 아니다: ${card.id}`);
|
|
62
|
+
const sections = card.sections.filter(s => s.key.startsWith('intake:wish:'));
|
|
63
|
+
const raw = sections.at(-1);
|
|
64
|
+
if (!raw) throw new CliUserError(`원문 없는 Wish 카드: ${card.id}`);
|
|
65
|
+
const value = JSON.parse(raw.content) as { source?: string; title?: string; text?: string | null; priority?: string; deadlineVersion?: string };
|
|
66
|
+
const source = value.source && value.source !== 'wish' ? value.source : 'folder';
|
|
67
|
+
const text = value.text?.trim() || (source === 'folder' ? value.title?.trim() : null);
|
|
68
|
+
if (!text) throw new CliUserError(`원문 없는 Wish 카드: ${card.id}`);
|
|
69
|
+
return { title: value.title || card.title, text, source, priority: value.priority === 'P0' || value.priority === 'P1' ? value.priority : 'P2', ...(value.deadlineVersion ? { deadlineVersion: value.deadlineVersion } : {}) };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Judge decisions are checked independently for sensitive text; a failed classification never schedules a sensitive part. */
|
|
73
|
+
export async function splitCard(card: TaskCard, deps: FlowDeps = {}): Promise<FlowSplit> {
|
|
74
|
+
const cached = card.sections.find(s => s.key === 'flow:split:0');
|
|
75
|
+
if (cached) return JSON.parse(cached.content) as FlowSplit;
|
|
76
|
+
const source = wish(card);
|
|
77
|
+
const decision = await (deps.judge ?? judge)({ site: 'flow.split', prompt: `Split this wish into 1-4 small, independently falsifiable release cells. Route owner using existing seats OP (operations), TC (engineering), MK (content), UX (experience). Return JSON only: {"cells":[{"id":"a","title":"...","owner":"TC","falsifier":"one-line check","predecessors":[],"priority":"P2","deadlineVersion":"... only for P1"}],"gated":[{"title":"...","text":"original sensitive part","category":"money|publish|security|irreversible"}]}. Never turn external publishing, payment, security or irreversible actions into cells; split ONLY those parts into gated decisions. Preserve the other parts. Card priority is ${source.priority}${source.deadlineVersion ? `, deadlineVersion ${source.deadlineVersion}` : ''}. Source ${source.source}. Title ${source.title}. Verbatim wish:\n${source.text}`, schema: valid });
|
|
78
|
+
const parsed = decision.ok && decision.value.cells.every(c => (c.priority ?? source.priority) !== 'P1' || Boolean(c.deadlineVersion ?? source.deadlineVersion)) ? decision.value : null;
|
|
79
|
+
const prefix = `FLOW-${card.id.replaceAll('-', '')}`;
|
|
80
|
+
const gated: FlowGate[] = parsed ? [...parsed.gated] : [];
|
|
81
|
+
const risky = portions(source.text).flatMap(part => { const category = unsafe(part); return category ? [gate(part, category)] : []; });
|
|
82
|
+
for (const part of risky) if (!gated.some(g => g.text.includes(part.text.replace(/[.!?。!?]+$/, '')))) gated.push(part);
|
|
83
|
+
const safeParts = portions(source.text).filter(part => !unsafe(part));
|
|
84
|
+
const rawCells = parsed ? parsed.cells : safeParts.length || !risky.length
|
|
85
|
+
? [{ id: 'a', title: risky.length ? safeParts.join(' ').slice(0, 80) : source.title, owner: 'OP', falsifier: `카드 ${card.id} 원문의 안전한 요구를 확인한다`, predecessors: [] }]
|
|
86
|
+
: [];
|
|
87
|
+
const cells: FlowCell[] = rawCells.filter(c => !unsafe(c.title) && !unsafe(c.falsifier) && !risky.some(part => c.title.includes(part.text)))
|
|
88
|
+
.map(c => ({ id: `${prefix}-${c.id}`, title: c.title, owner: c.owner, falsifier: c.falsifier,
|
|
89
|
+
priority: c.priority ?? source.priority, predecessors: c.predecessors.map(p => `${prefix}-${p}`),
|
|
90
|
+
...((c.deadlineVersion ?? source.deadlineVersion) ? { deadlineVersion: c.deadlineVersion ?? source.deadlineVersion } : {}) }));
|
|
91
|
+
if (parsed && risky.length && risky.some(part => !parsed.gated.some(g => g.text.includes(part.text.replace(/[.!?。!?]+$/, ''))))) {
|
|
92
|
+
cells.length = 0;
|
|
93
|
+
if (safeParts.length) cells.push({ id: `${prefix}-safe`, title: safeParts.join(' ').slice(0, 80), owner: 'OP', falsifier: `안전한 부분만 검증: ${safeParts.join(' ').slice(0, 100)}`, priority: source.priority, predecessors: [], ...(source.deadlineVersion ? { deadlineVersion: source.deadlineVersion } : {}) });
|
|
94
|
+
}
|
|
95
|
+
if (risky.length) for (let i = cells.length - 1; i >= 0; i--) if (cells[i]!.title === source.title && unsafe(source.text)) cells.splice(i, 1);
|
|
96
|
+
const result = { cells, gated, fallback: !parsed };
|
|
97
|
+
if (!parsed) log('fallback', { cardId: card.id, reason: decision.ok ? 'schema' : decision.reason });
|
|
98
|
+
log('split', { cardId: card.id, cells: cells.length, gated: gated.length });
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function decisionInput(card: TaskCard, part: FlowGate): RaiseInput {
|
|
103
|
+
return { title: part.title.slice(0, 120), category: part.category as DecisionCategory,
|
|
104
|
+
scqa: { s: `의뢰 카드 ${card.id}에 다음 작업이 포함됨`, c: `대표 판단 전 자동 판 배치 금지: ${part.category}` },
|
|
105
|
+
pendingQuestion: part.text, options: [
|
|
106
|
+
{ key: 'a', label: '승인 후 별도로 진행', consequence: '대표 결정 뒤 별도 집행' },
|
|
107
|
+
{ key: 'b', label: '진행하지 않음', consequence: '이 부분의 집행 중지' },
|
|
108
|
+
], recommendation: { skipped: true, reason: '대표가 직접 판단해야 함' },
|
|
109
|
+
crossCheckSkipped: '자동 안전 분기', raisedBy: { agent: 'flow', track: 'OP' } };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The only release write is placeCell: per-cell journals allow recovery after a process dies mid-card. */
|
|
113
|
+
export async function placeCards(card: TaskCard, split: FlowSplit, deps: FlowDeps = {}, dryRun = false): Promise<FlowResult> {
|
|
114
|
+
const store = deps.store ?? new CardStore();
|
|
115
|
+
const placements: PlacementDecision[] = [], unplaced: FlowResult['unplaced'] = [], decisions: string[] = [];
|
|
116
|
+
const preview = new Map<string, Checklist>();
|
|
117
|
+
const placement: PlacementDeps = { ...deps.placement, dryRun, ...(dryRun ? { checklist: (version: string) => {
|
|
118
|
+
let snapshot = preview.get(version);
|
|
119
|
+
if (!snapshot) { snapshot = structuredClone((deps.placement?.checklist ?? listChecklist)(version)); preview.set(version, snapshot); }
|
|
120
|
+
return snapshot;
|
|
121
|
+
} } : {}) };
|
|
122
|
+
try {
|
|
123
|
+
for (const [index, part] of split.gated.entries()) {
|
|
124
|
+
const key = `flow:gated:${index}`;
|
|
125
|
+
const existing = store.getCard(card.id)?.sections.find(s => s.key === key);
|
|
126
|
+
if (existing) { decisions.push(JSON.parse(existing.content).decisionId as string); continue; }
|
|
127
|
+
if (dryRun) { decisions.push('dry-run'); continue; }
|
|
128
|
+
const ref = `flow:${card.id}:gated:${index}`;
|
|
129
|
+
const decisionId = deps.raise ? deps.raise({ ...decisionInput(card, part), refs: [ref] }, ref)
|
|
130
|
+
: new DecisionLedger({ stateDir: store.root }).raiseOnce({ ...decisionInput(card, part), refs: [ref] }, ref).id;
|
|
131
|
+
store.appendSection(card.id, { key, owner: 'flow', content: JSON.stringify({ decisionId, category: part.category, title: part.title }) });
|
|
132
|
+
decisions.push(decisionId);
|
|
133
|
+
log('gated', { cardId: card.id, decisionId, category: part.category });
|
|
134
|
+
}
|
|
135
|
+
for (const cell of split.cells) {
|
|
136
|
+
const key = `flow:placed:${cell.id}`;
|
|
137
|
+
const existing = store.getCard(card.id)?.sections.find(s => s.key === key);
|
|
138
|
+
if (existing && !dryRun) {
|
|
139
|
+
const saved = JSON.parse(existing.content) as { decision?: PlacementDecision; reason?: string };
|
|
140
|
+
if (saved.decision) placements.push(saved.decision);
|
|
141
|
+
else unplaced.push({ id: cell.id, reason: saved.reason! });
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
let record: { decision?: PlacementDecision; reason?: string };
|
|
145
|
+
const blockedBy = cell.predecessors.find(id => unplaced.some(item => item.id === id));
|
|
146
|
+
if (blockedBy) {
|
|
147
|
+
record = { reason: `선행 칸 ${blockedBy} 미배치` };
|
|
148
|
+
unplaced.push({ id: cell.id, reason: record.reason! });
|
|
149
|
+
if (!dryRun) store.appendSection(card.id, { key, owner: 'flow', content: JSON.stringify(record) });
|
|
150
|
+
log('placed', { cardId: card.id, id: cell.id, reason: record.reason, dryRun });
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
const { falsifier: _falsifier, ...input } = cell;
|
|
155
|
+
const decision = (deps.place ?? placeCell)(input, placement);
|
|
156
|
+
record = { decision };
|
|
157
|
+
placements.push(decision);
|
|
158
|
+
if (dryRun) preview.get(decision.version)?.items.push({ ...input, status: 'yellow', updatedAt: new Date().toISOString(), updatedBy: 'flow' });
|
|
159
|
+
log('placed', { cardId: card.id, id: cell.id, version: decision.version, dryRun });
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (!(error instanceof CliUserError) || !error.message.includes('배치할 판이 없다')) throw error;
|
|
162
|
+
record = { reason: error.message };
|
|
163
|
+
unplaced.push({ id: cell.id, reason: error.message });
|
|
164
|
+
log('placed', { cardId: card.id, id: cell.id, reason: error.message, dryRun });
|
|
165
|
+
}
|
|
166
|
+
if (!dryRun) store.appendSection(card.id, { key, owner: 'flow', content: JSON.stringify(record) });
|
|
167
|
+
}
|
|
168
|
+
const versions = [...new Set(placements.map(p => p.version))];
|
|
169
|
+
const text = `칸 ${split.cells.length}개 · 판 ${versions.length ? versions.join(', ') : '미배치'}${unplaced.length ? ` · 미배치 ${unplaced.length}개` : ''}${decisions.length ? ` · 결정 ${decisions.length}개` : ''}`;
|
|
170
|
+
return { cardId: card.id, cells: split.cells.length, placements, unplaced, decisions, text };
|
|
171
|
+
} finally { if (!deps.store) store.close(); }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function locked<T>(store: CardStore, id: string, fn: () => Promise<T>): Promise<T> {
|
|
175
|
+
const dir = join(store.root, 'task-cards', 'flow-locks');
|
|
176
|
+
mkdirSync(dir, { recursive: true });
|
|
177
|
+
const path = join(dir, `${id}.lock`);
|
|
178
|
+
const until = Date.now() + 10_000;
|
|
179
|
+
let fd: number;
|
|
180
|
+
for (;;) {
|
|
181
|
+
try { fd = openSync(path, 'wx', 0o600); break; }
|
|
182
|
+
catch (error) {
|
|
183
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
|
|
184
|
+
try {
|
|
185
|
+
const pid = Number(readFileSync(path, 'utf8'));
|
|
186
|
+
if (pid > 0 && Date.now() - statSync(path).mtimeMs > 30_000) {
|
|
187
|
+
try { process.kill(pid, 0); } catch (e) { if ((e as NodeJS.ErrnoException).code === 'ESRCH') { unlinkSync(path); continue; } }
|
|
188
|
+
}
|
|
189
|
+
} catch (e) { if ((e as NodeJS.ErrnoException).code === 'ENOENT') continue; }
|
|
190
|
+
if (Date.now() > until) throw new CliUserError(`카드 처리 잠금 대기 초과: ${id}`);
|
|
191
|
+
await Bun.sleep(25);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
try { writeSync(fd, String(process.pid)); return await fn(); }
|
|
195
|
+
finally { closeSync(fd); unlinkSync(path); }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function notify(card: TaskCard, result: FlowResult, deps: FlowDeps): Promise<void> {
|
|
199
|
+
if (!card.sections.some(s => s.key === 'intake:reply:0' || s.key === 'reply:0')) {
|
|
200
|
+
debug.log('flow', 'reply-missing', { cardId: card.id });
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const send = deps.reply ?? (await import('../intake-plane/wish-reply.js')).sendCardReply;
|
|
205
|
+
if (send) await send(card.id, result.text);
|
|
206
|
+
} catch (error) { debug.log('flow', 'reply-failed', { cardId: card.id, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export async function flowCard(cardId: string, deps: FlowDeps = {}, dryRun = false): Promise<FlowResult> {
|
|
210
|
+
const store = deps.store ?? new CardStore(undefined, dryRun);
|
|
211
|
+
try {
|
|
212
|
+
const run = async () => {
|
|
213
|
+
const card = store.getCard(cardId);
|
|
214
|
+
if (!card) throw new CliUserError(`카드가 없다: ${cardId}`);
|
|
215
|
+
const completed = card.sections.find(s => s.key === 'flow:result:0');
|
|
216
|
+
if (completed && !dryRun) return { ...JSON.parse(completed.content) as FlowResult, duplicate: true };
|
|
217
|
+
if (card.status !== 'open') throw new CliUserError(`닫힌 카드: ${cardId}`);
|
|
218
|
+
const split = await splitCard(card, deps);
|
|
219
|
+
if (!dryRun && !card.sections.some(s => s.key === 'flow:split:0')) store.appendSection(cardId, { key: 'flow:split:0', owner: 'flow', content: JSON.stringify(split) });
|
|
220
|
+
const result = await placeCards(card, split, { ...deps, store }, dryRun);
|
|
221
|
+
if (!dryRun) {
|
|
222
|
+
store.appendSection(cardId, { key: 'flow:result:0', owner: 'flow', content: JSON.stringify(result) });
|
|
223
|
+
await notify(store.getCard(cardId)!, result, deps);
|
|
224
|
+
}
|
|
225
|
+
return result;
|
|
226
|
+
};
|
|
227
|
+
return dryRun ? await run() : await locked(store, cardId, run);
|
|
228
|
+
} finally { if (!deps.store) store.close(); }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** One fresh Wish card per invocation, shared with the orchestrator node rather than calling the CLI. */
|
|
232
|
+
export async function flowTick(deps: FlowDeps = {}): Promise<FlowResult | null> {
|
|
233
|
+
const store = deps.store ?? new CardStore();
|
|
234
|
+
try {
|
|
235
|
+
const card = store.listCards({ open: true }).filter(c => c.goalId.startsWith('wish:') && !c.sections.some(s => s.key === 'flow:result:0'))
|
|
236
|
+
.sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id))[0];
|
|
237
|
+
return card ? await flowCard(card.id, { ...deps, store }) : null;
|
|
238
|
+
} finally { if (!deps.store) store.close(); }
|
|
239
|
+
}
|
|
@@ -23,11 +23,11 @@ export interface NodeKindEntry {
|
|
|
23
23
|
export const HARNESS_CORE_KINDS = ['agent', 'gate', 'git', 'judge', 'observe', 'hitl', 'subgraph'] as const;
|
|
24
24
|
|
|
25
25
|
// Workflow validation, the palette, and executor variant labels read the same keys.
|
|
26
|
-
export const WORKFLOW_CORE_KINDS = ['prompt', 'bash', 'skill', 'cft', 'approval', 'if', 'switch', 'iteration', 'classify', 'extract', 'set', 'filter', 'template', 'http', 'showroom', 'task', 'scheduleTrigger', 'webhookTrigger', 'discordTrigger', 'telegramTrigger', 'manualTrigger', 'chatTrigger', 'subworkflow'] as const;
|
|
26
|
+
export const WORKFLOW_CORE_KINDS = ['prompt', 'bash', 'skill', 'cft', 'approval', 'if', 'switch', 'iteration', 'classify', 'extract', 'set', 'filter', 'template', 'http', 'showroom', 'task', 'scheduleTrigger', 'webhookTrigger', 'discordTrigger', 'telegramTrigger', 'manualTrigger', 'chatTrigger', 'subworkflow', 'knowledge'] as const;
|
|
27
27
|
|
|
28
28
|
const descriptions: Record<GraphKind, readonly string[]> = {
|
|
29
29
|
harness: ['Run an agent', 'Check an execution gate', 'Perform git operations', 'Judge an outcome', 'Observe a result', 'Request human input', 'Run a child graph'],
|
|
30
|
-
workflow: ['Run an LLM prompt', 'Run a shell command', 'Invoke a skill', 'Invoke a CFT method', 'Request approval', 'Branch on a condition', 'Select a case', 'Iterate items', 'Classify input', 'Extract structured data', 'Assign variables', 'Filter items', 'Render a template', 'Make an HTTP request', 'Run a showroom', 'Create a task', 'Start on a schedule', 'Start on a webhook', 'Start on Discord', 'Start on Telegram', 'Start manually', 'Start on chat', 'Call another workflow'],
|
|
30
|
+
workflow: ['Run an LLM prompt', 'Run a shell command', 'Invoke a skill', 'Invoke a CFT method', 'Request approval', 'Branch on a condition', 'Select a case', 'Iterate items', 'Classify input', 'Extract structured data', 'Assign variables', 'Filter items', 'Render a template', 'Make an HTTP request', 'Run a showroom', 'Create a task', 'Start on a schedule', 'Start on a webhook', 'Start on Discord', 'Start on Telegram', 'Start manually', 'Start on chat', 'Call another workflow', 'Search the vault for context'],
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
const kinds = new Map<GraphKind, Map<string, NodeKindEntry>>();
|
|
@@ -26,7 +26,7 @@ export function raiseApprovalCard(state: GraphRunState, pending: Pending, deps:
|
|
|
26
26
|
const nodeId = pending.nodeId;
|
|
27
27
|
const identity = { graphId: state.graphId, runId: state.runId, nodeId };
|
|
28
28
|
const visit = state.path.filter(node => node === nodeId).length;
|
|
29
|
-
if (state.dryRun || state.status !== 'awaiting-approval' || state.pending?.nodeId !== nodeId ||
|
|
29
|
+
if (state.dryRun || state.growthPark || state.status !== 'awaiting-approval' || state.pending?.nodeId !== nodeId ||
|
|
30
30
|
state.pending.decision || state.path.at(-1) !== nodeId || visit < 1 ||
|
|
31
31
|
existsSync(`${join(root, 'graph-runs', state.graphId, `${state.runId}.json`)}.${state.path.length}.decision.json`)) {
|
|
32
32
|
observation('skipped', identity);
|
|
@@ -88,7 +88,7 @@ function applyOne(entry: DecisionEntry, identity: { graphId: string; runId: stri
|
|
|
88
88
|
observation('skipped', identity, entry.id, entry.choice);
|
|
89
89
|
return false;
|
|
90
90
|
}
|
|
91
|
-
if (state.graphId !== graphId || state.runId !== runId || state.dryRun || state.status !== 'awaiting-approval' ||
|
|
91
|
+
if (state.graphId !== graphId || state.runId !== runId || state.dryRun || state.growthPark || state.status !== 'awaiting-approval' ||
|
|
92
92
|
!state.pending || state.pending.nodeId !== nodeId || state.pending.decision || state.path.at(-1) !== nodeId ||
|
|
93
93
|
state.path.filter(node => node === nodeId).length !== visit ||
|
|
94
94
|
existsSync(`${statePath}.${state.path.length}.decision.json`)) {
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { DecisionLedger, type DecisionEntry } from '../decisions/decision-ledger.js';
|
|
3
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
1
4
|
import { createGraphVariant } from '../self-implement/graph-variant.js';
|
|
2
5
|
import type { GraphEdgeSpec, GraphNodeSpec, GraphTemplateSpec } from '../self-implement/graph-yaml.js';
|
|
3
6
|
import { createLLMGrowthProposer } from './graph-grow-llm.js';
|
|
@@ -10,6 +13,8 @@ export interface GrowthProposal {
|
|
|
10
13
|
|
|
11
14
|
export type GrowthProposer = (input: { graph: GraphTemplateSpec; nodeId: string; outcome: string; runId: string; output?: unknown }) => GrowthProposal | undefined | Promise<GrowthProposal | undefined>;
|
|
12
15
|
export type GrowthRecipeEffect = 'read-only' | 'external-effect' | 'unknown';
|
|
16
|
+
export interface GrowthCommand { command: string; dry_run_command?: string; timeout_ms?: number }
|
|
17
|
+
export interface GrowthRecipeClassification { effect: GrowthRecipeEffect; command?: GrowthCommand; approval?: string }
|
|
13
18
|
|
|
14
19
|
export interface GraphGrowth {
|
|
15
20
|
node: GraphNodeSpec;
|
|
@@ -18,8 +23,52 @@ export interface GraphGrowth {
|
|
|
18
23
|
undo: { removeNode: string; removeOutcome: { from: string; outcome: string }; removeEdge?: GraphEdgeSpec };
|
|
19
24
|
}
|
|
20
25
|
|
|
26
|
+
export interface GrowthDecisionDeps {
|
|
27
|
+
ledger?: Pick<DecisionLedger, 'raiseOnce'>;
|
|
28
|
+
root?: string;
|
|
29
|
+
now?: () => Date;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function proposalGrowth(input: Parameters<GrowthProposer>[0], proposal: GrowthProposal): GraphGrowth {
|
|
33
|
+
const edges: GraphEdgeSpec[] = [
|
|
34
|
+
{ from: input.nodeId, on: 'outcome', map: { [input.outcome]: proposal.node.nodeId } },
|
|
35
|
+
{ from: proposal.node.nodeId, to: proposal.returnTo as string },
|
|
36
|
+
];
|
|
37
|
+
return { node: proposal.node, edges, reason: proposal.reason,
|
|
38
|
+
undo: { removeNode: proposal.node.nodeId, removeOutcome: { from: input.nodeId, outcome: input.outcome }, removeEdge: edges[1]! } };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The card's own ref commits to the entire growth and resolved recipe, independently of the parked run state. */
|
|
42
|
+
export function growthApprovalRef(identity: { graphId: string; runId: string; from: string; outcome: string },
|
|
43
|
+
growth: GraphGrowth, command?: GrowthCommand, approval?: string): string {
|
|
44
|
+
const hash = createHash('sha256').update(JSON.stringify({ identity, growth, command, approval })).digest('hex');
|
|
45
|
+
return `graph-growth:${hash}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function growthDecisionRef(input: Parameters<GrowthProposer>[0], proposal: GrowthProposal, command?: GrowthCommand, approval?: string): string {
|
|
49
|
+
return growthApprovalRef({ graphId: input.graph.graphId, runId: input.runId, from: input.nodeId, outcome: input.outcome },
|
|
50
|
+
proposalGrowth(input, proposal), command, approval);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function growthDecision(input: Parameters<GrowthProposer>[0], proposal: GrowthProposal, deps: GrowthDecisionDeps = {}, command?: GrowthCommand, approval?: string): DecisionEntry {
|
|
54
|
+
const ledger = deps.ledger ?? new DecisionLedger({ stateDir: deps.root ?? effectiveInstanceRoot() });
|
|
55
|
+
const ref = growthDecisionRef(input, proposal, command, approval);
|
|
56
|
+
const summary = `${proposal.node.nodeId} (${proposal.node.kind}, ${proposal.node.recipe}) → ${proposal.returnTo}`;
|
|
57
|
+
const commandDigest = command ? createHash('sha256').update(JSON.stringify(command)).digest('hex') : undefined;
|
|
58
|
+
return ledger.raiseOnce({ title: `${input.graph.graphId} / ${input.nodeId} / ${input.outcome} growth approval`, category: 'scope',
|
|
59
|
+
scqa: { s: `Graph ${input.graph.graphId}, node ${input.nodeId}, outcome ${input.outcome} has no mapped growth edge.`,
|
|
60
|
+
c: `Proposed node: ${summary}; command SHA-256: ${commandDigest ?? 'unresolved'}; reason: ${proposal.reason}`.slice(0, 240) },
|
|
61
|
+
options: [{ key: 'a', label: '승인', consequence: `Apply proposed node ${proposal.node.nodeId} and continue the run` },
|
|
62
|
+
{ key: 'b', label: '거절', consequence: 'Keep the original fallback route' }],
|
|
63
|
+
recommendation: { skipped: true, reason: 'Side-effect recipe requires a human decision' },
|
|
64
|
+
...(command || approval ? { pendingQuestion: [approval ? `Approval recipe: ${approval}` : undefined,
|
|
65
|
+
command ? `Proposed command (${commandDigest}):\n${command.command}` : undefined].filter(Boolean).join('\n') } : {}),
|
|
66
|
+
raisedBy: { agent: 'graph-runner' }, refs: [ref], dueAt: new Date((deps.now?.() ?? new Date()).getTime() + 8 * 60 * 60 * 1000).toISOString() }, ref);
|
|
67
|
+
}
|
|
68
|
+
|
|
21
69
|
export async function proposeGrowth(input: Parameters<GrowthProposer>[0],
|
|
22
|
-
proposer: GrowthProposer = createLLMGrowthProposer(), classifyRecipe?: (node: GraphNodeSpec) => GrowthRecipeEffect
|
|
70
|
+
proposer: GrowthProposer = createLLMGrowthProposer(), classifyRecipe?: (node: GraphNodeSpec) => GrowthRecipeEffect | GrowthRecipeClassification,
|
|
71
|
+
decisionDeps: GrowthDecisionDeps = {}): Promise<{ ok: true; graph: GraphTemplateSpec; growth: GraphGrowth } | { ok: false; reason: string; park?: boolean; growth?: GraphGrowth; decisionId?: string; decisionRef?: string; command?: GrowthCommand; approval?: string }> {
|
|
23
72
|
let proposal: GrowthProposal | undefined;
|
|
24
73
|
try { proposal = await proposer(input); }
|
|
25
74
|
catch (error) { return { ok: false, reason: `proposer failed: ${String(error)}` }; }
|
|
@@ -35,16 +84,20 @@ export async function proposeGrowth(input: Parameters<GrowthProposer>[0],
|
|
|
35
84
|
// Only a trusted classification of the resolved recipe may authorize execution.
|
|
36
85
|
let effect: GrowthRecipeEffect = 'unknown';
|
|
37
86
|
if (proposal.node.recipe !== 'none') {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
87
|
+
let command: GrowthCommand | undefined;
|
|
88
|
+
let approval: string | undefined;
|
|
89
|
+
try {
|
|
90
|
+
const classification = classifyRecipe?.(proposal.node) ?? 'unknown';
|
|
91
|
+
effect = typeof classification === 'string' ? classification : classification.effect;
|
|
92
|
+
command = typeof classification === 'string' ? undefined : classification.command;
|
|
93
|
+
approval = typeof classification === 'string' ? undefined : classification.approval;
|
|
94
|
+
} catch { effect = 'unknown'; }
|
|
95
|
+
if (effect !== 'read-only' || approval) {
|
|
96
|
+
const decision = growthDecision(input, proposal, decisionDeps, command, approval);
|
|
97
|
+
return { ok: false, park: true, reason: '사람 확인 필요: external or unclassified side-effect recipe',
|
|
98
|
+
decisionId: decision.id, decisionRef: growthDecisionRef(input, proposal, command, approval), command, approval,
|
|
99
|
+
growth: proposalGrowth(input, proposal) };
|
|
42
100
|
}
|
|
43
101
|
}
|
|
44
|
-
|
|
45
|
-
{ from: input.nodeId, on: 'outcome', map: { [input.outcome]: proposal.node.nodeId } },
|
|
46
|
-
{ from: proposal.node.nodeId, to: proposal.returnTo as string },
|
|
47
|
-
];
|
|
48
|
-
return { ok: true, graph: result.template, growth: { node: proposal.node, edges, reason: proposal.reason,
|
|
49
|
-
undo: { removeNode: proposal.node.nodeId, removeOutcome: { from: input.nodeId, outcome: input.outcome }, removeEdge: edges[1]! } } };
|
|
102
|
+
return { ok: true, graph: result.template, growth: proposalGrowth(input, proposal) };
|
|
50
103
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
import { closeSync, mkdirSync, openSync, readFileSync, rmdirSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
import { parse as parseYaml } from 'yaml';
|
|
4
5
|
import { debug } from '../debug/log.js';
|
|
6
|
+
import { observeModelInputTokens } from '../harness/model-input-observation.js';
|
|
5
7
|
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
8
|
+
import { DecisionLedger } from '../decisions/decision-ledger.js';
|
|
6
9
|
import { latestGraphRun, runGraph, type GraphRunOptions, type GraphRunState } from './runner.js';
|
|
7
10
|
|
|
8
11
|
export interface GraphTickOptions {
|
|
@@ -29,16 +32,23 @@ export async function graphTick(path: string, options: GraphTickOptions = {}): P
|
|
|
29
32
|
const lock = join(root, 'graph-runs', graphId, '.tick.lock');
|
|
30
33
|
mkdirSync(join(root, 'graph-runs', graphId), { recursive: true });
|
|
31
34
|
const releaseRecovery = await acquireRecoveryGuard(`${lock}.recovery.guard`, graphId);
|
|
35
|
+
const tickId = `graph-${randomUUID()}`;
|
|
32
36
|
let acquired = false;
|
|
33
37
|
try {
|
|
34
38
|
acquireTickLock(lock, graphId, options.deps?.isAlive ?? processAlive);
|
|
35
39
|
acquired = true;
|
|
36
40
|
await options.deps?.notify?.();
|
|
37
41
|
const latest = latestGraphRun(graphId, root);
|
|
42
|
+
const growthCard = latest?.growthPark
|
|
43
|
+
? (options.deps?.growthDecision?.list ?? ((filters: { status: 'all' }) => new DecisionLedger({ stateDir: root }).list(filters)))({ status: 'all' })
|
|
44
|
+
.find(entry => entry.id === latest.growthPark?.decisionId && entry.refs?.includes(latest.growthPark.decisionRef))
|
|
45
|
+
: undefined;
|
|
46
|
+
const growthDecided = growthCard?.status === 'decided' && growthCard.decidedBy?.kind === 'human' &&
|
|
47
|
+
(growthCard.choice === 'a' || growthCard.choice === 'b');
|
|
38
48
|
let result: GraphTickResult;
|
|
39
|
-
if (latest?.status === 'awaiting-approval' && !latest.pending?.decision) {
|
|
49
|
+
if (latest?.status === 'awaiting-approval' && !latest.pending?.decision && !growthDecided) {
|
|
40
50
|
result = { action: 'waiting', runId: latest.runId, status: latest.status };
|
|
41
|
-
} else if (latest?.status === 'running' || (latest?.status === 'awaiting-approval' && latest.pending?.decision)) {
|
|
51
|
+
} else if (latest?.status === 'running' || (latest?.status === 'awaiting-approval' && (latest.pending?.decision || growthDecided))) {
|
|
42
52
|
const state = await runGraph(path, { resumeRunId: latest.runId, deps: { ...options.deps, root } });
|
|
43
53
|
result = { action: 'resumed', runId: state.runId, status: state.status };
|
|
44
54
|
} else if (options.startIfIdle) {
|
|
@@ -53,6 +63,7 @@ export async function graphTick(path: string, options: GraphTickOptions = {}): P
|
|
|
53
63
|
} finally {
|
|
54
64
|
try {
|
|
55
65
|
if (acquired) {
|
|
66
|
+
observeModelInputTokens({ scope: 'loop-tick', nodeKind: 'graph-tick', graphId, tickId });
|
|
56
67
|
unlinkSync(join(lock, 'owner'));
|
|
57
68
|
rmdirSync(lock);
|
|
58
69
|
}
|