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
|
@@ -210,7 +210,7 @@ export function flushDeferred(path = DEFERRED_PATH): number {
|
|
|
210
210
|
].join('\n');
|
|
211
211
|
observeFlush(items.length, lagMin, [...new Set(items.map(i => i.kind))]);
|
|
212
212
|
let delivered = 0;
|
|
213
|
-
// origin 없는
|
|
213
|
+
// origin 없는 매매 알림만 report 묶음. 그 외는 kind 별로 운영 봇 경계를 유지한다.
|
|
214
214
|
const noOrigin = items.filter(i => !i.origin);
|
|
215
215
|
const tradingBatch = noOrigin.filter(i => !isOperationalKind(i.kind));
|
|
216
216
|
if (tradingBatch.length && deliver(fmt(tradingBatch), 'report')) delivered += tradingBatch.length;
|
|
@@ -420,6 +420,11 @@ export function sendTelegramDirect(
|
|
|
420
420
|
const target = kindRouteTarget(cfg, kind);
|
|
421
421
|
if (target) return sendRaw(target.botToken, String(target.chatId), text);
|
|
422
422
|
} catch { /* unavailable channel routing: fall through */ }
|
|
423
|
+
// A declared table is authoritative: missing roles cannot escape to an env bot.
|
|
424
|
+
if (cfg.telegram.channels?.length) {
|
|
425
|
+
logKindRouteFallback(kind, 'none', false);
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
423
428
|
}
|
|
424
429
|
if (isOperationalKind(kind)) {
|
|
425
430
|
let home: ReturnType<typeof mainHomeTarget> = null;
|
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
// 발송 목적(kind) → 텔레그램 채널 역할 · 대상. 가벼운 모듈(telegram.ts 를 import 하지 않는다 — outbound-alert 순환 방지).
|
|
2
|
-
//
|
|
2
|
+
// 명시적으로 매매 kind 인 경우만 trading; 나머지는 system. 역할 채널이 없으면 다른 역할로 폴백하지 않는다.
|
|
3
3
|
import type { UserConfig } from '../user-config.js';
|
|
4
4
|
import { resolveChannelBotToken } from '../channel-bot-token.js';
|
|
5
5
|
import { debug } from '../debug/log.js';
|
|
6
|
-
import {
|
|
6
|
+
import { resolveTelegramChannels } from './telegram-channels.js';
|
|
7
7
|
|
|
8
8
|
export const DEFAULT_KIND_ROLES: Readonly<Record<string, string>> = {
|
|
9
|
+
report: 'report', alert: 'report', digest: 'report',
|
|
9
10
|
intake: 'system', 'ops-report': 'system', 'ops-alert': 'system', 'ops-health': 'system',
|
|
10
|
-
regression: 'system', 'agent-mission': 'system',
|
|
11
|
+
regression: 'system', 'agent-mission': 'system', brief: 'system', 'op-report': 'system',
|
|
11
12
|
};
|
|
12
13
|
|
|
13
|
-
/**
|
|
14
|
+
/** Only named finance delivery kinds can use the trading/report bot. */
|
|
15
|
+
export function isTradingKind(kind?: string): boolean {
|
|
16
|
+
return kind === 'report' || kind === 'alert' || kind === 'digest';
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
export function isOperationalKind(kind?: string): boolean {
|
|
15
|
-
return kind
|
|
20
|
+
return !isTradingKind(kind);
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
export function mainHomeTarget(cfg: UserConfig): { botToken: string; chatId: number } | null {
|
|
19
|
-
const botToken =
|
|
24
|
+
const botToken = operationsBotToken(cfg);
|
|
20
25
|
const chatId = cfg.telegram.homeChannel ?? cfg.telegram.allowedUsers[0];
|
|
21
26
|
return botToken && chatId != null && Number.isFinite(chatId) ? { botToken, chatId } : null;
|
|
22
27
|
}
|
|
@@ -25,15 +30,48 @@ export function logKindRouteFallback(kind: string | undefined, to: 'report-chann
|
|
|
25
30
|
try { debug.log('telegram.kind-route', 'fallback', { kind, to, sameBot }); } catch { /* logging must not prevent delivery */ }
|
|
26
31
|
}
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
function operationsBotToken(cfg: UserConfig): string | undefined {
|
|
34
|
+
const main = resolveChannelBotToken('telegram', cfg);
|
|
35
|
+
const channels = cfg.telegram.channels?.length ? resolveTelegramChannels(cfg.telegram) : [];
|
|
36
|
+
const tradingTokens = new Set(channels.filter(c => c.roles.includes('report')).map(c => c.botToken));
|
|
37
|
+
// The report (trading) bot is never the operations bot — with or without a channels table (review must-fix · OUT1).
|
|
38
|
+
if (cfg.telegram.reportChannel?.botToken) tradingTokens.add(cfg.telegram.reportChannel.botToken);
|
|
39
|
+
if (main.source !== 'channels.main') return main.token && !tradingTokens.has(main.token) ? main.token : undefined;
|
|
40
|
+
// channels.main may mean "first channel", which can be the trading bot.
|
|
41
|
+
// Only a named operations channel (or the named main channel) with an
|
|
42
|
+
// operational role establishes identity; array order and role alone do not.
|
|
43
|
+
const tokens = new Set(channels
|
|
44
|
+
.filter(c => (c.name === 'ops' && c.roles.includes('system'))
|
|
45
|
+
|| (c.name === 'main' && c.roles.some(r => r === 'system' || r === 'qa' || r === 'default')))
|
|
46
|
+
.filter(c => !tradingTokens.has(c.botToken))
|
|
47
|
+
.map(c => c.botToken));
|
|
48
|
+
return tokens.size === 1 ? tokens.values().next().value : undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isAllowedBot(cfg: UserConfig, kind: string | undefined, token: string): boolean {
|
|
52
|
+
const mainToken = operationsBotToken(cfg);
|
|
53
|
+
if (!mainToken) return false;
|
|
54
|
+
if (!isTradingKind(kind)) return token === mainToken;
|
|
55
|
+
const reportToken = cfg.telegram.reportChannel?.botToken;
|
|
56
|
+
return token !== mainToken && (!reportToken || token === reportToken);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function roleForKind(cfg: UserConfig, kind?: string): string {
|
|
60
|
+
const fallback = isTradingKind(kind) ? 'report' : 'system';
|
|
61
|
+
if (!kind || !cfg.telegram.channels?.length) return fallback;
|
|
62
|
+
const override = cfg.telegram.kindRoles?.[kind];
|
|
63
|
+
if (!override) return fallback;
|
|
64
|
+
const channel = resolveTelegramChannels(cfg.telegram)
|
|
65
|
+
.find(c => c.roles.includes(override) && isAllowedBot(cfg, kind, c.botToken));
|
|
66
|
+
return channel ? override : fallback;
|
|
31
67
|
}
|
|
32
68
|
|
|
33
|
-
/**
|
|
69
|
+
/** Exact role only: channelForRole's default/first fallback can cross bot boundaries. */
|
|
34
70
|
export function kindRouteTarget(cfg: UserConfig, kind?: string): { botToken: string; chatId: number } | null {
|
|
71
|
+
if (!cfg.telegram.channels?.length) return null;
|
|
35
72
|
const role = roleForKind(cfg, kind);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
73
|
+
const ch = resolveTelegramChannels(cfg.telegram)
|
|
74
|
+
.find(c => c.roles.includes(role) && isAllowedBot(cfg, kind, c.botToken));
|
|
75
|
+
if (!ch || !Number.isFinite(ch.chatId)) return null;
|
|
76
|
+
return { botToken: ch.botToken, chatId: ch.chatId };
|
|
39
77
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const OUTPUT_KINDS = {
|
|
2
|
+
report: { fileName: 'report.md', cardTitle: '한 장 보고서', guidance: '보고·정리·요약·회고 → 한 장 보고서', words: ['보고', '정리', '요약', '회고', 'report', 'summary', 'summarize', 'recap', 'brief'] },
|
|
3
|
+
slides: { fileName: 'slides.md', cardTitle: '슬라이드', guidance: '발표·자료·덱·피치 → 슬라이드(장마다 --- 구분)', words: ['발표', '자료', '덱', '피치', 'presentation', 'slides', 'slide', 'deck', 'pitch'] },
|
|
4
|
+
research: { fileName: 'research.md', cardTitle: '조사 문서', guidance: '조사·비교·경쟁사·시장 → 조사 문서(출처 링크 칸 필수)', words: ['조사', '비교', '경쟁사', '시장', 'research', 'compare', 'comparison', 'competitor', 'competitors', 'market'] },
|
|
5
|
+
post: { fileName: 'post.md', cardTitle: '게시글 초안', guidance: '게시글·캡션·공지·SNS → 게시글 초안(게시는 승인 노드 뒤)', words: ['게시글', '캡션', '공지', 'SNS', 'post', 'caption', 'announcement', 'social media'] },
|
|
6
|
+
video: { fileName: '', cardTitle: '영상', guidance: '영상·릴스·쇼츠 → 영상(그래프가 있을 때만)', words: ['영상', '릴스', '쇼츠', 'video', 'reels', 'shorts'] },
|
|
7
|
+
answer: { fileName: '', cardTitle: '답', guidance: '단순한 질문 → 답 그대로(산출물 없음)', words: [] },
|
|
8
|
+
} as const;
|
|
9
|
+
|
|
10
|
+
export type OutputKind = keyof typeof OUTPUT_KINDS;
|
|
11
|
+
|
|
12
|
+
export function pickDefaultOutput(text: string): { kind: OutputKind; fileName: string; cardTitle: string } {
|
|
13
|
+
const normalized = text.toLowerCase();
|
|
14
|
+
// Explicit artifact formats outrank activity words such as 정리, 요약 and 비교.
|
|
15
|
+
for (const kind of ['slides', 'post', 'video', 'research', 'report'] as const) {
|
|
16
|
+
const entry = OUTPUT_KINDS[kind];
|
|
17
|
+
if (entry.words.some(word => /[a-z]/i.test(word)
|
|
18
|
+
? new RegExp(`(^|[^a-z])${word.toLowerCase()}(?=$|[^a-z])`).test(normalized)
|
|
19
|
+
: normalized.includes(word))) {
|
|
20
|
+
return { kind, fileName: entry.fileName, cardTitle: entry.cardTitle };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const kind = /(?:[??]|뭐|어떻게)\s*$/.test(text) ? 'answer' : 'report';
|
|
24
|
+
return { kind, fileName: OUTPUT_KINDS[kind].fileName, cardTitle: OUTPUT_KINDS[kind].cardTitle };
|
|
25
|
+
}
|
|
@@ -7,6 +7,7 @@ import { defaultGraphsDir } from '../self-implement/graph-templates.js';
|
|
|
7
7
|
import { getElanousConfigDir } from '../elanous-config-dir.js';
|
|
8
8
|
import type { ExecAttachment } from './store.js';
|
|
9
9
|
import { debug } from '../debug/log.js';
|
|
10
|
+
import { OUTPUT_KINDS, pickDefaultOutput, type OutputKind } from './default-outputs.js';
|
|
10
11
|
|
|
11
12
|
export interface InstalledGraph {
|
|
12
13
|
id: string;
|
|
@@ -21,6 +22,8 @@ export interface ExecPlanItem {
|
|
|
21
22
|
title: string;
|
|
22
23
|
graphId: string;
|
|
23
24
|
inputs: Record<string, unknown>;
|
|
25
|
+
/** Planner fills this for every returned row; older injected plan fixtures may omit it. */
|
|
26
|
+
output?: OutputKind;
|
|
24
27
|
reason?: string;
|
|
25
28
|
/** A5b — indexes of earlier items whose results this item needs (it starts after they finish). */
|
|
26
29
|
after?: number[];
|
|
@@ -147,7 +150,7 @@ export function latestFieldFolder(configRoot = getElanousConfigDir()): string |
|
|
|
147
150
|
|
|
148
151
|
export function execPlanPrompt(text: string, graphs: readonly InstalledGraph[], seats: readonly string[], attachments: readonly ExecAttachment[] = [], fieldFolder: string | null = latestFieldFolder()): string {
|
|
149
152
|
const supplied = attachments.map(({ name, path }) => `첨부: ${name} (${/\.(?:png|jpe?g|webp|gif|heic)$/i.test(name) ? '이미지' : '파일'}) — ${path}`).join('\n');
|
|
150
|
-
return `COO 역할. 요청을 실행 가능한 자리별 그래프로 나눠 맡겨라. JSON 배열만 출력: [{"seat":"자리","title":"할 일","graphId":"설치된 그래프 ID 또는 빈 문자열","inputs":{},"after":[]}]. 앞 항목의 결과가 있어야 할 수 있는 일(예: 점검·조사 «결과로» 쓰는 한 장 · 보고)은 after 에 그 앞 항목 번호(0부터)를 적는다 — 그 항목은 앞 항목이 끝난 뒤 그 결과를 받아 시작한다. 서로 기다릴 필요가 없으면 after 는 빈 배열. 자리 이름은 주어진 목록에서만, 그래프 ID는 설치된 목록에서만 고른다. 맞는 그래프가 없으면 graphId=""로 표시한다. 그래프에 inputKeys 가 있으면 inputs 를 그 키로만 채우되 한 줄과 아래 알려진 값에서 얻을 수 있는 값만 넣는다(모르는 값은 넣지 않는다). image/photo/file 입력에는 알맞은 첨부 경로를 쓸 수 있다. folder 입력에는 첨부가 없거나 첨부 사진이 그 현장 폴더 안에 있을 때만 오늘 현장 폴더를 쓸 수 있다. 다른 현장 사진이나 출처를 모르는 업로드 사진이 있으면 폴더를 추측하지 마라. 그 그래프가 꼭 받아야 할 값(예: 브랜드 이름 · 사진 파일)을 알 수 없으면 graphId=""로 두고 title 끝에 « — <무엇> 필요»를 적는다. 단순히 묻는 말에는 없는 그래프를 지어내지 말고 graphId=""로 표시한다. 없는 그래프나 산출을 지어내지 마라. 바깥 게시·발행·광고·결제는 승인 노드를 거치기 전 실행하면 안 된다.\n자리: ${JSON.stringify(seats)}\n실행 가능한 그래프: ${JSON.stringify(graphs.map(({ id, title, description, inputKeys }) => ({ id, title, description, ...(inputKeys ? { inputKeys } : {}) })))}\n${fieldFolder ? `알려진 값: 오늘 현장 폴더 = ${fieldFolder}\n` : ''}${supplied ? `${supplied}\n` : ''}한 줄: ${JSON.stringify(text)}`;
|
|
153
|
+
return `COO 역할. 요청을 실행 가능한 자리별 그래프로 나눠 맡겨라. JSON 배열만 출력: [{"seat":"자리","title":"할 일","graphId":"설치된 그래프 ID 또는 빈 문자열","inputs":{},"after":[]}]. 각 행에는 선택 칸 "output":"report|slides|research|post|video|answer"를 쓸 수 있다: ${Object.entries(OUTPUT_KINDS).map(([kind, entry]) => `${entry.guidance} = ${kind}${entry.fileName ? ` (${entry.fileName})` : ''}`).join(', ')}; 그 외 일은 report로 둔다. 앞 항목의 결과가 있어야 할 수 있는 일(예: 점검·조사 «결과로» 쓰는 한 장 · 보고)은 after 에 그 앞 항목 번호(0부터)를 적는다 — 그 항목은 앞 항목이 끝난 뒤 그 결과를 받아 시작한다. 서로 기다릴 필요가 없으면 after 는 빈 배열. 자리 이름은 주어진 목록에서만, 그래프 ID는 설치된 목록에서만 고른다. 맞는 그래프가 없으면 graphId=""로 표시한다. 그래프에 inputKeys 가 있으면 inputs 를 그 키로만 채우되 한 줄과 아래 알려진 값에서 얻을 수 있는 값만 넣는다(모르는 값은 넣지 않는다). image/photo/file 입력에는 알맞은 첨부 경로를 쓸 수 있다. folder 입력에는 첨부가 없거나 첨부 사진이 그 현장 폴더 안에 있을 때만 오늘 현장 폴더를 쓸 수 있다. 다른 현장 사진이나 출처를 모르는 업로드 사진이 있으면 폴더를 추측하지 마라. 그 그래프가 꼭 받아야 할 값(예: 브랜드 이름 · 사진 파일)을 알 수 없으면 graphId=""로 두고 title 끝에 « — <무엇> 필요»를 적는다. 단순히 묻는 말에는 없는 그래프를 지어내지 말고 graphId=""로 표시한다. 없는 그래프나 산출을 지어내지 마라. 바깥 게시·발행·광고·결제는 승인 노드를 거치기 전 실행하면 안 된다.\n자리: ${JSON.stringify(seats)}\n실행 가능한 그래프: ${JSON.stringify(graphs.map(({ id, title, description, inputKeys }) => ({ id, title, description, ...(inputKeys ? { inputKeys } : {}) })))}\n${fieldFolder ? `알려진 값: 오늘 현장 폴더 = ${fieldFolder}\n` : ''}${supplied ? `${supplied}\n` : ''}한 줄: ${JSON.stringify(text)}`;
|
|
151
154
|
}
|
|
152
155
|
|
|
153
156
|
export async function judgeExecPlan(text: string, graphs: readonly InstalledGraph[], seats: readonly string[], attachments: readonly ExecAttachment[] = [], fieldFolder: string | null = latestFieldFolder()): Promise<unknown> {
|
|
@@ -183,6 +186,11 @@ export async function planExecRequest(text: string, deps: {
|
|
|
183
186
|
? [...new Set(row.after.filter((n): n is number => Number.isInteger(n) && (n as number) >= 0 && (n as number) < index))]
|
|
184
187
|
: [];
|
|
185
188
|
const graph = known.get(row.graphId);
|
|
189
|
+
const modelOutput = typeof row.output === 'string' && Object.hasOwn(OUTPUT_KINDS, row.output)
|
|
190
|
+
? row.output as OutputKind : undefined;
|
|
191
|
+
const picked = modelOutput ?? pickDefaultOutput(row.title).kind;
|
|
192
|
+
const output = picked === 'video' && !graph ? 'report' : picked;
|
|
193
|
+
debug.log('exec.plan', 'default-output', { kind: output, source: modelOutput === output ? 'model' : 'rule' });
|
|
186
194
|
const inputs = { ...row.inputs as Record<string, unknown> };
|
|
187
195
|
let folderReason: string | undefined;
|
|
188
196
|
if (graph) {
|
|
@@ -206,7 +214,7 @@ export async function planExecRequest(text: string, deps: {
|
|
|
206
214
|
}
|
|
207
215
|
}
|
|
208
216
|
return {
|
|
209
|
-
seat: row.seat, title: row.title.trim(), graphId: row.graphId, inputs,
|
|
217
|
+
seat: row.seat, title: row.title.trim(), graphId: row.graphId, inputs, output,
|
|
210
218
|
...(after.length ? { after } : {}),
|
|
211
219
|
...(!graph ? { reason: `${row.seat}: 요청에 맞는 설치된 실행 그래프가 없습니다` } : folderReason ? { reason: folderReason } : {}),
|
|
212
220
|
};
|
|
@@ -9,6 +9,12 @@ import { installedGraphs, planExecRequest, type ExecPlanItem, type InstalledGrap
|
|
|
9
9
|
import { ExecRequestStore, type ExecAttachment, type ExecRequest, type ExecResult, type ExecSeat } from './store.js';
|
|
10
10
|
import { answerAsSeat } from '../intake-plane/seat-answer.js';
|
|
11
11
|
import { redactSecrets } from '../task-cards/card-store.js';
|
|
12
|
+
import { recordOutput } from '../outputs/ledger.js';
|
|
13
|
+
import type { OutputKind } from './default-outputs.js';
|
|
14
|
+
|
|
15
|
+
function recordResult(result: ExecResult, id: string, root: string, kind: OutputKind | 'file'): void {
|
|
16
|
+
recordOutput({ source: 'exec', sourceId: id, seat: result.seat, title: result.title, url: result.url, kind }, root);
|
|
17
|
+
}
|
|
12
18
|
|
|
13
19
|
export interface ExecRunnerDeps {
|
|
14
20
|
store?: ExecRequestStore;
|
|
@@ -137,14 +143,19 @@ function syncRun(item: ExecRequest, seat: ExecSeat, state: GraphRunState, root:
|
|
|
137
143
|
for (const entry of entries) {
|
|
138
144
|
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) continue;
|
|
139
145
|
const result = resultOf(entry as Record<string, unknown>, seat, item.id, root);
|
|
140
|
-
if (result && !item.results.some(existing => existing.seat === result.seat && existing.url === result.url))
|
|
146
|
+
if (result && !item.results.some(existing => existing.seat === result.seat && existing.url === result.url)) {
|
|
147
|
+
item.results.push(result);
|
|
148
|
+
recordResult(result, item.id, root, seat.output ?? 'file');
|
|
149
|
+
}
|
|
141
150
|
}
|
|
142
151
|
}
|
|
143
152
|
if (seat.status === 'done' && !item.results.some(existing => existing.seat === seat.seat)) {
|
|
144
153
|
const file = textResultFile(seat, state, root);
|
|
145
154
|
const result = file ? resultOf({ file, title: seat.title }, seat, item.id, root) : null;
|
|
146
|
-
if (result)
|
|
147
|
-
|
|
155
|
+
if (result) {
|
|
156
|
+
item.results.push(result);
|
|
157
|
+
recordResult(result, item.id, root, seat.output ?? 'file');
|
|
158
|
+
} else {
|
|
148
159
|
const keys = [...new Set((state.nodes ?? []).flatMap(node => Object.keys(lastJsonObject(node.output) ?? {})))];
|
|
149
160
|
debug.log('exec.result', 'none', { seat: seat.seat, graphId: seat.graphId, keys });
|
|
150
161
|
}
|
|
@@ -288,7 +299,7 @@ export class ExecRequestRunner {
|
|
|
288
299
|
reason: plan.reason ?? `${plan.seat}: 요청에 맞는 설치된 실행 그래프가 없습니다` });
|
|
289
300
|
continue;
|
|
290
301
|
}
|
|
291
|
-
const seat: ExecSeat = { seat: plan.seat, title: plan.title, status: 'done', graphId: 'seat-answer', runId: randomUUID() };
|
|
302
|
+
const seat: ExecSeat = { seat: plan.seat, title: plan.title, status: 'done', graphId: 'seat-answer', runId: randomUUID(), ...(plan.output ? { output: plan.output } : {}) };
|
|
292
303
|
const dir = join(this.root, 'graph-runs', seat.graphId);
|
|
293
304
|
const statePath = join(dir, `${seat.runId}.json`);
|
|
294
305
|
const artifactDir = join(dir, `${seat.runId}.json.artifacts`);
|
|
@@ -299,7 +310,10 @@ export class ExecRequestRunner {
|
|
|
299
310
|
writeFileSync(statePath, JSON.stringify(state), { mode: 0o600 });
|
|
300
311
|
item.seats.push(seat);
|
|
301
312
|
const result = resultOf({ file, title: answered.title }, seat, id, this.root);
|
|
302
|
-
if (result)
|
|
313
|
+
if (result) {
|
|
314
|
+
item.results.push(result);
|
|
315
|
+
recordResult(result, item.id, this.root, seat.output ?? 'file');
|
|
316
|
+
}
|
|
303
317
|
}
|
|
304
318
|
item.status = 'running';
|
|
305
319
|
aggregate(item);
|
|
@@ -307,7 +321,7 @@ export class ExecRequestRunner {
|
|
|
307
321
|
return;
|
|
308
322
|
}
|
|
309
323
|
item.seats = plans.map(plan => ({ seat: plan.seat, title: plan.title, status: plan.reason || !byId.has(plan.graphId) ? 'failed' : 'waiting', graphId: plan.graphId,
|
|
310
|
-
runId: plan.reason || !byId.has(plan.graphId) ? '' : randomUUID(), inputs: plan.inputs, ...(plan.after?.length ? { after: plan.after } : {}),
|
|
324
|
+
runId: plan.reason || !byId.has(plan.graphId) ? '' : randomUUID(), inputs: plan.inputs, ...(plan.output ? { output: plan.output } : {}), ...(plan.after?.length ? { after: plan.after } : {}),
|
|
311
325
|
...(plan.reason || !byId.has(plan.graphId) ? { reason: plan.reason ?? `${plan.seat}: 요청에 맞는 설치된 실행 그래프가 없습니다` } : {}) }));
|
|
312
326
|
item.status = 'running';
|
|
313
327
|
debug.log('exec-requests', 'planned', { id, seats: item.seats.map(seat => ({ seat: seat.seat, graphId: seat.graphId, status: seat.status })) });
|
|
@@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { getElanousConfigDir } from '../elanous-config-dir.js';
|
|
5
|
+
import type { OutputKind } from './default-outputs.js';
|
|
5
6
|
|
|
6
7
|
export type SeatStatus = 'waiting' | 'running' | 'done' | 'failed';
|
|
7
8
|
export interface ExecSeat {
|
|
@@ -10,6 +11,8 @@ export interface ExecSeat {
|
|
|
10
11
|
status: SeatStatus;
|
|
11
12
|
graphId: string;
|
|
12
13
|
runId: string;
|
|
14
|
+
/** Requested output category, distinct from the result file MIME kind. */
|
|
15
|
+
output?: OutputKind;
|
|
13
16
|
inputs?: Record<string, unknown>;
|
|
14
17
|
reason?: string;
|
|
15
18
|
/** A5b — indexes of earlier seats this seat waits for. */
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { debug } from '../debug/log.js';
|
|
4
|
+
import { extractVerbatimOriginalAsk } from '../self-implement/goal-author.js';
|
|
5
|
+
import { loadRunLedger } from '../self-implement/run-ledger.js';
|
|
6
|
+
import { queryRunningRuns, type QueriedRunningRunsResult } from '../self-implement/running-runs.js';
|
|
7
|
+
import type { BudgetDecision } from '../self-implement/budget-gate.js';
|
|
8
|
+
|
|
9
|
+
export type ActiveRunsForGoal = readonly string[] | 'unknown';
|
|
10
|
+
|
|
11
|
+
/** Match an un-authored CLI request to the verbatim ask recorded in an authored run. */
|
|
12
|
+
export function launchRequestId(text: string): string {
|
|
13
|
+
return `request-${createHash('sha256').update(text).digest('hex').slice(0, 32)}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ActiveRunsForGoalDeps {
|
|
17
|
+
queryRuns?: () => QueriedRunningRunsResult;
|
|
18
|
+
loadLedger?: typeof loadRunLedger;
|
|
19
|
+
readGoalDocument?: (path: string) => string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Only a live process with an exact goal ID or verbatim authored ask is a duplicate. Incomplete observation is not absence. */
|
|
23
|
+
export function activeRunsForGoal(goalId: string, deps: ActiveRunsForGoalDeps = {}): ActiveRunsForGoal {
|
|
24
|
+
try {
|
|
25
|
+
const observation = (deps.queryRuns ?? (() => queryRunningRuns({ includeTest: true, caller: 'execution-loop.launch-gate', noCache: true })))();
|
|
26
|
+
const loadLedger = deps.loadLedger ?? loadRunLedger;
|
|
27
|
+
const readGoalDocument = deps.readGoalDocument ?? ((path: string) => readFileSync(path, 'utf8'));
|
|
28
|
+
let unknown = observation.completeness !== 'complete' || observation.pty.unreadable.length > 0;
|
|
29
|
+
const active = new Set<string>();
|
|
30
|
+
for (const run of observation.entries) {
|
|
31
|
+
if (run.status === 'ended-unclosed') continue;
|
|
32
|
+
if (run.ledgerDirectories.length === 0) { unknown = true; continue; }
|
|
33
|
+
let identified = false;
|
|
34
|
+
for (const directory of run.ledgerDirectories) {
|
|
35
|
+
try {
|
|
36
|
+
const ledger = loadLedger(run.runId, directory);
|
|
37
|
+
if (ledger === null) { unknown = true; continue; }
|
|
38
|
+
const ids = new Set(ledger.flatMap((entry) => entry.goalId ? [entry.goalId] : []));
|
|
39
|
+
if (ids.size !== 1) { unknown = true; continue; }
|
|
40
|
+
identified = true;
|
|
41
|
+
let matches = ids.has(goalId);
|
|
42
|
+
if (!matches && goalId.startsWith('request-') && run.status === 'running' && run.ptyRefs.length > 0) {
|
|
43
|
+
const document = ledger.find((entry) => entry.event === 'start')?.data.goalFile;
|
|
44
|
+
if (typeof document === 'string') {
|
|
45
|
+
try {
|
|
46
|
+
const ask = extractVerbatimOriginalAsk(readGoalDocument(document));
|
|
47
|
+
matches = ask?.range !== undefined && launchRequestId(ask.ask) === goalId;
|
|
48
|
+
} catch { unknown = true; }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (matches) {
|
|
52
|
+
if (run.status === 'running' && run.ptyRefs.length > 0) active.add(run.runId);
|
|
53
|
+
else unknown = true;
|
|
54
|
+
}
|
|
55
|
+
} catch { unknown = true; }
|
|
56
|
+
}
|
|
57
|
+
if (!identified) unknown = true;
|
|
58
|
+
}
|
|
59
|
+
// Positive evidence wins even if a different process or store is unreadable.
|
|
60
|
+
return active.size > 0 ? [...active].sort() : unknown ? 'unknown' : [];
|
|
61
|
+
} catch {
|
|
62
|
+
return 'unknown';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface PreLaunchGateInput {
|
|
67
|
+
goalId: string;
|
|
68
|
+
budget?: BudgetDecision | 'unknown';
|
|
69
|
+
forceLaunch?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface PreLaunchGateDeps extends ActiveRunsForGoalDeps {
|
|
73
|
+
activeRuns?: (goalId: string) => ActiveRunsForGoal;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface PreLaunchGateDecision {
|
|
77
|
+
action: 'proceed' | 'blocked-duplicate' | 'blocked-budget' | 'wait-reset';
|
|
78
|
+
sameGoalActiveRuns: ActiveRunsForGoal;
|
|
79
|
+
budget: BudgetDecision | 'unknown';
|
|
80
|
+
reason: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Force bypasses only a duplicate observation, never an exhausted budget. */
|
|
84
|
+
export function preLaunchGate(input: PreLaunchGateInput, deps: PreLaunchGateDeps = {}): PreLaunchGateDecision {
|
|
85
|
+
const decision = decidePreLaunch(input, deps);
|
|
86
|
+
debug.log('execution-loop.launch-gate', 'decision', { goalId: input.goalId, action: decision.action, sameGoalActiveRuns: decision.sameGoalActiveRuns, budget: decision.budget === 'unknown' ? 'unknown' : decision.budget.action, forceLaunch: input.forceLaunch === true, reason: decision.reason });
|
|
87
|
+
return decision;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function decidePreLaunch(input: PreLaunchGateInput, deps: PreLaunchGateDeps): PreLaunchGateDecision {
|
|
91
|
+
const sameGoalActiveRuns = (deps.activeRuns ?? ((goalId) => activeRunsForGoal(goalId, deps)))(input.goalId);
|
|
92
|
+
const budget = input.budget ?? 'unknown';
|
|
93
|
+
if (budget !== 'unknown' && (budget.action === 'stop' || budget.action === 'wait-reset')) {
|
|
94
|
+
return { action: budget.action === 'stop' ? 'blocked-budget' : 'wait-reset', sameGoalActiveRuns, budget, reason: budget.reasons.join(' · ') || `budget: ${budget.action}` };
|
|
95
|
+
}
|
|
96
|
+
if (sameGoalActiveRuns !== 'unknown' && sameGoalActiveRuns.length > 0 && !input.forceLaunch) {
|
|
97
|
+
return { action: 'blocked-duplicate', sameGoalActiveRuns, budget, reason: `same-goal active runs: ${sameGoalActiveRuns.join(', ')}` };
|
|
98
|
+
}
|
|
99
|
+
return { action: 'proceed', sameGoalActiveRuns, budget, reason: sameGoalActiveRuns === 'unknown' ? 'active runs unknown' : input.forceLaunch && sameGoalActiveRuns.length > 0 ? 'duplicate overridden by force-launch' : 'no confirmed duplicate' };
|
|
100
|
+
}
|
package/src/field/field-reel.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { debug } from '../debug/log.js';
|
|
|
7
7
|
import { mergeCapturedPath } from '../shell-env-bootstrap.js';
|
|
8
8
|
import { listFieldMedia } from './field-media.js';
|
|
9
9
|
import { startFieldFeed, type FieldFeedStart } from './field-feed-auto.js';
|
|
10
|
+
import { recordOutput } from '../outputs/ledger.js';
|
|
10
11
|
|
|
11
12
|
export interface FieldReelResult { ok: boolean; file?: string; seconds: number; error?: string; feed?: FieldFeedStart }
|
|
12
13
|
export type FieldReelRunner = (dir: string, opts: { title: string; sub: string; mode?: 'standard' | 'instant' }) => Promise<FieldReelResult>;
|
|
@@ -137,6 +138,7 @@ async function render(dir: string, job: Job): Promise<void> {
|
|
|
137
138
|
// 현재 렌더의 수신자 스냅샷. 렌더 중 업로드의 수신자는 다음 렌더로 넘긴다.
|
|
138
139
|
const callbacks = job.callbacks;
|
|
139
140
|
const renderOpts = job.opts;
|
|
141
|
+
let reelTitle = renderOpts.title || event;
|
|
140
142
|
job.callbacks = new Map();
|
|
141
143
|
writeStatus(dir, { state: 'rendering', items, startedAt });
|
|
142
144
|
mkdirSync(join(dir, 'reel'), { recursive: true }); // 실행기 계약 — 산출 폴더는 있다(reel.sh 는 스스로 다시 만든다)
|
|
@@ -148,8 +150,9 @@ async function render(dir: string, job: Job): Promise<void> {
|
|
|
148
150
|
let titleLines: string[] = [];
|
|
149
151
|
try { titleLines = readFileSync(join(dir, 'title.txt'), 'utf8').split(/\r?\n/); }
|
|
150
152
|
catch (err) { if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err; }
|
|
153
|
+
reelTitle = titleLines[0]?.trim() || renderOpts.title || event;
|
|
151
154
|
result = await (renderOpts.runner ?? defaultFieldReelRunner)(dir, {
|
|
152
|
-
title:
|
|
155
|
+
title: reelTitle,
|
|
153
156
|
sub: titleLines[1]?.trim() || renderOpts.sub || sub,
|
|
154
157
|
...(renderOpts.mode ? { mode: renderOpts.mode } : {}),
|
|
155
158
|
});
|
|
@@ -163,6 +166,7 @@ async function render(dir: string, job: Job): Promise<void> {
|
|
|
163
166
|
writeStatus(dir, { state: result.ok ? 'done' : 'failed', items, startedAt, finishedAt,
|
|
164
167
|
seconds: result.seconds, ...(result.ok ? { file: result.file } : { error: result.error ?? 'render failed' }) });
|
|
165
168
|
debug.log('field.reel', result.ok ? 'done' : 'failed', { event, items, seconds: result.seconds });
|
|
169
|
+
if (result.ok && result.file) recordOutput({ source: 'field-reel', sourceId: event, kind: 'video', title: reelTitle, path: result.file });
|
|
166
170
|
if (result.ok) {
|
|
167
171
|
// EV10b: 엔진이 timeline.json 에 남긴 비전 호출 수·성공·«현장 N» 강등·걸린 ms — codex 잔량과 겹치므로 관측한다(OP 10-01).
|
|
168
172
|
let cardsCache: 'hit' | 'miss' | 'n/a' = 'n/a';
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
export interface FieldPhoto {
|
|
2
|
+
file: string;
|
|
3
|
+
/** EXIF `YYYY:MM:DD HH:mm:ss`, ISO local time, or ISO time with an offset. */
|
|
4
|
+
exifTakenAt?: string | null;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface TranscriptEntry {
|
|
8
|
+
speaker: string;
|
|
9
|
+
timestamp: string;
|
|
10
|
+
text: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface FieldNoteOptions {
|
|
14
|
+
/** Minutes east of UTC for offset-free EXIF and transcript timestamps; KST by default. */
|
|
15
|
+
timeZoneOffsetMinutes?: number;
|
|
16
|
+
/** Minutes added to every dated photo after interpreting its EXIF timestamp. */
|
|
17
|
+
photoCorrectionMinutes?: number;
|
|
18
|
+
/** Maximum gap between consecutive photos in the same scene; 15 minutes by default. */
|
|
19
|
+
sceneGapMinutes?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface TimedPhoto extends FieldPhoto {
|
|
23
|
+
takenAt: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface FieldScene {
|
|
27
|
+
start: string;
|
|
28
|
+
end: string;
|
|
29
|
+
photos: TimedPhoto[];
|
|
30
|
+
transcript: TranscriptEntry[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface FieldTimeline {
|
|
34
|
+
scenes: FieldScene[];
|
|
35
|
+
undatedPhotos: FieldPhoto[];
|
|
36
|
+
/** Chronological entries when there are no dated photos to anchor a scene. */
|
|
37
|
+
transcriptWithoutPhotos: TranscriptEntry[];
|
|
38
|
+
timeZoneOffsetMinutes: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface FieldNoteLlm {
|
|
42
|
+
/** Only used for a raw transcript string; JSON entries bypass transcription. */
|
|
43
|
+
transcribe?: (source: string) => Promise<TranscriptEntry[]>;
|
|
44
|
+
summarize?: (entries: readonly TranscriptEntry[]) => Promise<string>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const MINUTE = 60_000;
|
|
48
|
+
|
|
49
|
+
function validOffset(minutes: number): boolean {
|
|
50
|
+
return Number.isInteger(minutes) && minutes >= -14 * 60 && minutes <= 14 * 60;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseTime(value: string, offsetMinutes: number): number | null {
|
|
54
|
+
const input = value.trim();
|
|
55
|
+
const local = /^(\d{4})[:-](\d{2})[:-](\d{2})[ T](\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?$/.exec(input);
|
|
56
|
+
const zoned = local ? null : /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?(?:Z|[+-]\d{2}:\d{2})$/.exec(input);
|
|
57
|
+
const match = local ?? zoned;
|
|
58
|
+
if (!match) return null;
|
|
59
|
+
const [, year, month, day, hour, minute, second, fraction] = match;
|
|
60
|
+
if (+year! < 100) return null;
|
|
61
|
+
const milliseconds = fraction ? Number(fraction.padEnd(3, '0')) : 0;
|
|
62
|
+
const utc = Date.UTC(+year!, +month! - 1, +day!, +hour!, +minute!, +second!, milliseconds);
|
|
63
|
+
const date = new Date(utc);
|
|
64
|
+
if (date.getUTCFullYear() !== +year! || date.getUTCMonth() + 1 !== +month! ||
|
|
65
|
+
date.getUTCDate() !== +day! || date.getUTCHours() !== +hour! ||
|
|
66
|
+
date.getUTCMinutes() !== +minute! || date.getUTCSeconds() !== +second!) return null;
|
|
67
|
+
if (local) return utc - offsetMinutes * MINUTE;
|
|
68
|
+
// Offset-bearing ISO timestamps are absolute instants, not local wall-clock readings.
|
|
69
|
+
const parsed = Date.parse(input);
|
|
70
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function iso(ms: number): string {
|
|
74
|
+
return new Date(ms).toISOString();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Pure alignment: neither mutates its inputs nor consults the machine clock/timezone. */
|
|
78
|
+
export function alignFieldNote(
|
|
79
|
+
photos: readonly FieldPhoto[],
|
|
80
|
+
transcript: readonly TranscriptEntry[],
|
|
81
|
+
options: FieldNoteOptions = {},
|
|
82
|
+
): FieldTimeline {
|
|
83
|
+
const offset = options.timeZoneOffsetMinutes ?? 540;
|
|
84
|
+
const correction = options.photoCorrectionMinutes ?? 0;
|
|
85
|
+
const gap = options.sceneGapMinutes ?? 15;
|
|
86
|
+
if (!validOffset(offset) || !Number.isFinite(correction) || !Number.isFinite(gap) || gap < 0) {
|
|
87
|
+
throw new RangeError('Invalid field-note timezone, photo correction, or scene gap');
|
|
88
|
+
}
|
|
89
|
+
const dated: Array<{ photo: TimedPhoto; time: number; order: number }> = [];
|
|
90
|
+
const undatedPhotos: FieldPhoto[] = [];
|
|
91
|
+
photos.forEach((photo, order) => {
|
|
92
|
+
const parsed = photo.exifTakenAt ? parseTime(photo.exifTakenAt, offset) : null;
|
|
93
|
+
if (parsed === null) undatedPhotos.push({ ...photo });
|
|
94
|
+
else dated.push({ photo: { ...photo, takenAt: iso(parsed + correction * MINUTE) }, time: parsed + correction * MINUTE, order });
|
|
95
|
+
});
|
|
96
|
+
dated.sort((a, b) => a.time - b.time || a.order - b.order);
|
|
97
|
+
|
|
98
|
+
const groups: typeof dated[] = [];
|
|
99
|
+
for (const item of dated) {
|
|
100
|
+
const last = groups.at(-1);
|
|
101
|
+
if (!last || item.time - last[last.length - 1]!.time > gap * MINUTE) groups.push([item]);
|
|
102
|
+
else last.push(item);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const entries = transcript.map((entry, order) => {
|
|
106
|
+
const time = parseTime(entry.timestamp, offset);
|
|
107
|
+
if (time === null) throw new RangeError(`Invalid transcript timestamp at index ${order}: ${entry.timestamp}`);
|
|
108
|
+
return { entry: { ...entry }, time, order };
|
|
109
|
+
}).sort((a, b) => a.time - b.time || a.order - b.order);
|
|
110
|
+
|
|
111
|
+
// An utterance belongs to the scene whose photo span [first, last] is nearest in time (inside the span = distance 0);
|
|
112
|
+
// a tie goes to the earlier scene. So a 10:55 line next to an 11:00 photo joins the 11:00 scene, not 09:00.
|
|
113
|
+
const spans = groups.map((group) => ({ start: group[0]!.time, end: group[group.length - 1]!.time }));
|
|
114
|
+
const sceneEntries: Array<Array<(typeof entries)[number]>> = groups.map(() => []);
|
|
115
|
+
for (const item of entries) {
|
|
116
|
+
if (groups.length === 0) break;
|
|
117
|
+
let best = 0;
|
|
118
|
+
let bestDistance = Infinity;
|
|
119
|
+
spans.forEach((span, index) => {
|
|
120
|
+
const distance = item.time < span.start ? span.start - item.time : item.time > span.end ? item.time - span.end : 0;
|
|
121
|
+
if (distance < bestDistance) { best = index; bestDistance = distance; }
|
|
122
|
+
});
|
|
123
|
+
sceneEntries[best]!.push(item);
|
|
124
|
+
}
|
|
125
|
+
const scenes = groups.map((group, index) => {
|
|
126
|
+
const assigned = sceneEntries[index]!;
|
|
127
|
+
const times = [group[0]!.time, group[group.length - 1]!.time, ...assigned.map((item) => item.time)];
|
|
128
|
+
return {
|
|
129
|
+
start: iso(Math.min(...times)), end: iso(Math.max(...times)),
|
|
130
|
+
photos: group.map((item) => item.photo), transcript: assigned.map((item) => item.entry),
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
scenes, undatedPhotos,
|
|
135
|
+
transcriptWithoutPhotos: groups.length === 0 ? entries.map((item) => item.entry) : [],
|
|
136
|
+
timeZoneOffsetMinutes: offset,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function localTime(instant: string, offset: number): string {
|
|
141
|
+
const shifted = new Date(Date.parse(instant) + offset * MINUTE).toISOString().slice(0, 16).replace('T', ' ');
|
|
142
|
+
const sign = offset >= 0 ? '+' : '-';
|
|
143
|
+
const hours = String(Math.floor(Math.abs(offset) / 60)).padStart(2, '0');
|
|
144
|
+
const minutes = String(Math.abs(offset) % 60).padStart(2, '0');
|
|
145
|
+
return `${shifted} UTC${sign}${hours}:${minutes}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function photoLine(photo: FieldPhoto, label: string): string {
|
|
149
|
+
const file = encodeURIComponent(photo.file).replace(/%2F/gi, '/').replace(/[()]/g, (char) => char === '(' ? '%28' : '%29');
|
|
150
|
+
const alt = photo.file.split(/[\\/]/).at(-1)?.replace(/\]/g, '\\]') ?? photo.file;
|
|
151
|
+
return `- ${label} `;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Synchronous, side-effect-free Markdown rendering; summaries are optional placeholders. */
|
|
155
|
+
export function renderFieldNote(timeline: FieldTimeline, summaries: readonly string[] = []): string {
|
|
156
|
+
if (summaries.length > timeline.scenes.length + (timeline.transcriptWithoutPhotos.length ? 1 : 0)) {
|
|
157
|
+
throw new RangeError('More summaries than transcript sections');
|
|
158
|
+
}
|
|
159
|
+
const lines = ['# 현장 노트', ''];
|
|
160
|
+
timeline.scenes.forEach((scene, i) => {
|
|
161
|
+
lines.push(`## 장면 ${i + 1} · ${localTime(scene.start, timeline.timeZoneOffsetMinutes)} – ${localTime(scene.end, timeline.timeZoneOffsetMinutes)}`);
|
|
162
|
+
lines.push('', '### 전사 요약', summaries[i]?.trim() || '_(전사 요약 자리)_', '', '### 사진');
|
|
163
|
+
for (const photo of scene.photos) lines.push(photoLine(photo, localTime(photo.takenAt, timeline.timeZoneOffsetMinutes)));
|
|
164
|
+
lines.push('');
|
|
165
|
+
});
|
|
166
|
+
if (timeline.transcriptWithoutPhotos.length) {
|
|
167
|
+
lines.push('## 사진 없는 전사', '', '### 전사 요약',
|
|
168
|
+
summaries[timeline.scenes.length]?.trim() || '_(전사 요약 자리)_', '', '### 시간순 전사');
|
|
169
|
+
for (const entry of timeline.transcriptWithoutPhotos) {
|
|
170
|
+
lines.push(`- ${entry.timestamp} · ${entry.speaker}: ${entry.text}`);
|
|
171
|
+
}
|
|
172
|
+
lines.push('');
|
|
173
|
+
}
|
|
174
|
+
if (timeline.undatedPhotos.length) {
|
|
175
|
+
lines.push('## 시각 없음', '');
|
|
176
|
+
for (const photo of timeline.undatedPhotos) lines.push(photoLine(photo, '시각 없음'));
|
|
177
|
+
lines.push('');
|
|
178
|
+
}
|
|
179
|
+
return lines.join('\n').trimEnd() + '\n';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** LLM calls are supplied by the caller; JSON transcripts need no transcription call. */
|
|
183
|
+
export async function generateFieldNote(
|
|
184
|
+
photos: readonly FieldPhoto[],
|
|
185
|
+
transcript: readonly TranscriptEntry[] | string,
|
|
186
|
+
llm: FieldNoteLlm = {},
|
|
187
|
+
options: FieldNoteOptions = {},
|
|
188
|
+
): Promise<string> {
|
|
189
|
+
let entries: readonly TranscriptEntry[];
|
|
190
|
+
if (typeof transcript === 'string') {
|
|
191
|
+
if (!llm.transcribe) throw new Error('A transcribe dependency is required for raw transcript input');
|
|
192
|
+
entries = await llm.transcribe(transcript);
|
|
193
|
+
} else entries = transcript;
|
|
194
|
+
const timeline = alignFieldNote(photos, entries, options);
|
|
195
|
+
const sections = timeline.scenes.map((scene) => scene.transcript);
|
|
196
|
+
if (timeline.transcriptWithoutPhotos.length) sections.push(timeline.transcriptWithoutPhotos);
|
|
197
|
+
const summaries = llm.summarize
|
|
198
|
+
? await Promise.all(sections.map((segment) => segment.length ? llm.summarize!(segment) : ''))
|
|
199
|
+
: [];
|
|
200
|
+
return renderFieldNote(timeline, summaries);
|
|
201
|
+
}
|