elanous 0.2.10 → 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/{8067-87ce253ec2f23697.js → 5845-e6c003ce8d5fd36a.js} +1 -1
- 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-54b32bd27726588d.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-35414e0a007b4082.js +1 -0
- 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 -1
- package/apps/pwa/out/ops/seats/index.txt +22 -22
- 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 -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 -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/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/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/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 +12 -0
- 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/marketing/measure-site-numbers.ts +169 -0
- package/scripts/measure-gate-shards.ts +309 -0
- package/scripts/mission-request-judge.ts +31 -1
- package/scripts/morning-report.ts +2 -2
- package/scripts/opportunity-scan.ts +1 -1
- package/scripts/release-loop/auto-approve-node.ts +21 -4
- package/scripts/release-loop/cut-branch.ts +84 -0
- 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/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 +61 -10
- package/scripts/signal-digest-cycle.ts +2 -7
- package/scripts/signal-digest-photo.ts +11 -0
- package/scripts/tech1-public-measure.ts +94 -0
- package/scripts/test-diet/node.ts +1 -1
- package/scripts/tui-regress.ts +2 -2
- package/scripts/ux/demo-rehearsal.ts +243 -0
- package/scripts/ux/lib/rehearsal-verdict.ts +73 -0
- 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/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 +2 -2
- package/skills/pitch-deck/template.html +2 -2
- 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/server.ts +5 -2
- 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/autopilot/build/llm-conflict-merge.ts +16 -3
- package/src/autopilot/tool-registry.ts +2 -0
- package/src/boot/daemon-prompt-turn.ts +5 -0
- package/src/boot/daemon-runtime.ts +9 -3
- package/src/boot/daemon-tools/web-search.ts +3 -0
- package/src/card-followup/core.ts +109 -0
- package/src/chat/index.ts +1 -0
- 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/persona-cli.ts +90 -4
- package/src/cli/plugin-cli.ts +31 -22
- package/src/cli/pod-cli.ts +19 -9
- package/src/cli/pr-cli.ts +51 -15
- package/src/cli/pty-drive-cli.ts +41 -7
- package/src/cli/pty-drive-verdict.ts +40 -0
- package/src/cli/release-cli.ts +17 -4
- package/src/cli/setup-cli.ts +43 -1
- package/src/cli/skills-cli.ts +17 -1
- 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 +2 -2
- package/src/context-bus/external-events.ts +52 -0
- package/src/context-bus/long-term-memory.ts +108 -0
- package/src/dashboard/index.ts +9 -4
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +7 -1
- package/src/decisions/decision-cards.ts +10 -3
- package/src/decisions/decision-ledger.ts +13 -5
- 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 +17 -1
- package/src/domains/core-tools.ts +3 -2
- package/src/domains/outbound-alert.ts +6 -1
- package/src/domains/telegram-kind-route.ts +51 -13
- package/src/exec-requests/default-outputs.ts +25 -0
- package/src/exec-requests/planner.ts +10 -2
- package/src/exec-requests/runner.ts +20 -6
- package/src/exec-requests/store.ts +3 -0
- package/src/execution-loop/launch-gate.ts +100 -0
- package/src/field/field-reel.ts +5 -1
- package/src/field-note/index.ts +201 -0
- package/src/field-note/transcribe.ts +248 -0
- package/src/graph-kinds/registry.ts +3 -3
- package/src/graph-runner/graph-cli.ts +61 -1
- package/src/graph-runner/graph-grow-llm.ts +37 -0
- package/src/graph-runner/graph-grow.ts +47 -0
- package/src/graph-runner/runner.ts +317 -25
- package/src/grok/agent-search.ts +8 -2
- package/src/guardian/secret-exposure-scan.ts +101 -0
- package/src/harness/harness-cli-command.ts +247 -7
- package/src/harness/harness-pod-dispatch.ts +43 -23
- package/src/harness/harness-seams.ts +38 -4
- package/src/harness/harness-stop.ts +23 -4
- package/src/harness/launch-registry.ts +91 -0
- package/src/harness/process-garbage.ts +5 -3
- 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 +52 -14
- package/src/intake-plane/categorize.ts +18 -46
- package/src/intake-plane/discord-seat-work.ts +10 -0
- 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/telegram-seat-work.ts +36 -2
- 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/feature-maturity.ts +26 -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/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 +36 -0
- package/src/nexus/api/inside-events.ts +226 -0
- package/src/nexus/api/meta-api.ts +21 -8
- package/src/nexus/api/missions.ts +6 -1
- package/src/nexus/api/ops-seats.ts +33 -0
- package/src/nexus/api/outputs-route.ts +44 -0
- package/src/nexus/api/seat-requests.ts +44 -2
- package/src/nexus/api/sessions-store.ts +17 -38
- 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/workflow-run-event-bridge.ts +1 -0
- package/src/nexus/api/workflows.ts +80 -6
- package/src/nexus/index.ts +2 -0
- 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/voice-ai.ts +3 -3
- 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-suggestion.ts +16 -0
- package/src/project/session-context.ts +24 -0
- package/src/release-loop/checklist.ts +2 -2
- package/src/release-loop/feature-store.ts +5 -3
- package/src/release-loop/next-md-merge.ts +58 -0
- package/src/release-loop/release-note.ts +59 -4
- 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 +548 -29
- package/src/seat-loop/seat-report.ts +37 -2
- 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/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/gate-baseline.ts +1 -1
- package/src/self-implement/gate-cli.ts +40 -1
- 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 +242 -9
- package/src/self-implement/route-consumer-test-index.ts +122 -0
- 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/index.ts +36 -13
- package/src/skills/gift-install.ts +215 -0
- package/src/skills/tools/web-search.ts +4 -1
- package/src/steward/launch.ts +42 -26
- package/src/steward/steward-cards.ts +17 -9
- package/src/steward/triage.ts +46 -14
- package/src/task-cards/card-cli.ts +25 -1
- 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 +156 -19
- 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 +16 -96
- package/src/telegram-commands.ts +10 -1
- package/src/telegram-project-command.ts +71 -0
- package/src/telegram-report.ts +21 -17
- package/src/telegram.ts +29 -3
- package/src/tool-runtime/self-cognition-runtimes.ts +13 -3
- package/src/user-config.ts +102 -20
- package/src/version/packed-revision.json +1 -1
- 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/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.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/24-e6d28380efab7faf.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/3615-740dd270764ffa24.js +0 -1
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +0 -1
- package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.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/7633-1b3709b0854c9412.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/7951-8b03dbb10b24cd06.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8195-6fde3d7a39859206.js +0 -10
- package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +0 -1
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.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-d2253d7e58ef1890.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-f8116559c7424da7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-54e1f966ab60a4dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7ed95f296f5ee8e9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.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-2976df21ad74ab48.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-b31610deda76337b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-6cb0e1bbefe5fb3d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e168fa1030af0548.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.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-64a29fc80b11ab55.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-4fddc90b23feb215.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-26102fd7350f171e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-139ba5c367bbf60a.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-202b4e81f33895ea.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/43feca5fd387dcbd.css +0 -1
- /package/apps/pwa/out/_next/static/{2DsiDfDCks44g1cPUMhiW → Y4zKzL62sakG5momz3SoQ}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
4
|
+
|
|
5
|
+
/** Research is descriptive. Credential values are never an input to the generator. */
|
|
6
|
+
export interface WizardResearchDraft {
|
|
7
|
+
description: string;
|
|
8
|
+
connectors?: Array<{
|
|
9
|
+
id: string;
|
|
10
|
+
credentials?: Array<{ name: string }>;
|
|
11
|
+
}>;
|
|
12
|
+
skill?: { description: string; instructions: string; requires?: string[] };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const SLUG = /^[a-z0-9][a-z0-9-]{1,31}$/;
|
|
16
|
+
const FIELD = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
17
|
+
|
|
18
|
+
/** Materialize a research draft; its graph steps fail until the requested processing is implemented. */
|
|
19
|
+
export function generateWizardFiles(dir: string, slug: string, draft: WizardResearchDraft): string[] {
|
|
20
|
+
if (!SLUG.test(slug)) throw new Error(`invalid wizard plugin name: ${slug}`);
|
|
21
|
+
if (!draft || typeof draft.description !== 'string' || !draft.description.trim()) throw new Error('wizard description is required');
|
|
22
|
+
if (draft.connectors !== undefined && !Array.isArray(draft.connectors)) throw new Error('invalid wizard connectors');
|
|
23
|
+
const connectors = (draft.connectors ?? []).map(connector => {
|
|
24
|
+
if (!connector || typeof connector !== 'object' || Object.keys(connector).some(key => key !== 'id' && key !== 'credentials') ||
|
|
25
|
+
!SLUG.test(connector.id) || (connector.credentials !== undefined && !Array.isArray(connector.credentials))) {
|
|
26
|
+
throw new Error('wizard connectors must contain ids and credential names only');
|
|
27
|
+
}
|
|
28
|
+
const names = (connector.credentials ?? []).map(credential => {
|
|
29
|
+
if (!credential || typeof credential !== 'object' || !FIELD.test(credential.name) || Object.keys(credential).some(key => key !== 'name')) {
|
|
30
|
+
throw new Error('wizard credentials must contain names only');
|
|
31
|
+
}
|
|
32
|
+
return credential.name;
|
|
33
|
+
});
|
|
34
|
+
if (new Set(names).size !== names.length) throw new Error(`duplicate wizard credential name: ${connector.id}`);
|
|
35
|
+
return { id: connector.id, fields: names.map(name => ({ name, secret: true })) };
|
|
36
|
+
});
|
|
37
|
+
if (new Set(connectors.map(connector => connector.id)).size !== connectors.length) throw new Error('duplicate wizard connector id');
|
|
38
|
+
if (draft.skill && (typeof draft.skill.description !== 'string' || typeof draft.skill.instructions !== 'string')) {
|
|
39
|
+
throw new Error('invalid wizard skill');
|
|
40
|
+
}
|
|
41
|
+
const catalog = parseYaml(readFileSync(join(import.meta.dir, '../../../catalog/resources.yaml'), 'utf8')) as {
|
|
42
|
+
resources: Array<{ id: string }>;
|
|
43
|
+
};
|
|
44
|
+
const knownResources = new Set(catalog.resources.map(resource => resource.id));
|
|
45
|
+
const requires = draft.skill?.requires ?? [];
|
|
46
|
+
if (!Array.isArray(requires) || requires.some(resource => typeof resource !== 'string' || !knownResources.has(resource))) {
|
|
47
|
+
throw new Error('wizard requires must reference existing resource ids');
|
|
48
|
+
}
|
|
49
|
+
const manifestPath = join(dir, 'plugin.json');
|
|
50
|
+
const existing = existsSync(manifestPath) ? JSON.parse(readFileSync(manifestPath, 'utf8')) as {
|
|
51
|
+
name: string; extensions: { 'ai.elanous': { connectors?: unknown[]; graphs?: string[]; researchDraft?: boolean } };
|
|
52
|
+
} : undefined;
|
|
53
|
+
if (existing && (existing.name !== slug || !existing.extensions?.['ai.elanous'])) {
|
|
54
|
+
throw new Error('wizard scaffold name or extension mismatch');
|
|
55
|
+
}
|
|
56
|
+
if (existsSync(join(dir, `skills/${slug}/SKILL.md`))) {
|
|
57
|
+
throw new Error(`wizard file already exists: skills/${slug}/SKILL.md`);
|
|
58
|
+
}
|
|
59
|
+
const scaffoldConnectors = existing?.extensions['ai.elanous'].connectors;
|
|
60
|
+
if (scaffoldConnectors !== undefined && (!Array.isArray(scaffoldConnectors) || scaffoldConnectors.length > 0)) {
|
|
61
|
+
throw new Error('wizard cannot replace existing scaffold connectors');
|
|
62
|
+
}
|
|
63
|
+
const manifest = existing ?? { name: slug, version: '0.1.0', description: draft.description,
|
|
64
|
+
extensions: { 'ai.elanous': { graphs: [`./graphs/${slug}.yaml`],
|
|
65
|
+
capabilities: ['fs:workdir', 'proc:bun', 'proc:elanous'], connectors: [] as unknown[], researchDraft: true } } };
|
|
66
|
+
manifest.extensions['ai.elanous'].connectors = connectors;
|
|
67
|
+
manifest.extensions['ai.elanous'].researchDraft = true;
|
|
68
|
+
|
|
69
|
+
const files: Array<[string, string]> = [
|
|
70
|
+
['plugin.json', JSON.stringify(manifest, null, 2) + '\n'],
|
|
71
|
+
[`graphs/${slug}.yaml`, stringifyYaml({ graph_id: slug, version: 1, entry_node: 'prepare', terminal_nodes: ['done', 'failed'],
|
|
72
|
+
nodes: [
|
|
73
|
+
{ node_id: 'prepare', kind: 'agent', recipe: 'cmd:prepare', max_visits: 1 },
|
|
74
|
+
{ node_id: 'summarize', kind: 'agent', recipe: 'cmd:summarize', max_visits: 1 },
|
|
75
|
+
{ node_id: 'done', kind: 'gate', recipe: 'none', max_visits: 1 },
|
|
76
|
+
{ node_id: 'failed', kind: 'gate', recipe: 'none', max_visits: 1 },
|
|
77
|
+
], edges: [
|
|
78
|
+
{ from: 'prepare', on: 'outcome', map: { ok: 'summarize', fail: 'failed' } },
|
|
79
|
+
{ from: 'summarize', on: 'outcome', map: { ok: 'done', fail: 'failed' } },
|
|
80
|
+
] })],
|
|
81
|
+
['graphs/recipes.yaml', stringifyYaml({ prepare: { command: 'bun "$ELANOUS_GRAPH_DIR/run-step.ts" prepare', timeout_ms: 120000 },
|
|
82
|
+
summarize: { command: 'bun "$ELANOUS_GRAPH_DIR/run-step.ts" summarize', timeout_ms: 120000 } })],
|
|
83
|
+
['graphs/run-step.ts', `const step = process.argv[2];
|
|
84
|
+
console.log(JSON.stringify({ outcome: 'fail', error: step === 'prepare' || step === 'summarize'
|
|
85
|
+
? 'Research draft only: implement the requested processing before running this graph.'
|
|
86
|
+
: 'unknown step' }));
|
|
87
|
+
process.exitCode = 1;
|
|
88
|
+
`],
|
|
89
|
+
['examples/input.json', '{}\n'],
|
|
90
|
+
['README.md', `# ${slug}\n\n${draft.description}\n\nResearch draft only. Connector declarations contain credential names, not values. The graph cannot perform the requested processing yet: implement and validate its steps, then remove extensions["ai.elanous"].researchDraft from plugin.json before installing or running it. No messages are sent.\n`],
|
|
91
|
+
[`skills/${slug}/SKILL.md`, `---\n${stringifyYaml({ name: slug, description: draft.skill?.description ?? draft.description, requires: [...new Set(requires)] })}---\n\n${draft.skill?.instructions ?? draft.description}\n`],
|
|
92
|
+
];
|
|
93
|
+
const generated = existing ? files.filter(([path]) => path === 'plugin.json' || path.startsWith('skills/')) : files;
|
|
94
|
+
for (const [path] of generated) {
|
|
95
|
+
if (path !== 'plugin.json' && existsSync(join(dir, path))) throw new Error(`wizard file already exists: ${path}`);
|
|
96
|
+
}
|
|
97
|
+
for (const [path, content] of generated) {
|
|
98
|
+
const target = join(dir, path);
|
|
99
|
+
mkdirSync(join(target, '..'), { recursive: true });
|
|
100
|
+
writeFileSync(target, content, { flag: path === 'plugin.json' && existing ? 'w' : 'wx' });
|
|
101
|
+
}
|
|
102
|
+
return generated.map(([path]) => path);
|
|
103
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { debug } from '../debug/log.js';
|
|
2
|
+
import type { PoolLeaseRecommendation } from '../task-orchestrator/surfaces/pod-lease.js';
|
|
3
|
+
import type { PredecessorState } from './dependency-state.js';
|
|
4
|
+
|
|
5
|
+
/** A release belongs to one admitted launch and is idempotent. */
|
|
6
|
+
export type PodLeaseRelease = () => void;
|
|
7
|
+
|
|
8
|
+
/** A goal ID or PR number that must be merged before admission. */
|
|
9
|
+
export type PodLeasePredecessor = string | number;
|
|
10
|
+
|
|
11
|
+
export interface PodLeaseAdmissionOptions {
|
|
12
|
+
/** The same recommendation returned by pod lease status, for the launch pool. */
|
|
13
|
+
status: () => PoolLeaseRecommendation | Promise<PoolLeaseRecommendation>;
|
|
14
|
+
/** Predecessor state: merged admits · waiting stays queued (unknown/error too) · blocked rejects. A boolean means merged/waiting. */
|
|
15
|
+
dependencyMerged?: (after: PodLeasePredecessor, signal?: AbortSignal) => PredecessorState | boolean | Promise<PredecessorState | boolean>;
|
|
16
|
+
/** Recheck even when there are no local releases (another launcher may free a slot). */
|
|
17
|
+
pollMs?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** FIFO admission across launches sharing this instance. Unknown status never grants a slot. */
|
|
21
|
+
export class PodLeaseAdmission {
|
|
22
|
+
private readonly queue: Array<{ resolve: (release: PodLeaseRelease) => void; reject: (error: Error) => void; signal?: AbortSignal; after?: PodLeasePredecessor; onAbort?: () => void; cancelCheck?: () => void; waitReason?: string }> = [];
|
|
23
|
+
private active = 0;
|
|
24
|
+
private limit = 0;
|
|
25
|
+
private checking: Promise<void> | undefined;
|
|
26
|
+
private recheckRequested = false;
|
|
27
|
+
private timer: ReturnType<typeof setTimeout> | undefined;
|
|
28
|
+
private readonly pollMs: number;
|
|
29
|
+
|
|
30
|
+
constructor(private readonly options: PodLeaseAdmissionOptions) {
|
|
31
|
+
this.pollMs = options.pollMs ?? 15_000;
|
|
32
|
+
if (!Number.isFinite(this.pollMs) || this.pollMs <= 0) throw new Error('pod lease pollMs must be positive');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Queue view: each waiting item says why it waits (predecessor not merged yet, or no free slot). */
|
|
36
|
+
snapshot(): { active: number; queued: number; recommended: number; waiting: Array<{ after?: PodLeasePredecessor; reason: string }> } {
|
|
37
|
+
return { active: this.active, queued: this.queue.length, recommended: this.limit,
|
|
38
|
+
waiting: this.queue.map((w) => ({ ...(w.after !== undefined ? { after: w.after } : {}), reason: w.waitReason ?? 'no-free-slot' })) };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A launch stays queued until an admitted slot can be reserved. */
|
|
42
|
+
acquire(signal?: AbortSignal, after?: PodLeasePredecessor): Promise<PodLeaseRelease> {
|
|
43
|
+
if (signal?.aborted) return Promise.reject(new Error('pod lease admission aborted'));
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const waiter: (typeof this.queue)[number] = { resolve, reject, signal, after };
|
|
46
|
+
if (signal) {
|
|
47
|
+
waiter.onAbort = () => {
|
|
48
|
+
const index = this.queue.indexOf(waiter);
|
|
49
|
+
if (index < 0) return;
|
|
50
|
+
this.queue.splice(index, 1);
|
|
51
|
+
waiter.cancelCheck?.();
|
|
52
|
+
reject(new Error('pod lease admission aborted'));
|
|
53
|
+
this.schedule();
|
|
54
|
+
};
|
|
55
|
+
signal.addEventListener('abort', waiter.onAbort, { once: true });
|
|
56
|
+
}
|
|
57
|
+
this.queue.push(waiter);
|
|
58
|
+
void this.refresh();
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Re-evaluate the measured recommended N; never use accountSlots as an admission bound. */
|
|
63
|
+
refresh(): Promise<void> {
|
|
64
|
+
if (this.checking) {
|
|
65
|
+
if (this.queue.length) this.recheckRequested = true;
|
|
66
|
+
return this.checking;
|
|
67
|
+
}
|
|
68
|
+
if (!this.queue.length) return Promise.resolve();
|
|
69
|
+
if (this.timer) clearTimeout(this.timer);
|
|
70
|
+
this.timer = undefined;
|
|
71
|
+
this.checking = this.checkUntilCurrent();
|
|
72
|
+
return this.checking;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private async checkUntilCurrent(): Promise<void> {
|
|
76
|
+
try {
|
|
77
|
+
do {
|
|
78
|
+
this.recheckRequested = false;
|
|
79
|
+
try {
|
|
80
|
+
const status = await this.options.status();
|
|
81
|
+
this.limit = status.recommended !== null && Number.isSafeInteger(status.recommended) && status.recommended >= 0
|
|
82
|
+
? status.recommended : 0;
|
|
83
|
+
} catch {
|
|
84
|
+
this.limit = 0;
|
|
85
|
+
}
|
|
86
|
+
// No head-of-line blocking: a waiter whose predecessor is not merged yet is skipped and later waiters may admit.
|
|
87
|
+
let index = 0;
|
|
88
|
+
while (this.active < this.limit && index < this.queue.length) {
|
|
89
|
+
const waiter = this.queue[index]!;
|
|
90
|
+
if (waiter.after !== undefined) {
|
|
91
|
+
let state: PredecessorState = 'waiting';
|
|
92
|
+
const controller = new AbortController();
|
|
93
|
+
try {
|
|
94
|
+
const check = this.options.dependencyMerged?.(waiter.after, controller.signal);
|
|
95
|
+
const result = await Promise.race([
|
|
96
|
+
Promise.resolve(check),
|
|
97
|
+
new Promise<PredecessorState>((resolve) => { waiter.cancelCheck = () => { controller.abort(); resolve('waiting'); }; }),
|
|
98
|
+
]);
|
|
99
|
+
state = result === true ? 'merged' : result === false || result === undefined ? 'waiting' : result;
|
|
100
|
+
} catch {
|
|
101
|
+
// An unavailable dependency state cannot grant admission.
|
|
102
|
+
} finally {
|
|
103
|
+
waiter.cancelCheck = undefined;
|
|
104
|
+
}
|
|
105
|
+
if (!this.queue.includes(waiter)) continue; // aborted while checking
|
|
106
|
+
if (state === 'blocked') {
|
|
107
|
+
this.queue.splice(this.queue.indexOf(waiter), 1);
|
|
108
|
+
if (waiter.signal && waiter.onAbort) waiter.signal.removeEventListener('abort', waiter.onAbort);
|
|
109
|
+
debug.log('pod-lease', 'predecessor-blocked', { after: waiter.after });
|
|
110
|
+
waiter.reject(new Error(`pod lease predecessor blocked: ${String(waiter.after)} was closed without merging`));
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (state !== 'merged') {
|
|
114
|
+
waiter.waitReason = `predecessor-unmerged:${String(waiter.after)}`;
|
|
115
|
+
index++;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
this.queue.splice(this.queue.indexOf(waiter), 1);
|
|
120
|
+
if (waiter.signal && waiter.onAbort) waiter.signal.removeEventListener('abort', waiter.onAbort);
|
|
121
|
+
this.active++;
|
|
122
|
+
let released = false;
|
|
123
|
+
waiter.resolve(() => {
|
|
124
|
+
if (released) return;
|
|
125
|
+
released = true;
|
|
126
|
+
this.active--;
|
|
127
|
+
void this.refresh();
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
for (const waiter of this.queue.slice(index)) if (!waiter.waitReason?.startsWith('predecessor-')) waiter.waitReason = 'no-free-slot';
|
|
131
|
+
} while (this.recheckRequested && this.queue.length > 0);
|
|
132
|
+
} finally {
|
|
133
|
+
this.checking = undefined;
|
|
134
|
+
this.schedule();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private schedule(): void {
|
|
139
|
+
if (!this.queue.length) {
|
|
140
|
+
if (this.timer) clearTimeout(this.timer);
|
|
141
|
+
this.timer = undefined;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (this.timer) return;
|
|
145
|
+
this.timer = setTimeout(() => {
|
|
146
|
+
this.timer = undefined;
|
|
147
|
+
void this.refresh();
|
|
148
|
+
}, this.pollMs);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { listRunLedgers } from '../self-implement/run-ledger.js';
|
|
3
|
+
import { runGitCommand } from '../git-fs/runner.js';
|
|
4
|
+
import type { PodLeasePredecessor } from './admission.js';
|
|
5
|
+
|
|
6
|
+
/** merged = admit · waiting = keep queued (open, unknown, unreadable) · blocked = predecessor closed without merging. */
|
|
7
|
+
export type PredecessorState = 'merged' | 'waiting' | 'blocked';
|
|
8
|
+
|
|
9
|
+
type PrState = string | null;
|
|
10
|
+
|
|
11
|
+
type DependencyStateDeps = {
|
|
12
|
+
prState?: (number: number, signal?: AbortSignal) => PrState | Promise<PrState>;
|
|
13
|
+
ledgers?: () => ReturnType<typeof listRunLedgers>['matches'];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function githubRepository(): string | null {
|
|
17
|
+
const remote = runGitCommand(process.cwd(), ['config', '--get', 'remote.origin.url'], { encoding: 'utf8', timeout: 5_000 });
|
|
18
|
+
const repository = /^(?:https:\/\/github\.com\/|git@github\.com:|ssh:\/\/git@github\.com\/)([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+?)(?:\.git)?\/?$/.exec(remote.stdout.trim())?.[1];
|
|
19
|
+
return remote.status !== 0 || !repository || repository.includes('..') ? null : repository;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Async so a slow GitHub lookup never blocks the event loop (admission, cancellation, other launches). */
|
|
23
|
+
function githubPrState(number: number, signal?: AbortSignal): Promise<PrState> {
|
|
24
|
+
const repository = githubRepository();
|
|
25
|
+
if (!repository) return Promise.resolve(null);
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
execFile('gh', ['pr', 'view', String(number), '--repo', repository, '--json', 'state'], {
|
|
28
|
+
encoding: 'utf8', timeout: 15_000, env: process.env, ...(signal ? { signal } : {}),
|
|
29
|
+
}, (error, stdout) => {
|
|
30
|
+
if (error) return resolve(null);
|
|
31
|
+
try {
|
|
32
|
+
const response: unknown = JSON.parse(stdout);
|
|
33
|
+
resolve(response && typeof response === 'object' && 'state' in response && typeof response.state === 'string' ? response.state : null);
|
|
34
|
+
} catch { resolve(null); }
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const prNumberOf = (after: PodLeasePredecessor): number | null => {
|
|
40
|
+
const number = typeof after === 'number' ? after : Number(after.replace(/^#/, ''));
|
|
41
|
+
return Number.isSafeInteger(number) && number > 0 ? number : null;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
async function readState(number: number, deps: DependencyStateDeps, signal?: AbortSignal): Promise<PrState> {
|
|
45
|
+
try { return await (deps.prState ?? githubPrState)(number, signal); } catch { return null; }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Only a verified merged predecessor grants a Pod lease; a predecessor closed without merging blocks; everything else waits. */
|
|
49
|
+
export async function predecessorState(after: PodLeasePredecessor, deps: DependencyStateDeps = {}, signal?: AbortSignal): Promise<PredecessorState> {
|
|
50
|
+
const isPr = typeof after === 'number' || (typeof after === 'string' && !/^[a-f0-9]{16}$/.test(after) && /^#?[1-9]\d*$/.test(after));
|
|
51
|
+
if (isPr) {
|
|
52
|
+
const number = prNumberOf(after);
|
|
53
|
+
if (number === null) return 'waiting';
|
|
54
|
+
const state = await readState(number, deps, signal);
|
|
55
|
+
return state === 'MERGED' ? 'merged' : state === 'CLOSED' ? 'blocked' : 'waiting';
|
|
56
|
+
}
|
|
57
|
+
if (typeof after !== 'string' || !after.trim()) return 'waiting';
|
|
58
|
+
let numbers: Set<number>;
|
|
59
|
+
try {
|
|
60
|
+
const matches = (deps.ledgers ?? (() => listRunLedgers().matches))()
|
|
61
|
+
.filter(({ entries }) => entries.some((entry) => entry.goalId === after));
|
|
62
|
+
numbers = new Set<number>();
|
|
63
|
+
for (const { entries } of matches) {
|
|
64
|
+
for (const entry of entries) {
|
|
65
|
+
if (entry.event !== 'pr-opened' || entry.goalId !== after) continue;
|
|
66
|
+
const number = entry.data.number;
|
|
67
|
+
if (typeof number === 'number' && Number.isSafeInteger(number) && number > 0) numbers.add(number);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} catch { return 'waiting'; }
|
|
71
|
+
if (numbers.size === 0) return 'waiting';
|
|
72
|
+
// A later attempt or manual merge can land an opened PR without an in-run merged event.
|
|
73
|
+
let allClosed = true;
|
|
74
|
+
for (const number of numbers) {
|
|
75
|
+
const state = await readState(number, deps, signal);
|
|
76
|
+
if (state === 'MERGED') return 'merged';
|
|
77
|
+
if (state !== 'CLOSED') allClosed = false;
|
|
78
|
+
}
|
|
79
|
+
return allClosed ? 'blocked' : 'waiting';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Back-compat boolean view: true only when merged. */
|
|
83
|
+
export async function predecessorMerged(after: PodLeasePredecessor, deps: DependencyStateDeps = {}, signal?: AbortSignal): Promise<boolean> {
|
|
84
|
+
return (await predecessorState(after, deps, signal)) === 'merged';
|
|
85
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// L7b — cross-process Pod admission on one host. Each admitted launch holds a lease file (pid ⊕ process start time)
|
|
2
|
+
// in a per-user directory; a launch is admitted only while live leases stay under the pool's measured total.
|
|
3
|
+
// Without this, independent CLI processes each measured an empty pool and all launched (review round 2 of #23212).
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { mkdirSync, readdirSync, readFileSync, rmSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { tmpdir, userInfo } from 'node:os';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { debug } from '../debug/log.js';
|
|
9
|
+
|
|
10
|
+
export interface HostLeaseRecord { pid: number; startedAt: string; at: string }
|
|
11
|
+
|
|
12
|
+
export interface HostPoolLeaseOptions {
|
|
13
|
+
/** Base directory — default `$TMPDIR/elanous-pod-leases-<uid>` (0700). */
|
|
14
|
+
dir?: string;
|
|
15
|
+
pid?: number;
|
|
16
|
+
/** `ps -o lstart=` text for a pid, or null when the process is gone. */
|
|
17
|
+
processStart?: (pid: number) => string | null;
|
|
18
|
+
now?: () => number;
|
|
19
|
+
/** A lock older than this is a crash leftover and is cleared. */
|
|
20
|
+
staleLockMs?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function hostLeaseBaseDir(env: NodeJS.ProcessEnv = process.env): string {
|
|
24
|
+
if (env.ELANOUS_POD_LEASE_DIR?.trim()) return env.ELANOUS_POD_LEASE_DIR.trim();
|
|
25
|
+
// Test runs get a per-process directory so parallel test files never share real host leases.
|
|
26
|
+
if (env.NODE_ENV === 'test') return join(tmpdir(), `elanous-pod-leases-test-${process.pid}`);
|
|
27
|
+
let uid: string;
|
|
28
|
+
try { uid = String(userInfo().uid); } catch { uid = 'unknown'; }
|
|
29
|
+
return join(tmpdir(), `elanous-pod-leases-${uid}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Process start time as `ps` prints it (C locale so the text is stable). Null = no such process. */
|
|
33
|
+
export function psProcessStart(pid: number): string | null {
|
|
34
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return null;
|
|
35
|
+
const result = spawnSync('ps', ['-o', 'lstart=', '-p', String(pid)], { encoding: 'utf8', env: { ...process.env, LC_ALL: 'C', LANG: 'C' } });
|
|
36
|
+
const text = result.status === 0 ? result.stdout.trim() : '';
|
|
37
|
+
return text || null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class HostPoolLease {
|
|
41
|
+
readonly dir: string;
|
|
42
|
+
private readonly pid: number;
|
|
43
|
+
private readonly processStart: (pid: number) => string | null;
|
|
44
|
+
private readonly now: () => number;
|
|
45
|
+
private readonly staleLockMs: number;
|
|
46
|
+
private ownStart: string | null | undefined;
|
|
47
|
+
|
|
48
|
+
constructor(poolKey: string, options: HostPoolLeaseOptions = {}) {
|
|
49
|
+
this.dir = join(options.dir ?? hostLeaseBaseDir(), poolKey.replace(/[^A-Za-z0-9._-]/g, '_').slice(0, 120) || 'pool');
|
|
50
|
+
this.pid = options.pid ?? process.pid;
|
|
51
|
+
this.processStart = options.processStart ?? psProcessStart;
|
|
52
|
+
this.now = options.now ?? Date.now;
|
|
53
|
+
this.staleLockMs = options.staleLockMs ?? 10_000;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Live leases after removing stale ones (dead pid or a recycled pid whose start time differs). */
|
|
57
|
+
live(): HostLeaseRecord[] {
|
|
58
|
+
const out: HostLeaseRecord[] = [];
|
|
59
|
+
let names: string[] = [];
|
|
60
|
+
try { names = readdirSync(this.dir).filter((name) => name.endsWith('.json')); } catch { return out; }
|
|
61
|
+
for (const name of names) {
|
|
62
|
+
const file = join(this.dir, name);
|
|
63
|
+
let record: HostLeaseRecord | null = null;
|
|
64
|
+
try { record = JSON.parse(readFileSync(file, 'utf8')) as HostLeaseRecord; } catch { record = null; }
|
|
65
|
+
const alive = record && this.processStart(record.pid) === record.startedAt;
|
|
66
|
+
if (record && alive) { out.push(record); continue; }
|
|
67
|
+
try { unlinkSync(file); } catch { /* another process cleaned it */ }
|
|
68
|
+
debug.log('pod.lease', 'host-lease-stale-cleared', { file: name, pid: record?.pid ?? null });
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Unapplied leases held by other processes — launches admitted elsewhere whose Job the cluster can't see yet. */
|
|
74
|
+
othersPending(): number {
|
|
75
|
+
if (this.ownStart === undefined) this.ownStart = this.processStart(this.pid);
|
|
76
|
+
return this.live().filter((r) => !(r.pid === this.pid && r.startedAt === this.ownStart)).length;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Under the host lock, take a lease only if `allowed(othersPending)` holds — the check and the write are one step,
|
|
81
|
+
* so independent processes measuring the same empty pool cannot all pass. Null = refused or lock busy (retry later).
|
|
82
|
+
*/
|
|
83
|
+
tryReserve(allowed: (othersPending: number) => boolean): (() => void) | null {
|
|
84
|
+
try { mkdirSync(this.dir, { recursive: true, mode: 0o700 }); } catch { return null; }
|
|
85
|
+
if (!this.lock()) return null;
|
|
86
|
+
try {
|
|
87
|
+
if (this.ownStart === undefined) this.ownStart = this.processStart(this.pid);
|
|
88
|
+
if (!this.ownStart) return null;
|
|
89
|
+
const others = this.live().filter((r) => !(r.pid === this.pid && r.startedAt === this.ownStart)).length;
|
|
90
|
+
if (!allowed(others)) {
|
|
91
|
+
debug.log('pod.lease', 'host-lease-refused', { othersPending: others });
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const file = join(this.dir, `${this.pid}-${this.now()}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
95
|
+
const record: HostLeaseRecord = { pid: this.pid, startedAt: this.ownStart, at: new Date(this.now()).toISOString() };
|
|
96
|
+
writeFileSync(file, JSON.stringify(record), { flag: 'wx', mode: 0o600 });
|
|
97
|
+
debug.log('pod.lease', 'host-lease-acquired', { othersPending: others });
|
|
98
|
+
let released = false;
|
|
99
|
+
return () => {
|
|
100
|
+
if (released) return;
|
|
101
|
+
released = true;
|
|
102
|
+
try { unlinkSync(file); } catch { /* already cleaned */ }
|
|
103
|
+
};
|
|
104
|
+
} finally {
|
|
105
|
+
this.unlock();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private lockDir(): string { return join(this.dir, '.lock'); }
|
|
110
|
+
|
|
111
|
+
private lock(): boolean {
|
|
112
|
+
const dir = this.lockDir();
|
|
113
|
+
try { mkdirSync(dir); return true; } catch { /* held */ }
|
|
114
|
+
try {
|
|
115
|
+
if (this.now() - statSync(dir).mtimeMs > this.staleLockMs) {
|
|
116
|
+
rmSync(dir, { recursive: true, force: true });
|
|
117
|
+
mkdirSync(dir);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
} catch { /* raced */ }
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private unlock(): void {
|
|
125
|
+
try { rmSync(this.lockDir(), { recursive: true, force: true }); } catch { /* gone */ }
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// 작업 폴더 → 프로젝트 제안을 «폴더가 바뀔 때마다» 다시 계산한다(IA1 리뷰 must-fix · 시작 시 process.cwd() 한 번만 보던 것).
|
|
2
|
+
// 같은 프로젝트를 연달아 다시 알리지 않는다 — 바뀐 경우에만 돌려준다.
|
|
3
|
+
import type { Project } from './project-store.js';
|
|
4
|
+
|
|
5
|
+
export function createProjectSuggester(suggest: (folder: string) => Project | null): (folder: string) => Project | null {
|
|
6
|
+
let lastId: string | null = null;
|
|
7
|
+
return (folder) => {
|
|
8
|
+
let project: Project | null;
|
|
9
|
+
try { project = suggest(folder); }
|
|
10
|
+
catch { return null; } // 제안은 대화를 끊지 않는다
|
|
11
|
+
const id = project?.id ?? null;
|
|
12
|
+
if (id === lastId) return null;
|
|
13
|
+
lastId = id;
|
|
14
|
+
return project;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { getProject } from './project-store.js';
|
|
4
|
+
import { loadSession } from '../session/index.js';
|
|
5
|
+
|
|
6
|
+
/** Resolve the assigned project's folder for a persisted conversation, never from client-supplied paths. */
|
|
7
|
+
export function resolveSessionProjectContext(sessionId: string | undefined): { cwd: string; instructions?: string } | null {
|
|
8
|
+
if (!sessionId) return null;
|
|
9
|
+
const projectId = loadSession(sessionId)?.meta.projectId;
|
|
10
|
+
if (!projectId) return null;
|
|
11
|
+
const folder = getProject(projectId)?.primaryFolder;
|
|
12
|
+
if (!folder) return null;
|
|
13
|
+
try {
|
|
14
|
+
if (!statSync(folder).isDirectory()) return null;
|
|
15
|
+
} catch { return null; }
|
|
16
|
+
const file = join(folder, 'AGENTS.md');
|
|
17
|
+
try {
|
|
18
|
+
const instructions = readFileSync(file, 'utf8');
|
|
19
|
+
return { cwd: folder, ...(instructions.trim() ? { instructions: `Project instructions (${file}):\n${instructions}` } : {}) };
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
22
|
+
return { cwd: folder };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -68,8 +68,8 @@ function decodeClaimHistory(data: Checklist): Checklist {
|
|
|
68
68
|
return { ...data, history: data.history.map(decodeClaimHistoryEntry) };
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
export function listChecklist(v: string): Checklist {
|
|
72
|
-
return decodeClaimHistory(store.list(v));
|
|
71
|
+
export function listChecklist(v: string, root?: string): Checklist {
|
|
72
|
+
return decodeClaimHistory(root === undefined ? store.list(v) : store.list(v, undefined, undefined, root));
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/** Status events in the requested window; other ledger fields never become status changes. */
|
|
@@ -278,10 +278,12 @@ function importLegacyAll(): void {
|
|
|
278
278
|
} finally { db.close(); }
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
-
|
|
281
|
+
/** `root` reads another ledger (no legacy JSON import there); omitted = the default ledger as before. */
|
|
282
|
+
export function list(version: string, released?: string, dev = devVersion(), root?: string): Checklist {
|
|
282
283
|
validateVersion(version);
|
|
283
|
-
importJson(version);
|
|
284
|
-
|
|
284
|
+
if (root === undefined) importJson(version);
|
|
285
|
+
released ??= releasedVersion(root);
|
|
286
|
+
const db = open(root);
|
|
285
287
|
try { return readTransaction(db, () => snapshot(db, version, released, dev)); } finally { db.close(); }
|
|
286
288
|
}
|
|
287
289
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Deterministic merge for `release/next.md` — two landings that each append a line to the same section
|
|
2
|
+
// keep both lines (union per section, same sentence once). Returns null when it cannot decide (the caller
|
|
3
|
+
// falls back to its usual resolver): a side deleted a line, or a stage is missing.
|
|
4
|
+
|
|
5
|
+
export const NEXT_MD_PATH = 'release/next.md';
|
|
6
|
+
|
|
7
|
+
type Section = { header: string | null; lines: string[] };
|
|
8
|
+
|
|
9
|
+
function sections(text: string): Section[] {
|
|
10
|
+
const out: Section[] = [{ header: null, lines: [] }];
|
|
11
|
+
for (const line of text.replace(/\r\n/g, '\n').split('\n')) {
|
|
12
|
+
if (/^## /.test(line)) out.push({ header: line.trim(), lines: [] });
|
|
13
|
+
else out[out.length - 1]!.lines.push(line);
|
|
14
|
+
}
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const key = (line: string) => line.trim();
|
|
19
|
+
|
|
20
|
+
function added(base: Section | undefined, side: Section): string[] {
|
|
21
|
+
const before = new Set((base?.lines ?? []).map(key));
|
|
22
|
+
return side.lines.filter((line) => key(line) !== '' && !before.has(key(line)));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function deletedAny(base: Section[], side: Section[]): boolean {
|
|
26
|
+
for (const b of base) {
|
|
27
|
+
const s = side.find((x) => x.header === b.header);
|
|
28
|
+
const kept = new Set((s?.lines ?? []).map(key));
|
|
29
|
+
if (b.lines.some((line) => key(line) !== '' && !kept.has(key(line)))) return true;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Union of both sides' additions over `theirs` (the branch being merged in). Null = not decidable here. */
|
|
35
|
+
export function resolveNextMdConflict(base: string, ours: string, theirs: string): string | null {
|
|
36
|
+
const b = sections(base);
|
|
37
|
+
const o = sections(ours);
|
|
38
|
+
// Our side only appends (harness landings add one line); a deletion on our side is not ours to decide.
|
|
39
|
+
if (deletedAny(b, o)) return null;
|
|
40
|
+
const result = sections(theirs);
|
|
41
|
+
for (const section of o) {
|
|
42
|
+
const extra = added(b.find((x) => x.header === section.header), section);
|
|
43
|
+
if (extra.length === 0) continue;
|
|
44
|
+
let target = result.find((x) => x.header === section.header);
|
|
45
|
+
if (!target) {
|
|
46
|
+
target = { header: section.header, lines: [''] };
|
|
47
|
+
result.push(target);
|
|
48
|
+
}
|
|
49
|
+
const present = new Set(target.lines.map(key));
|
|
50
|
+
const fresh = extra.filter((line) => !present.has(key(line)));
|
|
51
|
+
if (fresh.length === 0) continue;
|
|
52
|
+
// Insert before the section's trailing blank lines so the blank separator stays last.
|
|
53
|
+
let end = target.lines.length;
|
|
54
|
+
while (end > 0 && key(target.lines[end - 1]!) === '') end -= 1;
|
|
55
|
+
target.lines.splice(end, 0, ...fresh);
|
|
56
|
+
}
|
|
57
|
+
return result.map((section) => (section.header === null ? section.lines : [section.header, ...section.lines]).join('\n')).join('\n');
|
|
58
|
+
}
|