elanous 0.2.9 → 0.2.11
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/Y4zKzL62sakG5momz3SoQ/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1394-556de6d71bbdb0fb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1452.93700483b9d4fc61.js +9 -0
- package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 1829-b453a1b22d3999bb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2310-7a59f043f6db207e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2548-6e316138d347cefe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3869-a39d1d9921e10d43.js +1 -0
- package/apps/pwa/out/_next/static/chunks/390.c373e31b3b9d0c41.js +5 -0
- package/apps/pwa/out/_next/static/chunks/3992-2e522a8ac5f2afde.js +25 -0
- package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4685-c3c1c625b71accd1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +2 -0
- package/apps/pwa/out/_next/static/chunks/507-5acd98bd6dab2d8a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5445-745eb795c5963d79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → 5cd7e3a9-404fd6b8d0ecd44c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 7334.a6679d9168f37aa8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +1 -0
- package/apps/pwa/out/_next/static/chunks/79f8be9e-957d68913b590b08.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8039-df92e08fede5de8f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8421-1c4acfb1395a0edf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8605-ea5bee9dfae512c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2415.ed6428ff334c7ce2.js → 9269.d2a4795bc6812717.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9656-c1540088f5859c80.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9906-482c6619b3fdda20.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-1dcdc58fc2de2454.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-6a061bd97f0b9398.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-cb95a7432c9ee744.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-f972fc1c00a62e88.js → page-b12b6d2f8d2d1aea.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b9efe868bfd36255.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-7c97dcdfe2e37a60.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-8fd36984f549270a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-5721537ca8a3598c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-b654240a572e4c48.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-b9750abc897508c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-4cd1de371f8ad048.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-d99f89657828421c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-50cb2a0eb15e6c83.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-c8b1ccda6ba7ae98.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-d6b1d156fb95e74b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-2a9932ddb62d76bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-9688f5b4f1ff950f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-6b15dd6456c5df09.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-adfbd2ba9598e59a.js → page-35414e0a007b4082.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-e7a529e0507ac86c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-e001823c35c9dd67.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-371423d1a573d64b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4b764b125339ac17.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cdf8e4b01067bcf1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a24072b35470157.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-94c82eb0ae5abb32.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-0be8d4b2ced75ba0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d37cb0d52f9b6bd5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-686da0df9bf23e5c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4dc614f3ce043e63.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-53f80edc6b8dc07c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-d02c782fc792e2af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-239c17b911b84e72.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-dc6a79fb003f746c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-0fffb84cafdc9da6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-1218753c82d585f6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-4b5935e67531efb5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-3285bc87caf1eaa5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-ad553b0e06505ecd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-fca096e3e860306f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-9ebfa78f0d9885c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-3dc5f21f7e39622d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-c6da2b468454532d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-93bc70e36f595414.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2ea96958-e8c7e112de50b067.js → f48170cc-d05d28bf87a43bf4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +1 -0
- package/apps/pwa/out/_next/static/css/543f56bcb1f79fca.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/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 -0
- package/apps/pwa/out/inside/index.txt +31 -0
- 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/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 -0
- package/apps/pwa/out/ops/seats/index.txt +30 -0
- package/apps/pwa/out/outputs/index.html +1 -0
- package/apps/pwa/out/outputs/index.txt +30 -0
- 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 -24
- package/apps/pwa/out/today/index.html +1 -0
- package/apps/pwa/out/today/index.txt +33 -0
- 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 -24
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +23 -23
- package/graphs/drafts/draft-cleanup.yaml +19 -0
- package/graphs/drafts/recipes.yaml +23 -0
- package/graphs/field/field-feed.yaml +2 -0
- package/graphs/implement-loop.yaml +1 -1
- package/graphs/intake/intake-daily.yaml +2 -1
- package/graphs/ops/recipes.yaml +3 -0
- package/graphs/ops/secret-exposure.yaml +24 -0
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +4 -1
- package/graphs/video/explainer-line.yaml +2 -2
- package/package.json +2 -1
- package/plugins/card-followup/graphs/run-step.ts +48 -13
- package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
- package/plugins/doc-draft/graphs/run-step.ts +13 -6
- package/plugins/elanous-hwp/README.md +25 -0
- package/plugins/elanous-hwp/cli.ts +22 -0
- package/plugins/elanous-hwp/converter.ts +237 -0
- package/plugins/elanous-hwp/fixtures/fixed-controls.hwpx +0 -0
- package/plugins/elanous-hwp/fixtures/fixed-table-controls.hwpx +0 -0
- package/plugins/elanous-hwp/fixtures/fixed.hwpx +0 -0
- package/plugins/elanous-hwp/plugin.json +16 -0
- package/plugins/elanous-hwp/plugin.ts +40 -0
- package/scripts/ci-public-export-leak-gate.ts +75 -14
- package/scripts/ci-public-leak-gate.ts +44 -9
- package/scripts/ci-pwa-build-gate.ts +160 -0
- package/scripts/context-hooks/claude-stop.sh +4 -0
- package/scripts/context-hooks/emit.ts +24 -0
- package/scripts/context-hooks/post-tool-use.sh +4 -0
- package/scripts/coord-channel-watch.sh +54 -8
- package/scripts/coord-digest.ts +199 -0
- package/scripts/coord-post.sh +60 -3
- package/scripts/coord-requests-bridge.ts +143 -0
- package/scripts/field-feed/lib.ts +4 -0
- package/scripts/fleet/node-setup.sh +20 -3
- package/scripts/homebrew-formula-bump.ts +69 -0
- package/scripts/install.ps1 +68 -9
- package/scripts/install.sh +39 -12
- package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
- package/scripts/lib/tui-regress-checks.ts +146 -0
- package/scripts/marketing/measure-site-numbers.ts +169 -0
- package/scripts/measure-gate-shards.ts +309 -0
- package/scripts/memory-lifecycle-cycle.ts +2 -2
- package/scripts/mission-request-judge.ts +31 -1
- package/scripts/morning-report.ts +2 -2
- package/scripts/opportunity-scan.ts +1 -1
- package/scripts/ops-now-probe.ts +327 -0
- package/scripts/publish-internal-dist.ts +11 -1
- package/scripts/release-loop/auto-approve-node.ts +21 -4
- package/scripts/release-loop/cut-branch.ts +84 -0
- package/scripts/release-loop/cutoff.ts +19 -8
- package/scripts/release-loop/gate-node.ts +14 -13
- package/scripts/release-loop/mac-smoke-node.ts +229 -0
- package/scripts/release-loop/node-verdict.ts +3 -3
- package/scripts/release-loop/ops-upgrade-node.ts +72 -16
- package/scripts/release-loop/release-readiness.ts +9 -0
- package/scripts/release-loop/tui-node-checks.ts +42 -0
- package/scripts/release-loop/tui-sim-node.ts +32 -8
- package/scripts/release-loop/unattended-release.ts +5 -3
- package/scripts/release-loop/version-node.ts +112 -13
- package/scripts/repo-watch-cycle.ts +1 -1
- package/scripts/retro-cycle.ts +2 -2
- package/scripts/schedule-health-report.ts +1 -1
- package/scripts/signal-digest-cycle.ts +2 -7
- package/scripts/signal-digest-photo.ts +11 -0
- package/scripts/storyboard/storyboard.ts +5 -161
- package/scripts/tech1-public-measure.ts +94 -0
- package/scripts/test-diet/lib.ts +30 -1
- package/scripts/test-diet/node.ts +83 -8
- package/scripts/test-timeout-audit.ts +114 -0
- package/scripts/tree-sync-alert.ts +1 -1
- package/scripts/tui-regress.ts +71 -0
- package/scripts/ux/demo-rehearsal.ts +243 -0
- package/scripts/ux/lib/rehearsal-verdict.ts +73 -0
- package/scripts/waitlist-daily-digest.ts +1 -1
- package/skills/diagram-master/SKILL.md +366 -0
- package/skills/diagram-master/references/engines/excalidraw/color-palette.md +67 -0
- package/skills/diagram-master/references/engines/excalidraw/element-templates.md +182 -0
- package/skills/diagram-master/references/engines/excalidraw/json-schema.md +71 -0
- package/skills/diagram-master/references/engines/excalidraw/render_excalidraw.py +189 -0
- package/skills/diagram-master/references/engines/excalidraw/render_template.html +57 -0
- package/skills/diagram-master/references/engines/matplotlib/matplotlib-patterns.md +235 -0
- package/skills/diagram-master/references/engines/matplotlib/render_matplotlib.py +184 -0
- package/skills/diagram-master/references/engines/mermaid/domain-templates.md +435 -0
- package/skills/diagram-master/references/engines/mermaid/render_mermaid.py +116 -0
- package/skills/diagram-master/references/engines/mermaid/syntax-reference.md +2247 -0
- package/skills/diagram-master/references/engines/nanobannana/render_nanobannana2.py +168 -0
- package/skills/diagram-master/references/engines/svg/color-palette.md +188 -0
- package/skills/diagram-master/references/engines/svg/html-base-template.html +332 -0
- package/skills/diagram-master/references/engines/svg/interactivity-patterns.md +435 -0
- package/skills/diagram-master/references/engines/svg/render_interactive_svg.py +212 -0
- package/skills/diagram-master/references/engines/svg/svg-element-templates.md +349 -0
- package/skills/diagram-master/references/examples/README.md +69 -0
- package/skills/diagram-master/references/examples/ref_grid_architecture.png +0 -0
- package/skills/diagram-master/references/examples/ref_layered_flow.png +0 -0
- package/skills/diagram-master/references/pyproject.toml +16 -0
- package/skills/diagram-master/references/quality-checklist.md +33 -0
- package/skills/diagram-master/references/routing-guide.md +164 -0
- package/skills/diagram-master/references/uv.lock +1356 -0
- package/skills/explainer-video/SKILL.md +4 -0
- package/skills/explainer-video/engine/reel.mjs +18 -13
- package/skills/explainer-video/engine/reel.sh +24 -5
- package/skills/explainer-video/engine/stitch.mjs +145 -0
- package/skills/google-workspace/SKILL.md +33 -0
- package/skills/google-workspace/scripts/gws-safe.sh +177 -0
- package/skills/grill-me/SKILL.md +1 -1
- package/skills/lecture-note-digitizer/SKILL.md +926 -0
- package/skills/lecture-note-digitizer/references/convert_to_pdf.py +30 -0
- package/skills/lecture-note-digitizer/references/extract_slide_diagram.py +73 -0
- package/skills/lecture-note-digitizer/references/html-template.md +167 -0
- package/skills/lecture-note-digitizer/references/katex-style-guide.md +165 -0
- package/skills/omni-crawl/SKILL.md +21 -11
- package/skills/omni-crawl/scripts/main.ts +6 -6
- package/skills/omni-crawl/src/router.ts +3 -3
- package/skills/omni-digest/SKILL.md +17 -9
- package/skills/omni-digest/digest.md +2 -2
- package/skills/omni-digest/src/fetch.ts +30 -3
- package/skills/pitch-deck/SKILL.md +36 -0
- package/skills/pitch-deck/template.html +80 -0
- package/skills/youtube-master/SKILL.md +163 -0
- package/skills/youtube-master/package-lock.json +558 -0
- package/skills/youtube-master/package.json +13 -0
- package/skills/youtube-master/references/study-note-template.md +124 -0
- package/skills/youtube-master/scripts/main.ts +435 -0
- package/skills/youtube-master/scripts/transcribe-local.ts +24 -0
- package/skills/youtube-master/src/audio.ts +63 -0
- package/skills/youtube-master/src/domain-staging.ts +50 -0
- package/skills/youtube-master/src/env.ts +36 -0
- package/skills/youtube-master/src/obsidian.ts +155 -0
- package/skills/youtube-master/src/router.ts +110 -0
- package/skills/youtube-master/src/stt.ts +199 -0
- package/skills/youtube-master/src/study-note.ts +175 -0
- package/skills/youtube-master/src/summarize.ts +358 -0
- package/skills/youtube-master/src/transcript.ts +76 -0
- package/skills/youtube-master/src/types.ts +91 -0
- package/skills/youtube-master/src/util.ts +93 -0
- package/skills/youtube-master/src/youtube.ts +70 -0
- package/skills/youtube-master/tsconfig.json +13 -0
- package/src/acp/codex-approval-adapter.ts +9 -3
- package/src/acp/core-turn-bridge.ts +117 -18
- package/src/acp/fact-question.ts +11 -0
- package/src/acp/server.ts +53 -9
- package/src/acp/tool-approval.ts +126 -0
- package/src/acp/turn-runner.ts +36 -0
- package/src/agent/monad-agent-turn.ts +8 -0
- package/src/agent-mission/driver.ts +80 -10
- package/src/agent-mission/pty-decision.ts +71 -0
- package/src/agent-mission/review-loop.ts +30 -0
- package/src/ask-user-question/pending-questions.ts +25 -10
- package/src/ask-user-question/types.ts +9 -0
- package/src/auth/setup-link-tokens.ts +16 -5
- package/src/autopilot/build/integrity-gate.ts +12 -0
- package/src/autopilot/build/llm-conflict-merge.ts +16 -3
- package/src/autopilot/tool-registry.ts +2 -0
- package/src/boot/daemon-prompt-turn.ts +8 -0
- package/src/boot/daemon-runtime.ts +9 -3
- package/src/boot/daemon-tools/index.ts +8 -0
- package/src/boot/daemon-tools/types.ts +2 -0
- package/src/boot/daemon-tools/web-search.ts +4 -0
- package/src/card-followup/core.ts +109 -0
- package/src/chat/ctrl-c-in-input.ts +14 -0
- package/src/chat/index.ts +53 -0
- package/src/chat/pickers/state.ts +6 -2
- package/src/cli/agent-cli.ts +7 -0
- package/src/cli/decisions-cli.ts +25 -3
- package/src/cli/doctor-bundle-upload.ts +68 -0
- package/src/cli/doctor-cli.ts +38 -4
- package/src/cli/drive-input-jail.ts +101 -0
- package/src/cli/hooks-cli.ts +17 -3
- package/src/cli/make-cli.ts +43 -0
- package/src/cli/ops-cli.ts +20 -0
- package/src/cli/persona-cli.ts +90 -4
- package/src/cli/plugin-cli.ts +31 -22
- package/src/cli/pod-cli.ts +61 -2
- package/src/cli/pr-cli.ts +82 -17
- package/src/cli/pty-drive-cli.ts +41 -7
- package/src/cli/pty-drive-verdict.ts +40 -0
- package/src/cli/pwa-stop.ts +163 -13
- package/src/cli/pwa-test.ts +3 -0
- package/src/cli/release-cli.ts +80 -24
- package/src/cli/self-update.ts +14 -0
- package/src/cli/setup-cli.ts +43 -1
- package/src/cli/skills-cli.ts +17 -1
- package/src/cli/storyboard-cli.ts +37 -0
- package/src/cli/test-state-dir-flag.ts +11 -4
- package/src/connectors/linear.ts +146 -0
- package/src/context-bus/claude-hooks-install.ts +10 -0
- package/src/context-bus/context-now-surfaces.ts +48 -0
- package/src/context-bus/context-now.ts +104 -0
- package/src/context-bus/coord-events.ts +115 -0
- package/src/context-bus/external-events.ts +52 -0
- package/src/context-bus/long-term-memory.ts +108 -0
- package/src/dashboard/dashboard-boot-fixture.ts +23 -0
- package/src/dashboard/index.ts +63 -21
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +14 -1
- package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
- package/src/decisions/decision-cards.ts +58 -3
- package/src/decisions/decision-ledger.ts +85 -6
- package/src/decisions/decision-linear-projection.ts +157 -0
- package/src/decisions/web-push-decision-cards.ts +50 -0
- package/src/directive/interpretation-card.ts +36 -0
- package/src/discord/sprint21-runtime.ts +5 -1
- package/src/discord-self-message.ts +32 -0
- package/src/discord-slash-wire.ts +5 -1
- package/src/discord.ts +19 -2
- package/src/domains/coo-admin-tool.ts +62 -0
- package/src/domains/core-tools.ts +13 -4
- package/src/domains/elanous-card.ts +29 -0
- package/src/domains/ops-facts-tool.ts +76 -0
- package/src/domains/outbound-alert.ts +46 -13
- package/src/domains/release-tool.ts +168 -0
- package/src/domains/telegram-kind-route.ts +65 -9
- package/src/exec-requests/default-outputs.ts +25 -0
- package/src/exec-requests/planner.ts +106 -28
- package/src/exec-requests/runner.ts +123 -12
- package/src/exec-requests/store.ts +11 -2
- package/src/execution-loop/launch-gate.ts +100 -0
- package/src/field/field-media.ts +10 -7
- package/src/field/field-reel.ts +19 -5
- package/src/field-note/index.ts +201 -0
- package/src/field-note/transcribe.ts +248 -0
- package/src/git-fs/worktree.ts +45 -6
- package/src/graph-kinds/registry.ts +3 -3
- package/src/graph-runner/graph-approval-card.ts +113 -0
- package/src/graph-runner/graph-cli.ts +189 -4
- package/src/graph-runner/graph-grow-llm.ts +37 -0
- package/src/graph-runner/graph-grow.ts +47 -0
- package/src/graph-runner/graph-notify.ts +25 -3
- package/src/graph-runner/runner.ts +453 -35
- package/src/grok/agent-search.ts +8 -2
- package/src/guardian/secret-exposure-scan.ts +101 -0
- package/src/harness/browser-verify.ts +25 -2
- package/src/harness/harness-cli-command.ts +478 -42
- package/src/harness/harness-incidents-writer.fixture.ts +24 -0
- package/src/harness/harness-incidents.ts +129 -0
- package/src/harness/harness-pod-dispatch.ts +43 -23
- package/src/harness/harness-seams.ts +38 -4
- package/src/harness/harness-stop.ts +75 -6
- package/src/harness/launch-registry.ts +91 -0
- package/src/harness/process-garbage.ts +5 -3
- package/src/hitl/clarification-policy.ts +3 -1
- package/src/hooks/dispatch.ts +82 -0
- package/src/hooks/expose.ts +254 -0
- package/src/hooks/github-shadow.ts +108 -0
- package/src/hooks/providers.ts +71 -5
- package/src/hooks/queue.ts +1 -1
- package/src/hooks/receiver.ts +21 -20
- package/src/index.ts +206 -49
- package/src/intake-plane/author-goals.ts +37 -4
- package/src/intake-plane/categorize.ts +18 -46
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/discord-seat-work.ts +57 -2
- package/src/intake-plane/front-route-classifier.ts +33 -39
- package/src/intake-plane/persona-answer.ts +30 -0
- package/src/intake-plane/runtime-callables.ts +8 -1
- package/src/intake-plane/seat-doc-route.ts +170 -6
- package/src/intake-plane/telegram-seat-work.ts +80 -4
- package/src/intake-plane/telegram-work.ts +1 -0
- package/src/intake-plane/wish-folder.ts +67 -0
- package/src/intent-gate/gate.ts +30 -10
- package/src/live/detail-switch.ts +5 -0
- package/src/llm/difficulty.ts +57 -0
- package/src/llm/judge-layer.ts +101 -0
- package/src/llm/mission-router.ts +33 -19
- package/src/loops/contract/examples/cmo-seat.loop.yaml +51 -0
- package/src/loops/contract/examples/orchestrator.loop.yaml +57 -0
- package/src/loops/contract/examples/steward.loop.yaml +50 -0
- package/src/loops/contract/loop-agent.schema.json +138 -0
- package/src/loops/contract/validate.ts +64 -0
- package/src/loops/registry.ts +20 -6
- package/src/market/signed-index.ts +4 -0
- package/src/maturity/bot-command-maturity.ts +45 -0
- package/src/maturity/cli-maturity.ts +34 -0
- package/src/maturity/feature-maturity.ts +120 -1
- package/src/maturity/tui-slash-maturity.ts +31 -0
- package/src/memory.ts +32 -4
- package/src/model-tier/index.ts +2 -2
- package/src/model-tier/tier-resolver.ts +20 -20
- package/src/model-tier/types.ts +6 -6
- package/src/mss/logging/log-store.ts +15 -0
- package/src/nexus/api/config-model-tier.ts +5 -4
- package/src/nexus/api/exec-requests.ts +33 -3
- package/src/nexus/api/fabric-plans.ts +53 -0
- package/src/nexus/api/harness-api.ts +5 -1
- package/src/nexus/api/health.ts +2 -0
- package/src/nexus/api/http-server.ts +89 -1
- package/src/nexus/api/inside-events.ts +226 -0
- package/src/nexus/api/meta-api.ts +32 -8
- package/src/nexus/api/missions.ts +6 -1
- package/src/nexus/api/ops-api.ts +10 -1
- package/src/nexus/api/ops-seats-sources.ts +37 -7
- package/src/nexus/api/ops-seats.ts +49 -6
- package/src/nexus/api/outputs-route.ts +44 -0
- package/src/nexus/api/seat-requests.ts +132 -10
- package/src/nexus/api/sessions-store.ts +42 -0
- package/src/nexus/api/setup-codex-login.ts +160 -0
- package/src/nexus/api/sub-seat-charters.ts +34 -0
- package/src/nexus/api/tabs-mutations.ts +3 -1
- package/src/nexus/api/trace.ts +18 -6
- package/src/nexus/api/usage-api.ts +102 -0
- package/src/nexus/api/workflow-run-event-bridge.ts +1 -0
- package/src/nexus/api/workflows.ts +80 -6
- package/src/nexus/index.ts +47 -4
- package/src/nexus/kinds/webterm.ts +2 -1
- package/src/nexus/webterm/pty.ts +6 -2
- package/src/onboarding/cli-auth-status.ts +80 -0
- package/src/onboarding/first-run.ts +89 -0
- package/src/onboarding/lan-address.ts +45 -0
- package/src/onboarding/voice-ai.ts +3 -3
- package/src/onboarding/web-first.ts +119 -24
- package/src/onboarding.ts +4 -0
- package/src/ops-now/ops-now-refresh.ts +100 -0
- package/src/org/seat-budget.ts +48 -0
- package/src/org/stall-escalation.ts +63 -0
- package/src/outputs/ledger.ts +120 -0
- package/src/persona/persona-todo.ts +79 -0
- package/src/persona/presets.ts +116 -2
- package/src/persona/team-set.ts +155 -0
- package/src/plugins/install/plugin-install.ts +14 -0
- package/src/plugins/maker/market-bundle.ts +109 -0
- package/src/plugins/maker/plugin-maker.ts +254 -11
- package/src/plugins/maker/wizard-generate.ts +103 -0
- package/src/pod-lease/admission.ts +150 -0
- package/src/pod-lease/dependency-state.ts +85 -0
- package/src/pod-lease/host-lease.ts +127 -0
- package/src/project/project-store.ts +115 -0
- package/src/project/project-suggestion.ts +16 -0
- package/src/project/session-context.ts +24 -0
- package/src/release-loop/checklist.ts +79 -5
- package/src/release-loop/feature-store.ts +159 -24
- package/src/release-loop/next-md-merge.ts +58 -0
- package/src/release-loop/release-note.ts +59 -4
- package/src/release-loop/release-schedule.ts +53 -0
- package/src/repl/index.ts +7 -0
- package/src/seat-dispatch/ceo-commands.ts +41 -8
- package/src/seat-dispatch/ceo-intent.ts +38 -0
- package/src/seat-dispatch/seat-ask.ts +130 -0
- package/src/seat-dispatch/seat-questions.ts +100 -0
- package/src/seat-loop/seat-loop.ts +750 -0
- package/src/seat-loop/seat-report.ts +99 -0
- package/src/self-dev/decompose.ts +66 -11
- package/src/self-dev/dev-cli.ts +4 -1
- package/src/self-dev/dev-pipeline.ts +37 -1
- package/src/self-dev/draft-sweep.ts +38 -33
- package/src/self-dev/draft-triage-rules.ts +8 -5
- package/src/self-dev/fabric-plan-core.ts +297 -0
- package/src/self-dev/orchestrate.ts +36 -4
- package/src/self-dev/release-path-guard.ts +62 -0
- package/src/self-dev/run-store.ts +10 -1
- package/src/self-dev/shard-aggregate.ts +230 -0
- package/src/self-dev/shard-placement.ts +60 -0
- package/src/self-dev/shard-plan.ts +48 -0
- package/src/self-dev/shard-pod-run.ts +77 -0
- package/src/self-dev/shard-run.ts +105 -0
- package/src/self-implement/execution-clarification.ts +115 -0
- package/src/self-implement/gate-baseline.ts +1 -1
- package/src/self-implement/gate-cli.ts +46 -4
- package/src/self-implement/gate-scope.ts +37 -7
- package/src/self-implement/graph-variant.ts +171 -0
- package/src/self-implement/harness-policy-gates.ts +1 -1
- package/src/self-implement/headless-elanous-driver.ts +22 -3
- package/src/self-implement/host-regate.ts +22 -1
- package/src/self-implement/importer-test-index.ts +2 -1
- package/src/self-implement/l8-merge-queue.ts +130 -0
- package/src/self-implement/orchestrator.ts +279 -12
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/route-consumer-test-index.ts +122 -0
- package/src/self-implement/run-status-mapping.ts +5 -3
- package/src/self-implement/seams.ts +72 -11
- package/src/self-implement/test-seams.ts +1 -0
- package/src/self-implement/uncommitted-work.ts +59 -0
- package/src/session/chat.ts +2 -0
- package/src/session/index.ts +45 -13
- package/src/shell-env-bootstrap.ts +17 -0
- package/src/skills/gift-install.ts +215 -0
- package/src/skills/tools/web-search.ts +4 -1
- package/src/steward/directive.ts +51 -2
- package/src/steward/launch.ts +266 -0
- package/src/steward/outcome-notify.ts +115 -0
- package/src/steward/steward-cards.ts +25 -11
- package/src/steward/triage-plan.ts +32 -4
- package/src/steward/triage.ts +129 -13
- package/src/steward/working-backwards.ts +64 -0
- package/src/storyboard/storyboard.ts +141 -0
- package/src/task-cards/card-cli.ts +25 -1
- package/src/task-cards/card-store.ts +2 -2
- package/src/task-orchestrator/mission.ts +5 -0
- package/src/task-orchestrator/store.ts +12 -3
- package/src/task-orchestrator/surfaces/pod-command-job.ts +29 -7
- package/src/task-orchestrator/surfaces/pod-lease.ts +397 -0
- package/src/task-orchestrator/surfaces/pod-pool.ts +127 -26
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +214 -40
- package/src/task-orchestrator/surfaces/self-implement.ts +15 -2
- package/src/task-orchestrator/types.ts +2 -0
- package/src/telegram-card-followup.ts +153 -0
- package/src/telegram-commands.ts +10 -1
- package/src/telegram-project-command.ts +71 -0
- package/src/telegram-report.ts +29 -19
- package/src/telegram-run.ts +7 -0
- package/src/telegram.ts +58 -11
- package/src/tool-runtime/self-cognition-runtimes.ts +13 -3
- package/src/tool-runtime/types.ts +4 -0
- package/src/tui-client/dashboard-session.ts +2 -0
- package/src/user-config.ts +216 -27
- package/src/version/packed-revision.json +1 -1
- package/src/web-push/notify-exec-request.ts +32 -0
- package/src/web-search/grok.ts +7 -1
- package/src/web-search/index.ts +28 -1
- package/src/web-search/provider.ts +4 -0
- package/src/web-search/x-intent.ts +5 -0
- package/src/workflow-runtime/executor.ts +277 -47
- package/src/workflow-runtime/index.ts +1 -0
- package/src/workflow-runtime/nodes/subworkflow.ts +60 -0
- package/src/workflow-runtime/plugin-kind-node.ts +1 -1
- package/src/workflow-runtime/schema.ts +79 -4
- package/src/workflow-runtime/types.ts +35 -4
- package/apps/pwa/out/_next/static/bFI5EEwg7T6aG88ibX4Zq/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2375-cb6debea56cce4fb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +0 -25
- package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +0 -5
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5080-1613752891fcadb4.js +0 -10
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8630-cdfc61e82c653ffc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8894-e263b332208e3957.js +0 -1
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-139040d37c79dbc9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
- package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-fb7c2d458f32fcad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-35e2713597dd24d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-918b656f064577ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-5f30e00693527ebd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-7fbae173b3c103a7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-3b4edcce364062ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-c5794f484c251f44.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-1130d8eefcd87e76.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-973996d584f008c1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-016e2bda04eecb1f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-b4d6d9a3b56f2940.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-ec49a466121233ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-f72db356744ceade.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-a6fd9b50d1b85378.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-8b0402a46a749a3d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-037f83e2a1edc8e2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-46fedff57d4bec67.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-0cf318e9dcee2bbf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-899b327cb8b6845c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-6c1e6ba743a29dfe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-1a8e4807292f6b17.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-28deba6bcb834916.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-de68e2205794037d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-c71c33a573230672.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-0df30e0c7e50148a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4fc56790fe0d2682.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-59df2aebb38a7c1e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4647be5dbcbd0795.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e025892bda74b743.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a0955de73b89f078.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-369846d2921af584.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-72cbc1c8cdd0f65d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-77b28bfb0996d5ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-3220c4934f49c6c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-90670589b87abea8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-7df9243ed021a4cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-4498c3853f4f688d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-35bedde6d885f08f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-e5bea36f183b25e6.js +0 -1
- package/apps/pwa/out/_next/static/css/f92f63a5df0ba229.css +0 -1
- package/src/dashboard/turn-typeahead-echo.ts +0 -23
- /package/apps/pwa/out/_next/static/{bFI5EEwg7T6aG88ibX4Zq → Y4zKzL62sakG5momz3SoQ}/_ssgManifest.js +0 -0
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { execFile } from 'node:child_process';
|
|
3
|
-
import { existsSync, linkSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmdirSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { existsSync, linkSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmdirSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { dirname, join, resolve, sep } from 'node:path';
|
|
5
5
|
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
6
6
|
import { getElanousConfigDir, getElanousConfigDirOverride } from '../elanous-config-dir.js';
|
|
7
7
|
import { debug } from '../debug/log.js';
|
|
8
|
+
import { publishInsideEvent } from '../nexus/api/inside-events.js';
|
|
8
9
|
import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
9
10
|
import { listInstalledPlugins } from '../plugins/install/plugin-install.js';
|
|
10
11
|
import { credentialStatus, pluginEnv } from '../plugins/install/plugin-credentials.js';
|
|
11
12
|
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
12
13
|
import { loadNodeCatalog } from '../self-implement/graph-catalog.js';
|
|
13
|
-
import {
|
|
14
|
+
import { proposeGrowth, type GraphGrowth, type GrowthProposer, type GrowthRecipeEffect } from './graph-grow.js';
|
|
15
|
+
import { createLLMGrowthProposer } from './graph-grow-llm.js';
|
|
16
|
+
import { parseGraphTemplateYaml, type GraphEdgeSpec, type GraphNodeSpec, type GraphTemplateSpec } from '../self-implement/graph-yaml.js';
|
|
17
|
+
import { createGraphVariant, type GraphVariantPlan } from '../self-implement/graph-variant.js';
|
|
18
|
+
import { psProcessStartMs, START_TOLERANCE_MS } from '../harness/harness-stop.js';
|
|
14
19
|
import { executeBashNode } from '../workflow-runtime/nodes/bash.js';
|
|
15
20
|
import type { BashNode, NodeExecContext, WorkflowDeps } from '../workflow-runtime/types.js';
|
|
16
21
|
|
|
@@ -21,6 +26,10 @@ export interface GraphRunState {
|
|
|
21
26
|
runId: string;
|
|
22
27
|
startedAt?: string;
|
|
23
28
|
finishedAt?: string;
|
|
29
|
+
pid?: number;
|
|
30
|
+
pidStartedAt?: string;
|
|
31
|
+
activeNode?: { nodeId: string; pid: number; pidStartedAt: string };
|
|
32
|
+
stoppedAt?: string;
|
|
24
33
|
status: 'running' | 'done' | 'failed' | 'budget-exceeded' | 'awaiting-approval';
|
|
25
34
|
path: string[];
|
|
26
35
|
nodes: Array<{ nodeId: string; ok: boolean; exit: number | null; executed: boolean; output?: unknown; error?: string; decidedBy?: string; decidedAt?: string }>;
|
|
@@ -29,7 +38,13 @@ export interface GraphRunState {
|
|
|
29
38
|
approvalSourceHash?: string;
|
|
30
39
|
/** Hash of the graph and recipes used for this run; required to safely restart a failed path. */
|
|
31
40
|
sourceHash?: string;
|
|
32
|
-
|
|
41
|
+
graphSnapshot?: { graphSha: string; recipesSha: string };
|
|
42
|
+
graphPath?: string;
|
|
43
|
+
variant?: { overlayId: string; goal: string; plan: GraphVariantPlan };
|
|
44
|
+
growth?: GraphGrowth[];
|
|
45
|
+
growthRejections?: Array<{ from: string; outcome: string; reason: string }>;
|
|
46
|
+
growthPark?: { from: string; outcome: string; reason: string };
|
|
47
|
+
resume?: { from: string; at: string; previousStatus: GraphRunState['status']; graph?: 'snapshot' | 'current' };
|
|
33
48
|
executed: number;
|
|
34
49
|
dryRun: boolean;
|
|
35
50
|
statePath: string;
|
|
@@ -38,10 +53,13 @@ export interface GraphRunState {
|
|
|
38
53
|
export interface GraphRunOptions {
|
|
39
54
|
input?: unknown;
|
|
40
55
|
dryRun?: boolean;
|
|
56
|
+
variant?: { goal: string; plan: GraphVariantPlan };
|
|
41
57
|
runId?: string;
|
|
42
58
|
resumeRunId?: string;
|
|
59
|
+
resumeGraphId?: string;
|
|
43
60
|
fromNodeId?: string;
|
|
44
|
-
|
|
61
|
+
useCurrentGraph?: boolean;
|
|
62
|
+
deps?: { root?: string; runBash?: BashRun; log?: (event: string, data: Record<string, unknown>) => void; processStartMs?: (pid: number) => number | null; growthProposer?: GrowthProposer; growthLLM?: (prompt: string) => Promise<string>; classifyGrowthRecipe?: (node: GraphNodeSpec, resolved: { command?: string; approval?: string }) => GrowthRecipeEffect };
|
|
45
63
|
}
|
|
46
64
|
|
|
47
65
|
function safeSegment(value: string): string {
|
|
@@ -108,15 +126,32 @@ function recipesFor(path: string, recipeSource: string): Record<string, Recipe>
|
|
|
108
126
|
return recipes;
|
|
109
127
|
}
|
|
110
128
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
129
|
+
function realRunBash(onSpawn: (pid: number) => boolean): BashRun {
|
|
130
|
+
return (body, opts) => new Promise((resolve, reject) => {
|
|
131
|
+
let unverified: ReturnType<typeof setTimeout> | undefined;
|
|
132
|
+
const child = execFile('/bin/bash', ['-c', body], { cwd: opts.cwd, env: opts.env, timeout: opts.timeoutMs, signal: opts.signal, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
133
|
+
if (unverified) clearTimeout(unverified);
|
|
134
|
+
if (error && typeof (error as NodeJS.ErrnoException).code !== 'number') {
|
|
135
|
+
reject(error);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
resolve({ stdout, stderr, exitCode: error ? (error as unknown as { code: number }).code : 0 });
|
|
139
|
+
});
|
|
140
|
+
if (child.pid) {
|
|
141
|
+
try {
|
|
142
|
+
if (!onSpawn(child.pid)) {
|
|
143
|
+
// A process can exit before ps observes it. A completed child is safe to report;
|
|
144
|
+
// an unverified child still running must not be left without a tracked PID.
|
|
145
|
+
unverified = setTimeout(() => {
|
|
146
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
147
|
+
child.kill('SIGTERM');
|
|
148
|
+
reject(new Error(`cannot verify node process start: ${child.pid}`));
|
|
149
|
+
}, 500);
|
|
150
|
+
}
|
|
151
|
+
} catch (error) { child.kill('SIGTERM'); reject(error); }
|
|
116
152
|
}
|
|
117
|
-
resolve({ stdout, stderr, exitCode: error ? (error as unknown as { code: number }).code : 0 });
|
|
118
153
|
});
|
|
119
|
-
}
|
|
154
|
+
}
|
|
120
155
|
|
|
121
156
|
function graphRunPath(graphId: string, runId: string, root: string): string {
|
|
122
157
|
return join(root, 'graph-runs', safeSegment(graphId), `${safeSegment(runId)}.json`);
|
|
@@ -157,13 +192,85 @@ function persistGraphRun(state: GraphRunState): void {
|
|
|
157
192
|
renameSync(temporary, state.statePath);
|
|
158
193
|
}
|
|
159
194
|
|
|
160
|
-
|
|
195
|
+
/** A synchronous, per-ledger critical section shared by runner writes and stop/destroy. */
|
|
196
|
+
function withRunWriteLock<T>(file: string, work: () => T): T {
|
|
197
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
198
|
+
const lock = `${file}.write.lock`;
|
|
199
|
+
const staged = `${lock}.${randomUUID()}.staged`;
|
|
200
|
+
mkdirSync(staged);
|
|
201
|
+
const until = Date.now() + 5_000;
|
|
202
|
+
let acquired = false;
|
|
203
|
+
try {
|
|
204
|
+
writeFileSync(join(staged, 'owner.json'), JSON.stringify({ pid: process.pid, started: Date.now() - process.uptime() * 1_000 }), { flag: 'wx' });
|
|
205
|
+
while (!acquired) {
|
|
206
|
+
try {
|
|
207
|
+
// Unlike mkdir(lock) followed by a write, publication exposes an already complete owner.
|
|
208
|
+
symlinkSync(staged, lock, 'dir');
|
|
209
|
+
acquired = true;
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
|
|
212
|
+
let owner: { pid: number; started: number } | undefined;
|
|
213
|
+
try { owner = JSON.parse(readFileSync(join(lock, 'owner.json'), 'utf8')); } catch { /* legacy unowned directory */ }
|
|
214
|
+
const ownerStart = owner && Number.isSafeInteger(owner.pid) && Number.isFinite(owner.started) ? psProcessStartMs(owner.pid) : undefined;
|
|
215
|
+
let stale = ownerStart !== undefined && (ownerStart === null || Math.abs(ownerStart - owner!.started) > START_TOLERANCE_MS);
|
|
216
|
+
if (!owner && !lstatSync(lock).isSymbolicLink()) {
|
|
217
|
+
// Legacy mkdir-before-owner crash: do not evict a writer still publishing its owner.
|
|
218
|
+
stale = Date.now() - statSync(lock).mtimeMs >= 5_000;
|
|
219
|
+
}
|
|
220
|
+
if (stale) {
|
|
221
|
+
try {
|
|
222
|
+
if (lstatSync(lock).isSymbolicLink()) {
|
|
223
|
+
const target = readlinkSync(lock);
|
|
224
|
+
if (!target.startsWith(`${lock}.`) || !target.endsWith('.staged')) throw new Error('invalid graph run write lock');
|
|
225
|
+
unlinkSync(lock);
|
|
226
|
+
rmSync(target, { recursive: true, force: true });
|
|
227
|
+
} else {
|
|
228
|
+
if (owner) unlinkSync(join(lock, 'owner.json'));
|
|
229
|
+
rmdirSync(lock);
|
|
230
|
+
}
|
|
231
|
+
} catch (cause) {
|
|
232
|
+
if ((cause as NodeJS.ErrnoException).code !== 'ENOENT') throw cause;
|
|
233
|
+
}
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (Date.now() >= until) throw new Error(`graph run write lock is held: ${file}`);
|
|
237
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return work();
|
|
241
|
+
} finally {
|
|
242
|
+
if (acquired && existsSync(lock) && lstatSync(lock).isSymbolicLink() && readlinkSync(lock) === staged) unlinkSync(lock);
|
|
243
|
+
rmSync(staged, { recursive: true, force: true });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function persistOwnedRun(state: GraphRunState, expected: Pick<GraphRunState, 'pid' | 'pidStartedAt' | 'stoppedAt'>, allowRestart = false): void {
|
|
248
|
+
withRunWriteLock(state.statePath, () => {
|
|
249
|
+
let disk: GraphRunState;
|
|
250
|
+
try { disk = JSON.parse(readFileSync(state.statePath, 'utf8')) as GraphRunState; }
|
|
251
|
+
catch (cause) { throw new Error(`run was removed or unreadable: ${state.graphId}/${state.runId}`, { cause }); }
|
|
252
|
+
if (disk.graphId !== state.graphId || disk.runId !== state.runId || disk.pid !== expected.pid ||
|
|
253
|
+
disk.pidStartedAt !== expected.pidStartedAt || disk.stoppedAt !== expected.stoppedAt ||
|
|
254
|
+
(disk.stoppedAt !== undefined && !allowRestart)) {
|
|
255
|
+
throw new Error(`run was stopped or ownership changed: ${state.graphId}/${state.runId}`);
|
|
256
|
+
}
|
|
257
|
+
persistGraphRun(state);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export interface ExpectedGraphApprovalVisit { nodeId: string; visit: number }
|
|
262
|
+
|
|
263
|
+
export function decideGraphApproval(graphId: string, runId: string, decision: 'approved' | 'rejected', by?: string, root = effectiveInstanceRoot(), expected?: ExpectedGraphApprovalVisit): GraphRunState {
|
|
161
264
|
const state = readGraphRun(graphId, runId, root);
|
|
162
|
-
if (state.status !== 'awaiting-approval' || !state.pending || state.pending.decision
|
|
265
|
+
if (state.status !== 'awaiting-approval' || !state.pending || state.growthPark || state.pending.decision ||
|
|
266
|
+
(expected && (state.dryRun || state.pending.nodeId !== expected.nodeId || state.path.at(-1) !== expected.nodeId ||
|
|
267
|
+
!Number.isSafeInteger(expected.visit) || expected.visit < 1 ||
|
|
268
|
+
state.path.filter(node => node === expected.nodeId).length !== expected.visit))) {
|
|
163
269
|
throw new Error(`run is not awaiting an undecided approval: ${graphId}/${runId}`);
|
|
164
270
|
}
|
|
165
271
|
if (by !== undefined && !by.trim()) throw new Error('approver name must not be empty');
|
|
166
272
|
const recorded = { nodeId: state.pending.nodeId, decision, ...(by === undefined ? {} : { decidedBy: by }), decidedAt: new Date().toISOString() };
|
|
273
|
+
// A card can claim only the visit it names; never derive a later visit's claim path from a fresh read.
|
|
167
274
|
const decisionPath = approvalDecisionPath(state);
|
|
168
275
|
const temporary = `${decisionPath}.${randomUUID()}.tmp`;
|
|
169
276
|
writeFileSync(temporary, JSON.stringify(recorded) + '\n', { flag: 'wx' });
|
|
@@ -195,6 +302,22 @@ export function lastJsonObject(stdout: unknown): Record<string, unknown> | undef
|
|
|
195
302
|
}
|
|
196
303
|
}
|
|
197
304
|
|
|
305
|
+
/** Reconstruct the run-local edges from the ledger without rewriting the YAML snapshot. */
|
|
306
|
+
function withGrowth(graph: GraphTemplateSpec, growth: readonly GraphGrowth[]): GraphTemplateSpec {
|
|
307
|
+
return growth.reduce<GraphTemplateSpec>((current, item) => {
|
|
308
|
+
const edge = current.edges.find((candidate) => candidate.from === item.undo.removeOutcome.from);
|
|
309
|
+
if (!edge || !edge.map || !item.node || item.undo.removeNode !== item.node.nodeId ||
|
|
310
|
+
current.nodes.some((node) => node.nodeId === item.node.nodeId) ||
|
|
311
|
+
Object.hasOwn(edge.map, item.undo.removeOutcome.outcome) ||
|
|
312
|
+
!item.edges.some((added) => added.from === edge.from && added.map?.[item.undo.removeOutcome.outcome] === item.node.nodeId)) {
|
|
313
|
+
throw new Error('invalid saved graph growth');
|
|
314
|
+
}
|
|
315
|
+
return { ...current, nodes: [...current.nodes, item.node], edges: current.edges.map((candidate) => candidate === edge
|
|
316
|
+
? { ...edge, map: { ...edge.map, [item.undo.removeOutcome.outcome]: item.node.nodeId } } : candidate)
|
|
317
|
+
.concat(item.undo.removeEdge ? [item.undo.removeEdge] : []) };
|
|
318
|
+
}, graph);
|
|
319
|
+
}
|
|
320
|
+
|
|
198
321
|
/** 이름 붙은 결과: 산출의 `outcome` 이 그 간선 map 의 키면 그 목적지 — 아니면 종전 ok/fail(exit) → fallback. */
|
|
199
322
|
function nextNode(edges: readonly GraphEdgeSpec[], nodeId: string, exitOutcome: 'ok' | 'fail', namedOutcome?: string): string | undefined {
|
|
200
323
|
const edge = edges.find((e) => e.from === nodeId);
|
|
@@ -206,7 +329,27 @@ function nextNode(edges: readonly GraphEdgeSpec[], nodeId: string, exitOutcome:
|
|
|
206
329
|
}
|
|
207
330
|
|
|
208
331
|
export async function runGraph(path: string, options: GraphRunOptions = {}): Promise<GraphRunState> {
|
|
209
|
-
|
|
332
|
+
if (options.resumeRunId && options.runId) throw new Error('runId and resumeRunId cannot be combined');
|
|
333
|
+
if (options.fromNodeId && !options.resumeRunId) throw new Error('--from requires --resume');
|
|
334
|
+
if (options.useCurrentGraph && !options.resumeRunId) throw new Error('--use-current-graph requires --resume');
|
|
335
|
+
if (options.variant && options.resumeRunId) throw new Error('variant cannot be combined with --resume');
|
|
336
|
+
const root = options.deps?.root ?? effectiveInstanceRoot();
|
|
337
|
+
const matchingRuns = options.resumeRunId && !options.resumeGraphId
|
|
338
|
+
? listGraphRuns(root).runs.filter((run) => run.runId === options.resumeRunId && run.graphPath === resolve(path)) : [];
|
|
339
|
+
if (matchingRuns.length > 1) throw new Error(`ambiguous run id: ${options.resumeRunId}`);
|
|
340
|
+
const currentSource = options.resumeRunId && (options.resumeGraphId || matchingRuns.length) && !options.useCurrentGraph
|
|
341
|
+
? undefined : readFileSync(path, 'utf8');
|
|
342
|
+
const currentHeader: unknown = options.resumeRunId && !options.resumeGraphId && !matchingRuns.length ? parseYaml(currentSource!) : undefined;
|
|
343
|
+
const resumeGraphId = options.resumeGraphId ?? matchingRuns[0]?.graphId ?? (currentHeader && typeof currentHeader === 'object' && !Array.isArray(currentHeader)
|
|
344
|
+
? (currentHeader as Record<string, unknown>).graph_id : undefined);
|
|
345
|
+
const saved = options.resumeRunId && typeof resumeGraphId === 'string'
|
|
346
|
+
? readGraphRun(safeSegment(resumeGraphId), safeSegment(options.resumeRunId), root) : undefined;
|
|
347
|
+
if (saved?.graphSnapshot && (!saved.graphPath || resolve(path) !== resolve(saved.graphPath))) {
|
|
348
|
+
throw new Error('--file cannot change the graph path of a snapshot run (이 런은 시작 때 그래프 사본이 있다 — --file 대신 --use-current-graph)');
|
|
349
|
+
}
|
|
350
|
+
const snapshotDir = saved ? `${saved.statePath}.graph` : undefined;
|
|
351
|
+
const graphMode: 'snapshot' | 'current' = snapshotDir && saved?.graphSnapshot && !options.useCurrentGraph ? 'snapshot' : 'current';
|
|
352
|
+
const source = graphMode === 'snapshot' ? readFileSync(join(snapshotDir!, 'graph.yaml'), 'utf8') : currentSource ?? readFileSync(path, 'utf8');
|
|
210
353
|
// The shared YAML parser requires a recipe string. A command-less node in
|
|
211
354
|
// this runner is represented internally as `none`, without changing that parser.
|
|
212
355
|
const raw: unknown = parseYaml(source);
|
|
@@ -218,9 +361,24 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
218
361
|
}
|
|
219
362
|
const parsed = parseGraphTemplateYaml(stringifyYaml(document), path);
|
|
220
363
|
if (!parsed.template || parsed.errors.length) throw new Error(parsed.errors.map((e) => `${e.path}: ${e.message}`).join('\n'));
|
|
221
|
-
|
|
364
|
+
let graph = parsed.template;
|
|
365
|
+
const variant = options.variant ? createGraphVariant({ template: graph, ...options.variant }) : undefined;
|
|
366
|
+
if (variant && !variant.ok) throw new Error(`graph variant rejected: ${JSON.stringify(variant.rejections)}`);
|
|
367
|
+
if (variant?.ok) graph = variant.template;
|
|
368
|
+
const variantSource = variant?.ok ? stringifyYaml({ ...document,
|
|
369
|
+
nodes: graph.nodes.map((node, index) => ({
|
|
370
|
+
...((document.nodes as Record<string, unknown>[])[index] ?? {}), node_id: node.nodeId, kind: node.kind,
|
|
371
|
+
recipe: node.recipe, max_visits: node.maxVisits,
|
|
372
|
+
...(node.contract ? { contract: node.contract } : {}),
|
|
373
|
+
})),
|
|
374
|
+
edges: graph.edges.map((edge, index) => ({
|
|
375
|
+
...((document.edges as Record<string, unknown>[])[index] ?? {}), from: edge.from,
|
|
376
|
+
...(edge.to === undefined ? { on: edge.on, map: edge.map } : { to: edge.to }),
|
|
377
|
+
...(edge.fallback ? { fallback: edge.fallback } : {}),
|
|
378
|
+
})),
|
|
379
|
+
}) : source;
|
|
222
380
|
const pluginRoot = elanousStateRoot();
|
|
223
|
-
const graphFile = realpathSync(path);
|
|
381
|
+
const graphFile = graphMode === 'snapshot' && !existsSync(path) ? resolve(path) : realpathSync(path);
|
|
224
382
|
const installed = listInstalledPlugins(pluginRoot);
|
|
225
383
|
const owner = installed.find(item => {
|
|
226
384
|
const graphsDir = join(item.path, 'graphs');
|
|
@@ -235,11 +393,16 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
235
393
|
throw new Error(`unsupported terminal node: ${terminal} (expected done or failed)`);
|
|
236
394
|
}
|
|
237
395
|
}
|
|
238
|
-
const recipeSource = readFileSync(join(dirname(path), 'recipes.yaml'), 'utf8');
|
|
396
|
+
const recipeSource = readFileSync(graphMode === 'snapshot' ? join(snapshotDir!, 'recipes.yaml') : join(dirname(path), 'recipes.yaml'), 'utf8');
|
|
397
|
+
if (graphMode === 'snapshot' && saved?.graphSnapshot &&
|
|
398
|
+
(createHash('sha256').update(source).digest('hex') !== saved.graphSnapshot.graphSha ||
|
|
399
|
+
createHash('sha256').update(recipeSource).digest('hex') !== saved.graphSnapshot.recipesSha)) {
|
|
400
|
+
throw new Error('graph snapshot changed since run started');
|
|
401
|
+
}
|
|
239
402
|
const recipes = recipesFor(path, recipeSource);
|
|
240
403
|
const catalogRoles = new Set(loadNodeCatalog().roles.keys());
|
|
241
404
|
// Bind a human decision to the exact graph and recipe contents, not just its id or approval prompt.
|
|
242
|
-
const approvalSourceHash = createHash('sha256').update(
|
|
405
|
+
const approvalSourceHash = createHash('sha256').update(variantSource).update('\0').update(recipeSource).digest('hex');
|
|
243
406
|
// Resolve all commands before executing any node; an unknown recipe must never produce a partial publication.
|
|
244
407
|
for (const node of graph.nodes) {
|
|
245
408
|
if (node.recipe === 'none') continue;
|
|
@@ -247,10 +410,12 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
247
410
|
if (!resolved.command && !resolved.approval) throw new Error(`unknown command recipe for ${node.nodeId}: ${node.recipe}`);
|
|
248
411
|
}
|
|
249
412
|
const graphId = safeSegment(graph.graphId);
|
|
250
|
-
if (
|
|
251
|
-
|
|
413
|
+
if (saved && graphId !== saved.graphId) {
|
|
414
|
+
if (options.fromNodeId && graphMode === 'current') throw new Error('graph or recipes changed since failed run');
|
|
415
|
+
throw new Error(`run identity mismatch: ${saved.graphId}/${saved.runId}`);
|
|
416
|
+
}
|
|
252
417
|
const runId = safeSegment(options.resumeRunId ?? options.runId ?? randomUUID());
|
|
253
|
-
const statePath = graphRunPath(graphId, runId,
|
|
418
|
+
const statePath = graphRunPath(graphId, runId, root);
|
|
254
419
|
// A resume owns the run from the first state read through its last write and command.
|
|
255
420
|
// Fail closed on a concurrent (or interrupted) owner rather than replaying a command.
|
|
256
421
|
const resumeLock = `${statePath}.resume.lock`;
|
|
@@ -263,12 +428,25 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
263
428
|
}
|
|
264
429
|
}
|
|
265
430
|
try {
|
|
431
|
+
const startMs = (options.deps?.processStartMs ?? psProcessStartMs)(process.pid);
|
|
432
|
+
const runnerOwner = { pid: process.pid, pidStartedAt: new Date(startMs ?? Date.now() - process.uptime() * 1_000).toISOString() };
|
|
266
433
|
const state: GraphRunState = options.resumeRunId
|
|
267
|
-
? readGraphRun(graphId, runId,
|
|
268
|
-
: { graphId, runId, startedAt: new Date().toISOString(), status: 'running', path: [], nodes: [], sourceHash: approvalSourceHash,
|
|
434
|
+
? readGraphRun(graphId, runId, root)
|
|
435
|
+
: { graphId, runId, startedAt: new Date().toISOString(), ...runnerOwner, status: 'running', path: [], nodes: [], sourceHash: approvalSourceHash,
|
|
436
|
+
graphSnapshot: { graphSha: createHash('sha256').update(variantSource).digest('hex'), recipesSha: createHash('sha256').update(recipeSource).digest('hex') },
|
|
437
|
+
graphPath: resolve(path), ...(variant?.ok ? { variant: { overlayId: variant.overlay.overlayId, ...options.variant! } } : {}),
|
|
438
|
+
...(options.input === undefined ? {} : { input: options.input }), executed: 0, dryRun: options.dryRun === true, statePath };
|
|
439
|
+
if (options.resumeRunId && graphMode === 'snapshot' && state.sourceHash && state.sourceHash !== approvalSourceHash) {
|
|
440
|
+
throw new Error('graph snapshot changed since run started');
|
|
441
|
+
}
|
|
442
|
+
if (options.resumeRunId && options.dryRun !== undefined && options.dryRun !== state.dryRun) throw new Error('cannot change dryRun when resuming');
|
|
443
|
+
if (options.resumeRunId && state.growth?.length && graphMode === 'current' && state.sourceHash !== approvalSourceHash) {
|
|
444
|
+
throw new Error('graph or recipes changed since grown run');
|
|
445
|
+
}
|
|
446
|
+
if (state.growth?.length) graph = withGrowth(graph, state.growth);
|
|
269
447
|
if (options.fromNodeId) {
|
|
270
448
|
if (state.status !== 'failed' || state.pending || state.dryRun) throw new Error('--from requires a failed, non-dry run without pending approval');
|
|
271
|
-
if (!state.sourceHash || state.sourceHash !== approvalSourceHash) throw new Error('graph or recipes changed since failed run');
|
|
449
|
+
if (graphMode === 'current' && (!state.sourceHash || state.sourceHash !== approvalSourceHash)) throw new Error('graph or recipes changed since failed run');
|
|
272
450
|
const from = state.path.indexOf(options.fromNodeId);
|
|
273
451
|
if (from < 0 || state.path.lastIndexOf(options.fromNodeId) !== from || graph.terminalNodes.includes(options.fromNodeId)) throw new Error(`--from node is not unique on the saved executable path: ${options.fromNodeId}`);
|
|
274
452
|
if (state.path.length !== state.nodes.length || state.nodes.some((record, i) => record.nodeId !== state.path[i]) ||
|
|
@@ -306,10 +484,14 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
306
484
|
state.nodes = state.nodes.slice(0, from);
|
|
307
485
|
state.executed = state.nodes.filter((node) => node.executed).length;
|
|
308
486
|
state.status = 'running';
|
|
309
|
-
state.
|
|
487
|
+
delete state.stoppedAt;
|
|
488
|
+
Object.assign(state, runnerOwner);
|
|
489
|
+
state.resume = { from: options.fromNodeId, at: new Date().toISOString(), previousStatus: 'failed', graph: graphMode };
|
|
310
490
|
// Persist the restart boundary before executing it, so previous outputs remain durable.
|
|
311
|
-
|
|
312
|
-
|
|
491
|
+
persistOwnedRun(state, saved!, true);
|
|
492
|
+
}
|
|
493
|
+
if (options.resumeRunId && state.growthPark) {
|
|
494
|
+
throw new Error(`growth is parked for human confirmation: ${graphId}/${runId}`);
|
|
313
495
|
}
|
|
314
496
|
if (options.resumeRunId && state.status === 'running' && state.pending) {
|
|
315
497
|
throw new Error(`run is not awaiting approval: ${graphId}/${runId}`);
|
|
@@ -323,13 +505,19 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
323
505
|
if (options.resumeRunId && (state.graphId !== graphId || state.runId !== runId)) {
|
|
324
506
|
throw new Error(`run is not awaiting approval: ${graphId}/${runId}`);
|
|
325
507
|
}
|
|
326
|
-
if (options.resumeRunId && options.dryRun !== undefined && options.dryRun !== state.dryRun) throw new Error('cannot change dryRun when resuming');
|
|
327
508
|
if (options.resumeRunId && state.status === 'awaiting-approval' && state.approvalSourceHash !== approvalSourceHash) {
|
|
328
509
|
throw new Error(`approval source changed since run was paused: ${graphId}/${runId}`);
|
|
329
510
|
}
|
|
511
|
+
if (options.resumeRunId && !options.fromNodeId) {
|
|
512
|
+
const previousStatus = state.status;
|
|
513
|
+
Object.assign(state, runnerOwner);
|
|
514
|
+
state.resume = { from: state.pending?.nodeId ?? state.path.at(-1) ?? graph.entryNode, at: new Date().toISOString(), previousStatus, graph: graphMode };
|
|
515
|
+
persistOwnedRun(state, saved!);
|
|
516
|
+
}
|
|
517
|
+
if (options.resumeRunId) debug.log('graph.runs', 'resume', { graphId, runId, from: state.resume?.from, graph: graphMode });
|
|
330
518
|
const persist = () => {
|
|
331
519
|
if (state.status === 'done' || state.status === 'failed' || state.status === 'budget-exceeded') state.finishedAt ??= new Date().toISOString();
|
|
332
|
-
|
|
520
|
+
persistOwnedRun(state, runnerOwner);
|
|
333
521
|
};
|
|
334
522
|
const log = options.deps?.log ?? ((event: string, data: Record<string, unknown>) => debug.log('graph.runner', event, data));
|
|
335
523
|
const visits = new Map<string, number>();
|
|
@@ -345,13 +533,17 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
345
533
|
if ((error as NodeJS.ErrnoException).code === 'EEXIST') throw new Error(`run id already exists: ${graphId}/${runId}`);
|
|
346
534
|
throw error;
|
|
347
535
|
}
|
|
536
|
+
mkdirSync(`${statePath}.graph`);
|
|
537
|
+
writeFileSync(join(`${statePath}.graph`, 'graph.yaml'), variantSource, { flag: 'wx' });
|
|
538
|
+
writeFileSync(join(`${statePath}.graph`, 'recipes.yaml'), recipeSource, { flag: 'wx' });
|
|
348
539
|
}
|
|
349
540
|
while (current !== undefined) {
|
|
350
541
|
const node = graph.nodes.find((n) => n.nodeId === current);
|
|
351
542
|
if (!node) throw new Error(`undeclared node: ${current}`);
|
|
352
543
|
const resumingPending = state.pending?.nodeId === current;
|
|
353
|
-
const completed =
|
|
354
|
-
const resumingCompleted = options.resumeRunId !== undefined && !options.fromNodeId &&
|
|
544
|
+
const completed = state.nodes.at(-1);
|
|
545
|
+
const resumingCompleted = options.resumeRunId !== undefined && !options.fromNodeId && completed?.nodeId === current &&
|
|
546
|
+
state.path.at(-1) === current && !resumingPending;
|
|
355
547
|
if (!resumingPending && !resumingCompleted && (visits.get(current) ?? 0) >= node.maxVisits) {
|
|
356
548
|
state.status = 'budget-exceeded';
|
|
357
549
|
log('budget-exceeded', { graphId, runId, nodeId: current });
|
|
@@ -391,6 +583,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
391
583
|
const startedAt = performance.now();
|
|
392
584
|
console.error(`[graph] ${current} start (0.00s)`);
|
|
393
585
|
debug.log('graph.run', 'node', { graphId, runId, nodeId: current, phase: 'start', dryRun: state.dryRun });
|
|
586
|
+
publishInsideEvent({ kind: 'node', graphId, runId, nodeId: current, phase: 'start' });
|
|
394
587
|
log('node-start', { graphId, runId, nodeId: current, visit: visits.get(current), dryRun: state.dryRun });
|
|
395
588
|
persist();
|
|
396
589
|
let ok = approval && !state.dryRun ? state.pending?.decision === 'approved' : true;
|
|
@@ -408,9 +601,27 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
408
601
|
const redactCredentials = (text: string) => Object.values(credentials).filter(value => value.length > 0)
|
|
409
602
|
.reduce((safe, value) => safe.replaceAll(value, '[REDACTED]'), text);
|
|
410
603
|
const runBash: BashRun = async (body, opts) => {
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
604
|
+
const execute = options.deps?.runBash ?? realRunBash((pid) => {
|
|
605
|
+
// Publish the child before its start time is known, so a concurrent stop refuses instead of failing
|
|
606
|
+
// the ledger while an untracked node keeps working (REL8b review round 3).
|
|
607
|
+
state.activeNode = { nodeId: current!, pid, pidStartedAt: UNVERIFIED_NODE_START };
|
|
608
|
+
persist();
|
|
609
|
+
const started = (options.deps?.processStartMs ?? psProcessStartMs)(pid);
|
|
610
|
+
if (started === null) return false;
|
|
611
|
+
state.activeNode = { nodeId: current!, pid, pidStartedAt: new Date(started).toISOString() };
|
|
612
|
+
persist();
|
|
613
|
+
return true;
|
|
614
|
+
});
|
|
615
|
+
try {
|
|
616
|
+
const result = await execute(body, opts);
|
|
617
|
+
code = result.exitCode;
|
|
618
|
+
return { ...result, stdout: redactCredentials(result.stdout), stderr: redactCredentials(result.stderr) };
|
|
619
|
+
} finally {
|
|
620
|
+
if (state.activeNode?.nodeId === current) {
|
|
621
|
+
delete state.activeNode;
|
|
622
|
+
persist();
|
|
623
|
+
}
|
|
624
|
+
}
|
|
414
625
|
};
|
|
415
626
|
if (owner) debug.log('plugin.credentials', 'injected', { plugin: owner.name, fields: credentialFields, count: credentialFields.length });
|
|
416
627
|
const env = { ...process.env };
|
|
@@ -455,6 +666,8 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
455
666
|
const seconds = Number(((performance.now() - startedAt) / 1000).toFixed(2));
|
|
456
667
|
console.error(`[graph] ${current} ${ok ? 'ok' : 'fail'} (${seconds.toFixed(2)}s)`);
|
|
457
668
|
debug.log('graph.run', 'node', { graphId, runId, nodeId: current, phase: ok ? 'ok' : 'fail', seconds, exit });
|
|
669
|
+
publishInsideEvent({ kind: 'node', graphId, runId, nodeId: current, phase: ok ? 'ok' : 'fail', seconds });
|
|
670
|
+
publishInsideEvent({ kind: 'verdict', graphId, runId, nodeId: current, verdict: ok ? 'ok' : 'fail', ...(namedOutcome === undefined ? {} : { outcome: namedOutcome }) });
|
|
458
671
|
log('node-end', { graphId, runId, nodeId: current, ok, exit, ...(namedOutcome === undefined ? {} : { outcome: namedOutcome }), dryRun: state.dryRun });
|
|
459
672
|
persist();
|
|
460
673
|
if (graph.terminalNodes.includes(current)) {
|
|
@@ -462,7 +675,42 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
462
675
|
persist();
|
|
463
676
|
break;
|
|
464
677
|
}
|
|
678
|
+
const fromNodeId = current;
|
|
679
|
+
const edge = graph.edges.find((candidate) => candidate.from === current);
|
|
680
|
+
if (document.grow === 'on' && !state.dryRun && namedOutcome !== undefined &&
|
|
681
|
+
edge?.on === 'outcome' && edge.map && !Object.hasOwn(edge.map, namedOutcome) &&
|
|
682
|
+
!state.growth?.some((item) => item.undo.removeOutcome.from === current && item.undo.removeOutcome.outcome === namedOutcome) &&
|
|
683
|
+
!state.growthRejections?.some((item) => item.from === current && item.outcome === namedOutcome)) {
|
|
684
|
+
const result = (state.growth?.length ?? 0) >= 3 ? { ok: false as const, reason: 'growth limit 3 reached' }
|
|
685
|
+
: await proposeGrowth({ graph, nodeId: current, outcome: namedOutcome, runId, output: state.nodes.at(-1)?.output },
|
|
686
|
+
options.deps?.growthProposer ?? createLLMGrowthProposer(options.deps?.growthLLM), (candidate) => {
|
|
687
|
+
const resolved = resolveNodeRecipe(candidate, recipes, catalogRoles);
|
|
688
|
+
if (resolved.approval || (!resolved.command && candidate.recipe !== 'none')) return 'unknown';
|
|
689
|
+
return options.deps?.classifyGrowthRecipe?.(candidate, { command: resolved.command?.command }) ?? 'unknown';
|
|
690
|
+
});
|
|
691
|
+
if (result.ok) {
|
|
692
|
+
graph = result.graph;
|
|
693
|
+
state.growth ??= [];
|
|
694
|
+
state.growth.push(result.growth);
|
|
695
|
+
persist();
|
|
696
|
+
log('edge-added', { graphId, runId, from: current, outcome: namedOutcome, to: result.growth.node.nodeId });
|
|
697
|
+
debug.log('graph.run', 'edge-added', { graphId, runId, from: current, outcome: namedOutcome, to: result.growth.node.nodeId });
|
|
698
|
+
publishInsideEvent({ kind: 'edge-added', graphId, runId, from: current, outcome: namedOutcome, to: result.growth.node.nodeId });
|
|
699
|
+
} else {
|
|
700
|
+
state.growthRejections ??= [];
|
|
701
|
+
state.growthRejections.push({ from: current, outcome: namedOutcome, reason: result.reason });
|
|
702
|
+
if (result.park) {
|
|
703
|
+
state.status = 'awaiting-approval';
|
|
704
|
+
state.pending = { nodeId: current, message: result.reason, since: new Date().toISOString() };
|
|
705
|
+
state.growthPark = { from: current, outcome: namedOutcome, reason: result.reason };
|
|
706
|
+
persist();
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
persist();
|
|
711
|
+
}
|
|
465
712
|
current = nextNode(graph.edges, current, state.dryRun ? 'ok' : ok ? 'ok' : 'fail', state.dryRun ? undefined : namedOutcome);
|
|
713
|
+
if (current !== undefined) publishInsideEvent({ kind: 'edge', graphId, runId, from: fromNodeId, to: current });
|
|
466
714
|
if (current === undefined) {
|
|
467
715
|
state.status = 'failed';
|
|
468
716
|
persist();
|
|
@@ -470,8 +718,178 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
470
718
|
}
|
|
471
719
|
return state;
|
|
472
720
|
} finally {
|
|
473
|
-
if (options.resumeRunId) rmdirSync(resumeLock);
|
|
721
|
+
if (options.resumeRunId && existsSync(resumeLock)) rmdirSync(resumeLock);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
export type GraphRunAlive = boolean | 'unknown';
|
|
726
|
+
|
|
727
|
+
/** activeNode.pidStartedAt while the node child is spawned but its start time is not read yet — stop must not pass it. */
|
|
728
|
+
const UNVERIFIED_NODE_START = 'unverified';
|
|
729
|
+
/** A failed run's empty resume lock younger than this may belong to a resume that has not persisted «running» yet. */
|
|
730
|
+
const RESUME_LOCK_GRACE_MS = 60_000;
|
|
731
|
+
|
|
732
|
+
function runArtifacts(state: GraphRunState): string[] {
|
|
733
|
+
const file = state.statePath;
|
|
734
|
+
const claims = readdirSync(dirname(file))
|
|
735
|
+
.filter(name => name.startsWith(`${state.runId}.json.`) && /^\d+\.decision\.json$/.test(name.slice(`${state.runId}.json.`.length)))
|
|
736
|
+
.map(name => join(dirname(file), name));
|
|
737
|
+
for (const claim of claims) {
|
|
738
|
+
if (lstatSync(claim).isSymbolicLink()) throw new Error('graph run artifact is a symlink');
|
|
739
|
+
let value: unknown;
|
|
740
|
+
try { value = JSON.parse(readFileSync(claim, 'utf8')); }
|
|
741
|
+
catch { throw new Error(`invalid graph run decision claim: ${claim}`); }
|
|
742
|
+
const visit = Number(claim.slice(`${file}.`.length, -'.decision.json'.length));
|
|
743
|
+
if (!isDecisionClaim(value) || !Number.isSafeInteger(visit) || visit < 1 ||
|
|
744
|
+
(value as { nodeId: string }).nodeId !== state.path[visit - 1]) {
|
|
745
|
+
throw new Error(`invalid graph run decision claim: ${claim}`);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return [file, `${file}.graph`, `${file}.contexts`, ...claims];
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/** Manage only an identity-checked ledger in this instance, never a caller-supplied path. */
|
|
752
|
+
export function manageGraphRun(graphId: string, runId: string, action: 'stop' | 'destroy',
|
|
753
|
+
root = effectiveInstanceRoot(), processStartMs: (pid: number) => number | null = psProcessStartMs,
|
|
754
|
+
signal: (pid: number) => void = (pid) => process.kill(pid, 'SIGTERM')): GraphRunState {
|
|
755
|
+
const file = graphRunPath(safeSegment(graphId), safeSegment(runId), root);
|
|
756
|
+
return withRunWriteLock(file, () => {
|
|
757
|
+
if (lstatSync(join(root, 'graph-runs')).isSymbolicLink() || lstatSync(dirname(file)).isSymbolicLink() || lstatSync(file).isSymbolicLink()) throw new Error('graph run path is a symlink');
|
|
758
|
+
const state = readGraphRun(graphId, runId, root);
|
|
759
|
+
if (state.statePath !== file || !Array.isArray(state.path) || !Array.isArray(state.nodes) ||
|
|
760
|
+
!Number.isSafeInteger(state.executed) || state.executed < 0 || (state.status === 'running' && state.pending)) {
|
|
761
|
+
throw new Error(`invalid run state: ${graphId}/${runId}`);
|
|
762
|
+
}
|
|
763
|
+
for (const path of runArtifacts(state)) {
|
|
764
|
+
try { if (lstatSync(path).isSymbolicLink()) throw new Error('graph run artifact is a symlink'); }
|
|
765
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
|
|
766
|
+
}
|
|
767
|
+
const live = graphRunAlive(state, processStartMs);
|
|
768
|
+
if (action === 'stop' && state.status !== 'running') throw new Error(`run is not running: ${graphId}/${runId}`);
|
|
769
|
+
const lock = `${file}.resume.lock`;
|
|
770
|
+
if (existsSync(lock) && (!lstatSync(lock).isDirectory() || readdirSync(lock).length > 0)) throw new Error('invalid resume lock');
|
|
771
|
+
if (existsSync(lock) && state.status !== 'running') {
|
|
772
|
+
// A resume persists «running» right after taking the lock; an older empty lock on a finished run is a crash leftover.
|
|
773
|
+
const ageMs = Date.now() - statSync(lock).mtimeMs;
|
|
774
|
+
if (ageMs < RESUME_LOCK_GRACE_MS) throw new Error(`run is being resumed: ${graphId}/${runId}`);
|
|
775
|
+
rmdirSync(lock);
|
|
776
|
+
debug.log('graph.runs', 'stale-resume-lock-cleared', { graphId, runId, ageMs: Math.round(ageMs), status: state.status });
|
|
777
|
+
}
|
|
778
|
+
if (state.status === 'running') {
|
|
779
|
+
if (live === 'unknown') throw new Error(`cannot verify run owner: ${graphId}/${runId}`);
|
|
780
|
+
if (live && state.pid === process.pid) throw new Error('cannot stop the current graph runner');
|
|
781
|
+
const node = state.activeNode;
|
|
782
|
+
if (node && (node.nodeId !== state.path.at(-1) || state.path.length !== state.nodes.length + 1)) throw new Error('active node does not match saved path');
|
|
783
|
+
if (state.path.length !== state.nodes.length && state.path.length !== state.nodes.length + 1) {
|
|
784
|
+
throw new Error('saved run path and node records do not match');
|
|
785
|
+
}
|
|
786
|
+
if (state.path.some((nodeId, i) => state.nodes[i] && state.nodes[i]?.nodeId !== nodeId) ||
|
|
787
|
+
state.executed !== state.nodes.filter(record => record.executed).length) throw new Error('saved run path and node records do not match');
|
|
788
|
+
if (node?.pidStartedAt === UNVERIFIED_NODE_START) {
|
|
789
|
+
// While the runner lives it will either verify this child or kill it; retry stop after that.
|
|
790
|
+
if (live) throw new Error(`node process start is not verified yet — retry stop: ${graphId}/${runId}`);
|
|
791
|
+
// Runner gone (e.g. reboot): the pid may be reused, so never signal an identity we could not verify.
|
|
792
|
+
debug.log('graph.runs', 'unverified-node-not-signaled', { graphId, runId, nodeId: node.nodeId, pid: node.pid });
|
|
793
|
+
} else if (node) {
|
|
794
|
+
const nodeAlive = graphRunAlive({ ...state, pid: node.pid, pidStartedAt: node.pidStartedAt }, processStartMs);
|
|
795
|
+
if (nodeAlive === 'unknown') throw new Error('cannot verify node process owner');
|
|
796
|
+
if (nodeAlive && node.pid === process.pid) throw new Error('cannot stop the current process');
|
|
797
|
+
if (nodeAlive && node.pid !== state.pid) {
|
|
798
|
+
try { signal(node.pid); }
|
|
799
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ESRCH') throw error; }
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
if (state.path.length === state.nodes.length + 1) {
|
|
803
|
+
state.nodes.push({ nodeId: state.path.at(-1)!, ok: false, exit: null, executed: false, error: 'stopped before node completed' });
|
|
804
|
+
}
|
|
805
|
+
state.status = 'failed';
|
|
806
|
+
state.finishedAt = new Date().toISOString();
|
|
807
|
+
state.stoppedAt = state.finishedAt;
|
|
808
|
+
delete state.activeNode;
|
|
809
|
+
persistGraphRun(state);
|
|
810
|
+
try {
|
|
811
|
+
if (live) signal(state.pid!);
|
|
812
|
+
} catch (error) {
|
|
813
|
+
if ((error as NodeJS.ErrnoException).code !== 'ESRCH') throw error;
|
|
814
|
+
} finally {
|
|
815
|
+
// The failed ledger is durable even if its runner vanished between verification and signaling.
|
|
816
|
+
if (existsSync(lock)) rmdirSync(lock);
|
|
817
|
+
}
|
|
818
|
+
} else if (existsSync(lock)) rmdirSync(lock);
|
|
819
|
+
if (action === 'destroy') {
|
|
820
|
+
for (const path of runArtifacts(state)) rmSync(path, { recursive: true, force: true });
|
|
821
|
+
}
|
|
822
|
+
debug.log('graph.runs', action, { graphId, runId, previousAlive: live, status: state.status });
|
|
823
|
+
return state;
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
export function graphRunAlive(state: GraphRunState, processStartMs: (pid: number) => number | null = psProcessStartMs): GraphRunAlive {
|
|
828
|
+
if (state.pid === undefined) return 'unknown';
|
|
829
|
+
if (!Number.isSafeInteger(state.pid) || state.pid <= 0 || typeof state.pidStartedAt !== 'string') return false;
|
|
830
|
+
const recorded = Date.parse(state.pidStartedAt);
|
|
831
|
+
if (!Number.isFinite(recorded)) return false;
|
|
832
|
+
try {
|
|
833
|
+
const actual = processStartMs(state.pid);
|
|
834
|
+
return actual !== null && Number.isFinite(actual) && Math.abs(actual - recorded) <= START_TOLERANCE_MS;
|
|
835
|
+
} catch { return false; }
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
function isDecisionClaim(raw: unknown): boolean {
|
|
839
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return false;
|
|
840
|
+
const value = raw as Record<string, unknown>;
|
|
841
|
+
return typeof value.nodeId === 'string' && (value.decision === 'approved' || value.decision === 'rejected')
|
|
842
|
+
&& typeof value.decidedAt === 'string' && (value.decidedBy === undefined || typeof value.decidedBy === 'string')
|
|
843
|
+
&& !('graphId' in value) && !('runId' in value);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
export function listGraphRuns(root = effectiveInstanceRoot()): { runs: GraphRunState[]; unreadable: number } {
|
|
847
|
+
const base = join(root, 'graph-runs');
|
|
848
|
+
let graphs: string[];
|
|
849
|
+
try { graphs = readdirSync(base); }
|
|
850
|
+
catch (error) {
|
|
851
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { runs: [], unreadable: 0 };
|
|
852
|
+
throw error;
|
|
853
|
+
}
|
|
854
|
+
const runs: GraphRunState[] = [];
|
|
855
|
+
let unreadable = 0;
|
|
856
|
+
for (const graphId of graphs) {
|
|
857
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(graphId) || graphId === '.' || graphId === '..') continue;
|
|
858
|
+
const dir = join(base, graphId);
|
|
859
|
+
let files: string[];
|
|
860
|
+
try {
|
|
861
|
+
if (!statSync(dir).isDirectory()) continue;
|
|
862
|
+
files = readdirSync(dir);
|
|
863
|
+
} catch { unreadable++; continue; }
|
|
864
|
+
for (const file of files) {
|
|
865
|
+
if (!file.endsWith('.json')) continue;
|
|
866
|
+
const runId = file.slice(0, -5);
|
|
867
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(runId) || runId === '.' || runId === '..') continue;
|
|
868
|
+
// Decision claims are named <runId>.json.<visit>.decision.json; a run
|
|
869
|
+
// may itself end in .decision, so check the ledger identity before skipping.
|
|
870
|
+
const decisionParent = file.match(/^(.*\.json)\.\d+\.decision\.json$/)?.[1];
|
|
871
|
+
try {
|
|
872
|
+
const raw: unknown = JSON.parse(readFileSync(join(dir, file), 'utf8'));
|
|
873
|
+
// Skip only a VALID decision claim (decideGraphApproval writes { nodeId, decision, decidedBy?, decidedAt });
|
|
874
|
+
// anything else under a claim-like name is judged as a ledger and counts as unreadable when broken.
|
|
875
|
+
if (decisionParent && isDecisionClaim(raw)) continue;
|
|
876
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw) ||
|
|
877
|
+
(raw as Partial<GraphRunState>).graphId !== graphId || (raw as Partial<GraphRunState>).runId !== runId) {
|
|
878
|
+
unreadable++;
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
881
|
+
const state = readGraphRun(graphId, runId, root);
|
|
882
|
+
if (typeof state.startedAt !== 'string' || !Number.isFinite(Date.parse(state.startedAt)) ||
|
|
883
|
+
!Array.isArray(state.path) || !state.path.every((node) => typeof node === 'string') ||
|
|
884
|
+
!Array.isArray(state.nodes) || !state.nodes.every((node) => node && typeof node.nodeId === 'string' && typeof node.ok === 'boolean')) {
|
|
885
|
+
throw new Error('invalid run state');
|
|
886
|
+
}
|
|
887
|
+
runs.push(state);
|
|
888
|
+
} catch { unreadable++; }
|
|
889
|
+
}
|
|
474
890
|
}
|
|
891
|
+
runs.sort((a, b) => Date.parse(b.startedAt!) - Date.parse(a.startedAt!) || a.graphId.localeCompare(b.graphId) || a.runId.localeCompare(b.runId));
|
|
892
|
+
return { runs, unreadable };
|
|
475
893
|
}
|
|
476
894
|
|
|
477
895
|
export function latestGraphRun(graphId: string, root = effectiveInstanceRoot()): GraphRunState | null {
|