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
package/src/nexus/index.ts
CHANGED
|
@@ -1949,10 +1949,12 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
|
|
|
1949
1949
|
const tab = registry.get(tabId);
|
|
1950
1950
|
if (!tab || tab.spec.kind !== 'webterm') return;
|
|
1951
1951
|
const cwdMeta = (tab.spec.meta as { cwd?: string } | undefined)?.cwd;
|
|
1952
|
+
const projectSessionId = (tab.spec.meta as { sessionId?: string } | undefined)?.sessionId;
|
|
1952
1953
|
const backendFactory = effectiveWebtermSpawn
|
|
1953
1954
|
? () => {
|
|
1954
1955
|
const decided = resolveWebtermCwd({
|
|
1955
1956
|
...(cwdMeta ? { tabCwd: cwdMeta } : {}),
|
|
1957
|
+
...(projectSessionId ? { sessionId: projectSessionId } : {}),
|
|
1956
1958
|
...(runtimeToolCwd ? { toolCwd: runtimeToolCwd } : {}),
|
|
1957
1959
|
instance: resolveCurrentInstance(),
|
|
1958
1960
|
processCwd: process.cwd(),
|
|
@@ -24,6 +24,7 @@ export interface WebtermTabOpts {
|
|
|
24
24
|
id?: string; // default: 'webterm:1'
|
|
25
25
|
label?: string;
|
|
26
26
|
cwd?: string;
|
|
27
|
+
sessionId?: string;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
export const WEBTERM_KIND: TabKind = 'webterm';
|
|
@@ -34,7 +35,7 @@ export function createWebtermTabSpec(opts: WebtermTabOpts = {}): TabSpec {
|
|
|
34
35
|
id,
|
|
35
36
|
kind: WEBTERM_KIND,
|
|
36
37
|
label: opts.label ?? id,
|
|
37
|
-
...(opts.cwd ? { meta: { cwd: opts.cwd } } : {}),
|
|
38
|
+
...(opts.cwd || opts.sessionId ? { meta: { ...(opts.cwd ? { cwd: opts.cwd } : {}), ...(opts.sessionId ? { sessionId: opts.sessionId } : {}) } } : {}),
|
|
38
39
|
};
|
|
39
40
|
}
|
|
40
41
|
|
package/src/nexus/webterm/pty.ts
CHANGED
|
@@ -31,6 +31,7 @@ import { startPty as realStartPty, onPtyEvent as realOnPtyEvent } from '../../pt
|
|
|
31
31
|
import type { StartOpts, PtyHandle, PtyEvent } from '../../pty-shell/registry.js';
|
|
32
32
|
|
|
33
33
|
import { debug } from '../../debug/log.js';
|
|
34
|
+
import { resolveSessionProjectContext } from '../../project/session-context.js';
|
|
34
35
|
|
|
35
36
|
// ── PtyBackend interface ─────────────────────────────────────────────
|
|
36
37
|
|
|
@@ -229,15 +230,18 @@ export function createRegistryBackend(
|
|
|
229
230
|
}
|
|
230
231
|
|
|
231
232
|
/** 웹 터미널 셸이 열릴 작업 디렉토리를 정한다(실행 맥락 SSOT: 맥락은 «한 번» 정해지고 인자로 내려간다).
|
|
232
|
-
* 순서: 탭이 명시한 cwd →
|
|
233
|
+
* 순서: 탭이 명시한 cwd → 대화 프로젝트 폴더 → 데몬 tool cwd → (격리 우주면) 우주 뿌리 → 프로세스 cwd.
|
|
233
234
|
* ⛔ 격리(test) 우주에서 `process.cwd()` 로 떨어지면 셸이 «사람 작업 트리»에서 열린다(🅕 실측 2026-09-26). */
|
|
234
235
|
export function resolveWebtermCwd(input: {
|
|
235
236
|
tabCwd?: string;
|
|
237
|
+
sessionId?: string;
|
|
236
238
|
toolCwd?: string;
|
|
237
239
|
instance: { kind: 'prod' | 'test'; root: string };
|
|
238
240
|
processCwd: string;
|
|
239
|
-
}): { cwd: string; source: 'tab' | 'tool-cwd' | 'isolated-root' | 'process-cwd' } {
|
|
241
|
+
}): { cwd: string; source: 'tab' | 'project' | 'tool-cwd' | 'isolated-root' | 'process-cwd' } {
|
|
240
242
|
if (input.tabCwd) return { cwd: input.tabCwd, source: 'tab' };
|
|
243
|
+
const projectCwd = resolveSessionProjectContext(input.sessionId)?.cwd;
|
|
244
|
+
if (projectCwd) return { cwd: projectCwd, source: 'project' };
|
|
241
245
|
if (input.toolCwd) return { cwd: input.toolCwd, source: 'tool-cwd' };
|
|
242
246
|
if (input.instance.kind === 'test') return { cwd: input.instance.root, source: 'isolated-root' };
|
|
243
247
|
return { cwd: input.processCwd, source: 'process-cwd' };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { accessSync, constants, statSync } from 'node:fs';
|
|
3
|
+
import { delimiter, join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
export type SkillCli = 'gh' | 'gws' | 'op' | 'himalaya';
|
|
6
|
+
|
|
7
|
+
export interface CliAuthStatus {
|
|
8
|
+
cli: SkillCli;
|
|
9
|
+
found: boolean;
|
|
10
|
+
authenticated: boolean;
|
|
11
|
+
skippedReason?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface CliAuthStatusDeps {
|
|
15
|
+
path?: string;
|
|
16
|
+
findExecutable?: (name: SkillCli, path: string) => string | null;
|
|
17
|
+
runStatus?: (executable: string, args: readonly string[]) => { status: number | null; stdout?: string; error?: unknown };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const STATUS_COMMANDS: readonly { cli: SkillCli; args: readonly string[] }[] = [
|
|
21
|
+
{ cli: 'gh', args: ['auth', 'status'] },
|
|
22
|
+
{ cli: 'gws', args: ['auth', 'status'] },
|
|
23
|
+
{ cli: 'op', args: ['whoami'] },
|
|
24
|
+
{ cli: 'himalaya', args: ['account', 'check'] },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
function findOnPath(name: SkillCli, path: string): string | null {
|
|
28
|
+
for (const directory of path.split(delimiter).filter(Boolean)) {
|
|
29
|
+
const executable = join(directory, name);
|
|
30
|
+
try {
|
|
31
|
+
accessSync(executable, constants.X_OK);
|
|
32
|
+
if (statSync(executable).isFile()) return executable;
|
|
33
|
+
} catch { /* Try the next PATH entry. */ }
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function runReadOnlyStatus(executable: string, args: readonly string[]) {
|
|
39
|
+
const result = spawnSync(executable, [...args], {
|
|
40
|
+
encoding: 'utf8', timeout: 5_000, maxBuffer: 64 * 1024,
|
|
41
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
42
|
+
});
|
|
43
|
+
return { status: result.status, stdout: result.stdout, error: result.error };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function gwsHasSession(stdout: string | undefined): boolean {
|
|
47
|
+
try {
|
|
48
|
+
const status: unknown = JSON.parse(stdout ?? '');
|
|
49
|
+
if (!status || typeof status !== 'object' || Array.isArray(status)) return false;
|
|
50
|
+
const auth = status as Record<string, unknown>;
|
|
51
|
+
return auth.token_valid === true;
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** No credential-file reads, login calls, prompts, or token-bearing output leave this probe. */
|
|
58
|
+
export function detectSkillCliAuth(deps: CliAuthStatusDeps = {}): CliAuthStatus[] {
|
|
59
|
+
const path = deps.path ?? process.env.PATH ?? '';
|
|
60
|
+
const find = deps.findExecutable ?? findOnPath;
|
|
61
|
+
const run = deps.runStatus ?? runReadOnlyStatus;
|
|
62
|
+
return STATUS_COMMANDS.map(({ cli, args }) => {
|
|
63
|
+
let executable: string | null;
|
|
64
|
+
try { executable = find(cli, path); }
|
|
65
|
+
catch { executable = null; }
|
|
66
|
+
if (!executable) return { cli, found: false, authenticated: false, skippedReason: 'not on PATH' };
|
|
67
|
+
try {
|
|
68
|
+
const result = run(executable, args);
|
|
69
|
+
if (result.error || result.status === null) {
|
|
70
|
+
return { cli, found: true, authenticated: false, skippedReason: 'status check unavailable' };
|
|
71
|
+
}
|
|
72
|
+
if (result.status !== 0 || (cli === 'gws' && !gwsHasSession(result.stdout))) {
|
|
73
|
+
return { cli, found: true, authenticated: false, skippedReason: 'not authenticated' };
|
|
74
|
+
}
|
|
75
|
+
return { cli, found: true, authenticated: true };
|
|
76
|
+
} catch {
|
|
77
|
+
return { cli, found: true, authenticated: false, skippedReason: 'status check unavailable' };
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
@@ -154,12 +154,12 @@ export async function askVoiceAI(
|
|
|
154
154
|
|
|
155
155
|
if (mode === 'preset') {
|
|
156
156
|
// Phase 2 catalog isn't shipped yet; the wizard records the intent
|
|
157
|
-
// by keeping the user on Smart defaults but tagging
|
|
157
|
+
// by keeping the user on Smart defaults but tagging profile='power'
|
|
158
158
|
// so the auto-suggest hint in PWA opens the preset card directly.
|
|
159
159
|
io.print(' (Preset catalog ships in Phase 2 · for now Smart defaults stay active.)');
|
|
160
160
|
io.print(' elanous has noted your preference — preset hints will appear when ready.');
|
|
161
161
|
return {
|
|
162
|
-
modelTier: {
|
|
162
|
+
modelTier: { profile: 'power' },
|
|
163
163
|
};
|
|
164
164
|
}
|
|
165
165
|
|
|
@@ -175,7 +175,7 @@ export async function askVoiceAI(
|
|
|
175
175
|
|
|
176
176
|
const answer: VoiceAIAnswer = {
|
|
177
177
|
modelTier: {
|
|
178
|
-
|
|
178
|
+
profile: 'custom',
|
|
179
179
|
voice: { stt: tier as ModelTier },
|
|
180
180
|
},
|
|
181
181
|
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { getUserConfig, type UserConfig } from '../user-config.js';
|
|
2
|
+
import type { SeatEntry } from '../seat-loop/seat-loop.js';
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_DAILY_GOALS = 6;
|
|
5
|
+
export const DEFAULT_CONCURRENT_PODS = 2;
|
|
6
|
+
|
|
7
|
+
export type SeatBudgetResult = { allowed: true } | { allowed: false; reason: string };
|
|
8
|
+
export type RunningSeatPod = { seat: string; substrate?: string; status?: string };
|
|
9
|
+
export type SeatBudgetInput = {
|
|
10
|
+
seat: string;
|
|
11
|
+
now: Date;
|
|
12
|
+
ledger: readonly SeatEntry[];
|
|
13
|
+
running: readonly RunningSeatPod[];
|
|
14
|
+
config?: Pick<UserConfig, 'org'>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function limit(value: unknown, fallback: number): number {
|
|
18
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : fallback;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function day(date: Date): string {
|
|
22
|
+
return new Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Seoul', year: 'numeric', month: '2-digit', day: '2-digit' }).format(date);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** A read-only seat quota judgment; the caller owns the launch and ledger write. */
|
|
26
|
+
export function checkSeatBudget({ seat, now, ledger, running, config }: SeatBudgetInput): SeatBudgetResult {
|
|
27
|
+
const override = (config ?? getUserConfig()).org?.budget?.[seat];
|
|
28
|
+
const dailyGoals = limit(override?.dailyGoals, DEFAULT_DAILY_GOALS);
|
|
29
|
+
const concurrentPods = limit(override?.concurrentPods, DEFAULT_CONCURRENT_PODS);
|
|
30
|
+
const today = day(now);
|
|
31
|
+
const launchedToday = ledger.filter((entry) => entry.seat === seat && entry.status === 'launched'
|
|
32
|
+
&& Number.isFinite(Date.parse(entry.ts ?? entry.at))
|
|
33
|
+
&& day(new Date(entry.ts ?? entry.at)) === today).length;
|
|
34
|
+
if (launchedToday >= dailyGoals) {
|
|
35
|
+
return { allowed: false, reason: `${seat} daily goals budget reached (${launchedToday}/${dailyGoals})` };
|
|
36
|
+
}
|
|
37
|
+
const active = running.filter((run) => run.seat === seat && (run.status === undefined || run.status === 'running' || run.status === 'unknown'));
|
|
38
|
+
// Confirmed = a Pod known to be running; an unknown substrate OR unknown liveness only counts as «cannot verify».
|
|
39
|
+
const pods = active.filter((run) => run.substrate === 'pod' && run.status !== 'unknown').length;
|
|
40
|
+
if (pods >= concurrentPods) {
|
|
41
|
+
return { allowed: false, reason: `${seat} concurrent Pods budget reached (${pods}/${concurrentPods})` };
|
|
42
|
+
}
|
|
43
|
+
const unknown = active.filter((run) => run.substrate === undefined || run.status === 'unknown').length;
|
|
44
|
+
if (pods + unknown >= concurrentPods) {
|
|
45
|
+
return { allowed: false, reason: `${seat} concurrent Pods budget cannot be verified (${pods} confirmed, ${unknown} unknown; limit ${concurrentPods})` };
|
|
46
|
+
}
|
|
47
|
+
return { allowed: true };
|
|
48
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Checklist, ChecklistItem } from '../release-loop/checklist.js';
|
|
2
|
+
import { parseOwner } from '../release-loop/checklist.js';
|
|
3
|
+
import type { SeatLoopConfig } from '../user-config.js';
|
|
4
|
+
|
|
5
|
+
export interface StallEscalation {
|
|
6
|
+
item: ChecklistItem;
|
|
7
|
+
from: string;
|
|
8
|
+
to: string;
|
|
9
|
+
stalledMin: number;
|
|
10
|
+
/** Stable start of this uninterrupted stall interval (independent of the observation clock). */
|
|
11
|
+
startedAt: string;
|
|
12
|
+
reason: 'red' | 'blocked';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** A missing parent is intentional: OP has no automatic escalation destination. */
|
|
16
|
+
export const DEFAULT_STALL_PARENTS: Readonly<Record<string, string>> = { MK: 'OP', TC: 'OP', UX: 'OP' };
|
|
17
|
+
export const DEFAULT_RED_MINUTES = 30;
|
|
18
|
+
export const DEFAULT_BLOCKED_MINUTES = 60;
|
|
19
|
+
|
|
20
|
+
function stallReason(item: ChecklistItem): StallEscalation['reason'] | null {
|
|
21
|
+
if (item.status === 'red') return 'red';
|
|
22
|
+
return item.status === 'yellow' && item.disposition === 'block' ? 'blocked' : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Find the beginning of the current uninterrupted red/blocked interval, not the most recent evidence edit. */
|
|
26
|
+
function stallStart(item: ChecklistItem, history: Checklist['history'], reason: StallEscalation['reason']): number {
|
|
27
|
+
const changes = history.filter((entry) => entry.id === item.id && (entry.field === 'status' || entry.field === 'disposition'))
|
|
28
|
+
.filter((entry) => Number.isFinite(Date.parse(entry.at)))
|
|
29
|
+
.sort((a, b) => Date.parse(b.at) - Date.parse(a.at));
|
|
30
|
+
let status = item.status;
|
|
31
|
+
let disposition = item.disposition;
|
|
32
|
+
for (const entry of changes) {
|
|
33
|
+
const current = status === 'red' ? 'red' : status === 'yellow' && disposition === 'block' ? 'blocked' : null;
|
|
34
|
+
if (entry.field === 'status') status = entry.from as ChecklistItem['status'];
|
|
35
|
+
else disposition = entry.from as ChecklistItem['disposition'];
|
|
36
|
+
const previous = status === 'red' ? 'red' : status === 'yellow' && disposition === 'block' ? 'blocked' : null;
|
|
37
|
+
if (current === reason && previous !== reason) return Date.parse(entry.at);
|
|
38
|
+
}
|
|
39
|
+
return Date.parse(item.updatedAt);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Pure checklist snapshot judgment. Age is measured in minutes against the injected clock. */
|
|
43
|
+
export function findStalls(checklist: Checklist, config: SeatLoopConfig = { mode: 'off' }, now: Date = new Date()): StallEscalation[] {
|
|
44
|
+
const nowMs = now.getTime();
|
|
45
|
+
const parents = { ...DEFAULT_STALL_PARENTS, ...config.stall?.parents };
|
|
46
|
+
const results: StallEscalation[] = [];
|
|
47
|
+
for (const item of checklist.items) {
|
|
48
|
+
const reason = stallReason(item);
|
|
49
|
+
if (!reason || !item.owner) continue;
|
|
50
|
+
let from: string;
|
|
51
|
+
try { from = parseOwner(item.owner).seat; } catch { continue; }
|
|
52
|
+
const to = parents[from];
|
|
53
|
+
if (!to || to === from) continue;
|
|
54
|
+
const start = stallStart(item, checklist.history, reason);
|
|
55
|
+
if (!Number.isFinite(start) || start > nowMs) continue;
|
|
56
|
+
const stalledMin = (nowMs - start) / 60_000;
|
|
57
|
+
const threshold = reason === 'red' ? config.stall?.redMinutes ?? DEFAULT_RED_MINUTES
|
|
58
|
+
: config.stall?.blockedMinutes ?? DEFAULT_BLOCKED_MINUTES;
|
|
59
|
+
if (stalledMin < threshold) continue;
|
|
60
|
+
results.push({ item, from, to, stalledMin, startedAt: new Date(start).toISOString(), reason });
|
|
61
|
+
}
|
|
62
|
+
return results;
|
|
63
|
+
}
|
|
@@ -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,118 @@ 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
|
+
function assertSchemaKeys(value: Record<string, unknown>, parent = ''): void {
|
|
115
|
+
const allowed: ReadonlySet<string> = parent ? new Set(NESTED_KEYS[parent] ?? []) : PROFILE_KEYS;
|
|
116
|
+
for (const [key, child] of Object.entries(value)) {
|
|
117
|
+
if (!allowed.has(key)) {
|
|
118
|
+
throw new Error(`unknown persona schema key: ${parent ? `${parent}.` : ''}${key}`);
|
|
119
|
+
}
|
|
120
|
+
const path = parent ? `${parent}.${key}` : key;
|
|
121
|
+
if (NESTED_KEYS[path] && mapping(child)) assertSchemaKeys(child, path);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function storedProfiles(dir: string): Array<{ path: string; data: Record<string, unknown> }> {
|
|
126
|
+
if (!existsSync(dir)) return [];
|
|
127
|
+
return readdirSync(dir).filter((file) => file.endsWith('.yaml') && !file.startsWith('_')).map((file) => {
|
|
128
|
+
const path = join(dir, file);
|
|
129
|
+
const text = readFileSync(path, 'utf8');
|
|
130
|
+
const checked = parsePersonaYaml(text, path);
|
|
131
|
+
if (!checked.ok) throw new Error(`invalid stored persona ${path}: ${checked.error.message}`);
|
|
132
|
+
const data: unknown = parse(text);
|
|
133
|
+
if (!mapping(data)) throw new Error(`invalid stored persona ${path}`);
|
|
134
|
+
return { path, data };
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function normalizedName(name: string): string { return name.trim().toLocaleLowerCase(); }
|
|
139
|
+
function assertUnusedName(name: string, dir: string, except?: string): void {
|
|
140
|
+
if (!name.trim()) throw new Error('persona name is required');
|
|
141
|
+
const all = [...storedProfiles(dir), ...(dir === resolveRepositoryPersonaDir() ? [] : storedProfiles(resolveRepositoryPersonaDir()))];
|
|
142
|
+
if (all.some(({ path, data }) => path !== except &&
|
|
143
|
+
(normalizedName(String(data.displayName)) === normalizedName(name) || normalizedName(String(data.personaId)) === normalizedName(name)))) {
|
|
144
|
+
throw new Error(`persona name already exists: ${name}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function checkedYaml(data: Record<string, unknown>, path: string): { yaml: string; personaId: string; displayName: string } {
|
|
149
|
+
const yaml = stringify(data, { lineWidth: 0 });
|
|
150
|
+
const check = parsePersonaYaml(yaml, path);
|
|
151
|
+
if (!check.ok) throw new Error(`invalid persona: ${check.error.message}`);
|
|
152
|
+
return { yaml, personaId: check.profile.personaId, displayName: check.profile.displayName };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Write beside the destination, then atomically replace it (or install a new file without overwriting). */
|
|
156
|
+
function atomicPersonaWrite(path: string, yaml: string, create: boolean): void {
|
|
157
|
+
const temp = `${path}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
158
|
+
try {
|
|
159
|
+
writeFileSync(temp, yaml, { flag: 'wx' });
|
|
160
|
+
if (create) linkSync(temp, path); // atomic, exclusive: concurrent creations cannot replace a persona
|
|
161
|
+
else renameSync(temp, path);
|
|
162
|
+
} finally {
|
|
163
|
+
if (existsSync(temp)) unlinkSync(temp);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Edit a stored persona by exact id or display name; dry-run validates but never creates a directory or file. */
|
|
168
|
+
export function editPersona(name: string, edits: Record<string, unknown>, opts: PersonaWriteOptions = {}): PersonaWriteResult {
|
|
169
|
+
const dir = opts.dir ?? resolveStatePersonaDir();
|
|
170
|
+
if (!name.trim()) throw new Error('persona name is required');
|
|
171
|
+
if (!mapping(edits)) throw new Error('persona edits must be a mapping');
|
|
172
|
+
assertSchemaKeys(edits);
|
|
173
|
+
const matches = storedProfiles(dir).filter(({ data }) =>
|
|
174
|
+
normalizedName(String(data.personaId)) === normalizedName(name) || normalizedName(String(data.displayName)) === normalizedName(name));
|
|
175
|
+
if (matches.length !== 1) throw new Error(matches.length ? `ambiguous persona name: ${name}` : `persona not found: ${name}`);
|
|
176
|
+
const { path, data } = matches[0]!;
|
|
177
|
+
if (edits.personaId !== undefined && edits.personaId !== data.personaId) throw new Error('personaId cannot be changed');
|
|
178
|
+
const updated = { ...data, ...edits };
|
|
179
|
+
const result = checkedYaml(updated, path);
|
|
180
|
+
assertUnusedName(result.displayName, dir, path);
|
|
181
|
+
if (!opts.dryRun) atomicPersonaWrite(path, result.yaml, false);
|
|
182
|
+
return { ...result, path, dryRun: Boolean(opts.dryRun) };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Clone a shipped preset into the state persona store under a new, unique display name. */
|
|
186
|
+
export function clonePreset(preset: PersonaPreset | string, name: string, opts: PersonaWriteOptions = {}): PersonaWriteResult {
|
|
187
|
+
const source = typeof preset === 'string' ? findPreset(loadPresets(), preset) : preset;
|
|
188
|
+
if (!source) throw new Error(`preset not found: ${preset}`);
|
|
189
|
+
const dir = opts.dir ?? resolveStatePersonaDir();
|
|
190
|
+
assertUnusedName(name, dir);
|
|
191
|
+
const taken = new Set([...storedProfiles(dir), ...storedProfiles(resolveRepositoryPersonaDir())]
|
|
192
|
+
.map(({ data }) => String(data.personaId)));
|
|
193
|
+
const personaId = choosePersonaId(source, name, taken);
|
|
194
|
+
const path = join(dir, `${personaId}.yaml`);
|
|
195
|
+
if (existsSync(path)) throw new Error(`persona already exists: ${personaId}`);
|
|
196
|
+
const generated = presetToProfileYaml(source, loadPresetIndex().common, { personaId, name });
|
|
197
|
+
const raw: unknown = parse(generated);
|
|
198
|
+
if (!mapping(raw)) throw new Error('invalid generated persona');
|
|
199
|
+
const result = checkedYaml(raw, path);
|
|
200
|
+
assertUnusedName(result.displayName, dir);
|
|
201
|
+
if (!opts.dryRun) {
|
|
202
|
+
mkdirSync(dir, { recursive: true });
|
|
203
|
+
atomicPersonaWrite(path, generated, true);
|
|
204
|
+
}
|
|
205
|
+
return { ...result, yaml: generated, path, dryRun: Boolean(opts.dryRun) };
|
|
206
|
+
}
|
|
207
|
+
|
|
94
208
|
/** Short block for `persona show` and `persona list`. */
|
|
95
209
|
export function describePreset(p: PersonaPreset): string[] {
|
|
96
210
|
return [
|