elanous 0.2.10 → 0.2.12
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/-6XrAoD3eU7NyMHAE4xPS/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1147-466ec15afacc1a65.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1394-ee3795b112907122.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/2426.8c6ee0c668d53def.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-3cd46526879f454c.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/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/7277-0b2fc7c16ba2d64b.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/7758-0b018a9e4fc56200.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-0259994816604c49.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/9920-5cfdc5e8fd209825.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-cb169620945d1952.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-cefe8169e36cf62a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-d2e35228bc497119.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-54b32bd27726588d.js → page-5c05b0da4bc1392c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e0ccdb576812b523.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-813c4a52c9d05ca8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-4731f2c215358937.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-a022b257950f39e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-a2ffc717e04b8b0a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-b59ee4e70fc18fcf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-a67e11ba962337b5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-c172dabd796eb7a3.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-1efd0cb9e0ed9d13.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-d5dc7ff69a37b4cf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-e71c84189e8c683e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-1d6a533ce5a6efcb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-dfca9b7d2de0b135.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-793fa536b3a6ef37.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-6dd3bfbbde2a5c2c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-490653e8d4cc8852.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-77447356baad75d2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-29e961191f6057df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-c822f55b884d9104.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fa4ccfc432424e52.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-6d604227e1664b71.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-9c7e8f744a765daf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-4048543b4b7ad5e0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4c3a3126abed551a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-64b0aecb1ca75e75.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4292d0149020e8a7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-db14d0be7f1c460c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-e5d8072cd7cfef73.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-9547b5cec7cf76bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-e6dcb83265530636.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-45248f08d6aecae5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-2cddeb9e2aeedc55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-e297866051595d81.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-baba2e047872fb0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-e895da4ec9cac345.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-4344faf20216233b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-698bb29f11864b89.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-bd6a6dc1ddc273f2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2e9e2fb01c9da1c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-d62ceb13411ebba4.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/128fb7fb670b2fbf.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/context/nightly-condense.yaml +21 -0
- package/graphs/context/recipes.yaml +12 -0
- package/graphs/demo/inside-seed.yaml +20 -0
- package/graphs/demo/recipes.yaml +12 -0
- 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/card-followup/examples/sample-card.png +0 -0
- 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/lib/telegram-inject-args.ts +31 -0
- package/scripts/marketing/measure-site-numbers.ts +169 -0
- package/scripts/measure-gate-shards.ts +309 -0
- package/scripts/measure-pod-memory-by-goal.ts +130 -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/checklist-gate-node.ts +2 -1
- package/scripts/release-loop/cut-branch.ts +84 -0
- package/scripts/release-loop/gate-node.ts +22 -15
- 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 +47 -6
- package/scripts/release-loop/publish-node.ts +4 -1
- 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 +74 -13
- 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/telegram-inject.ts +14 -6
- package/scripts/test-diet/node.ts +1 -1
- package/scripts/test-diet/remeasure-list.ts +112 -0
- package/scripts/tui-regress.ts +2 -2
- package/scripts/ux/demo-rehearsal.ts +247 -0
- package/scripts/ux/demo-seed.ts +108 -0
- package/scripts/ux/lib/rehearsal-verdict.ts +80 -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 +215 -23
- 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/pty-control-loop.ts +4 -1
- 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 +2 -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 +98 -9
- package/src/cli/plugin-cli.ts +31 -22
- package/src/cli/pod-cli.ts +36 -9
- package/src/cli/pod-memory-advice.ts +35 -0
- 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 +63 -10
- 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 +65 -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/context-bus/nightly-condense.ts +153 -0
- package/src/context-bus/session-events.ts +35 -0
- package/src/dashboard/dashboard-status-lines.ts +2 -0
- package/src/dashboard/index.ts +17 -4
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +31 -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 +105 -32
- package/src/domains/outbound-log-probe.ts +8 -0
- 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 +109 -0
- package/src/field/field-reel.ts +5 -1
- package/src/field-note/index.ts +201 -0
- package/src/field-note/telegram.ts +142 -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 +47 -0
- package/src/graph-runner/graph-grow.ts +50 -0
- package/src/graph-runner/runner.ts +329 -33
- package/src/grok/agent-search.ts +8 -2
- package/src/guardian/secret-exposure-scan.ts +101 -0
- package/src/harness/harness-cli-command.ts +375 -13
- 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/consult-intake.ts +153 -0
- 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 +94 -16
- package/src/index.ts +76 -14
- package/src/intake-plane/categorize.ts +18 -46
- package/src/intake-plane/discord-seat-work.ts +38 -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/telegram-seat-work.ts +36 -2
- package/src/intake-plane/wish-card.ts +33 -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/feature-maturity.ts +27 -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 +67 -8
- 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/outbound-report.ts +40 -7
- package/src/nexus/api/outputs-route.ts +44 -0
- package/src/nexus/api/personas.ts +95 -53
- 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/task-cards-api.ts +19 -0
- package/src/nexus/api/trace.ts +18 -6
- package/src/nexus/api/workflow-history.ts +88 -0
- package/src/nexus/api/workflow-run-event-bridge.ts +1 -0
- package/src/nexus/api/workflows.ts +132 -6
- package/src/nexus/index.ts +2 -0
- package/src/nexus/kinds/webterm.ts +2 -1
- package/src/nexus/outbound/delivery-ledger.ts +17 -8
- package/src/nexus/outbound/router.ts +8 -3
- 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 +123 -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 +110 -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 +17 -5
- package/src/release-loop/feature-store.ts +29 -13
- package/src/release-loop/landed-but-yellow.ts +48 -0
- 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 +709 -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 +79 -40
- package/src/self-dev/draft-triage-rules.ts +62 -7
- 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/budget-gate.ts +29 -12
- package/src/self-implement/gate-baseline.ts +1 -1
- package/src/self-implement/gate-candidate-ranking.ts +60 -0
- package/src/self-implement/gate-cli.ts +40 -1
- package/src/self-implement/gate-scope.ts +44 -13
- 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 +27 -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 +257 -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 +2 -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 +43 -17
- 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 +222 -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 +31 -1
- package/src/telegram-project-command.ts +145 -0
- package/src/telegram-report.ts +21 -17
- package/src/telegram.ts +61 -8
- package/src/tool-runtime/self-cognition-runtimes.ts +13 -3
- package/src/user-config.ts +110 -24
- 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 → -6XrAoD3eU7NyMHAE4xPS}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { appendFileSync, closeSync, mkdirSync, openSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { dlopen, FFIType } from 'bun:ffi';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { debug } from '../debug/log.js';
|
|
5
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
6
|
+
import { OUTPUT_KINDS, type OutputKind } from '../exec-requests/default-outputs.js';
|
|
7
|
+
|
|
8
|
+
export interface OutputEntry {
|
|
9
|
+
kind: OutputKind | 'file';
|
|
10
|
+
title: string;
|
|
11
|
+
path?: string;
|
|
12
|
+
url?: string;
|
|
13
|
+
source: 'exec' | 'field-reel' | 'field-feed';
|
|
14
|
+
sourceId: string;
|
|
15
|
+
seat?: string;
|
|
16
|
+
at: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type NewOutput = Omit<OutputEntry, 'at'> & { at?: string };
|
|
20
|
+
const validKinds = new Set<unknown>([...Object.keys(OUTPUT_KINDS), 'file']);
|
|
21
|
+
|
|
22
|
+
function kstMonth(at: Date): string {
|
|
23
|
+
return new Date(at.getTime() + 9 * 60 * 60 * 1000).toISOString().slice(0, 7);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function ledgerPath(root: string, month: string): string { return join(root, 'outputs', `outputs-${month}.jsonl`); }
|
|
27
|
+
|
|
28
|
+
function readEntries(file: string): { entries: OutputEntry[]; newline: boolean } {
|
|
29
|
+
let text: string;
|
|
30
|
+
try { text = readFileSync(file, 'utf8'); }
|
|
31
|
+
catch (error) {
|
|
32
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { entries: [], newline: true };
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
const entries: OutputEntry[] = [];
|
|
36
|
+
text.split('\n').forEach((line, index) => {
|
|
37
|
+
if (!line.trim()) return;
|
|
38
|
+
try {
|
|
39
|
+
const entry: unknown = JSON.parse(line);
|
|
40
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)
|
|
41
|
+
|| !('source' in entry) || (entry.source !== 'exec' && entry.source !== 'field-reel' && entry.source !== 'field-feed')
|
|
42
|
+
|| !('sourceId' in entry) || typeof entry.sourceId !== 'string' || !entry.sourceId
|
|
43
|
+
|| !('kind' in entry) || !validKinds.has(entry.kind)
|
|
44
|
+
|| !('title' in entry) || typeof entry.title !== 'string' || !entry.title
|
|
45
|
+
|| !('at' in entry) || typeof entry.at !== 'string' || !entry.at
|
|
46
|
+
|| (!('path' in entry) || typeof entry.path !== 'string' || !entry.path)
|
|
47
|
+
&& (!('url' in entry) || typeof entry.url !== 'string' || !entry.url)
|
|
48
|
+
|| ('path' in entry && entry.path !== undefined && typeof entry.path !== 'string')
|
|
49
|
+
|| ('url' in entry && entry.url !== undefined && typeof entry.url !== 'string')
|
|
50
|
+
|| ('seat' in entry && entry.seat !== undefined && typeof entry.seat !== 'string')) throw new Error('invalid output entry');
|
|
51
|
+
entries.push(entry as OutputEntry);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
debug.log('outputs.ledger', 'invalid-line', { line: index + 1, error: String(error) });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
return { entries, newline: !text || text.endsWith('\n') };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Keep the lock inode in place: unlinking it lets a second writer lock a new inode while the first still holds the old one.
|
|
60
|
+
const flock = dlopen(process.platform === 'darwin' ? 'libSystem.B.dylib' : 'libc.so.6',
|
|
61
|
+
{ flock: { args: [FFIType.int, FFIType.int], returns: FFIType.int } }).symbols.flock;
|
|
62
|
+
function acquireLock(file: string): () => void {
|
|
63
|
+
const fd = openSync(`${file}.lock`, 'a+', 0o600);
|
|
64
|
+
const deadline = Date.now() + 5_000;
|
|
65
|
+
try {
|
|
66
|
+
while (flock(fd, 2 | 4) !== 0) {
|
|
67
|
+
if (Date.now() >= deadline) throw new Error('outputs ledger lock timed out');
|
|
68
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
|
|
69
|
+
}
|
|
70
|
+
} catch (error) { closeSync(fd); throw error; }
|
|
71
|
+
return () => { try { flock(fd, 8); } finally { closeSync(fd); } };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The optional root keeps writers with an explicitly selected instance in the same universe. */
|
|
75
|
+
export function recordOutput(output: NewOutput, root?: string): void {
|
|
76
|
+
try {
|
|
77
|
+
const instanceRoot = root ?? effectiveInstanceRoot();
|
|
78
|
+
const file = ledgerPath(instanceRoot, kstMonth(new Date()));
|
|
79
|
+
mkdirSync(join(instanceRoot, 'outputs'), { recursive: true, mode: 0o700 });
|
|
80
|
+
const release = acquireLock(file);
|
|
81
|
+
try {
|
|
82
|
+
const { entries, newline } = readEntries(file);
|
|
83
|
+
if (entries.some(entry => entry.source === output.source && entry.sourceId === output.sourceId
|
|
84
|
+
&& (entry.path ?? entry.url) === (output.path ?? output.url))) return;
|
|
85
|
+
appendFileSync(file, `${newline ? '' : '\n'}${JSON.stringify({ ...output, at: output.at ?? new Date().toISOString() })}\n`, { mode: 0o600 });
|
|
86
|
+
debug.log('outputs.ledger', 'recorded', { source: output.source, kind: output.kind });
|
|
87
|
+
} finally { release(); }
|
|
88
|
+
} catch (error) {
|
|
89
|
+
debug.log('outputs.ledger', 'write-failed', { source: output.source, kind: output.kind, error: String(error) });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function listOutputs({ since, source, limit }: { since?: string; source?: OutputEntry['source']; limit?: number } = {}, root = effectiveInstanceRoot()): OutputEntry[] {
|
|
94
|
+
let months = 0;
|
|
95
|
+
let rows = 0;
|
|
96
|
+
const entries: OutputEntry[] = [];
|
|
97
|
+
const take = (file: string) => {
|
|
98
|
+
try {
|
|
99
|
+
const read = readEntries(file).entries;
|
|
100
|
+
rows += read.length;
|
|
101
|
+
for (const entry of read) {
|
|
102
|
+
if ((!since || entry.at >= since) && (!source || entry.source === source)) entries.push(entry);
|
|
103
|
+
}
|
|
104
|
+
} catch { /* A damaged month does not hide the other months. */ }
|
|
105
|
+
};
|
|
106
|
+
try {
|
|
107
|
+
const dir = join(root, 'outputs');
|
|
108
|
+
const files = readdirSync(dir).filter(name => /^outputs-\d{4}-(0[1-9]|1[0-2])\.jsonl$/.test(name)).sort().reverse();
|
|
109
|
+
// Month names reflect write time, not entry.at; an older file may hold the newest entry.
|
|
110
|
+
for (const name of files) {
|
|
111
|
+
take(join(dir, name));
|
|
112
|
+
months++;
|
|
113
|
+
}
|
|
114
|
+
// The unrotated ledger has no month boundary; its rows can be newer than any monthly row.
|
|
115
|
+
take(join(dir, 'outputs.jsonl'));
|
|
116
|
+
} catch { /* Missing outputs directory. */ }
|
|
117
|
+
entries.sort((a, b) => b.at.localeCompare(a.at));
|
|
118
|
+
debug.log('outputs.ledger', 'rotated-read', { months, rows });
|
|
119
|
+
return entries.slice(0, limit === undefined ? undefined : Math.max(0, limit));
|
|
120
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export interface PersonaTodo {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
status: 'open' | 'done';
|
|
8
|
+
createdAt: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface PersonaTodoReadResult {
|
|
12
|
+
todos: PersonaTodo[];
|
|
13
|
+
errors: { line: number; message: string }[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** One JSONL file per persona. Reject path components in the id. */
|
|
17
|
+
export function personaTodoPath(dir: string, personaId: string): string {
|
|
18
|
+
if (!personaId || personaId === '.' || personaId === '..' || /[/\\]/.test(personaId)) {
|
|
19
|
+
throw new Error('Invalid persona id');
|
|
20
|
+
}
|
|
21
|
+
return join(dir, `${personaId}.todo.jsonl`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isPersonaTodo(value: unknown): value is PersonaTodo {
|
|
25
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
26
|
+
const item = value as Record<string, unknown>;
|
|
27
|
+
return typeof item.id === 'string' && item.id.length > 0
|
|
28
|
+
&& typeof item.title === 'string' && item.title.length > 0
|
|
29
|
+
&& (item.status === 'open' || item.status === 'done')
|
|
30
|
+
&& typeof item.createdAt === 'string'
|
|
31
|
+
&& Number.isFinite(Date.parse(item.createdAt));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Missing files are empty; malformed lines are skipped and returned for observation. */
|
|
35
|
+
export function readPersonaTodos(dir: string, personaId: string): PersonaTodoReadResult {
|
|
36
|
+
const path = personaTodoPath(dir, personaId);
|
|
37
|
+
let text: string;
|
|
38
|
+
try {
|
|
39
|
+
text = readFileSync(path, 'utf8');
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { todos: [], errors: [] };
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const todos: PersonaTodo[] = [];
|
|
46
|
+
const errors: PersonaTodoReadResult['errors'] = [];
|
|
47
|
+
for (const [index, line] of text.split('\n').entries()) {
|
|
48
|
+
if (!line.trim()) continue;
|
|
49
|
+
try {
|
|
50
|
+
const item: unknown = JSON.parse(line);
|
|
51
|
+
if (!isPersonaTodo(item)) throw new Error('Invalid todo record');
|
|
52
|
+
todos.push(item);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
errors.push({ line: index + 1, message: error instanceof Error ? error.message : String(error) });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { todos, errors };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Replace a persona's JSONL file with one valid record per line. */
|
|
61
|
+
export function writePersonaTodos(dir: string, personaId: string, todos: readonly PersonaTodo[]): void {
|
|
62
|
+
const path = personaTodoPath(dir, personaId);
|
|
63
|
+
for (const todo of todos) {
|
|
64
|
+
if (!isPersonaTodo(todo)) throw new Error('Invalid todo record');
|
|
65
|
+
}
|
|
66
|
+
mkdirSync(dir, { recursive: true });
|
|
67
|
+
writeFileSync(path, todos.map(todo => JSON.stringify(todo)).join('\n') + (todos.length ? '\n' : ''), 'utf8');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Earliest created open item; original order breaks equal-timestamp ties. */
|
|
71
|
+
export function nextPersonaTodo(todos: readonly PersonaTodo[]): PersonaTodo | null {
|
|
72
|
+
let oldest: PersonaTodo | null = null;
|
|
73
|
+
for (const todo of todos) {
|
|
74
|
+
if (todo.status === 'open' && (!oldest || Date.parse(todo.createdAt) < Date.parse(oldest.createdAt))) {
|
|
75
|
+
oldest = todo;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return oldest;
|
|
79
|
+
}
|
package/src/persona/presets.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// PS1 — 페르소나 프리셋(MK 문면 · persona-presets/<id>.yaml)을 «내 페르소나»로 만든다.
|
|
2
2
|
// 용어: «페르소나»(대표 10-01) — 사용자가 고르고 꾸미는 AI 동료. ⚠️ `SessionSurfaceId`(이름만 Surface 인 캐릭터 축) ·
|
|
3
|
-
// `modelTier.
|
|
3
|
+
// `modelTier.profile`(모델 등급 프로필 축)와는 «다른 것»이다 — 여기서 만드는 것은 기존 페르소나 저장소(`resolveStatePersonaDir()`,
|
|
4
4
|
// Discord·PWA 가 이미 읽는 `<id>.yaml`)의 한 파일이다. 프리셋 원본은 고치지 않는다.
|
|
5
|
-
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { existsSync, linkSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import { parse, stringify } from 'yaml';
|
|
8
|
+
import { resolveRepositoryPersonaDir, resolveStatePersonaDir } from './global-registry.js';
|
|
9
|
+
import { parsePersonaYaml } from './loader.js';
|
|
8
10
|
|
|
9
11
|
export interface PersonaPreset {
|
|
10
12
|
personaId: string;
|
|
@@ -91,6 +93,125 @@ export function presetToProfileYaml(p: PersonaPreset, common: PresetCommon, opts
|
|
|
91
93
|
return `# 프리셋 «${p.personaId}» 에서 만든 페르소나 — elanous persona add (원본: persona-presets/${p.personaId}.yaml)\n${stringify(profile, { lineWidth: 0 })}`;
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
export interface PersonaWriteOptions { dir?: string; dryRun?: boolean }
|
|
97
|
+
export interface PersonaWriteResult { personaId: string; displayName: string; path: string; yaml: string; dryRun: boolean }
|
|
98
|
+
|
|
99
|
+
// Only fields in the runtime persona schema may be edited; provenance (`preset`) is retained, not editable.
|
|
100
|
+
const PROFILE_KEYS = new Set([
|
|
101
|
+
'personaId', 'displayName', 'description', 'systemPrompt', 'actionHosts', 'offsiteNavigation',
|
|
102
|
+
'brand', 'models', 'mentionPatterns', 'avatarUrl', 'brandColor', 'browserPort', 'residence', 'capabilities',
|
|
103
|
+
]);
|
|
104
|
+
const NESTED_KEYS: Record<string, readonly string[]> = {
|
|
105
|
+
models: ['primary', 'fallback', 'providers'], 'models.providers': ['ollama', 'openai'],
|
|
106
|
+
'models.providers.ollama': ['model'], 'models.providers.openai': ['model'],
|
|
107
|
+
capabilities: ['core', 'extended', 'evidence'],
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
function mapping(value: unknown): value is Record<string, unknown> {
|
|
111
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export class UnknownPersonaSchemaKeyError extends Error {
|
|
115
|
+
constructor(key: string) {
|
|
116
|
+
super(`unknown persona schema key: ${key}`);
|
|
117
|
+
this.name = 'UnknownPersonaSchemaKeyError';
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function assertSchemaKeys(value: Record<string, unknown>, parent = ''): void {
|
|
122
|
+
const allowed: ReadonlySet<string> = parent ? new Set(NESTED_KEYS[parent] ?? []) : PROFILE_KEYS;
|
|
123
|
+
for (const [key, child] of Object.entries(value)) {
|
|
124
|
+
if (!allowed.has(key)) {
|
|
125
|
+
throw new UnknownPersonaSchemaKeyError(`${parent ? `${parent}.` : ''}${key}`);
|
|
126
|
+
}
|
|
127
|
+
const path = parent ? `${parent}.${key}` : key;
|
|
128
|
+
if (NESTED_KEYS[path] && mapping(child)) assertSchemaKeys(child, path);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function storedProfiles(dir: string): Array<{ path: string; data: Record<string, unknown> }> {
|
|
133
|
+
if (!existsSync(dir)) return [];
|
|
134
|
+
return readdirSync(dir).filter((file) => file.endsWith('.yaml') && !file.startsWith('_')).map((file) => {
|
|
135
|
+
const path = join(dir, file);
|
|
136
|
+
const text = readFileSync(path, 'utf8');
|
|
137
|
+
const checked = parsePersonaYaml(text, path);
|
|
138
|
+
if (!checked.ok) throw new Error(`invalid stored persona ${path}: ${checked.error.message}`);
|
|
139
|
+
const data: unknown = parse(text);
|
|
140
|
+
if (!mapping(data)) throw new Error(`invalid stored persona ${path}`);
|
|
141
|
+
return { path, data };
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function normalizedName(name: string): string { return name.trim().toLocaleLowerCase(); }
|
|
146
|
+
function assertUnusedName(name: string, dir: string, except?: string): void {
|
|
147
|
+
if (!name.trim()) throw new Error('persona name is required');
|
|
148
|
+
const all = [...storedProfiles(dir), ...(dir === resolveRepositoryPersonaDir() ? [] : storedProfiles(resolveRepositoryPersonaDir()))];
|
|
149
|
+
if (all.some(({ path, data }) => path !== except &&
|
|
150
|
+
(normalizedName(String(data.displayName)) === normalizedName(name) || normalizedName(String(data.personaId)) === normalizedName(name)))) {
|
|
151
|
+
throw new Error(`persona name already exists: ${name}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function checkedYaml(data: Record<string, unknown>, path: string): { yaml: string; personaId: string; displayName: string } {
|
|
156
|
+
const yaml = stringify(data, { lineWidth: 0 });
|
|
157
|
+
const check = parsePersonaYaml(yaml, path);
|
|
158
|
+
if (!check.ok) throw new Error(`invalid persona: ${check.error.message}`);
|
|
159
|
+
return { yaml, personaId: check.profile.personaId, displayName: check.profile.displayName };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Write beside the destination, then atomically replace it (or install a new file without overwriting). */
|
|
163
|
+
function atomicPersonaWrite(path: string, yaml: string, create: boolean): void {
|
|
164
|
+
const temp = `${path}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
165
|
+
try {
|
|
166
|
+
writeFileSync(temp, yaml, { flag: 'wx' });
|
|
167
|
+
if (create) linkSync(temp, path); // atomic, exclusive: concurrent creations cannot replace a persona
|
|
168
|
+
else renameSync(temp, path);
|
|
169
|
+
} finally {
|
|
170
|
+
if (existsSync(temp)) unlinkSync(temp);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Edit a stored persona by exact id or display name; dry-run validates but never creates a directory or file. */
|
|
175
|
+
export function editPersona(name: string, edits: Record<string, unknown>, opts: PersonaWriteOptions = {}): PersonaWriteResult {
|
|
176
|
+
const dir = opts.dir ?? resolveStatePersonaDir();
|
|
177
|
+
if (!name.trim()) throw new Error('persona name is required');
|
|
178
|
+
if (!mapping(edits)) throw new Error('persona edits must be a mapping');
|
|
179
|
+
assertSchemaKeys(edits);
|
|
180
|
+
const matches = storedProfiles(dir).filter(({ data }) =>
|
|
181
|
+
normalizedName(String(data.personaId)) === normalizedName(name) || normalizedName(String(data.displayName)) === normalizedName(name));
|
|
182
|
+
if (matches.length !== 1) throw new Error(matches.length ? `ambiguous persona name: ${name}` : `persona not found: ${name}`);
|
|
183
|
+
const { path, data } = matches[0]!;
|
|
184
|
+
if (edits.personaId !== undefined && edits.personaId !== data.personaId) throw new Error('personaId cannot be changed');
|
|
185
|
+
const updated = { ...data, ...edits };
|
|
186
|
+
const result = checkedYaml(updated, path);
|
|
187
|
+
assertUnusedName(result.displayName, dir, path);
|
|
188
|
+
if (!opts.dryRun) atomicPersonaWrite(path, result.yaml, false);
|
|
189
|
+
return { ...result, path, dryRun: Boolean(opts.dryRun) };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Clone a shipped preset into the state persona store under a new, unique display name. */
|
|
193
|
+
export function clonePreset(preset: PersonaPreset | string, name: string, opts: PersonaWriteOptions = {}): PersonaWriteResult {
|
|
194
|
+
const source = typeof preset === 'string' ? findPreset(loadPresets(), preset) : preset;
|
|
195
|
+
if (!source) throw new Error(`preset not found: ${preset}`);
|
|
196
|
+
const dir = opts.dir ?? resolveStatePersonaDir();
|
|
197
|
+
assertUnusedName(name, dir);
|
|
198
|
+
const taken = new Set([...storedProfiles(dir), ...storedProfiles(resolveRepositoryPersonaDir())]
|
|
199
|
+
.map(({ data }) => String(data.personaId)));
|
|
200
|
+
const personaId = choosePersonaId(source, name, taken);
|
|
201
|
+
const path = join(dir, `${personaId}.yaml`);
|
|
202
|
+
if (existsSync(path)) throw new Error(`persona already exists: ${personaId}`);
|
|
203
|
+
const generated = presetToProfileYaml(source, loadPresetIndex().common, { personaId, name });
|
|
204
|
+
const raw: unknown = parse(generated);
|
|
205
|
+
if (!mapping(raw)) throw new Error('invalid generated persona');
|
|
206
|
+
const result = checkedYaml(raw, path);
|
|
207
|
+
assertUnusedName(result.displayName, dir);
|
|
208
|
+
if (!opts.dryRun) {
|
|
209
|
+
mkdirSync(dir, { recursive: true });
|
|
210
|
+
atomicPersonaWrite(path, generated, true);
|
|
211
|
+
}
|
|
212
|
+
return { ...result, yaml: generated, path, dryRun: Boolean(opts.dryRun) };
|
|
213
|
+
}
|
|
214
|
+
|
|
94
215
|
/** Short block for `persona show` and `persona list`. */
|
|
95
216
|
export function describePreset(p: PersonaPreset): string[] {
|
|
96
217
|
return [
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmdirSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { parse, stringify } from 'yaml';
|
|
4
|
+
import { resolveRepositoryPersonaDir, resolveStatePersonaDir } from './global-registry.js';
|
|
5
|
+
import { parsePersonaYaml } from './loader.js';
|
|
6
|
+
|
|
7
|
+
interface Member { persona: string; title: string; checklistTemplate: string; rules: string[] }
|
|
8
|
+
interface Handoff { from: string; to: string; when: string }
|
|
9
|
+
export interface TeamSet { 'team-set': string; members: Member[]; handoffs: Handoff[] }
|
|
10
|
+
const idPattern = /^[a-z0-9_][a-z0-9_-]*$/i;
|
|
11
|
+
const namePattern = /^[\p{L}\p{N}_][\p{L}\p{N} _-]*$/u;
|
|
12
|
+
const mapping = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
13
|
+
const nonempty = (value: unknown): value is string => typeof value === 'string' && value.trim().length > 0;
|
|
14
|
+
|
|
15
|
+
function readDefinition(text: string): TeamSet {
|
|
16
|
+
const raw: unknown = parse(text);
|
|
17
|
+
if (!mapping(raw) || !nonempty(raw['team-set']) || !namePattern.test(raw['team-set'])) {
|
|
18
|
+
throw new Error('team-set must be a non-empty filesystem-safe name');
|
|
19
|
+
}
|
|
20
|
+
if (!Array.isArray(raw.members) || raw.members.length === 0) throw new Error('members must be a non-empty list');
|
|
21
|
+
const members: Member[] = raw.members.map((item: unknown) => {
|
|
22
|
+
if (!mapping(item) || !nonempty(item.persona) || !idPattern.test(item.persona)
|
|
23
|
+
|| !nonempty(item.title) || !nonempty(item.checklistTemplate)
|
|
24
|
+
|| !(nonempty(item.rules) || (Array.isArray(item.rules) && item.rules.length > 0 && item.rules.every(nonempty)))) {
|
|
25
|
+
throw new Error('each member requires persona (safe id), title, checklistTemplate and rules');
|
|
26
|
+
}
|
|
27
|
+
return { persona: item.persona, title: item.title, checklistTemplate: item.checklistTemplate,
|
|
28
|
+
rules: typeof item.rules === 'string' ? [item.rules] : item.rules as string[] };
|
|
29
|
+
});
|
|
30
|
+
const ids = new Set(members.map((member) => member.persona));
|
|
31
|
+
if (ids.size !== members.length) throw new Error('duplicate team-set member persona');
|
|
32
|
+
if (!Array.isArray(raw.handoffs)) throw new Error('handoffs must be a list');
|
|
33
|
+
const handoffs: Handoff[] = raw.handoffs.map((item: unknown) => {
|
|
34
|
+
if (!mapping(item) || !nonempty(item.from) || !nonempty(item.to) || !nonempty(item.when)) {
|
|
35
|
+
throw new Error('each handoff requires from, to and when');
|
|
36
|
+
}
|
|
37
|
+
if (!ids.has(item.from) || !ids.has(item.to)) throw new Error(`handoff references missing member: ${item.from} -> ${item.to}`);
|
|
38
|
+
return { from: item.from, to: item.to, when: item.when };
|
|
39
|
+
});
|
|
40
|
+
const edges = new Map(members.map((member) => [member.persona, [] as string[]]));
|
|
41
|
+
for (const edge of handoffs) edges.get(edge.from)!.push(edge.to);
|
|
42
|
+
const visiting = new Set<string>();
|
|
43
|
+
const visited = new Set<string>();
|
|
44
|
+
const visit = (id: string): void => {
|
|
45
|
+
if (visiting.has(id)) throw new Error(`handoff cycle at ${id}`);
|
|
46
|
+
if (visited.has(id)) return;
|
|
47
|
+
visiting.add(id);
|
|
48
|
+
for (const next of edges.get(id)!) visit(next);
|
|
49
|
+
visiting.delete(id);
|
|
50
|
+
visited.add(id);
|
|
51
|
+
};
|
|
52
|
+
for (const id of ids) visit(id);
|
|
53
|
+
return { 'team-set': raw['team-set'], members, handoffs };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function store(dir: string): string { return join(dir, '_team-sets'); }
|
|
57
|
+
function record(dir: string, name: string): string { return join(store(dir), `${name}.yaml`); }
|
|
58
|
+
function memberPath(dir: string, id: string): string { return join(dir, `${id}.yaml`); }
|
|
59
|
+
|
|
60
|
+
function recordedOwners(dir: string): Map<string, string> {
|
|
61
|
+
const owners = new Map<string, string>();
|
|
62
|
+
if (!existsSync(store(dir))) return owners;
|
|
63
|
+
for (const file of readdirSync(store(dir)).filter((name) => name.endsWith('.yaml'))) {
|
|
64
|
+
const data: unknown = parse(readFileSync(join(store(dir), file), 'utf8'));
|
|
65
|
+
if (!mapping(data) || !mapping(data.definition)) throw new Error(`invalid installed team-set: ${file}`);
|
|
66
|
+
const definition = readDefinition(stringify(data.definition));
|
|
67
|
+
const name = file.slice(0, -'.yaml'.length);
|
|
68
|
+
if (definition['team-set'] !== name) throw new Error(`invalid installed team-set: ${file}`);
|
|
69
|
+
for (const member of definition.members) {
|
|
70
|
+
const previous = owners.get(member.persona);
|
|
71
|
+
if (previous !== undefined) throw new Error(`persona has multiple team-set owners: ${member.persona} (${previous}, ${name})`);
|
|
72
|
+
owners.set(member.persona, name);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return owners;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function occupiedIds(dir: string): Set<string> {
|
|
79
|
+
const ids = new Set<string>();
|
|
80
|
+
if (!existsSync(dir)) return ids;
|
|
81
|
+
for (const file of readdirSync(dir).filter((name) => /\.ya?ml$/.test(name))) {
|
|
82
|
+
ids.add(file.replace(/\.ya?ml$/, ''));
|
|
83
|
+
const path = join(dir, file);
|
|
84
|
+
const parsed = parsePersonaYaml(readFileSync(path, 'utf8'), path);
|
|
85
|
+
if (parsed.ok) ids.add(parsed.profile.personaId);
|
|
86
|
+
}
|
|
87
|
+
return ids;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function addTeamSet(file: string, dir = resolveStatePersonaDir()): TeamSet {
|
|
91
|
+
const definition = readDefinition(readFileSync(file, 'utf8'));
|
|
92
|
+
if (existsSync(record(dir, definition['team-set']))) throw new Error(`team-set already exists: ${definition['team-set']}`);
|
|
93
|
+
const occupied = new Set([...occupiedIds(dir), ...occupiedIds(resolveRepositoryPersonaDir()), ...recordedOwners(dir).keys()]);
|
|
94
|
+
for (const member of definition.members) {
|
|
95
|
+
if (occupied.has(member.persona) || existsSync(memberPath(dir, member.persona))) throw new Error(`persona already exists: ${member.persona}`);
|
|
96
|
+
}
|
|
97
|
+
const files = definition.members.map((member) => {
|
|
98
|
+
const yaml = stringify({ personaId: member.persona, displayName: `${member.persona} · ${member.title}`,
|
|
99
|
+
systemPrompt: [`직함: ${member.title}`, `체크리스트 템플릿: ${member.checklistTemplate}`, '규칙:', ...member.rules.map((rule) => `- ${rule}`)].join('\n') }, { lineWidth: 0 });
|
|
100
|
+
const check = parsePersonaYaml(yaml, memberPath(dir, member.persona));
|
|
101
|
+
if (!check.ok) throw new Error(check.error.message);
|
|
102
|
+
return { persona: member.persona, yaml };
|
|
103
|
+
});
|
|
104
|
+
mkdirSync(dir, { recursive: true });
|
|
105
|
+
mkdirSync(store(dir), { recursive: true });
|
|
106
|
+
const created: string[] = [];
|
|
107
|
+
try {
|
|
108
|
+
for (const entry of files) {
|
|
109
|
+
const path = memberPath(dir, entry.persona);
|
|
110
|
+
writeFileSync(path, entry.yaml, { flag: 'wx' });
|
|
111
|
+
created.push(path);
|
|
112
|
+
}
|
|
113
|
+
writeFileSync(record(dir, definition['team-set']), stringify({ definition, files }, { lineWidth: 0 }), { flag: 'wx' });
|
|
114
|
+
} catch (error) {
|
|
115
|
+
for (const path of created) unlinkSync(path);
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
return definition;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function listTeamSets(dir = resolveStatePersonaDir()): TeamSet[] {
|
|
122
|
+
if (!existsSync(store(dir))) return [];
|
|
123
|
+
return readdirSync(store(dir)).filter((name) => name.endsWith('.yaml')).sort().map((name) => {
|
|
124
|
+
const data: unknown = parse(readFileSync(join(store(dir), name), 'utf8'));
|
|
125
|
+
if (!mapping(data) || !mapping(data.definition)) throw new Error(`invalid installed team-set: ${name}`);
|
|
126
|
+
return readDefinition(stringify(data.definition));
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function removeTeamSet(name: string, dir = resolveStatePersonaDir()): void {
|
|
131
|
+
if (!namePattern.test(name)) throw new Error('invalid team-set name');
|
|
132
|
+
const path = record(dir, name);
|
|
133
|
+
if (!existsSync(path)) throw new Error(`team-set not found: ${name}`);
|
|
134
|
+
const data: unknown = parse(readFileSync(path, 'utf8'));
|
|
135
|
+
if (!mapping(data) || !mapping(data.definition) || !Array.isArray(data.files)) throw new Error(`invalid installed team-set: ${name}`);
|
|
136
|
+
const definition = readDefinition(stringify(data.definition));
|
|
137
|
+
if (definition['team-set'] !== name || data.files.length !== definition.members.length) throw new Error(`invalid installed team-set: ${name}`);
|
|
138
|
+
const owners = recordedOwners(dir);
|
|
139
|
+
const filesToRemove: string[] = [];
|
|
140
|
+
for (let index = 0; index < definition.members.length; index++) {
|
|
141
|
+
const file: unknown = data.files[index];
|
|
142
|
+
const id = definition.members[index]!.persona;
|
|
143
|
+
if (!mapping(file) || file.persona !== id || typeof file.yaml !== 'string') {
|
|
144
|
+
throw new Error(`invalid installed team-set: ${name}`);
|
|
145
|
+
}
|
|
146
|
+
if (owners.get(id) !== name) throw new Error(`persona is not owned by team-set ${name}: ${id}`);
|
|
147
|
+
const personaPath = memberPath(dir, id);
|
|
148
|
+
if (existsSync(personaPath) && readFileSync(personaPath, 'utf8') === file.yaml) {
|
|
149
|
+
filesToRemove.push(personaPath);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (const personaPath of filesToRemove) unlinkSync(personaPath);
|
|
153
|
+
unlinkSync(path);
|
|
154
|
+
if (readdirSync(store(dir)).length === 0) rmdirSync(store(dir));
|
|
155
|
+
}
|
|
@@ -314,6 +314,20 @@ export async function installPlugin(spec: string, opts: InstallOptions = {}): Pr
|
|
|
314
314
|
const source = marketSpec(spec, opts)
|
|
315
315
|
? await fetchedMarketSource(spec, join(stage, 'package'), opts)
|
|
316
316
|
: resolvePluginSource(spec, join(stage, 'package'), opts);
|
|
317
|
+
const stagedManifest = join(stage, 'package', 'plugin.json');
|
|
318
|
+
if (existsSync(stagedManifest)) {
|
|
319
|
+
const rawManifest: unknown = JSON.parse(readFileSync(stagedManifest, 'utf8'));
|
|
320
|
+
if (rawManifest && typeof rawManifest === 'object' && !Array.isArray(rawManifest)) {
|
|
321
|
+
const rawExtensions = (rawManifest as Record<string, unknown>).extensions;
|
|
322
|
+
if (rawExtensions && typeof rawExtensions === 'object' && !Array.isArray(rawExtensions)) {
|
|
323
|
+
const elanous = (rawExtensions as Record<string, unknown>)['ai.elanous'];
|
|
324
|
+
if (elanous && typeof elanous === 'object' && !Array.isArray(elanous) &&
|
|
325
|
+
(elanous as Record<string, unknown>).researchDraft === true) {
|
|
326
|
+
fail('io', 'research draft only: implement and validate graph steps before installation');
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
317
331
|
const manifest: PluginManifest = loadPluginManifestFromDir(join(stage, 'package'), { id: source.expectedName ?? basename(spec) }).manifest;
|
|
318
332
|
if (!NAME.test(manifest.id) || !VERSION.test(manifest.version)) fail('io', 'invalid plugin name or version');
|
|
319
333
|
if (source.expectedName && (manifest.id !== source.expectedName || manifest.version !== source.expectedVersion)) fail('conflict', 'market name or version differs from manifest');
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { createHash, createPublicKey } from 'node:crypto';
|
|
2
|
+
import { signIndex, validateMarketplaceIndex, verifyIndex, type MarketplaceIndex } from '../../market/signed-index.js';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { dirname, join, resolve, sep } from 'node:path';
|
|
6
|
+
import { userConfigPath } from '../../user-config.js';
|
|
7
|
+
import { loadPluginManifestFromDir } from '../core/manifest.js';
|
|
8
|
+
import { LEAK_MARKERS, loadPrivateRedactions, privateIdentifierMarkers, scanLeaks } from '../../../scripts/public-export.js';
|
|
9
|
+
|
|
10
|
+
export interface MarketBundleResult {
|
|
11
|
+
path: string;
|
|
12
|
+
artifact: string;
|
|
13
|
+
signature: 'signed' | 'signing-required';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function packageFiles(dir: string, prefix = ''): string[] {
|
|
17
|
+
const files: string[] = [];
|
|
18
|
+
for (const entry of readdirSync(join(dir, prefix), { withFileTypes: true })) {
|
|
19
|
+
const path = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
20
|
+
if (entry.name === '.git' || entry.name === '.elanous' || entry.name === '.elanous-local' ||
|
|
21
|
+
entry.name === '.elanous-test' || entry.name === 'graph-runs' || entry.name === 'node_modules') continue;
|
|
22
|
+
if (entry.isSymbolicLink() || (!entry.isFile() && !entry.isDirectory())) throw new Error(`unsafe market bundle entry: ${path}`);
|
|
23
|
+
if (entry.isDirectory()) files.push(...packageFiles(dir, path));
|
|
24
|
+
else files.push(path);
|
|
25
|
+
}
|
|
26
|
+
return files;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Prepare a local, reviewable official-market index and tar artifact; never publishes either file. */
|
|
30
|
+
export function bundleInstalledWizardPlugin(installedPath: string, outputDir: string, configPath = userConfigPath()): MarketBundleResult {
|
|
31
|
+
const source = resolve(installedPath);
|
|
32
|
+
const manifestFile = join(source, 'plugin.json');
|
|
33
|
+
if (!lstatSync(source).isDirectory() || !lstatSync(manifestFile).isFile() || lstatSync(manifestFile).isSymbolicLink()) {
|
|
34
|
+
throw new Error('market bundle requires an installed plugin.json');
|
|
35
|
+
}
|
|
36
|
+
const raw: unknown = JSON.parse(readFileSync(manifestFile, 'utf8'));
|
|
37
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw) || typeof (raw as { name?: unknown }).name !== 'string') {
|
|
38
|
+
throw new Error('invalid market plugin manifest name');
|
|
39
|
+
}
|
|
40
|
+
const { manifest, inferred } = loadPluginManifestFromDir(source, { id: (raw as { name: string }).name });
|
|
41
|
+
if (inferred || !/^[a-z0-9][a-z0-9-]{1,39}$/.test(manifest.id) ||
|
|
42
|
+
!/^[0-9]+\.[0-9]+\.[0-9]+(?:[-+][a-zA-Z0-9.-]+)?$/.test(manifest.version)) {
|
|
43
|
+
throw new Error('invalid market plugin manifest name or version');
|
|
44
|
+
}
|
|
45
|
+
if ((raw as { extensions?: { 'ai.elanous'?: { researchDraft?: boolean } } }).extensions?.['ai.elanous']?.researchDraft === true) {
|
|
46
|
+
throw new Error('research draft cannot be bundled for market');
|
|
47
|
+
}
|
|
48
|
+
const files = packageFiles(source);
|
|
49
|
+
if (!files.length) throw new Error('empty plugin bundle');
|
|
50
|
+
if (manifest.id !== (raw as { name: string }).name) throw new Error('market plugin manifest name mismatch');
|
|
51
|
+
for (const asset of [manifest.main, ...(manifest.contributes.graphs ?? []), ...(manifest.contributes.vocab ?? []), ...(manifest.contributes.nodes ?? [])]) {
|
|
52
|
+
if (asset === './plugin.ts' && !('main' in (raw as object))) continue;
|
|
53
|
+
if (!files.includes(asset.replace(/^\.\//, ''))) throw new Error(`market manifest asset missing: ${asset}`);
|
|
54
|
+
}
|
|
55
|
+
const redactions = loadPrivateRedactions();
|
|
56
|
+
if (!redactions) throw new Error('private export redaction list unavailable; market bundle refused');
|
|
57
|
+
const hits = scanLeaks(source, files, [...LEAK_MARKERS, ...privateIdentifierMarkers(redactions)]);
|
|
58
|
+
if (hits.length) throw new Error(`market bundle leak: ${hits.map(hit => `${hit.file}:${hit.line} ${hit.marker}`).join(', ')}`);
|
|
59
|
+
const config: unknown = existsSync(configPath) ? JSON.parse(readFileSync(configPath, 'utf8')) : {};
|
|
60
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) throw new Error('invalid market signing config');
|
|
61
|
+
const signing = (config as { market?: { signing?: { keyId?: unknown; privateKey?: unknown } } }).market?.signing;
|
|
62
|
+
const hasKey = signing?.privateKey !== undefined || signing?.keyId !== undefined;
|
|
63
|
+
if (hasKey && (typeof signing?.privateKey !== 'string' || !signing.privateKey.trim() || typeof signing.keyId !== 'string' || !signing.keyId.trim())) {
|
|
64
|
+
throw new Error('market signing requires configured keyId and privateKey');
|
|
65
|
+
}
|
|
66
|
+
const target = resolve(outputDir);
|
|
67
|
+
if (target === source || target.startsWith(`${source}${sep}`) || source.startsWith(`${target}${sep}`)) throw new Error('market bundle output must be outside plugin directory');
|
|
68
|
+
if (existsSync(target)) throw new Error(`market bundle already exists: ${target}`);
|
|
69
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
70
|
+
mkdirSync(target);
|
|
71
|
+
try {
|
|
72
|
+
const artifactKey = `${manifest.id}-${manifest.version}.tgz`;
|
|
73
|
+
const artifact = join(target, artifactKey);
|
|
74
|
+
const tar = spawnSync('tar', ['-czf', artifact, '-C', source, '--', ...files], { encoding: 'utf8' });
|
|
75
|
+
if (tar.status !== 0 || tar.error) throw new Error(`market archive failed: ${tar.error?.message ?? tar.stderr}`);
|
|
76
|
+
const bytes = readFileSync(artifact);
|
|
77
|
+
const index: MarketplaceIndex = { name: 'elanous', interface: { displayName: 'Elanous' }, sequence: 1, plugins: [{
|
|
78
|
+
name: manifest.id, version: manifest.version, description: manifest.description,
|
|
79
|
+
source: { source: 'local', path: '.' },
|
|
80
|
+
artifact: { key: artifactKey, sha256: createHash('sha256').update(bytes).digest('hex'), bytes: bytes.length },
|
|
81
|
+
'ai.elanous': {
|
|
82
|
+
capabilities: manifest.capabilities.map(capability => capability.kind),
|
|
83
|
+
connectors: (manifest.contributes.connectors ?? []).map(connector => ({
|
|
84
|
+
id: connector.id, kind: 'user-config', userConfig: (connector.userConfig ?? connector.fields?.map(field => ({
|
|
85
|
+
key: field.name, label: field.name, secret: field.secret === true,
|
|
86
|
+
})) ?? []).map(field => ({ key: field.key, label: field.label ?? field.key, secret: field.secret === true })),
|
|
87
|
+
})),
|
|
88
|
+
graphs: manifest.contributes.graphs ?? [], vocab: manifest.contributes.vocab ?? [],
|
|
89
|
+
pricing: { model: 'free' },
|
|
90
|
+
},
|
|
91
|
+
}] };
|
|
92
|
+
const invalid = validateMarketplaceIndex(index);
|
|
93
|
+
if (invalid) throw new Error(`invalid market index: ${invalid}`);
|
|
94
|
+
const indexBytes = Buffer.from(JSON.stringify(index, null, 2) + '\n');
|
|
95
|
+
writeFileSync(join(target, 'marketplace.json'), indexBytes);
|
|
96
|
+
if (hasKey) {
|
|
97
|
+
const signatureText = signIndex(indexBytes, signing!.privateKey as string, signing!.keyId as string);
|
|
98
|
+
const check = verifyIndex({ marketplaceBytes: indexBytes, signatureText, trustedKeys: [{
|
|
99
|
+
keyId: signing!.keyId as string,
|
|
100
|
+
publicKey: createPublicKey(signing!.privateKey as string).export({ format: 'der', type: 'spki' }).subarray(12).toString('base64'),
|
|
101
|
+
}] });
|
|
102
|
+
if (!check.ok) throw new Error(`market index signature invalid: ${check.detail}`);
|
|
103
|
+
writeFileSync(join(target, 'index.sig'), signatureText);
|
|
104
|
+
}
|
|
105
|
+
return { path: target, artifact, signature: hasKey ? 'signed' : 'signing-required' };
|
|
106
|
+
} catch (error) {
|
|
107
|
+
rmSync(target, { recursive: true, force: true });
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
}
|