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
|
@@ -1,35 +1,71 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
|
|
3
4
|
import { Database } from 'bun:sqlite';
|
|
4
5
|
import { dirname, join, resolve } from 'node:path';
|
|
5
6
|
import { promisify } from 'node:util';
|
|
6
7
|
import { TRACK_AGENT_FORBIDDEN_ACTION_REGEX, universeLaunch } from '../autopilot/track-agent.js';
|
|
7
8
|
import { debug } from '../debug/log.js';
|
|
9
|
+
import { DecisionLedger, type DecisionEntry } from '../decisions/decision-ledger.js';
|
|
8
10
|
import { effectiveInstanceRoot, releaseLedgerRoot } from '../instance/resolve.js';
|
|
9
|
-
import { getUserConfig, type SeatLoopConfig } from '../user-config.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
11
|
+
import { getUserConfig, type PersonaLoopConfig, type SeatLoopConfig, type UserConfig } from '../user-config.js';
|
|
12
|
+
import { checkSeatBudget, type RunningSeatPod } from '../org/seat-budget.js';
|
|
13
|
+
import { DEFAULT_STALL_PARENTS, findStalls } from '../org/stall-escalation.js';
|
|
14
|
+
import { openMsgStore, type MessageEnvelope } from '../msg/msg-store.js';
|
|
15
|
+
import { loadLayeredPersonaDirs, resolveRepositoryPersonaDir, resolveStatePersonaDir } from '../persona/global-registry.js';
|
|
16
|
+
import { nextPersonaTodo, personaTodoPath, readPersonaTodos, type PersonaTodo } from '../persona/persona-todo.js';
|
|
17
|
+
import { PersonaRegistry } from '../persona/registry.js';
|
|
18
|
+
import { answerSeat, askSeat, deliveredSeatQuestion, hasSeatAnswer, isSeatId, seatQuestions, type SeatId } from '../seat-dispatch/seat-questions.js';
|
|
19
|
+
import { queryRunningRuns } from '../self-implement/running-runs.js';
|
|
20
|
+
import { listChecklist, type Checklist } from '../release-loop/checklist.js';
|
|
21
|
+
import { readSchedules, releasedVersion } from '../release-loop/feature-store.js';
|
|
12
22
|
|
|
13
23
|
// `kind`/`createdAt` are the V3 shadow-compare keys (내부 문서 `METHOD-v3-shadow-compare-2026-10-02` · MK 10-02 18:54).
|
|
14
|
-
export type SeatItem = { source: 'request' | 'checklist'; kind?: 'request' | 'cell'; id: string; title: string; text: string; version?: string; queuedAt?: string; createdAt?: string };
|
|
24
|
+
export type SeatItem = { source: 'request' | 'checklist' | 'hook' | 'seat-question'; kind?: 'request' | 'cell' | 'hook-task' | 'seat-question'; id: string; title: string; text: string; evidence?: string; status?: string; version?: string; seat?: string; evidenceHash?: string; asOf?: string; queuedAt?: string; createdAt?: string; from?: SeatId };
|
|
15
25
|
export type SeatInputs = { requests: SeatItem[]; checklist: SeatItem[]; role: string };
|
|
16
|
-
// V3 ledger row: `ts` · `item.id` · `item.kind` · `item.createdAt` · `action` (door or skipped-*) · `reason` (
|
|
17
|
-
export type SeatAction = 'decision' | 'harness' | 'skipped-budget' | 'skipped-empty';
|
|
18
|
-
export type
|
|
26
|
+
// V3 ledger row: `ts` · `item.id` · `item.kind` · `item.createdAt` · `action` (door, wait or skipped-*) · `reason` (matched rule or forbidden word).
|
|
27
|
+
export type SeatAction = 'decision' | 'harness' | 'wait' | 'seat-question' | 'seat-answer' | 'skipped-budget' | 'skipped-empty';
|
|
28
|
+
export type SeatInquiry = { to: SeatId | 'CEO'; question: string };
|
|
29
|
+
export type SeatReply = { answer: string } | { to: 'CEO'; question: string };
|
|
30
|
+
export type OpCandidate =
|
|
31
|
+
| { kind: 'release-readiness'; version: string | null; verdict: 'ready' | 'not-ready' | 'no-open-release'; cutAt?: string; landBy?: string | null; red: string[]; undecided: string[]; blocked: string[] }
|
|
32
|
+
| { kind: 'unassigned-cell'; version: string | null; id: string | null; title?: string; status?: string; verdict: 'assign' | 'none' }
|
|
33
|
+
| { kind: 'decision-delegation'; id: string | null; title?: string; category?: DecisionEntry['category']; dueAt?: string; verdict: 'review-delegation' | 'none' };
|
|
34
|
+
export type SeatEntry = { seat: string; ts?: string; at: string; status: 'shadow' | 'attempting' | 'outcome-unknown' | 'launched' | 'hitl' | 'wait' | 'asked' | 'answered' | 'awaiting-answer' | 'skipped-budget' | 'skipped-empty'; item?: SeatItem; candidate?: OpCandidate; action?: SeatAction; reason?: string; runId?: string; inquiry?: SeatInquiry; answer?: string; escalated?: boolean };
|
|
19
35
|
export type SeatLoopResult = SeatEntry | { seat: string; status: 'skipped-off' };
|
|
36
|
+
export type PersonaShadowEntry = { personaId: string; ts: string; status: 'shadow' | 'skipped-empty'; todo: PersonaTodo | null };
|
|
20
37
|
export type SeatDeps = {
|
|
38
|
+
personaConfig?: PersonaLoopConfig;
|
|
39
|
+
personaRegistry?: PersonaRegistry;
|
|
40
|
+
personaDir?: string;
|
|
41
|
+
appendPersona?: (path: string, entry: PersonaShadowEntry) => void;
|
|
21
42
|
root?: string;
|
|
22
43
|
repo?: string;
|
|
23
44
|
config?: SeatLoopConfig;
|
|
45
|
+
budgetConfig?: Pick<UserConfig, 'org'>;
|
|
46
|
+
running?: () => readonly RunningSeatPod[];
|
|
47
|
+
escalate?: (parent: string, line: string) => Promise<void>;
|
|
24
48
|
now?: () => Date;
|
|
25
49
|
read?: (path: string) => string;
|
|
26
50
|
run?: (args: string[]) => Promise<string>;
|
|
27
51
|
append?: (path: string, entry: SeatEntry) => void;
|
|
28
52
|
versions?: () => string[];
|
|
53
|
+
/** Release schedules (version · cutAt) — a version is open while its cut is still ahead. Default reads the release ledger; unreadable ⇒ no checklist candidates (fail closed). */
|
|
54
|
+
schedules?: () => Array<{ version: string; cutAt: string; landBy?: string | null }>;
|
|
29
55
|
/** Checklist items of one version — default reads the release ledger DB (checklist.json is only a legacy import source since REL5b). */
|
|
30
|
-
checklistItems?: (version: string) => Array<{ id: string; title: string; status: string; owner?: string | null }>;
|
|
56
|
+
checklistItems?: (version: string) => Array<{ id: string; title: string; status: string; owner?: string | null; disposition?: string; evidence?: string }>;
|
|
57
|
+
/** Full checklist snapshot including history, for stall detection. */
|
|
58
|
+
stallChecklist?: (version: string) => Checklist;
|
|
59
|
+
/** Inject a mailbox write for isolated failure tests; production uses the seat message store. */
|
|
60
|
+
stallDelivery?: (message: MessageEnvelope, key: string, root: string) => boolean;
|
|
61
|
+
/** Open cards from the decision ledger; injected only for isolated ledger tests. */
|
|
62
|
+
pendingDecisions?: () => Array<Pick<DecisionEntry, 'id' | 'title' | 'category' | 'dueAt'>>;
|
|
31
63
|
ledgerFiles?: (directory: string) => string[];
|
|
32
64
|
lockContended?: () => void;
|
|
65
|
+
/** Supply a question only when this seat lacks evidence for the selected work item. */
|
|
66
|
+
inquire?: (seat: SeatId, item: SeatItem) => Promise<SeatInquiry | null> | SeatInquiry | null;
|
|
67
|
+
/** Supply the receiving seat's answer; absent evidence must not invent a reply. */
|
|
68
|
+
reply?: (seat: SeatId, question: string, from: SeatId) => Promise<SeatReply | null> | SeatReply | null;
|
|
33
69
|
};
|
|
34
70
|
|
|
35
71
|
const repoRoot = resolve(import.meta.dir, '../..');
|
|
@@ -84,6 +120,48 @@ export function seatLedgerPath(seat: string, root: string, now: Date): string {
|
|
|
84
120
|
return join(root, 'seat-loop', seat, `${seatDay(now)}.jsonl`);
|
|
85
121
|
}
|
|
86
122
|
|
|
123
|
+
export function personaShadowLedgerPath(personaId: string, root: string, now: Date): string {
|
|
124
|
+
// Use the todo module's ID guard for this second per-persona filesystem path too.
|
|
125
|
+
personaTodoPath(root, personaId);
|
|
126
|
+
return join(root, 'persona-loop', personaId, `${seatDay(now)}.jsonl`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function runPersonaShadowOnce(deps: SeatDeps = {}): Promise<PersonaShadowEntry[]> {
|
|
130
|
+
if ((deps.personaConfig ?? getUserConfig().loops?.persona)?.enabled !== true) return [];
|
|
131
|
+
const root = deps.root ?? effectiveInstanceRoot();
|
|
132
|
+
const dir = deps.personaDir ?? (deps.root ? join(root, 'personas') : resolveStatePersonaDir());
|
|
133
|
+
const registry = deps.personaRegistry ?? new PersonaRegistry();
|
|
134
|
+
if (!deps.personaRegistry) {
|
|
135
|
+
const loaded = await loadLayeredPersonaDirs(registry, deps.root ? [dir] : [dir, resolveRepositoryPersonaDir()]);
|
|
136
|
+
for (const error of loaded.errors) observe('persona-load-error', { error });
|
|
137
|
+
}
|
|
138
|
+
const now = (deps.now ?? (() => new Date()))();
|
|
139
|
+
const entries: PersonaShadowEntry[] = [];
|
|
140
|
+
for (const profile of registry.list().sort((a, b) => a.personaId.localeCompare(b.personaId))) {
|
|
141
|
+
const personaId = profile.personaId;
|
|
142
|
+
const { todos, errors } = readPersonaTodos(dir, personaId);
|
|
143
|
+
for (const error of errors) observe('persona-todo-error', { personaId, error });
|
|
144
|
+
const todo = nextPersonaTodo(todos);
|
|
145
|
+
const entry: PersonaShadowEntry = { personaId, ts: now.toISOString(), status: todo ? 'shadow' : 'skipped-empty', todo };
|
|
146
|
+
const path = personaShadowLedgerPath(personaId, root, now);
|
|
147
|
+
if (deps.appendPersona) deps.appendPersona(path, entry);
|
|
148
|
+
else {
|
|
149
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
150
|
+
appendFileSync(path, `${JSON.stringify(entry)}\n`, { mode: 0o600 });
|
|
151
|
+
}
|
|
152
|
+
observe('persona-shadow', { personaId, status: entry.status, todoId: todo?.id ?? null });
|
|
153
|
+
entries.push(entry);
|
|
154
|
+
}
|
|
155
|
+
return entries;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export async function runSeatLoopTurn(seat: string, deps: SeatDeps = {}): Promise<SeatLoopResult> {
|
|
159
|
+
const result = await runSeatLoopOnce(seat, deps);
|
|
160
|
+
try { await runPersonaShadowOnce(deps); }
|
|
161
|
+
catch (error) { observe('persona-shadow-error', { error: String(error).slice(0, 200) }); }
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
|
|
87
165
|
function rows(text: string): Record<string, unknown>[] {
|
|
88
166
|
return text.split('\n').filter(Boolean).map((line) => JSON.parse(line) as Record<string, unknown>);
|
|
89
167
|
}
|
|
@@ -93,17 +171,102 @@ function versionOrder(a: string, b: string): number {
|
|
|
93
171
|
return (aa[0]! - bb[0]!) || (aa[1]! - bb[1]!) || (aa[2]! - bb[2]!);
|
|
94
172
|
}
|
|
95
173
|
|
|
96
|
-
|
|
174
|
+
function itemKey(item: SeatItem): string { return `${item.source}:${item.version ?? ''}:${item.id}`; }
|
|
175
|
+
|
|
176
|
+
function itemSnapshot(item: SeatItem): string {
|
|
177
|
+
return JSON.stringify([item.title, item.text, item.evidence ?? null, item.status ?? null]);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function decisionRef(seat: string, item: SeatItem): string {
|
|
181
|
+
return `seat-loop:${seat}:${encodeURIComponent(itemKey(item))}`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function decisionReceipt(root: string, seat: string, item: SeatItem): DecisionEntry | undefined {
|
|
185
|
+
const ref = decisionRef(seat, item);
|
|
186
|
+
return new DecisionLedger({ stateDir: root }).list({ status: 'all' }).find((card) => card.refs?.includes(ref));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// RM3 (asked/answered · decision-card receipts for seat inquiries) ⊕ main (checklist cells re-picked by evidence, not by a shadow row).
|
|
190
|
+
function handledKeys(ledger: readonly SeatEntry[], shadow: boolean, root?: string): Set<string> {
|
|
191
|
+
const inquiryRow = (entry: SeatEntry) => entry.action === 'seat-question' || entry.action === 'seat-answer' || !!entry.inquiry;
|
|
192
|
+
return new Set(ledger.filter((entry, index) => entry.status === 'asked' || entry.status === 'answered'
|
|
193
|
+
|| (entry.status === 'hitl' && (!entry.inquiry || (!!root && !!entry.item && !!decisionReceipt(root, entry.seat, entry.item))))
|
|
194
|
+
|| (entry.inquiry?.to === 'CEO' && !!root && !!entry.item && !!decisionReceipt(root, entry.seat, entry.item))
|
|
195
|
+
|| (!inquiryRow(entry) && entry.status === 'outcome-unknown')
|
|
196
|
+
|| (!inquiryRow(entry) && entry.status === 'attempting' && !(entry.item?.source === 'checklist' && ledger.slice(index + 1).some((later) =>
|
|
197
|
+
later.seat === entry.seat && later.status === 'launched' && later.item?.source === 'checklist'
|
|
198
|
+
&& later.item.version === entry.item?.version && later.item.id === entry.item?.id
|
|
199
|
+
&& later.item.title === entry.item?.title && later.item.evidenceHash === entry.item?.evidenceHash)))
|
|
200
|
+
|| (shadow && entry.status === 'shadow' && entry.action !== 'wait' && entry.item?.source !== 'checklist')
|
|
201
|
+
|| (entry.status === 'launched' && entry.item?.source !== 'checklist'))
|
|
202
|
+
.filter((entry): entry is SeatEntry & { item: SeatItem } => !!entry.item && !!entry.item.source && typeof entry.item.id === 'string')
|
|
203
|
+
.map((entry) => itemKey(entry.item)));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function eligibleItem(item: SeatItem, ledger: readonly SeatEntry[], handled: ReadonlySet<string>, shadow: boolean): boolean {
|
|
207
|
+
const key = itemKey(item);
|
|
208
|
+
if (handled.has(key)) return false;
|
|
209
|
+
const lastWait = [...ledger].reverse().find((entry) => entry.item && itemKey(entry.item) === key
|
|
210
|
+
&& (entry.status === 'wait' || (shadow && entry.status === 'shadow' && entry.action === 'wait')));
|
|
211
|
+
return !lastWait?.item || itemSnapshot(lastWait.item) !== itemSnapshot(item);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Compare checklist snapshots without consulting release history or treating a version move as new work.
|
|
215
|
+
export function alreadyHandled(item: SeatItem, ledger: readonly SeatEntry[]): boolean {
|
|
216
|
+
if (item.source !== 'checklist' || !item.seat || !item.asOf || item.evidenceHash === undefined) return false;
|
|
217
|
+
const now = Date.parse(item.asOf);
|
|
218
|
+
if (!Number.isFinite(now)) return false;
|
|
219
|
+
return ledger.some((entry) => {
|
|
220
|
+
const at = Date.parse(entry.at || entry.ts || '');
|
|
221
|
+
return entry.seat === item.seat && at >= now - 7 * 24 * 60 * 60 * 1000 && at <= now
|
|
222
|
+
// A shadowed seat question was never delivered — it must be picked again once delivery is on (RM3 · TC merge 10-03).
|
|
223
|
+
&& (entry.status === 'launched' || (entry.status === 'shadow' && !entry.inquiry))
|
|
224
|
+
&& entry.item?.source === 'checklist' && entry.item.id === item.id
|
|
225
|
+
&& entry.item.title === item.title && entry.item.evidenceHash === item.evidenceHash;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function readSeatLedger(seat: string, deps: SeatDeps, now: Date): SeatEntry[] {
|
|
230
|
+
const directory = dirname(seatLedgerPath(seat, deps.root ?? effectiveInstanceRoot(), now));
|
|
231
|
+
const files = deps.ledgerFiles?.(directory) ?? (existsSync(directory) ? readdirSync(directory) : []);
|
|
232
|
+
return files.filter((name) => /^\d{4}-\d{2}-\d{2}\.jsonl$/.test(name) && name <= `${seatDay(now)}.jsonl`)
|
|
233
|
+
.flatMap((name) => rows((deps.read ?? defaultRead)(join(directory, name))) as SeatEntry[]);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export async function gatherSeatInputs(seat: string, deps: SeatDeps = {}, ledger?: readonly SeatEntry[]): Promise<SeatInputs> {
|
|
97
237
|
const read = deps.read ?? defaultRead;
|
|
98
238
|
const root = deps.root ?? effectiveInstanceRoot();
|
|
239
|
+
const activeLedger = ledger ?? readSeatLedger(seat, deps, (deps.now ?? (() => new Date()))());
|
|
240
|
+
const consumed = handledKeys(activeLedger, (deps.config ?? getUserConfig().loops?.seat)?.mode === 'shadow', root);
|
|
99
241
|
const requests = new Map<string, Record<string, unknown>>();
|
|
100
242
|
for (const row of rows(read(join(root, 'seat-requests', 'requests.jsonl')))) {
|
|
101
243
|
if (typeof row.key === 'string') requests.set(row.key, row);
|
|
102
244
|
}
|
|
103
|
-
const pending =
|
|
245
|
+
const pending: SeatItem[] = seatQuestions(root, seat as SeatId)
|
|
246
|
+
.filter((message) => !hasSeatAnswer(root, message) && !consumed.has(`seat-question::${message.id}`))
|
|
247
|
+
.map((message) => ({ source: 'seat-question', kind: 'seat-question', id: String(message.id), title: message.body, text: message.body,
|
|
248
|
+
from: message.from as SeatId, queuedAt: message.createdAt, createdAt: message.createdAt }));
|
|
249
|
+
const ordinary: SeatItem[] = [...requests.values()]
|
|
104
250
|
.filter((row) => row.seat === seat && (row.status === 'pending' || row.status === 'queued') && typeof row.text === 'string')
|
|
105
|
-
.map((row) => ({ source: 'request' as const, kind: 'request' as const, id: String(row.key), title: String(row.text), text: String(row.text), queuedAt: String(row.queuedAt ?? ''), createdAt: String(row.queuedAt ?? '') }))
|
|
251
|
+
.map((row) => ({ source: 'request' as const, kind: 'request' as const, id: String(row.key), title: String(row.text), text: String(row.text), ...(typeof row.evidence === 'string' ? { evidence: row.evidence } : {}), queuedAt: String(row.queuedAt ?? ''), createdAt: String(row.queuedAt ?? '') }))
|
|
106
252
|
.sort((a, b) => a.queuedAt.localeCompare(b.queuedAt) || a.id.localeCompare(b.id));
|
|
253
|
+
const store = openMsgStore(join(root, 'msg', 'messages.db'));
|
|
254
|
+
const hookMessages: ReturnType<typeof store.listByRecipient> = [];
|
|
255
|
+
try {
|
|
256
|
+
let cursor = 0;
|
|
257
|
+
while (true) {
|
|
258
|
+
const batch = store.listByRecipient(seat, cursor, 1000);
|
|
259
|
+
hookMessages.push(...batch.filter(message => message.kind === 'hook-task'));
|
|
260
|
+
if (batch.length < 1000) break;
|
|
261
|
+
cursor = batch[batch.length - 1]!.id;
|
|
262
|
+
}
|
|
263
|
+
} finally { store.close(); }
|
|
264
|
+
for (const message of hookMessages) {
|
|
265
|
+
const item: SeatItem = { source: 'hook', kind: 'hook-task', id: String(message.id), title: message.body, text: message.body, queuedAt: message.createdAt, createdAt: message.createdAt };
|
|
266
|
+
if (eligibleItem(item, activeLedger, consumed, (deps.config ?? getUserConfig().loops?.seat)?.mode === 'shadow')) ordinary.push(item);
|
|
267
|
+
}
|
|
268
|
+
ordinary.sort((a, b) => (a.queuedAt ?? '').localeCompare(b.queuedAt ?? '') || a.id.localeCompare(b.id));
|
|
269
|
+
pending.push(...ordinary);
|
|
107
270
|
const releaseRoot = deps.root ?? releaseLedgerRoot();
|
|
108
271
|
const versions = deps.versions ?? (() => {
|
|
109
272
|
const dir = join(releaseRoot, 'release');
|
|
@@ -113,16 +276,42 @@ export async function gatherSeatInputs(seat: string, deps: SeatDeps = {}): Promi
|
|
|
113
276
|
return [...new Set([...fromDirs, ...fromSchedule])].filter((v) => /^\d+\.\d+\.\d+$/.test(v));
|
|
114
277
|
});
|
|
115
278
|
const itemsOf = deps.checklistItems ?? ((version: string) => listChecklist(version).items);
|
|
279
|
+
let shipped: string | undefined;
|
|
280
|
+
try { shipped = releasedVersion(releaseRoot); }
|
|
281
|
+
catch (error) {
|
|
282
|
+
console.warn(`seat loop: cannot read published versions; retaining previous order (${String(error).slice(0, 200)})`);
|
|
283
|
+
}
|
|
284
|
+
// UX J2 criterion (10-03): only versions whose cut is still ahead are open; if the schedule can't be read, pick no cell.
|
|
285
|
+
const asOf = (deps.now ?? (() => new Date()))().toISOString();
|
|
286
|
+
const nowMs = Date.parse(asOf);
|
|
287
|
+
let open: Set<string> | null = null;
|
|
288
|
+
try {
|
|
289
|
+
const rows = (deps.schedules ?? (() => readSchedules()))();
|
|
290
|
+
open = new Set(rows.filter((row) => Number.isFinite(Date.parse(row.cutAt)) && Date.parse(row.cutAt) > nowMs).map((row) => row.version));
|
|
291
|
+
} catch (error) {
|
|
292
|
+
console.warn(`seat loop: cannot read release schedules; no checklist cell is picked (${String(error).slice(0, 200)})`);
|
|
293
|
+
debug.log('seat.loop', 'schedule-unreadable', { seat, error: String(error).slice(0, 200) });
|
|
294
|
+
}
|
|
116
295
|
const checklist: SeatItem[] = [];
|
|
117
|
-
for (const version of versions().sort(versionOrder)) {
|
|
296
|
+
for (const version of open === null ? [] : versions().sort(versionOrder)) {
|
|
297
|
+
if (!open!.has(version)) {
|
|
298
|
+
debug.log('seat.loop', 'skip-closed-version', { seat, version });
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
118
301
|
let items: ReturnType<typeof itemsOf>;
|
|
119
302
|
try { items = itemsOf(version); } catch (error) {
|
|
120
303
|
debug.log('seat.loop', 'checklist-unreadable', { seat, version, error: String(error).slice(0, 200) });
|
|
121
304
|
continue;
|
|
122
305
|
}
|
|
306
|
+
if (shipped && versionOrder(version, shipped) <= 0) {
|
|
307
|
+
const cells = items.filter((item) => item.owner === seat && (item.status === 'yellow' || item.status === 'red')).length;
|
|
308
|
+
debug.log('seat.loop', 'skip-shipped-version', { version, cells });
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
123
311
|
for (const item of items) {
|
|
124
312
|
if (item.owner === seat && (item.status === 'yellow' || item.status === 'red')) {
|
|
125
|
-
checklist.push({ source: 'checklist', kind: 'cell', version, id: item.id, title: item.title, text: item.title
|
|
313
|
+
checklist.push({ source: 'checklist', kind: 'cell', version, seat, asOf, id: item.id, title: item.title, text: item.title, status: item.status,
|
|
314
|
+
...(item.evidence ? { evidence: item.evidence } : {}), evidenceHash: createHash('sha256').update(item.evidence ?? '').digest('hex') });
|
|
126
315
|
}
|
|
127
316
|
}
|
|
128
317
|
}
|
|
@@ -130,24 +319,139 @@ export async function gatherSeatInputs(seat: string, deps: SeatDeps = {}): Promi
|
|
|
130
319
|
return { requests: pending, checklist, role: read(join(deps.repo ?? repoRoot, 'docs', 'roles', `${seat}.md`)).slice(0, 4_000) };
|
|
131
320
|
}
|
|
132
321
|
|
|
133
|
-
function
|
|
322
|
+
function opCandidates(deps: SeatDeps, now: Date): OpCandidate[] {
|
|
323
|
+
const releaseRoot = deps.root ?? releaseLedgerRoot();
|
|
324
|
+
const shipped = releasedVersion(releaseRoot);
|
|
325
|
+
let schedules: ReturnType<NonNullable<SeatDeps['schedules']>>;
|
|
326
|
+
try { schedules = (deps.schedules ?? (() => readSchedules(releaseRoot)))(); }
|
|
327
|
+
catch (error) {
|
|
328
|
+
observe('schedule-unreadable', { seat: 'OP', error: String(error).slice(0, 200) });
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
const open = schedules.filter((row) => /^\d+\.\d+\.\d+$/.test(row.version)
|
|
332
|
+
&& Number.isFinite(Date.parse(row.cutAt)) && Date.parse(row.cutAt) > now.getTime()
|
|
333
|
+
&& (!shipped || versionOrder(row.version, shipped) > 0)).sort((a, b) => versionOrder(a.version, b.version));
|
|
334
|
+
const itemsOf = deps.checklistItems ?? ((version: string) => listChecklist(version).items);
|
|
335
|
+
const releases = open.map((schedule) => ({ schedule, items: itemsOf(schedule.version).sort((a, b) => a.id.localeCompare(b.id)) }));
|
|
336
|
+
const first = releases[0];
|
|
337
|
+
const red = first?.items.filter((item) => item.status === 'red').map((item) => item.id) ?? [];
|
|
338
|
+
const undecided = first?.items.filter((item) => item.status === 'yellow' && !item.disposition).map((item) => item.id) ?? [];
|
|
339
|
+
const blocked = first?.items.filter((item) => item.status === 'yellow' && item.disposition === 'block').map((item) => item.id) ?? [];
|
|
340
|
+
const ready = first !== undefined && first.items.length > 0 && first.items.every((item) =>
|
|
341
|
+
item.status === 'green' || item.status === 'done'
|
|
342
|
+
|| (item.status === 'yellow' && (item.disposition === 'move' || item.disposition === 'known-issue')));
|
|
343
|
+
const unassigned = releases.flatMap(({ schedule, items }) => items
|
|
344
|
+
.filter((item) => !item.owner && (item.status === 'yellow' || item.status === 'red'))
|
|
345
|
+
.map((item): OpCandidate => ({ kind: 'unassigned-cell', version: schedule.version, id: item.id,
|
|
346
|
+
title: item.title, status: item.status, verdict: 'assign' })));
|
|
347
|
+
const pending = (deps.pendingDecisions ?? (() => new DecisionLedger({ stateDir: deps.root }).list({ status: 'open' })))()
|
|
348
|
+
.sort((a, b) => (a.dueAt ?? '\uffff').localeCompare(b.dueAt ?? '\uffff') || a.id.localeCompare(b.id))[0];
|
|
349
|
+
return [
|
|
350
|
+
{ kind: 'release-readiness', version: first?.schedule.version ?? null,
|
|
351
|
+
verdict: !first ? 'no-open-release' : ready ? 'ready' : 'not-ready',
|
|
352
|
+
...(first ? { cutAt: first.schedule.cutAt, landBy: first.schedule.landBy ?? null } : {}), red, undecided, blocked },
|
|
353
|
+
...(unassigned.length ? unassigned : [{ kind: 'unassigned-cell' as const, version: null, id: null, verdict: 'none' as const }]),
|
|
354
|
+
{ kind: 'decision-delegation', id: pending?.id ?? null,
|
|
355
|
+
...(pending ? { title: pending.title, category: pending.category, ...(pending.dueAt ? { dueAt: pending.dueAt } : {}) } : {}),
|
|
356
|
+
verdict: pending ? 'review-delegation' : 'none' },
|
|
357
|
+
];
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function deliverStall(message: MessageEnvelope, key: string, root: string): boolean {
|
|
361
|
+
const store = openMsgStore(join(root, 'msg', 'messages.db'));
|
|
362
|
+
try {
|
|
363
|
+
// Persist the exact stall identity alongside its message, atomically across processes.
|
|
364
|
+
store.db.exec('BEGIN IMMEDIATE');
|
|
365
|
+
try {
|
|
366
|
+
store.db.exec('CREATE TABLE IF NOT EXISTS seat_stall_deliveries (key TEXT PRIMARY KEY)');
|
|
367
|
+
const inserted = store.db.query('INSERT OR IGNORE INTO seat_stall_deliveries (key) VALUES (?)').run(key);
|
|
368
|
+
if (inserted.changes === 0) { store.db.exec('COMMIT'); return false; }
|
|
369
|
+
store.append(message);
|
|
370
|
+
store.db.exec('COMMIT');
|
|
371
|
+
return true;
|
|
372
|
+
} catch (error) { store.db.exec('ROLLBACK'); throw error; }
|
|
373
|
+
} finally { store.close(); }
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function escalateStalls(seat: string, deps: SeatDeps, config: SeatLoopConfig, now: Date): void {
|
|
377
|
+
const root = deps.root ?? releaseLedgerRoot();
|
|
378
|
+
const schedules = (deps.schedules ?? (() => readSchedules(root)))();
|
|
379
|
+
const shipped = releasedVersion(root);
|
|
380
|
+
const failures: Error[] = [];
|
|
381
|
+
for (const schedule of schedules) {
|
|
382
|
+
if (!/^\d+\.\d+\.\d+$/.test(schedule.version)
|
|
383
|
+
|| !Number.isFinite(Date.parse(schedule.cutAt)) || Date.parse(schedule.cutAt) <= now.getTime()
|
|
384
|
+
|| (shipped && versionOrder(schedule.version, shipped) <= 0)) continue;
|
|
385
|
+
try {
|
|
386
|
+
// Same ledger root as the schedules and shipped version above (TC harvest · review round 3).
|
|
387
|
+
const checklist = (deps.stallChecklist ?? ((version: string) => listChecklist(version, deps.root)))(schedule.version);
|
|
388
|
+
for (const stall of findStalls(checklist, config, now)) {
|
|
389
|
+
if (stall.from !== seat) continue;
|
|
390
|
+
const { item, from, to, stalledMin, startedAt, reason } = stall;
|
|
391
|
+
const data = { item: item.id, from, to, stalledMin, version: schedule.version, reason };
|
|
392
|
+
if (config.mode === 'shadow') {
|
|
393
|
+
debug.log('org.stall', 'escalate', { ...data, mode: 'shadow' });
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
const key = JSON.stringify([schedule.version, item.id, from, to, reason, startedAt]);
|
|
397
|
+
const message: MessageEnvelope = { from, to, kind: 'stall-escalation',
|
|
398
|
+
body: `[stall:${key}] ${schedule.version} 체크리스트 ${item.id} · ${item.title} · ${reason} · ${stalledMin}분 정체 (${from} → ${to})` };
|
|
399
|
+
try {
|
|
400
|
+
const sent = (deps.stallDelivery ?? deliverStall)(message, key, deps.root ?? effectiveInstanceRoot());
|
|
401
|
+
if (sent) debug.log('org.stall', 'escalate', data);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
failures.push(new Error(`stall escalation ${schedule.version}/${item.id}: ${String(error)}`, { cause: error }));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
} catch (error) {
|
|
407
|
+
failures.push(new Error(`stall checklist ${schedule.version}: ${String(error)}`, { cause: error }));
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (failures.length) throw new AggregateError(failures, `stall escalation failed for ${seat}: ${failures.map((error) => error.message).join('; ')}`);
|
|
411
|
+
}
|
|
134
412
|
|
|
135
413
|
// In shadow mode a shadowed item counts as handled, so a rehearsal day walks the queue like the seat would.
|
|
136
|
-
export function pickNext(inputs: SeatInputs, ledger: readonly SeatEntry[], opts: { shadow?: boolean } = {}): SeatItem | null {
|
|
137
|
-
const handled =
|
|
138
|
-
|
|
139
|
-
|
|
414
|
+
export function pickNext(inputs: SeatInputs, ledger: readonly SeatEntry[], opts: { shadow?: boolean; root?: string } = {}): SeatItem | null {
|
|
415
|
+
const handled = handledKeys(ledger, opts.shadow === true, opts.root);
|
|
416
|
+
return [...inputs.requests, ...inputs.checklist].find((item) => {
|
|
417
|
+
if (alreadyHandled(item, ledger)) {
|
|
418
|
+
debug.log('seat.loop', 'skip-handled', { id: item.id });
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
return eligibleItem(item, ledger, handled, opts.shadow === true);
|
|
422
|
+
}) ?? null;
|
|
140
423
|
}
|
|
141
424
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
425
|
+
const SEAT_ACTION_RULES: ReadonlyArray<{ kind: 'wait' | 'harness' | 'decision'; signal: RegExp; source: 'content' | 'task'; resolvedBy?: RegExp }> = [
|
|
426
|
+
{ kind: 'wait', signal: /실측\s*대기|(?:사람|실제)\s*기기(?!\s*(?:(?:실측|측정|확인)\s*)?완료)(?:\s*(?:실측|측정|확인|대기))?|실물\s*측정(?!\s*완료)/iu, source: 'content', resolvedBy: /(?:실측|측정|(?:사람|실제)\s*기기|실물\s*측정)\s*완료/iu },
|
|
427
|
+
{ kind: 'wait', signal: /\u{1F451}\s*확인\s*대기/iu, source: 'content', resolvedBy: /\u{1F451}\s*확인\s*완료/iu },
|
|
428
|
+
{ kind: 'decision', signal: TRACK_AGENT_FORBIDDEN_ACTION_REGEX, source: 'task' },
|
|
429
|
+
{ kind: 'decision', signal: /(?:마켓|레지스트리)(?:에|로)?\s*(?:게시|등록|발행)|사이트\s*운영\s*반영|SNS(?:에|로)?\s*(?:게시|발행|업로드)|공개\s*발행/iu, source: 'content' },
|
|
430
|
+
{ kind: 'harness', signal: /(?:마켓|레지스트리|사이트|SNS|공개)?(?:에|로)?\s*(?:게시|발행|배포|등록|운영\s*반영)(?:를?\s*위한)?\s*(?:준비|초안|원고|자료|구현|점검|테스트)|(?:마켓|레지스트리|사이트|SNS|공개)?(?:에|로)?\s*(?:게시|발행|배포|등록)(?:용|를?\s*위한)\s*(?:초안|원고|자료|코드|기능)/iu, source: 'task' },
|
|
431
|
+
];
|
|
432
|
+
|
|
433
|
+
export function planAction(item: SeatItem, seat?: string): { kind: 'decision' | 'harness' | 'wait'; text: string; reason?: string } {
|
|
434
|
+
const task = `${item.id}\n${item.title}\n${item.text}`;
|
|
435
|
+
const content = `${task}\n${item.evidence ?? ''}`;
|
|
436
|
+
const preparation = SEAT_ACTION_RULES.find(({ kind }) => kind === 'harness')!;
|
|
437
|
+
const prepared = [...content.matchAll(new RegExp(preparation.signal.source, `${preparation.signal.flags.replace('g', '')}g`))];
|
|
438
|
+
const rule = SEAT_ACTION_RULES.map(({ kind, signal, source, resolvedBy }) => {
|
|
439
|
+
const text = source === 'content' ? content : task;
|
|
440
|
+
if (resolvedBy?.test(item.evidence ?? '')) return null;
|
|
441
|
+
const hits = [...text.matchAll(new RegExp(signal.source, `${signal.flags.replace('g', '')}g`))];
|
|
442
|
+
const hit = kind === 'decision' ? hits.find((match) => !prepared.some((prep) =>
|
|
443
|
+
match.index! >= prep.index! && match.index! + match[0].length <= prep.index! + prep[0].length)) : hits[0];
|
|
444
|
+
return hit ? { kind, reason: hit[0] } : null;
|
|
445
|
+
}).find((hit) => hit !== null);
|
|
446
|
+
const kind = rule?.kind ?? 'harness';
|
|
447
|
+
const reason = rule?.reason;
|
|
448
|
+
observe('plan', { kind, reason: reason ?? '' });
|
|
449
|
+
return { kind, text: seat && kind === 'harness' ? seatTaskText(seat, item) : `${item.id} ${item.title}`, ...(reason ? { reason } : {}) };
|
|
146
450
|
}
|
|
147
451
|
|
|
148
452
|
// The launched sentence names the seat, the source and the release so same-named items stay distinct.
|
|
149
453
|
export function seatTaskText(seat: string, item: SeatItem): string {
|
|
150
|
-
const where = item.source === 'checklist' ? `${item.version ?? '?'} 체크리스트 칸 ${item.id}` : `자리 요청 ${item.id}`;
|
|
454
|
+
const where = item.source === 'checklist' ? `${item.version ?? '?'} 체크리스트 칸 ${item.id}` : item.source === 'hook' ? `웹훅 작업 카드 ${item.id}` : `자리 요청 ${item.id}`;
|
|
151
455
|
return `[${seat} 자리 · ${where} · 역할 docs/roles/${seat}.md] ${item.title.replace(/[\r\n]+/g, ' ')}`;
|
|
152
456
|
}
|
|
153
457
|
|
|
@@ -162,21 +466,178 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
|
|
|
162
466
|
const path = seatLedgerPath(seat, deps.root ?? effectiveInstanceRoot(), now);
|
|
163
467
|
const directory = dirname(path);
|
|
164
468
|
return withSeatLock(directory, async () => {
|
|
165
|
-
const
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
469
|
+
const ledger = readSeatLedger(seat, deps, now);
|
|
470
|
+
const stateRoot = deps.root ?? effectiveInstanceRoot();
|
|
471
|
+
let turnError: unknown;
|
|
472
|
+
try {
|
|
473
|
+
if (seat === 'OP' && !seatQuestions(stateRoot, 'OP').some((question) =>
|
|
474
|
+
!hasSeatAnswer(stateRoot, question)
|
|
475
|
+
&& !handledKeys(ledger, config.mode !== 'on' || (config.questions ?? 'shadow') !== 'on', stateRoot).has(`seat-question::${question.id}`))) {
|
|
476
|
+
const append = deps.append ?? defaultAppend;
|
|
477
|
+
const candidates = opCandidates(deps, now);
|
|
478
|
+
const assigned = candidates.filter((candidate): candidate is Extract<OpCandidate, { kind: 'unassigned-cell' }> => candidate.kind === 'unassigned-cell' && candidate.verdict === 'assign');
|
|
479
|
+
if (assigned.length) {
|
|
480
|
+
const previous = new Map<string, Extract<OpCandidate, { kind: 'unassigned-cell' }>>();
|
|
481
|
+
for (const row of ledger) {
|
|
482
|
+
const candidate = row.candidate;
|
|
483
|
+
if (candidate?.kind !== 'unassigned-cell') continue;
|
|
484
|
+
if (candidate.verdict === 'none' && !candidate.id) previous.clear();
|
|
485
|
+
else if (candidate.id) previous.set(JSON.stringify([candidate.version, candidate.id]), candidate);
|
|
486
|
+
}
|
|
487
|
+
const active = new Set(assigned.map((candidate) => JSON.stringify([candidate.version, candidate.id])));
|
|
488
|
+
for (const [key, candidate] of previous) {
|
|
489
|
+
if (candidate.verdict === 'assign' && !active.has(key)) candidates.push({ kind: 'unassigned-cell', version: candidate.version, id: candidate.id, verdict: 'none' });
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
const previousDecisions = new Set<string>();
|
|
493
|
+
for (const row of ledger) {
|
|
494
|
+
const candidate = row.candidate;
|
|
495
|
+
if (candidate?.kind !== 'decision-delegation') continue;
|
|
496
|
+
if (candidate.verdict === 'none' && !candidate.id) previousDecisions.clear();
|
|
497
|
+
else if (candidate.id) {
|
|
498
|
+
if (candidate.verdict === 'none') previousDecisions.delete(candidate.id);
|
|
499
|
+
else previousDecisions.add(candidate.id);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const currentDecision = candidates.find((candidate): candidate is Extract<OpCandidate, { kind: 'decision-delegation' }> =>
|
|
503
|
+
candidate.kind === 'decision-delegation' && candidate.verdict === 'review-delegation');
|
|
504
|
+
if (currentDecision) {
|
|
505
|
+
for (const id of previousDecisions) {
|
|
506
|
+
if (id !== currentDecision.id) candidates.push({ kind: 'decision-delegation', id, verdict: 'none' });
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
const entries = candidates.map((candidate): SeatEntry => ({ seat, ts: now.toISOString(), at: now.toISOString(), status: 'shadow', candidate }));
|
|
510
|
+
for (const entry of entries) {
|
|
511
|
+
const candidate = entry.candidate!;
|
|
512
|
+
const previous = [...ledger].reverse().find((row) => row.candidate?.kind === candidate.kind &&
|
|
513
|
+
(candidate.kind === 'release-readiness' || row.candidate?.kind === candidate.kind && (
|
|
514
|
+
candidate.kind === 'unassigned-cell' && row.candidate.kind === 'unassigned-cell'
|
|
515
|
+
? (candidate.verdict === 'none' && !candidate.id && row.candidate.verdict === 'none' && !row.candidate.id)
|
|
516
|
+
|| row.candidate.version === candidate.version && row.candidate.id === candidate.id
|
|
517
|
+
: candidate.kind === 'decision-delegation' && row.candidate.kind === 'decision-delegation'
|
|
518
|
+
&& row.candidate.id === candidate.id)));
|
|
519
|
+
if (previous && JSON.stringify(previous.candidate) === JSON.stringify(candidate)) continue;
|
|
520
|
+
append(path, entry);
|
|
521
|
+
observe('op-judgment-shadow', { seat, candidate: entry.candidate, status: entry.status });
|
|
522
|
+
}
|
|
523
|
+
return entries[0]!;
|
|
524
|
+
}
|
|
525
|
+
const inputs = await gatherSeatInputs(seat, deps, ledger);
|
|
526
|
+
const questionsMode = config.mode === 'on' ? config.questions ?? 'shadow' : 'shadow';
|
|
527
|
+
const questionHandled = handledKeys(ledger, questionsMode !== 'on', stateRoot);
|
|
528
|
+
const pendingQuestion = inputs.requests.find((candidate) => candidate.source === 'seat-question' && !questionHandled.has(itemKey(candidate)));
|
|
529
|
+
const ordinary = pickNext({ ...inputs, requests: inputs.requests.filter((candidate) => candidate.source !== 'seat-question') }, ledger, { shadow: config.mode === 'shadow', root: stateRoot });
|
|
530
|
+
const item = pendingQuestion ?? ordinary;
|
|
531
|
+
const append = deps.append ?? defaultAppend;
|
|
532
|
+
// RM3 review must-fix: `questions` now has a default, so it can't be the trigger — a shadow seat loop never calls the model on its own.
|
|
533
|
+
if (item && (item.source === 'seat-question' || deps.inquire || (config.mode === 'on' && config.questions !== undefined))) {
|
|
534
|
+
const root = deps.root ?? effectiveInstanceRoot();
|
|
535
|
+
const unfinished = [...ledger].reverse().find((row) => row.item && itemKey(row.item) === itemKey(item)
|
|
536
|
+
&& (row.status === 'attempting' || row.status === 'outcome-unknown') && (row.action === 'seat-question' || row.action === 'seat-answer'));
|
|
537
|
+
const inquiry = item.source === 'seat-question' ? null : unfinished?.inquiry ?? await (deps.inquire ?? (async (asking: SeatId, work: SeatItem) => { try {
|
|
538
|
+
const output = JSON.parse((await (deps.run ?? defaultRun)(['agent', '--json', '--no-tools',
|
|
539
|
+
`[${asking} 자리 · 역할 docs/roles/${asking}.md] 이 항목을 판단할 근거가 부족한 경우에만 다른 자리 또는 사람에게 질문한다. 근거가 충분하면 null. JSON 객체 하나만 반환: null 또는 {"to":"OP|TC|MK|UX|CEO","question":"필요한 근거를 묻는 질문"}. 항목: ${work.id} ${work.title}\n내용: ${work.text}\n역할: ${(deps.read ?? defaultRead)(join(deps.repo ?? repoRoot, 'docs', 'roles', `${asking}.md`)).slice(0, 4_000)}`])).trim()) as { reply?: unknown };
|
|
540
|
+
const value: unknown = typeof output?.reply === 'string' ? JSON.parse(output.reply) : output;
|
|
541
|
+
if (value === null) return null;
|
|
542
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('invalid seat inquiry');
|
|
543
|
+
const record = value as Record<string, unknown>;
|
|
544
|
+
if ((record.to === 'CEO' || (typeof record.to === 'string' && isSeatId(record.to) && record.to !== asking))
|
|
545
|
+
&& typeof record.question === 'string' && record.question.trim()) return { to: record.to as SeatId | 'CEO', question: record.question };
|
|
546
|
+
throw new Error('invalid seat inquiry');
|
|
547
|
+
} catch (error) {
|
|
548
|
+
// A failed judgment call must not break the record-only path — no question this turn.
|
|
549
|
+
observe('inquire-failed', { seat: asking, item: work.id, error: String(error).slice(0, 200) });
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
}))(seat as SeatId, item);
|
|
553
|
+
const reply = item.source === 'seat-question' && item.from && (questionsMode === 'on' || deps.reply || config.questions !== undefined)
|
|
554
|
+
? unfinished?.answer ? { answer: unfinished.answer } : unfinished?.inquiry ? unfinished.inquiry : await (deps.reply ?? (async (receiver: SeatId, question: string, from: SeatId) => {
|
|
555
|
+
const evidence = inputs.checklist.map((candidate) =>
|
|
556
|
+
`${candidate.id} · ${candidate.title} (판 ${candidate.version}, ${candidate.kind})`).join('\n').slice(0, 8_000);
|
|
557
|
+
const output = JSON.parse((await (deps.run ?? defaultRun)(['agent', '--json', '--no-tools',
|
|
558
|
+
`[${receiver} 자리 · 역할 docs/roles/${receiver}.md] ${from} 자리의 질문에 근거를 갖고 답하라. 아래 ${receiver} 자리의 실제 체크리스트 근거에 없는 사실은 지어내지 마라. 모르면 사람 결정이 필요한 질문을 작성하라. JSON 하나만 반환: {"answer":"근거를 포함한 답"} 또는 {"to":"CEO","question":"결정할 질문"}. 질문: ${question}\n역할: ${inputs.role}\n${receiver} 체크리스트 근거:\n${evidence || '(없음)'}`])).trim()) as { reply?: unknown };
|
|
559
|
+
const value: unknown = typeof output.reply === 'string' ? JSON.parse(output.reply) : output;
|
|
560
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('invalid seat reply');
|
|
561
|
+
const record = value as Record<string, unknown>;
|
|
562
|
+
if (typeof record.answer === 'string' && record.answer.trim()) return { answer: record.answer };
|
|
563
|
+
if (record.to === 'CEO' && typeof record.question === 'string' && record.question.trim()) return { to: 'CEO' as const, question: record.question };
|
|
564
|
+
throw new Error('invalid seat reply');
|
|
565
|
+
}))(seat as SeatId, item.text, item.from) : null;
|
|
566
|
+
if (item.source === 'seat-question' && reply && 'to' in reply && reply.to !== 'CEO') {
|
|
567
|
+
throw new Error('seat reply may only answer or escalate to CEO');
|
|
568
|
+
}
|
|
569
|
+
if (item.source === 'seat-question' || inquiry) {
|
|
570
|
+
const entry: SeatEntry = { seat, ts: now.toISOString(), at: now.toISOString(), item, status: 'shadow',
|
|
571
|
+
action: item.source === 'seat-question' ? 'seat-answer' : inquiry?.to === 'CEO' ? 'decision' : 'seat-question',
|
|
572
|
+
...(inquiry ? { inquiry } : {}), ...(reply && 'answer' in reply ? { answer: reply.answer } : {}) };
|
|
573
|
+
if (item.source === 'seat-question' && reply && 'to' in reply) {
|
|
574
|
+
entry.inquiry = reply;
|
|
575
|
+
entry.action = 'decision';
|
|
576
|
+
}
|
|
577
|
+
if (questionsMode !== 'on') {
|
|
578
|
+
append(path, entry);
|
|
579
|
+
observe('question-shadow', { seat, item, inquiry: entry.inquiry, answered: !!entry.answer });
|
|
580
|
+
return entry;
|
|
581
|
+
}
|
|
582
|
+
if (item.source === 'seat-question' && !reply) {
|
|
583
|
+
entry.status = 'awaiting-answer';
|
|
584
|
+
append(path, entry);
|
|
585
|
+
return entry;
|
|
586
|
+
}
|
|
587
|
+
append(path, { ...entry, status: 'attempting' });
|
|
588
|
+
try {
|
|
589
|
+
if (item.source === 'seat-question' && reply && 'answer' in reply) {
|
|
590
|
+
const question = { id: Number(item.id), from: item.from!, to: seat, body: item.text, kind: 'seat-question', createdAt: item.createdAt ?? '' };
|
|
591
|
+
if (!hasSeatAnswer(root, question)) answerSeat(root, question, reply.answer);
|
|
592
|
+
entry.status = 'answered';
|
|
593
|
+
} else {
|
|
594
|
+
const ask = entry.inquiry!;
|
|
595
|
+
if (!ask.question.trim()) throw new Error('empty seat question');
|
|
596
|
+
if (ask.to === 'CEO') {
|
|
597
|
+
const prior = decisionReceipt(root, seat, item);
|
|
598
|
+
if (!prior) {
|
|
599
|
+
const run = deps.run ?? defaultRun;
|
|
600
|
+
await run(['decisions', 'raise', '--title', `${seat}: ${ask.question}`, '--category', 'other',
|
|
601
|
+
'--s', `${seat} 질문 · ${item.title}`, '--c', '판단 근거 부족 — 사람의 결정이 필요하다',
|
|
602
|
+
'--option', 'a=승인:사람 판단으로 진행', '--option', 'b=보류:진행하지 않음',
|
|
603
|
+
'--skip-recommend', '근거가 부족하다', '--agent', 'seat-loop', '--ref', decisionRef(seat, item), '--json']);
|
|
604
|
+
}
|
|
605
|
+
if (!decisionReceipt(root, seat, item)) throw new Error('decisions raise did not deliver a matching card');
|
|
606
|
+
entry.status = 'hitl';
|
|
607
|
+
} else {
|
|
608
|
+
if (!isSeatId(ask.to) || ask.to === seat) throw new Error('invalid recipient seat');
|
|
609
|
+
const key = `ask:${seat}:${itemKey(item)}`;
|
|
610
|
+
const delivered = deliveredSeatQuestion(root, key);
|
|
611
|
+
if (delivered && (delivered.from !== seat || delivered.to !== ask.to || delivered.body !== ask.question.trim())) {
|
|
612
|
+
throw new Error('seat delivery retry changed its recorded question');
|
|
613
|
+
}
|
|
614
|
+
askSeat(root, seat as SeatId, ask.to, ask.question, key);
|
|
615
|
+
entry.status = 'asked';
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
append(path, entry);
|
|
619
|
+
observe('question-outcome', { seat, item, status: entry.status });
|
|
620
|
+
return entry;
|
|
621
|
+
} catch (error) {
|
|
622
|
+
append(path, { ...entry, status: 'outcome-unknown' });
|
|
623
|
+
throw error;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
170
627
|
if (item) observe('picked', { seat, item, date: seatDay(now) });
|
|
171
628
|
const planned = item ? planAction(item, seat) : undefined;
|
|
172
629
|
const entry: SeatEntry = { seat, ts: now.toISOString(), at: now.toISOString(), status: item ? 'shadow' : 'skipped-empty',
|
|
173
630
|
...(item ? { item, action: planned!.kind, ...(planned!.reason ? { reason: planned!.reason } : {}) } : { action: 'skipped-empty' as const }) };
|
|
174
|
-
const append = deps.append ?? defaultAppend;
|
|
175
631
|
if (config.mode === 'shadow' || !item) {
|
|
176
632
|
append(path, entry);
|
|
177
633
|
if (config.mode === 'shadow') observe('shadow', { seat, item, status: entry.status });
|
|
178
634
|
return entry;
|
|
179
635
|
}
|
|
636
|
+
if (planned!.kind === 'wait') {
|
|
637
|
+
entry.status = 'wait';
|
|
638
|
+
append(path, entry);
|
|
639
|
+
return entry;
|
|
640
|
+
}
|
|
180
641
|
const run = deps.run ?? defaultRun;
|
|
181
642
|
let outcome: string | undefined;
|
|
182
643
|
try { outcome = (JSON.parse(await run(['harness', 'budget', '--json'])) as { outcome?: string }).outcome; }
|
|
@@ -190,6 +651,52 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
|
|
|
190
651
|
return entry;
|
|
191
652
|
}
|
|
192
653
|
const action = planned!;
|
|
654
|
+
if (action.kind === 'harness') {
|
|
655
|
+
const launchedRunIds = new Set(ledger.flatMap((row) => row.seat === seat && row.status === 'launched' && row.runId ? [row.runId] : []));
|
|
656
|
+
let running: readonly RunningSeatPod[] = [];
|
|
657
|
+
let observationUnavailable = false;
|
|
658
|
+
try {
|
|
659
|
+
if (deps.running) running = deps.running();
|
|
660
|
+
else if (launchedRunIds.size > 0) {
|
|
661
|
+
const observed = queryRunningRuns({ runIds: [...launchedRunIds], caller: 'seat-loop' });
|
|
662
|
+
if (observed.completeness !== 'complete' || observed.pty.unreadable.length > 0) observationUnavailable = true;
|
|
663
|
+
running = observed.entries
|
|
664
|
+
.filter((row) => launchedRunIds.has(row.runId) && (row.status === 'running' || row.status === 'probable-running' || row.status === 'unknown'))
|
|
665
|
+
// Seat launches always use `--substrate pod` (see the harness say call), so the substrate is known from the launch;
|
|
666
|
+
// only liveness can be uncertain — probable-running/unknown stay «unknown», never counted as confirmed Pods.
|
|
667
|
+
.map((row) => ({ seat, substrate: 'pod', status: row.status === 'running' ? 'running' : 'unknown' }));
|
|
668
|
+
}
|
|
669
|
+
} catch { observationUnavailable = true; }
|
|
670
|
+
const budget = observationUnavailable
|
|
671
|
+
? { allowed: false as const, reason: `${seat} concurrent Pods budget cannot be verified (running observation unavailable)` }
|
|
672
|
+
: checkSeatBudget({ seat, now, ledger, running, ...(deps.budgetConfig ? { config: deps.budgetConfig } : {}) });
|
|
673
|
+
if (!budget.allowed) {
|
|
674
|
+
entry.status = 'skipped-budget';
|
|
675
|
+
entry.action = 'skipped-budget';
|
|
676
|
+
entry.reason = budget.reason;
|
|
677
|
+
observe('skipped-budget', { seat, item, reason: budget.reason });
|
|
678
|
+
const parent = (config.stall?.parents ?? {})[seat] ?? DEFAULT_STALL_PARENTS[seat];
|
|
679
|
+
// Dedupe only on a DELIVERED escalation, so a failed send is retried on the next turn (TC harvest · review round 1).
|
|
680
|
+
if (parent && parent !== seat && !ledger.some((prior) => prior.status === 'skipped-budget' && prior.escalated === true && prior.reason === budget.reason
|
|
681
|
+
&& prior.item && itemKey(prior.item) === itemKey(item) && seatDay(new Date(prior.ts ?? prior.at)) === seatDay(now))) {
|
|
682
|
+
const line = `[${seat} → ${parent}] ${item.id}: ${budget.reason}`;
|
|
683
|
+
try {
|
|
684
|
+
if (deps.escalate) await deps.escalate(parent, line);
|
|
685
|
+
else {
|
|
686
|
+
const store = openMsgStore(join(deps.root ?? effectiveInstanceRoot(), 'msg', 'messages.db'));
|
|
687
|
+
try { store.append({ from: seat, to: parent, body: line, kind: 'seat-budget-escalation' }); }
|
|
688
|
+
finally { store.close(); }
|
|
689
|
+
}
|
|
690
|
+
entry.escalated = true;
|
|
691
|
+
} catch (error) {
|
|
692
|
+
entry.escalated = false;
|
|
693
|
+
observe('budget-escalation-failed', { seat, parent, item: item.id, error: String(error).slice(0, 200) });
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
append(path, entry);
|
|
697
|
+
return entry;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
193
700
|
// Persist the intent before crossing the process boundary: an unreturned call may already have acted.
|
|
194
701
|
append(path, { ...entry, status: 'attempting' });
|
|
195
702
|
try {
|
|
@@ -227,5 +734,17 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
|
|
|
227
734
|
append(path, { ...entry, status: 'outcome-unknown' });
|
|
228
735
|
throw error;
|
|
229
736
|
}
|
|
737
|
+
} catch (error) {
|
|
738
|
+
turnError = error;
|
|
739
|
+
throw error;
|
|
740
|
+
} finally {
|
|
741
|
+
// A turn ends even when there is no picked item or a launch fails.
|
|
742
|
+
try { escalateStalls(seat, deps, config, (deps.now ?? (() => new Date()))()); }
|
|
743
|
+
catch (error) {
|
|
744
|
+
debug.log('org.stall', 'escalate-error', { seat, error: String(error).slice(0, 200) }, { level: 'warn' });
|
|
745
|
+
if (turnError !== undefined) throw new AggregateError([turnError, error], `seat turn failed (${String(turnError)}); stall escalation failed (${String(error)}) for ${seat}`);
|
|
746
|
+
throw error;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
230
749
|
}, deps.lockContended);
|
|
231
750
|
}
|