elanous 0.2.8 → 0.2.10
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/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +1 -0
- package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8067-87ce253ec2f23697.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8195-6fde3d7a39859206.js} +5 -5
- package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-d4b6df905f185055.js → page-54b32bd27726588d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-186fc6952f027acc.js → page-f8116559c7424da7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-cbbe03c0da7b081a.js → page-54e1f966ab60a4dc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7ed95f296f5ee8e9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-c0c0d19d51342d3e.js → page-b31610deda76337b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-2e002ad8faa43291.js → page-6cb0e1bbefe5fb3d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-7464c1987dd76e6d.js → page-e168fa1030af0548.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-d8ab4d1489223459.js → page-4fddc90b23feb215.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-26102fd7350f171e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-139ba5c367bbf60a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +1 -0
- package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +30 -29
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +30 -29
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +30 -29
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +27 -26
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +27 -26
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +27 -26
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +30 -29
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +30 -29
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +30 -29
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +30 -29
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +30 -29
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +30 -29
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +27 -26
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +30 -29
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +30 -29
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +27 -26
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +27 -26
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +30 -29
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +30 -29
- package/apps/pwa/out/ops/checklist/index.html +1 -0
- package/apps/pwa/out/ops/checklist/index.txt +30 -0
- package/apps/pwa/out/ops/release/index.html +1 -0
- package/apps/pwa/out/ops/release/index.txt +30 -0
- package/apps/pwa/out/ops/seats/index.html +1 -0
- package/apps/pwa/out/ops/seats/index.txt +30 -0
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +28 -27
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +30 -29
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +27 -26
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +30 -29
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +30 -29
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +30 -29
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +30 -29
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +30 -29
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +27 -26
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +30 -29
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +30 -29
- package/apps/pwa/out/today/index.html +1 -0
- package/apps/pwa/out/today/index.txt +33 -0
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +30 -29
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +30 -29
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +30 -29
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +30 -29
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +30 -29
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +30 -29
- package/graphs/field/field-feed.yaml +2 -0
- package/graphs/implement-loop.yaml +1 -1
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +3 -1
- package/graphs/test-diet/nightly-audit.yaml +18 -0
- package/graphs/test-diet/recipes.yaml +3 -0
- package/package.json +1 -1
- package/plugins/card-followup/graphs/run-step.ts +48 -13
- package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
- package/plugins/doc-draft/graphs/run-step.ts +14 -7
- package/scripts/ci-pwa-build-gate.ts +160 -0
- package/scripts/coord-channel-watch.sh +42 -8
- package/scripts/coord-digest.ts +199 -0
- package/scripts/coord-post.sh +60 -3
- package/scripts/install.ps1 +68 -9
- package/scripts/install.sh +39 -12
- package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
- package/scripts/lib/tui-regress-checks.ts +146 -0
- package/scripts/memory-lifecycle-cycle.ts +2 -2
- package/scripts/ops-now-probe.ts +327 -0
- package/scripts/publish-internal-dist.ts +11 -1
- package/scripts/release-loop/cutoff.ts +31 -2
- package/scripts/release-loop/export-check-node.ts +85 -0
- package/scripts/release-loop/gate-node.ts +18 -4
- package/scripts/release-loop/node-verdict.ts +8 -1
- package/scripts/release-loop/ops-upgrade-node.ts +72 -16
- package/scripts/release-loop/release-readiness.ts +9 -0
- package/scripts/release-loop/version-node.ts +58 -10
- package/scripts/repo-watch-cycle.ts +1 -1
- package/scripts/retro-cycle.ts +2 -2
- package/scripts/schedule-health-report.ts +1 -1
- package/scripts/storyboard/storyboard.ts +5 -161
- package/scripts/test-diet/lib.ts +31 -1
- package/scripts/test-diet/node.ts +134 -17
- package/scripts/test-timeout-audit.ts +114 -0
- package/scripts/tree-sync-alert.ts +1 -1
- package/scripts/tui-regress.ts +71 -0
- package/scripts/waitlist-daily-digest.ts +1 -1
- package/skills/explainer-video/SKILL.md +4 -0
- package/skills/explainer-video/engine/reel.mjs +18 -13
- package/skills/explainer-video/engine/reel.sh +24 -5
- package/skills/explainer-video/engine/stitch.mjs +145 -0
- package/skills/pitch-deck/SKILL.md +36 -0
- package/skills/pitch-deck/template.html +80 -0
- package/src/acp/codex-approval-adapter.ts +9 -3
- package/src/acp/core-turn-bridge.ts +117 -18
- package/src/acp/fact-question.ts +11 -0
- package/src/acp/server.ts +48 -7
- package/src/acp/tool-approval.ts +126 -0
- package/src/acp/turn-runner.ts +36 -0
- package/src/agent/monad-agent-turn.ts +8 -0
- package/src/ask-user-question/pending-questions.ts +25 -10
- package/src/ask-user-question/types.ts +9 -0
- package/src/auth/setup-link-tokens.fixture.ts +19 -0
- package/src/auth/setup-link-tokens.ts +164 -0
- package/src/autopilot/build/integrity-gate.ts +12 -0
- package/src/boot/daemon-prompt-turn.ts +3 -0
- package/src/boot/daemon-tools/index.ts +8 -0
- package/src/boot/daemon-tools/types.ts +2 -0
- package/src/boot/daemon-tools/web-search.ts +1 -0
- package/src/budget/unified-usage.ts +75 -0
- package/src/channel-bot-token.ts +37 -10
- package/src/chat/ctrl-c-in-input.ts +14 -0
- package/src/chat/index.ts +61 -9
- package/src/chat/pickers/state.ts +6 -2
- package/src/cli/agent-cli.ts +7 -0
- package/src/cli/decisions-cli.ts +25 -3
- package/src/cli/doctor-bundle-upload.ts +68 -0
- package/src/cli/doctor-cli.ts +38 -4
- package/src/cli/op-proxy-sync.ts +83 -0
- package/src/cli/ops-cli.ts +20 -0
- package/src/cli/pod-cli.ts +49 -0
- package/src/cli/pr-cli.ts +37 -4
- package/src/cli/pwa-stop.ts +163 -13
- package/src/cli/pwa-test.ts +3 -0
- package/src/cli/release-cli.ts +98 -15
- package/src/cli/self-update.ts +14 -0
- package/src/cli/storyboard-cli.ts +37 -0
- package/src/cli/test-state-dir-flag.ts +11 -4
- package/src/connectors/linear.ts +146 -0
- package/src/context-bus/coord-events.ts +115 -0
- package/src/dashboard/dashboard-boot-fixture.ts +23 -0
- package/src/dashboard/dashboard-status-lines.ts +45 -0
- package/src/dashboard/first-screen-band.ts +35 -0
- package/src/dashboard/index.ts +128 -34
- package/src/dashboard/provider-slash-runtime.ts +28 -11
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +17 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
- package/src/dashboard/turn-stream-formatter.ts +1 -1
- package/src/decisions/decision-cards.ts +50 -2
- package/src/decisions/decision-ledger.ts +75 -4
- package/src/decisions/decision-linear-projection.ts +157 -0
- package/src/decisions/web-push-decision-cards.ts +50 -0
- package/src/directive/interpretation-card.ts +36 -0
- package/src/discord.ts +2 -1
- package/src/domains/coo-admin-tool.ts +62 -0
- package/src/domains/core-tools.ts +11 -3
- package/src/domains/elanous-card.ts +29 -0
- package/src/domains/ops-facts-tool.ts +76 -0
- package/src/domains/outbound-alert.ts +41 -13
- package/src/domains/release-tool.ts +168 -0
- package/src/domains/telegram-kind-route.ts +19 -1
- package/src/exec-requests/planner.ts +98 -28
- package/src/exec-requests/runner.ts +107 -10
- package/src/exec-requests/store.ts +8 -2
- package/src/field/field-media.ts +10 -7
- package/src/field/field-reel.ts +14 -4
- package/src/git-fs/worktree.ts +45 -6
- package/src/graph-runner/graph-approval-card.ts +113 -0
- package/src/graph-runner/graph-cli.ts +129 -4
- package/src/graph-runner/graph-notify.ts +25 -3
- package/src/graph-runner/runner.ts +142 -16
- package/src/harness/browser-verify.ts +25 -2
- package/src/harness/harness-cli-command.ts +231 -35
- package/src/harness/harness-incidents-writer.fixture.ts +24 -0
- package/src/harness/harness-incidents.ts +129 -0
- package/src/harness/harness-stop.ts +53 -3
- package/src/hitl/clarification-policy.ts +3 -1
- package/src/index.ts +172 -41
- package/src/intake-plane/author-goals.ts +37 -4
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/discord-seat-work.ts +47 -2
- package/src/intake-plane/seat-doc-route.ts +170 -6
- package/src/intake-plane/telegram-seat-work.ts +44 -2
- package/src/intake-plane/telegram-work.ts +1 -0
- package/src/maturity/cli-maturity.ts +34 -0
- package/src/maturity/feature-maturity.ts +100 -1
- package/src/maturity/tui-slash-maturity.ts +31 -0
- package/src/memory.ts +32 -4
- package/src/nexus/api/exec-requests.ts +33 -3
- package/src/nexus/api/http-server.ts +73 -1
- package/src/nexus/api/meta-api.ts +38 -0
- package/src/nexus/api/operator.ts +61 -0
- package/src/nexus/api/ops-api.ts +148 -0
- package/src/nexus/api/ops-seats-sources.ts +95 -0
- package/src/nexus/api/ops-seats.ts +124 -0
- package/src/nexus/api/public-routes.ts +14 -1
- package/src/nexus/api/seat-requests.ts +88 -8
- package/src/nexus/api/sessions-store.ts +63 -0
- package/src/nexus/api/setup-claim.ts +21 -0
- package/src/nexus/api/setup-codex-login.ts +160 -0
- package/src/nexus/api/usage-api.ts +102 -0
- package/src/nexus/config/secrets/file-backend.ts +56 -18
- package/src/nexus/index.ts +45 -4
- package/src/onboarding/first-run-doctor.ts +54 -0
- package/src/onboarding/first-run.ts +89 -0
- package/src/onboarding/lan-address.ts +45 -0
- package/src/onboarding/web-first.ts +119 -24
- package/src/onboarding.ts +4 -0
- package/src/ops-now/ops-now-refresh.ts +100 -0
- package/src/project/project-store.ts +115 -0
- package/src/release-loop/checklist.ts +76 -61
- package/src/release-loop/feature-store.ts +460 -0
- package/src/release-loop/manifest.ts +59 -5
- package/src/release-loop/release-schedule.ts +53 -0
- package/src/repl/index.ts +7 -0
- package/src/seat-dispatch/ceo-commands.ts +98 -0
- package/src/seat-dispatch/ceo-intent.ts +38 -0
- package/src/seat-loop/seat-loop.ts +231 -0
- package/src/seat-loop/seat-report.ts +64 -0
- package/src/self-dev/decompose.ts +66 -11
- package/src/self-dev/run-store.ts +10 -1
- package/src/self-implement/execution-clarification.ts +115 -0
- package/src/self-implement/gate-cli.ts +6 -3
- package/src/self-implement/orchestrator.ts +37 -3
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/run-status-mapping.ts +5 -3
- package/src/session/chat.ts +2 -0
- package/src/session/index.ts +9 -0
- package/src/shell-env-bootstrap.ts +17 -0
- package/src/skills/tools/usage-slash.ts +8 -7
- package/src/steward/directive.ts +51 -2
- package/src/steward/launch.ts +250 -0
- package/src/steward/outcome-notify.ts +115 -0
- package/src/steward/steward-cards.ts +8 -2
- package/src/steward/triage-plan.ts +32 -4
- package/src/steward/triage.ts +93 -9
- package/src/steward/working-backwards.ts +64 -0
- package/src/storyboard/storyboard.ts +141 -0
- package/src/task-cards/card-store.ts +2 -2
- package/src/task-orchestrator/surfaces/pod-lease.ts +260 -0
- package/src/telegram-card-followup.ts +233 -0
- package/src/telegram-commands.ts +16 -0
- package/src/telegram-report.ts +15 -9
- package/src/telegram-run.ts +90 -13
- package/src/telegram.ts +29 -8
- package/src/thinking-line.ts +28 -20
- package/src/tool-runtime/types.ts +4 -0
- package/src/tui/chat/execution-badge.ts +1 -1
- package/src/tui-client/dashboard-session.ts +2 -0
- package/src/user-config.ts +118 -11
- package/src/ux-sim/turn-states.ts +1 -1
- package/src/version/packed-revision.json +1 -1
- package/src/web-push/notify-exec-request.ts +32 -0
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4836-3084c58c1725a40b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8894-7317998f6cb97d8c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-9aa47b0bc6d570ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f4d2ecbbae6088d6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-2053e49930c82748.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a70ba27c33003dd0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-18166f885d831d3e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-1c9d6cfe68b121c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c108ff1fdb665c97.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-147a328753d983e2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-799395a9df7d95aa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-6a8158b71d1fe9d7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-9567560270857531.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-e7ed3d010153d818.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-b672e02e4bec8deb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-1ba4a5d857276334.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-9db2226f38c542fb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-70ee65f5f7a151dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-0bb8e43203efb23b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-092e1bcf27d751cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-eb52580c4b75b905.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-e573437b0a1c728f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-294300625e49e4d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-085847b088675978.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-9fe9d38c36999eb4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-6141f67f531d8b6a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-295a02b1026b8255.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-1eb9a0e1e054209d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-82efa177a30f3741.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d0be51cec4f62606.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-2e367075ffdbc062.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-647ed2b66c1d49f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ba058c56e38f2792.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
- package/apps/pwa/out/_next/static/css/5ef72a618a8994f3.css +0 -1
- package/apps/pwa/out/_next/static/lywc8m_HDzH9MKSDQhyEj/_buildManifest.js +0 -1
- package/src/dashboard/turn-typeahead-echo.ts +0 -23
- /package/apps/pwa/out/_next/static/{lywc8m_HDzH9MKSDQhyEj → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// The owner hands a seat a task from Telegram: /coo /cto /cmo /cxo → one «[대표] → <seat>» line on the coordination PR,
|
|
2
|
+
// one 'ceo-task' message in the seat store, and a «받음» reply. The Telegram entrypoint writes only for the owner in a private chat.
|
|
3
|
+
// Goal: 내부 문서 `ASK-the-ceo-cannot-hand-a-seat-a-task-from-telegram-2026-10-02` (C2 phase 1).
|
|
4
|
+
import { debug } from '../debug/log.js';
|
|
5
|
+
import type { MessageEnvelope } from '../msg/msg-store.js';
|
|
6
|
+
|
|
7
|
+
export const CEO_SEAT_COMMANDS = { coo: 'OP', cto: 'TC', cmo: 'MK', cxo: 'UX' } as const;
|
|
8
|
+
export type CeoSeatCommand = keyof typeof CEO_SEAT_COMMANDS;
|
|
9
|
+
|
|
10
|
+
export interface CeoCommandContext { chatId: number | string; userId: number | string }
|
|
11
|
+
|
|
12
|
+
export interface CeoCommandDeps {
|
|
13
|
+
/** Telegram user id of the owner (`telegramDecisionOwner`), null when none is configured. */
|
|
14
|
+
ownerId: string | null;
|
|
15
|
+
/** `decisions.replyGhPr` — "owner/repo#N". */
|
|
16
|
+
replyTarget: string | null;
|
|
17
|
+
runGh: (args: string[], stdin: string) => Promise<number>;
|
|
18
|
+
append: (message: MessageEnvelope) => unknown;
|
|
19
|
+
/** Whether the seat inbox already holds a message carrying this request ref (retry after a crash must not deliver twice). */
|
|
20
|
+
hasMessage?: (seat: string, ref: string) => boolean;
|
|
21
|
+
now?: () => Date;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function kstStamp(at: Date): string {
|
|
25
|
+
return new Intl.DateTimeFormat('sv-SE', { timeZone: 'Asia/Seoul', dateStyle: 'short', timeStyle: 'short' }).format(at);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function ceoTaskLine(seat: string, task: string, at: Date): string {
|
|
29
|
+
return `**[대표]** ${kstStamp(at)} KST → ${seat} · ${task}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function handleCeoSeatCommand(command: CeoSeatCommand, args: string[], ctx: CeoCommandContext, deps: CeoCommandDeps): Promise<string> {
|
|
33
|
+
const seat = CEO_SEAT_COMMANDS[command];
|
|
34
|
+
if (!deps.ownerId || String(ctx.userId) !== deps.ownerId) {
|
|
35
|
+
debug.log('seat.dispatch', 'refused', { seat, reason: 'not-owner' });
|
|
36
|
+
return '소유자만 쓸 수 있습니다.';
|
|
37
|
+
}
|
|
38
|
+
// A group would show the task to every member — private chat only (same rule as /decisions).
|
|
39
|
+
if (String(ctx.chatId) !== String(ctx.userId)) {
|
|
40
|
+
debug.log('seat.dispatch', 'refused', { seat, reason: 'not-private' });
|
|
41
|
+
return '개인 대화에서만 쓸 수 있습니다.';
|
|
42
|
+
}
|
|
43
|
+
const task = args.join(' ').replace(/\s+/g, ' ').trim();
|
|
44
|
+
if (!task) {
|
|
45
|
+
debug.log('seat.dispatch', 'refused', { seat, reason: 'empty' });
|
|
46
|
+
return `사용법: /${command} <할 일>`;
|
|
47
|
+
}
|
|
48
|
+
return (await dispatchCeoTask(seat, task, deps)).reply;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface CeoTaskExtra {
|
|
52
|
+
attachments?: Array<{ name: string; path: string }>;
|
|
53
|
+
/** Paths supplied by callers that do not have attachment names. */
|
|
54
|
+
attachmentPaths?: readonly string[];
|
|
55
|
+
via?: 'telegram' | 'discord' | 'pwa';
|
|
56
|
+
/** Request ref written into the message body; with `deps.hasMessage` it makes delivery idempotent per request. */
|
|
57
|
+
ref?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface CeoTaskResult { reply: string; channel: 'posted' | 'failed' | 'unknown'; channelError?: string }
|
|
61
|
+
|
|
62
|
+
/** Dispatch an already-authorized seat request; callers must authenticate before invoking this helper. */
|
|
63
|
+
export async function dispatchCeoTask(seat: string, text: string, deps: CeoCommandDeps, extra: CeoTaskExtra = {}): Promise<CeoTaskResult> {
|
|
64
|
+
const task = text.replace(/\s+/g, ' ').trim();
|
|
65
|
+
if (!task) {
|
|
66
|
+
debug.log('seat.dispatch', 'refused', { seat, reason: 'empty' });
|
|
67
|
+
return { reply: '할 일을 입력해 주세요.', channel: 'unknown' };
|
|
68
|
+
}
|
|
69
|
+
const attachments = extra.attachments ?? [];
|
|
70
|
+
const attachmentPaths = extra.attachmentPaths ?? [];
|
|
71
|
+
const body = `${task}${attachments.map(({ name, path }) => `\n첨부: ${name} — ${path}`).join('')}${attachmentPaths.length ? `\n\n첨부 파일:\n${attachmentPaths.join('\n')}` : ''}${extra.ref ? `\n요청: ${extra.ref}` : ''}`;
|
|
72
|
+
if (extra.ref && deps.hasMessage?.(seat, extra.ref)) {
|
|
73
|
+
// A retry after a crash between delivery and the journal outcome: the message is already there, and whether
|
|
74
|
+
// the channel line went out is unknown — say so instead of posting it again or claiming it was posted.
|
|
75
|
+
debug.log('seat.dispatch', 'ceo-task-already-delivered', { seat, via: extra.via });
|
|
76
|
+
return { reply: `받음 — ${seat} 메시지함에 이미 있습니다.`, channel: 'unknown' };
|
|
77
|
+
}
|
|
78
|
+
deps.append({ from: 'CEO', to: seat, body, kind: 'ceo-task' });
|
|
79
|
+
debug.log('seat.dispatch', 'ceo-task', { seat, ...(extra.via ? { via: extra.via, attachments: attachments.length + attachmentPaths.length } : {}) });
|
|
80
|
+
const channelError = await postToChannel(seat, task, deps, extra);
|
|
81
|
+
if (channelError) {
|
|
82
|
+
debug.log('seat.dispatch', 'channel-failed', { seat, reason: channelError }, { level: 'warn' });
|
|
83
|
+
return { reply: `받음 — ${seat} 메시지함에는 넣었습니다. 다만 조율 채널에 못 남겼습니다(${channelError}).`, channel: 'failed', channelError };
|
|
84
|
+
}
|
|
85
|
+
return { reply: `받음 — ${seat}에 전했습니다.`, channel: 'posted' };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function postToChannel(seat: string, task: string, deps: CeoCommandDeps, extra: CeoTaskExtra): Promise<string | null> {
|
|
89
|
+
const target = /^([\w.-]+\/[\w.-]+)#(\d+)$/.exec(deps.replyTarget?.trim() ?? '');
|
|
90
|
+
if (!target) return deps.replyTarget ? 'decisions.replyGhPr 형식이 owner/repo#N 이 아님' : 'decisions.replyGhPr 미설정';
|
|
91
|
+
try {
|
|
92
|
+
const attachmentCount = (extra.attachments?.length ?? 0) + (extra.attachmentPaths?.length ?? 0);
|
|
93
|
+
const code = await deps.runGh(['pr', 'comment', target[2]!, '--repo', target[1]!, '--body-file', '-'], `${ceoTaskLine(seat, task, deps.now?.() ?? new Date())}${attachmentCount ? ` · 첨부 ${attachmentCount}` : ''}${extra.via === 'pwa' ? ' (PWA)' : ''}`);
|
|
94
|
+
return code === 0 ? null : `gh 종료 코드 ${code}`;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
return error instanceof Error ? error.message.slice(0, 80) : 'gh 실행 실패';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { runGh } from '../decisions/decision-cards.js';
|
|
2
|
+
import { openMsgStore } from '../msg/msg-store.js';
|
|
3
|
+
import type { UserConfig } from '../user-config.js';
|
|
4
|
+
import type { CeoCommandDeps } from './ceo-commands.js';
|
|
5
|
+
|
|
6
|
+
/** Endings are ordered from explicit punctuation to Korean question endings. Every row has a test example. */
|
|
7
|
+
export const CEO_QUESTION_ENDINGS = [
|
|
8
|
+
/[??]$/,
|
|
9
|
+
/야$/,
|
|
10
|
+
/니$/,
|
|
11
|
+
/나요$/,
|
|
12
|
+
/까요$/,
|
|
13
|
+
/인가요$/,
|
|
14
|
+
/어때$/,
|
|
15
|
+
/알려\s?줘$/,
|
|
16
|
+
/보여줘$/,
|
|
17
|
+
/상황$/,
|
|
18
|
+
/어디까지$/,
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
export function classifyCeoIntent(text: string): 'question' | 'task' {
|
|
22
|
+
const body = text.trim();
|
|
23
|
+
return CEO_QUESTION_ENDINGS.some((ending) => ending.test(body)) ? 'question' : 'task';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The same channel + seat inbox delivery dependencies used by /coo, /cto, /cmo and /cxo. */
|
|
27
|
+
export function ceoTaskDeps(cfg: UserConfig, ownerId: string | null): CeoCommandDeps {
|
|
28
|
+
const replyTarget = (cfg.raw?.decisions as { replyGhPr?: unknown } | undefined)?.replyGhPr;
|
|
29
|
+
return {
|
|
30
|
+
ownerId,
|
|
31
|
+
replyTarget: typeof replyTarget === 'string' ? replyTarget : null,
|
|
32
|
+
runGh,
|
|
33
|
+
append: (message) => {
|
|
34
|
+
const store = openMsgStore();
|
|
35
|
+
try { return store.append(message); } finally { store.close(); }
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { Database } from 'bun:sqlite';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { TRACK_AGENT_FORBIDDEN_ACTION_REGEX, universeLaunch } from '../autopilot/track-agent.js';
|
|
7
|
+
import { debug } from '../debug/log.js';
|
|
8
|
+
import { effectiveInstanceRoot, releaseLedgerRoot } from '../instance/resolve.js';
|
|
9
|
+
import { getUserConfig, type SeatLoopConfig } from '../user-config.js';
|
|
10
|
+
import { listChecklist } from '../release-loop/checklist.js';
|
|
11
|
+
import { readSchedules } from '../release-loop/feature-store.js';
|
|
12
|
+
|
|
13
|
+
// `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 };
|
|
15
|
+
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` (forbidden word for a decision).
|
|
17
|
+
export type SeatAction = 'decision' | 'harness' | 'skipped-budget' | 'skipped-empty';
|
|
18
|
+
export type SeatEntry = { seat: string; ts?: string; at: string; status: 'shadow' | 'attempting' | 'outcome-unknown' | 'launched' | 'hitl' | 'skipped-budget' | 'skipped-empty'; item?: SeatItem; action?: SeatAction; reason?: string; runId?: string };
|
|
19
|
+
export type SeatLoopResult = SeatEntry | { seat: string; status: 'skipped-off' };
|
|
20
|
+
export type SeatDeps = {
|
|
21
|
+
root?: string;
|
|
22
|
+
repo?: string;
|
|
23
|
+
config?: SeatLoopConfig;
|
|
24
|
+
now?: () => Date;
|
|
25
|
+
read?: (path: string) => string;
|
|
26
|
+
run?: (args: string[]) => Promise<string>;
|
|
27
|
+
append?: (path: string, entry: SeatEntry) => void;
|
|
28
|
+
versions?: () => string[];
|
|
29
|
+
/** 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 }>;
|
|
31
|
+
ledgerFiles?: (directory: string) => string[];
|
|
32
|
+
lockContended?: () => void;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const repoRoot = resolve(import.meta.dir, '../..');
|
|
36
|
+
const exec = promisify(execFile);
|
|
37
|
+
const defaultRead = (path: string): string => {
|
|
38
|
+
try { return readFileSync(path, 'utf8'); }
|
|
39
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') return ''; throw error; }
|
|
40
|
+
};
|
|
41
|
+
const defaultRun = async (args: string[]): Promise<string> => {
|
|
42
|
+
const { argv, env } = universeLaunch(args);
|
|
43
|
+
const { stdout } = await exec('bun', argv, {
|
|
44
|
+
cwd: repoRoot, env, encoding: 'utf8',
|
|
45
|
+
timeout: args[0] === 'harness' && args[1] === 'say' ? 3_600_000 : 120_000, maxBuffer: 4 * 1024 * 1024,
|
|
46
|
+
});
|
|
47
|
+
return stdout;
|
|
48
|
+
};
|
|
49
|
+
const defaultAppend = (path: string, entry: SeatEntry): void => {
|
|
50
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
51
|
+
appendFileSync(path, `${JSON.stringify(entry)}\n`, { mode: 0o600 });
|
|
52
|
+
};
|
|
53
|
+
const observe = (event: string, data: Record<string, unknown>): void => {
|
|
54
|
+
try { debug.log('seat.loop', event, data); } catch { /* logging cannot change execution */ }
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// SQLite's OS lock survives the awaited external call and is released if the process exits.
|
|
58
|
+
async function withSeatLock<T>(directory: string, action: () => Promise<T>, contended?: () => void): Promise<T> {
|
|
59
|
+
mkdirSync(directory, { recursive: true });
|
|
60
|
+
const path = join(directory, '.once.mutex.sqlite');
|
|
61
|
+
const db = new Database(path, { create: true, strict: true });
|
|
62
|
+
try {
|
|
63
|
+
chmodSync(path, 0o600);
|
|
64
|
+
const deadline = Date.now() + 120_000;
|
|
65
|
+
let signalled = false;
|
|
66
|
+
while (true) {
|
|
67
|
+
try { db.exec('BEGIN IMMEDIATE'); break; }
|
|
68
|
+
catch (error) {
|
|
69
|
+
if ((error as { code?: string }).code !== 'SQLITE_BUSY' || Date.now() >= deadline) throw error;
|
|
70
|
+
if (!signalled) { contended?.(); signalled = true; }
|
|
71
|
+
await new Promise((done) => setTimeout(done, 25));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
try { return await action(); }
|
|
75
|
+
finally { db.exec('ROLLBACK'); }
|
|
76
|
+
} finally { db.close(); }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function seatDay(now: Date): string {
|
|
80
|
+
return new Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Seoul', year: 'numeric', month: '2-digit', day: '2-digit' }).format(now);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function seatLedgerPath(seat: string, root: string, now: Date): string {
|
|
84
|
+
return join(root, 'seat-loop', seat, `${seatDay(now)}.jsonl`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function rows(text: string): Record<string, unknown>[] {
|
|
88
|
+
return text.split('\n').filter(Boolean).map((line) => JSON.parse(line) as Record<string, unknown>);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function versionOrder(a: string, b: string): number {
|
|
92
|
+
const aa = a.split('.').map(Number), bb = b.split('.').map(Number);
|
|
93
|
+
return (aa[0]! - bb[0]!) || (aa[1]! - bb[1]!) || (aa[2]! - bb[2]!);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function gatherSeatInputs(seat: string, deps: SeatDeps = {}): Promise<SeatInputs> {
|
|
97
|
+
const read = deps.read ?? defaultRead;
|
|
98
|
+
const root = deps.root ?? effectiveInstanceRoot();
|
|
99
|
+
const requests = new Map<string, Record<string, unknown>>();
|
|
100
|
+
for (const row of rows(read(join(root, 'seat-requests', 'requests.jsonl')))) {
|
|
101
|
+
if (typeof row.key === 'string') requests.set(row.key, row);
|
|
102
|
+
}
|
|
103
|
+
const pending = [...requests.values()]
|
|
104
|
+
.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 ?? '') }))
|
|
106
|
+
.sort((a, b) => a.queuedAt.localeCompare(b.queuedAt) || a.id.localeCompare(b.id));
|
|
107
|
+
const releaseRoot = deps.root ?? releaseLedgerRoot();
|
|
108
|
+
const versions = deps.versions ?? (() => {
|
|
109
|
+
const dir = join(releaseRoot, 'release');
|
|
110
|
+
const fromDirs = existsSync(dir) ? readdirSync(dir).filter((v) => /^\d+\.\d+\.\d+$/.test(v)) : [];
|
|
111
|
+
let fromSchedule: string[] = [];
|
|
112
|
+
try { fromSchedule = readSchedules().map((row) => row.version); } catch { /* ledger unreadable — directories only */ }
|
|
113
|
+
return [...new Set([...fromDirs, ...fromSchedule])].filter((v) => /^\d+\.\d+\.\d+$/.test(v));
|
|
114
|
+
});
|
|
115
|
+
const itemsOf = deps.checklistItems ?? ((version: string) => listChecklist(version).items);
|
|
116
|
+
const checklist: SeatItem[] = [];
|
|
117
|
+
for (const version of versions().sort(versionOrder)) {
|
|
118
|
+
let items: ReturnType<typeof itemsOf>;
|
|
119
|
+
try { items = itemsOf(version); } catch (error) {
|
|
120
|
+
debug.log('seat.loop', 'checklist-unreadable', { seat, version, error: String(error).slice(0, 200) });
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
for (const item of items) {
|
|
124
|
+
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 });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
checklist.sort((a, b) => versionOrder(a.version!, b.version!) || a.id.localeCompare(b.id));
|
|
130
|
+
return { requests: pending, checklist, role: read(join(deps.repo ?? repoRoot, 'docs', 'roles', `${seat}.md`)).slice(0, 4_000) };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function itemKey(item: SeatItem): string { return `${item.source}:${item.version ?? ''}:${item.id}`; }
|
|
134
|
+
|
|
135
|
+
// 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 = new Set(ledger.filter((entry) => entry.status === 'launched' || entry.status === 'hitl' || entry.status === 'attempting' || entry.status === 'outcome-unknown' || (opts.shadow === true && entry.status === 'shadow'))
|
|
138
|
+
.filter((entry): entry is SeatEntry & { item: SeatItem } => !!entry.item && !!entry.item.source && typeof entry.item.id === 'string').map((entry) => itemKey(entry.item)));
|
|
139
|
+
return [...inputs.requests, ...inputs.checklist].find((item) => !handled.has(itemKey(item))) ?? null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function planAction(item: SeatItem, seat?: string): { kind: 'decision' | 'harness'; text: string; reason?: string } {
|
|
143
|
+
const hit = `${item.id}\n${item.title}\n${item.text}`.match(TRACK_AGENT_FORBIDDEN_ACTION_REGEX);
|
|
144
|
+
const kind = hit ? 'decision' : 'harness';
|
|
145
|
+
return { kind, text: seat && kind === 'harness' ? seatTaskText(seat, item) : `${item.id} ${item.title}`, ...(hit ? { reason: hit[0] } : {}) };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// The launched sentence names the seat, the source and the release so same-named items stay distinct.
|
|
149
|
+
export function seatTaskText(seat: string, item: SeatItem): string {
|
|
150
|
+
const where = item.source === 'checklist' ? `${item.version ?? '?'} 체크리스트 칸 ${item.id}` : `자리 요청 ${item.id}`;
|
|
151
|
+
return `[${seat} 자리 · ${where} · 역할 docs/roles/${seat}.md] ${item.title.replace(/[\r\n]+/g, ' ')}`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promise<SeatLoopResult> {
|
|
155
|
+
if (!/^(?:MK|OP|TC|UX)$/.test(seat)) throw new Error(`unknown seat: ${seat}`);
|
|
156
|
+
const config = deps.config ?? getUserConfig().loops?.seat ?? { mode: 'off' };
|
|
157
|
+
if (config.mode === 'off' || !(config.seats ?? ['MK']).includes(seat)) {
|
|
158
|
+
observe('skipped-off', { seat });
|
|
159
|
+
return { seat, status: 'skipped-off' };
|
|
160
|
+
}
|
|
161
|
+
const now = (deps.now ?? (() => new Date()))();
|
|
162
|
+
const path = seatLedgerPath(seat, deps.root ?? effectiveInstanceRoot(), now);
|
|
163
|
+
const directory = dirname(path);
|
|
164
|
+
return withSeatLock(directory, async () => {
|
|
165
|
+
const files = deps.ledgerFiles?.(directory) ?? (existsSync(directory) ? readdirSync(directory) : []);
|
|
166
|
+
const ledger = files.filter((name) => /^\d{4}-\d{2}-\d{2}\.jsonl$/.test(name) && name <= `${seatDay(now)}.jsonl`)
|
|
167
|
+
.flatMap((name) => rows((deps.read ?? defaultRead)(join(directory, name))) as SeatEntry[]);
|
|
168
|
+
const inputs = await gatherSeatInputs(seat, deps);
|
|
169
|
+
const item = pickNext(inputs, ledger, { shadow: config.mode === 'shadow' });
|
|
170
|
+
if (item) observe('picked', { seat, item, date: seatDay(now) });
|
|
171
|
+
const planned = item ? planAction(item, seat) : undefined;
|
|
172
|
+
const entry: SeatEntry = { seat, ts: now.toISOString(), at: now.toISOString(), status: item ? 'shadow' : 'skipped-empty',
|
|
173
|
+
...(item ? { item, action: planned!.kind, ...(planned!.reason ? { reason: planned!.reason } : {}) } : { action: 'skipped-empty' as const }) };
|
|
174
|
+
const append = deps.append ?? defaultAppend;
|
|
175
|
+
if (config.mode === 'shadow' || !item) {
|
|
176
|
+
append(path, entry);
|
|
177
|
+
if (config.mode === 'shadow') observe('shadow', { seat, item, status: entry.status });
|
|
178
|
+
return entry;
|
|
179
|
+
}
|
|
180
|
+
const run = deps.run ?? defaultRun;
|
|
181
|
+
let outcome: string | undefined;
|
|
182
|
+
try { outcome = (JSON.parse(await run(['harness', 'budget', '--json'])) as { outcome?: string }).outcome; }
|
|
183
|
+
catch { /* failed budget observation is never permission to launch */ }
|
|
184
|
+
if (outcome !== 'proceed' && outcome !== 'next-provider') {
|
|
185
|
+
entry.status = 'skipped-budget';
|
|
186
|
+
entry.action = 'skipped-budget';
|
|
187
|
+
delete entry.reason;
|
|
188
|
+
append(path, entry);
|
|
189
|
+
observe('skipped-budget', { seat, item, outcome: outcome ?? 'unavailable' });
|
|
190
|
+
return entry;
|
|
191
|
+
}
|
|
192
|
+
const action = planned!;
|
|
193
|
+
// Persist the intent before crossing the process boundary: an unreturned call may already have acted.
|
|
194
|
+
append(path, { ...entry, status: 'attempting' });
|
|
195
|
+
try {
|
|
196
|
+
if (action.kind === 'decision') {
|
|
197
|
+
const title = `${seat}: ${action.text.replace(/[\r\n]+/g, ' ')}`;
|
|
198
|
+
const raised = await run(['decisions', 'raise', '--title', title, '--category', 'other',
|
|
199
|
+
'--s', `${seat} 배정 항목: ${action.text.replace(/[\r\n]+/g, ' ')}\n역할: ${inputs.role}`, '--c', '자동 실행 금지 문면에 해당하여 사람 결정이 필요하다',
|
|
200
|
+
'--option', 'a=사람 승인:승인 후 별도로 집행', '--option', 'b=보류:집행하지 않음',
|
|
201
|
+
'--skip-recommend', '금지 문면은 자동으로 권고하지 않는다', '--agent', 'seat-loop', '--json']);
|
|
202
|
+
const decision: unknown = JSON.parse(raised.trim());
|
|
203
|
+
if (!decision || typeof decision !== 'object' || typeof (decision as { id?: unknown }).id !== 'string') {
|
|
204
|
+
throw new Error('decisions raise returned no decision id');
|
|
205
|
+
}
|
|
206
|
+
entry.status = 'hitl';
|
|
207
|
+
append(path, entry);
|
|
208
|
+
observe('hitl', { seat, item });
|
|
209
|
+
return entry;
|
|
210
|
+
}
|
|
211
|
+
const output = await run(['harness', 'say', action.text, '--substrate', 'pod', '--pod-pool', config.podPool ?? 'pool-node-b@node-b:8', '--base', 'main', '--json']);
|
|
212
|
+
const results: unknown = JSON.parse(output.trim());
|
|
213
|
+
if (!Array.isArray(results) || results.length === 0 || results.some((result) =>
|
|
214
|
+
!result || typeof result !== 'object' || (result as { status?: unknown }).status !== 'done')) {
|
|
215
|
+
throw new Error('harness say did not complete successfully');
|
|
216
|
+
}
|
|
217
|
+
const runId = (results[0] as { runId?: unknown }).runId;
|
|
218
|
+
if (typeof runId !== 'string' || !/^run-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(runId)) {
|
|
219
|
+
throw new Error('harness say output has no runId');
|
|
220
|
+
}
|
|
221
|
+
entry.status = 'launched';
|
|
222
|
+
entry.runId = runId;
|
|
223
|
+
append(path, entry);
|
|
224
|
+
observe('launched', { seat, item, runId });
|
|
225
|
+
return entry;
|
|
226
|
+
} catch (error) {
|
|
227
|
+
append(path, { ...entry, status: 'outcome-unknown' });
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
}, deps.lockContended);
|
|
231
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join, resolve } from 'node:path';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { debug } from '../debug/log.js';
|
|
7
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
8
|
+
import { getUserConfig, type SeatLoopConfig } from '../user-config.js';
|
|
9
|
+
import { seatDay, seatLedgerPath, type SeatEntry } from './seat-loop.js';
|
|
10
|
+
|
|
11
|
+
export type SeatReportDeps = {
|
|
12
|
+
root?: string;
|
|
13
|
+
repo?: string;
|
|
14
|
+
now?: () => Date;
|
|
15
|
+
read?: (path: string) => string;
|
|
16
|
+
config?: SeatLoopConfig;
|
|
17
|
+
post?: boolean;
|
|
18
|
+
send?: (body: string, seat: string, pr: number) => Promise<void> | void;
|
|
19
|
+
};
|
|
20
|
+
export type SeatReportResult = { seat: string; date: string; body: string; posted: boolean };
|
|
21
|
+
const repoRoot = resolve(import.meta.dir, '../..');
|
|
22
|
+
const exec = promisify(execFile);
|
|
23
|
+
|
|
24
|
+
async function defaultSend(body: string, seat: string, pr: number, repo: string): Promise<void> {
|
|
25
|
+
const dir = mkdtempSync(join(tmpdir(), 'seat-report-'));
|
|
26
|
+
try {
|
|
27
|
+
const file = join(dir, 'body.md');
|
|
28
|
+
writeFileSync(file, body);
|
|
29
|
+
await exec('bash', [join(repo, 'scripts', 'coord-post.sh'), file], {
|
|
30
|
+
cwd: repo, env: { ...process.env, COORD_ID: seat, CH_PR: String(pr) }, timeout: 30_000,
|
|
31
|
+
});
|
|
32
|
+
} finally { rmSync(dir, { recursive: true, force: true }); }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function seatReport(seat: string, deps: SeatReportDeps = {}): Promise<SeatReportResult> {
|
|
36
|
+
if (!/^(?:MK|OP|TC|UX)$/.test(seat)) throw new Error(`unknown seat: ${seat}`);
|
|
37
|
+
const now = (deps.now ?? (() => new Date()))();
|
|
38
|
+
const date = seatDay(now);
|
|
39
|
+
let raw = '';
|
|
40
|
+
try { raw = (deps.read ?? ((path) => readFileSync(path, 'utf8')))(seatLedgerPath(seat, deps.root ?? effectiveInstanceRoot(), now)); }
|
|
41
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
|
|
42
|
+
const entries = raw.split('\n').filter(Boolean).map((line) => JSON.parse(line) as SeatEntry);
|
|
43
|
+
// One line per item, its last state wins: an `attempting` row followed by `launched` is not «unconfirmed».
|
|
44
|
+
const latest = new Map<string, SeatEntry>();
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const key = entry.item ? `${entry.item.source}:${entry.item.version ?? ''}:${entry.item.id}` : `none:${entry.status}`;
|
|
47
|
+
latest.delete(key);
|
|
48
|
+
latest.set(key, entry);
|
|
49
|
+
}
|
|
50
|
+
const detail = [...latest.values()].map((entry) => {
|
|
51
|
+
const label = entry.item ? `${entry.item.version ? `${entry.item.version} ` : ''}${entry.item.id} ${entry.item.title}`.replace(/\s+/g, ' ').trim() : '배정 없음';
|
|
52
|
+
if (entry.status === 'launched') return `발사 ${label} (${entry.runId ?? 'runId 없음'})`;
|
|
53
|
+
if (entry.status === 'hitl') return `결정 상정 ${label}`;
|
|
54
|
+
if (entry.status === 'shadow') return `shadow ${label}`;
|
|
55
|
+
if (entry.status === 'attempting' || entry.status === 'outcome-unknown') return `결과 확인 필요 ${entry.status} ${label}`;
|
|
56
|
+
return `건너뜀 ${entry.status} ${label}`;
|
|
57
|
+
});
|
|
58
|
+
const body = `**[${seat}]** {{TS}} → 보고 ${date}: ${detail.join(' · ') || '원장 기록 없음'}`;
|
|
59
|
+
const pr = (deps.config ?? getUserConfig().loops?.seat)?.reportPr;
|
|
60
|
+
const posted = deps.post === true && pr !== undefined;
|
|
61
|
+
if (posted) await (deps.send ?? ((text, id, number) => defaultSend(text, id, number, deps.repo ?? repoRoot)))(body, seat, pr);
|
|
62
|
+
try { debug.log('seat.loop', 'reported', { seat, date, entries: entries.length, posted }); } catch { /* observation is fail-soft */ }
|
|
63
|
+
return { seat, date, body, posted };
|
|
64
|
+
}
|
|
@@ -156,6 +156,8 @@ interface SelfDevSubtaskGradeRationale {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
export interface SelfDevDecompositionObservation extends SelfDevDecompositionMeta {
|
|
159
|
+
/** Number of registration-only goals folded into their dependency. */
|
|
160
|
+
foldedRegistration: number;
|
|
159
161
|
/** Number of retained subtasks whose original feature text grades `too_large`. */
|
|
160
162
|
tooLargeSubtaskCount: number;
|
|
161
163
|
/** Number of retained subtasks whose original feature text grades `too_small`. */
|
|
@@ -250,7 +252,7 @@ function observeDecomposerSelection(selection: SelfDevDecomposerSelection, obser
|
|
|
250
252
|
}
|
|
251
253
|
|
|
252
254
|
function applyGoalDefaultsAndHotPaths(goals: SelfDevGoal[], defaults: { base?: string; autoMerge?: boolean }): SelfDevGoal[] {
|
|
253
|
-
return
|
|
255
|
+
return goals.map((goal) => {
|
|
254
256
|
const hotPaths = [...new Set([...(goal.hotPaths ?? []), ...inferHotPaths(goal.feature ?? '')])];
|
|
255
257
|
return {
|
|
256
258
|
...goal,
|
|
@@ -258,7 +260,55 @@ function applyGoalDefaultsAndHotPaths(goals: SelfDevGoal[], defaults: { base?: s
|
|
|
258
260
|
...(defaults.base !== undefined ? { base: defaults.base } : {}),
|
|
259
261
|
...(defaults.autoMerge !== undefined ? { autoMerge: defaults.autoMerge } : {}),
|
|
260
262
|
};
|
|
261
|
-
})
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const REGISTRATION_FILES = new Set([
|
|
267
|
+
'src/nexus/api/http-server.ts',
|
|
268
|
+
'src/index.ts',
|
|
269
|
+
'src/telegram-commands.ts',
|
|
270
|
+
]);
|
|
271
|
+
|
|
272
|
+
function isRegistrationFile(path: string): boolean {
|
|
273
|
+
return REGISTRATION_FILES.has(path) || /^graphs\/(?:[^/]+\/)*[^/]+\.yaml$/.test(path);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Fold only registration-only goals into their one retained dependency. */
|
|
277
|
+
export function foldRegistrationOnlyGoals(goals: SelfDevGoal[]): SelfDevGoal[] {
|
|
278
|
+
let remaining = [...goals];
|
|
279
|
+
let changed = true;
|
|
280
|
+
while (changed) {
|
|
281
|
+
changed = false;
|
|
282
|
+
for (const registration of remaining) {
|
|
283
|
+
const files = registration.hotPaths ?? [];
|
|
284
|
+
if (files.length === 0 || !files.every(isRegistrationFile) || registration.dependsOn?.length !== 1) continue;
|
|
285
|
+
const dependencyId = registration.dependsOn[0];
|
|
286
|
+
const targetIndex = remaining.findIndex((goal) => goal.id === dependencyId && goal !== registration);
|
|
287
|
+
if (targetIndex < 0) continue;
|
|
288
|
+
const target = remaining[targetIndex]!;
|
|
289
|
+
remaining = remaining.filter((goal) => goal !== registration).map((goal) => {
|
|
290
|
+
if (goal === target) {
|
|
291
|
+
const dependsOn = goal.dependsOn?.filter((id) => id !== registration.id && id !== goal.id);
|
|
292
|
+
const { dependsOn: _old, ...rest } = goal;
|
|
293
|
+
return {
|
|
294
|
+
...rest,
|
|
295
|
+
...(dependsOn?.length ? { dependsOn } : {}),
|
|
296
|
+
feature: `${goal.feature}\nAlso register: ${registration.feature}`,
|
|
297
|
+
hotPaths: [...new Set([...(goal.hotPaths ?? []), ...files])],
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
if (!goal.dependsOn?.includes(registration.id!)) return goal;
|
|
301
|
+
const dependsOn = [...new Set(goal.dependsOn.map((id) => id === registration.id ? target.id! : id))]
|
|
302
|
+
.filter((id) => id !== goal.id);
|
|
303
|
+
const { dependsOn: _old, ...rest } = goal;
|
|
304
|
+
return dependsOn.length ? { ...rest, dependsOn } : rest;
|
|
305
|
+
});
|
|
306
|
+
debug.log('self-dev.decompose', 'fold-registration', { from: registration.id, into: target.id, files });
|
|
307
|
+
changed = true;
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return remaining;
|
|
262
312
|
}
|
|
263
313
|
|
|
264
314
|
function observeDecomposition(
|
|
@@ -267,12 +317,14 @@ function observeDecomposition(
|
|
|
267
317
|
arcClassification?: { arcs: SelfDevArc[]; hintDeviation?: { requested: number; actual: number } },
|
|
268
318
|
observationContext: SelfDevDecompositionObservationContext = {},
|
|
269
319
|
pathGroundingOptions?: DecomposePathGroundingOptions,
|
|
320
|
+
foldedRegistration = 0,
|
|
270
321
|
): SelfDevDecomposition {
|
|
271
322
|
const retainedSubtasks = decomposition.outcome === 'decomposed' ? goals : [];
|
|
272
323
|
const gradedSubtasks = retainedSubtasks.map(gradeRetainedSubtask);
|
|
273
324
|
const tooLargeSubtaskCount = gradedSubtasks.filter(({ grade }) => grade.verdict === 'too_large').length;
|
|
274
325
|
const observation: SelfDevDecompositionObservation & { goalId: string | null; runId: string | null } = {
|
|
275
326
|
...decomposition,
|
|
327
|
+
foldedRegistration,
|
|
276
328
|
tooLargeSubtaskCount,
|
|
277
329
|
tooSmallSubtaskCount: gradedSubtasks.filter(({ grade }) => grade.verdict === 'too_small').length,
|
|
278
330
|
hasMultipleTooLargeSubtasks: tooLargeSubtaskCount >= 2,
|
|
@@ -467,14 +519,15 @@ async function finalizeDecomposition(
|
|
|
467
519
|
decomposition: SelfDevDecompositionMeta,
|
|
468
520
|
opts: SelfDevDecomposeOptions,
|
|
469
521
|
arcHint: number | undefined,
|
|
522
|
+
foldedRegistration = 0,
|
|
470
523
|
): Promise<SelfDevDecomposition> {
|
|
471
|
-
if (arcHint === undefined || goals.length === 0) return observeDecomposition(goals, decomposition, undefined, opts.observation, opts.pathGrounding);
|
|
524
|
+
if (arcHint === undefined || goals.length === 0) return observeDecomposition(goals, decomposition, undefined, opts.observation, opts.pathGrounding, foldedRegistration);
|
|
472
525
|
try {
|
|
473
526
|
const arcLlm = opts.arcLlm ?? opts.llm ?? (await defaultDecomposeLlm(opts.model));
|
|
474
527
|
const tasks = goals.map((goal) => ({ id: goal.id ?? '', title: goal.feature ?? '', description: goal.feature, dependsOn: goal.dependsOn }));
|
|
475
528
|
const grouping = parseSelfDevArcGrouping(await arcLlm(buildSelfDevArcClassifyPrompt(feature, tasks, arcHint)));
|
|
476
529
|
const classification = grouping ? deriveSelfDevArcsFromGrouping(tasks, grouping, arcHint) : null;
|
|
477
|
-
if (!classification) return observeDecomposition(goals, decomposition, undefined, opts.observation, opts.pathGrounding);
|
|
530
|
+
if (!classification) return observeDecomposition(goals, decomposition, undefined, opts.observation, opts.pathGrounding, foldedRegistration);
|
|
478
531
|
const arcByTask = new Map(classification.arcs.flatMap((arc) => arc.taskIds.map((taskId) => [taskId, arc])));
|
|
479
532
|
const taskIdsForArc = new Map(classification.arcs.map((arc) => [arc.id, arc.taskIds]));
|
|
480
533
|
const orderedGoals = goals.map((goal) => {
|
|
@@ -482,9 +535,9 @@ async function finalizeDecomposition(
|
|
|
482
535
|
const dependsOn = [...new Set([...(goal.dependsOn ?? []), ...arcDependencies])].filter((id) => id !== goal.id);
|
|
483
536
|
return dependsOn.length ? { ...goal, dependsOn } : goal;
|
|
484
537
|
});
|
|
485
|
-
return observeDecomposition(pruneDanglingDependencies(orderedGoals), decomposition, classification, opts.observation, opts.pathGrounding);
|
|
538
|
+
return observeDecomposition(pruneDanglingDependencies(orderedGoals), decomposition, classification, opts.observation, opts.pathGrounding, foldedRegistration);
|
|
486
539
|
} catch {
|
|
487
|
-
return observeDecomposition(goals, decomposition, undefined, opts.observation, opts.pathGrounding);
|
|
540
|
+
return observeDecomposition(goals, decomposition, undefined, opts.observation, opts.pathGrounding, foldedRegistration);
|
|
488
541
|
}
|
|
489
542
|
}
|
|
490
543
|
|
|
@@ -514,17 +567,18 @@ export async function decomposeSelfDevGoal(
|
|
|
514
567
|
}
|
|
515
568
|
observeDecomposerSelection('fabric', opts.observation);
|
|
516
569
|
const actualTaskCount = result.goals.length;
|
|
517
|
-
const
|
|
570
|
+
const prepared = actualTaskCount > 0
|
|
518
571
|
? applyGoalDefaultsAndHotPaths(result.goals.slice(0, hardMaxTasks), defaults)
|
|
519
572
|
: [single];
|
|
520
|
-
|
|
573
|
+
const goals = foldRegistrationOnlyGoals(prepared);
|
|
574
|
+
return finalizeDecomposition(feature, pruneDanglingDependencies(goals), {
|
|
521
575
|
recommendedMaxTasks: maxTasks,
|
|
522
576
|
actualTaskCount,
|
|
523
577
|
truncatedAtHardMax: actualTaskCount > hardMaxTasks,
|
|
524
578
|
exceededRecommendedMax: actualTaskCount > maxTasks,
|
|
525
579
|
outcome: actualTaskCount === 0 ? 'single-no-subtasks' : 'decomposed',
|
|
526
580
|
omittedGoalCount: result.omittedGoalCount,
|
|
527
|
-
}, opts, actualTaskCount > 0 ? arcHint : undefined);
|
|
581
|
+
}, opts, actualTaskCount > 0 ? arcHint : undefined, prepared.length - goals.length);
|
|
528
582
|
}
|
|
529
583
|
|
|
530
584
|
observeDecomposerSelection('default', opts.observation);
|
|
@@ -539,8 +593,9 @@ export async function decomposeSelfDevGoal(
|
|
|
539
593
|
const actualTaskCount = rawSubtasks.length;
|
|
540
594
|
const decomposition: SelfDevDecompositionMeta = { recommendedMaxTasks: maxTasks, actualTaskCount, truncatedAtHardMax: actualTaskCount > hardMaxTasks, exceededRecommendedMax: actualTaskCount > maxTasks, outcome: actualTaskCount === 0 ? 'single-no-subtasks' : 'decomposed' };
|
|
541
595
|
const parsedGoals = parseSelfDevDecomposition(JSON.stringify({ subtasks: rawSubtasks.slice(0, hardMaxTasks) }), defaults);
|
|
542
|
-
const
|
|
543
|
-
|
|
596
|
+
const foldedGoals = foldRegistrationOnlyGoals(parsedGoals);
|
|
597
|
+
const goals = parsedGoals.length > 0 ? pruneDanglingDependencies(foldedGoals) : [single];
|
|
598
|
+
return finalizeDecomposition(feature, goals, decomposition, opts, parsedGoals.length > 0 ? arcHint : undefined, parsedGoals.length - foldedGoals.length);
|
|
544
599
|
}
|
|
545
600
|
|
|
546
601
|
/** Default LLM seam — lazy `streamLLM` wire (kept out of the test path). */
|
|
@@ -111,6 +111,8 @@ function runPath(runId: string, dir: string): string {
|
|
|
111
111
|
return path;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
const loggedAbsentCheckpoints = new Set<string>();
|
|
115
|
+
|
|
114
116
|
function loadSelfDevRunFromPath(path: string): SelfDevRunState | null {
|
|
115
117
|
const runId = path.slice(path.lastIndexOf(sep) + 1, -'.json'.length);
|
|
116
118
|
try {
|
|
@@ -118,7 +120,14 @@ function loadSelfDevRunFromPath(path: string): SelfDevRunState | null {
|
|
|
118
120
|
if (o && typeof o.runId === 'string' && Array.isArray(o.results)) return o;
|
|
119
121
|
try { debug.log('self-dev.run-store', 'read-failed', { runId, reason: 'invalid checkpoint shape' }); } catch { /* preserve fail-soft reads */ }
|
|
120
122
|
} catch (error) {
|
|
121
|
-
|
|
123
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
124
|
+
if (!loggedAbsentCheckpoints.has(runId)) {
|
|
125
|
+
loggedAbsentCheckpoints.add(runId);
|
|
126
|
+
try { debug.log('self-dev.run-store', 'checkpoint-absent', { runId }); } catch { /* preserve fail-soft reads */ }
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
try { debug.log('self-dev.run-store', 'read-failed', { runId, reason: String(error) }); } catch { /* preserve fail-soft reads */ }
|
|
130
|
+
}
|
|
122
131
|
}
|
|
123
132
|
return null;
|
|
124
133
|
}
|