elanous 0.2.21 → 0.2.22
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/chunks/1307-08b716476f1d50af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2489-ff55a35b202c0f94.js → 2489-d238d3e2eb5b2bf5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3857-5985236d9307f676.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5633-d6852385000738ef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-5b9e8a45a5b0c823.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8857-aa1cc4ac90af4d40.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9247-55bcfbb0e8057f59.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-88459e0903da2803.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-d781be3e3a9cfd74.js → page-d85c43f46f58716b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-b0a80997903852d8.js → page-4e645600579ae952.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-2b1263165630d9c8.js → page-87edbfd19117823b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-7f0c2b7801ac6a54.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-bf1f030a9d1adffd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-e2d6bed48ac20120.js → page-cbaf66b1e1804adf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-111cabe4d1fd69ae.js → page-80ed511e02c81412.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/{page-544dd4bd4847eeb7.js → page-9f5610caaa06178c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/{page-3ee191319bf97d87.js → page-ec8f3575e339de74.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-86d753e460f1a989.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-4166a7d51cf0a57d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-a449f1ed472b81c2.js → page-2a19c5cd3b562580.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-9045094d6d3b5cd0.js → page-b6a09cde2e07521a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/{page-c06673596849ef45.js → page-f7eae1518ca0c353.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-456a8573ad811381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/{layout-9f81233f29dcd409.js → layout-8462d6a543cbb2d9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-2974cdfb87c0f9c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-f927ffa25f3c86d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-abd86cd832aaa011.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-79daf68f6256adaa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8c3f168fc3f91939.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-7a12f61be4a579d1.js → page-786fb4504d0f2fa9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-c85b0589591a99c9.js → not-found-75209f14597dcb8f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/{page-725a4da02dd129eb.js → page-bf30affe3a85af7e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1187a44b7515434b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-3d0910185a66ed54.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f401ee1dd5a26e25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-5dfe7604fd842784.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/{page-21bec4afeb2f0917.js → page-76cf838050a8d681.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-dc39ee7a36d26385.js → page-7ec8e289b4c14df6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-84ae759f76053672.js → page-8c8ac17d52be2a79.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-3db58ddb61fe2be9.js → page-d359ab048d63a9d9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-c5315e30f3528dc4.js → page-b8b345c880bf78d1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-2fe368efb6de3b6a.js → page-f151cc10ad026940.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-9875f103057da433.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-9789328af9a9aa62.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-c69762987b751174.js → page-bf495a1e284f4e00.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a263d94bca4be22a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-3962eab8ec58d16e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-9abcfac07e02a542.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-bee49f927c333abb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-bac2d56dcb02d955.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-937cbcc13176295c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d09495594f7f480d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-7f8d7fcdf058a845.js → page-69cb93d1a2e57e0e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-22163556cd38bc88.js → page-72ec543928d67c3e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-82906eeda0186a7b.js +1 -0
- package/apps/pwa/out/_next/static/css/f2bd5257e6af88d8.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +14 -14
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +14 -14
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +14 -14
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +14 -14
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/graphs/catalog/catalog.yaml +1 -1
- package/graphs/drafts/recipes.yaml +2 -2
- package/graphs/test-diet/recipes.yaml +3 -0
- package/graphs/test-diet/test-diet.yaml +3 -1
- package/package.json +1 -1
- package/scripts/f-session-monitor.ts +69 -0
- package/scripts/hq/standby-snapshot.ts +7 -3
- package/scripts/knowledge-compress.ts +11 -0
- package/scripts/nightly-docops.ts +90 -3
- package/scripts/research-coo-admin-dates.ts +39 -0
- package/scripts/skills/public-check.ts +7 -2
- package/scripts/smart-merge/resync-shadow.ts +5 -3
- package/scripts/test-diet/lib.ts +81 -1
- package/scripts/test-diet/node.ts +48 -2
- package/scripts/verify-coo-admin-evidence.ts +83 -0
- package/src/acp/client.ts +4 -2
- package/src/agent/run-context.ts +4 -0
- package/src/agent-env/profile.ts +25 -5
- package/src/agent-mission/driver.ts +26 -12
- package/src/agent-mission/supervisor-metrics.ts +17 -2
- package/src/agent-substrate/acp-reviewer.ts +12 -5
- package/src/agent-substrate/pr-reviewer.ts +11 -4
- package/src/agent-substrate/review-intent.ts +102 -3
- package/src/autopilot/build/llm-conflict-merge.ts +22 -8
- package/src/autopilot/pr-manager.ts +18 -8
- package/src/away/release-watch.ts +47 -8
- package/src/briefing/brief-items.ts +104 -11
- package/src/chat/index.ts +1 -0
- package/src/cli/brief-cli.ts +20 -2
- package/src/cli/config-classify.ts +83 -0
- package/src/cli/control-cli.ts +14 -0
- package/src/cli/decisions-cli.ts +26 -16
- package/src/cli/logs-model-input.ts +1 -1
- package/src/cli/pr-cli.ts +65 -8
- package/src/cli/release-cli.ts +21 -4
- package/src/cli/tasks-cli.ts +108 -1
- package/src/connectors/types.ts +6 -6
- package/src/context-bus/context-now-surfaces.ts +18 -0
- package/src/context-bus/coord-events.ts +32 -4
- package/src/context-bus/long-term-memory.ts +23 -6
- package/src/context-bus/nightly-condense.ts +169 -21
- package/src/control/control-status.ts +87 -0
- package/src/control-plane/light-member.ts +5 -3
- package/src/control-plane/member.ts +172 -7
- package/src/core-turn/run-goal-loop.ts +22 -1
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +9 -0
- package/src/decisions/decision-cards.ts +20 -1
- package/src/decisions/decision-ledger.ts +11 -8
- package/src/decisions/web-push-decision-cards.ts +19 -3
- package/src/discord-self-message.ts +2 -2
- package/src/domains/coo-admin-dates.evidence.json +876 -0
- package/src/domains/coo-admin-tool.ts +48 -14
- package/src/domains/ops-status.ts +53 -0
- package/src/domains/outbound-alert.ts +24 -1
- package/src/exec-requests/planner.ts +6 -0
- package/src/graph-kinds/registry.ts +6 -6
- package/src/graph-runner/graph-cli.ts +34 -2
- package/src/graph-runner/graph-run-api.ts +7 -0
- package/src/graph-runner/graph-tick.ts +73 -12
- package/src/graph-runner/runner.ts +43 -11
- package/src/graph-unify/wf-to-graph.ts +79 -0
- package/src/graph-wizard/generate.ts +34 -2
- package/src/graph-wizard/steps.ts +13 -0
- package/src/harness/harness-cli-command.ts +121 -23
- package/src/harness/harness-queue.ts +58 -14
- package/src/harness/launch-advice.ts +23 -0
- package/src/index.ts +36 -7
- package/src/knowledge/conclusion-compress.ts +93 -0
- package/src/knowledge/know-find.ts +9 -2
- package/src/knowledge/query.ts +20 -0
- package/src/llm.ts +26 -16
- package/src/loops/neighbor-runtime.ts +88 -0
- package/src/loops/orchestrator/author-depth.ts +29 -11
- package/src/loops/orchestrator/overlap.ts +7 -1
- package/src/loops/orchestrator/resync-shadow.ts +1 -1
- package/src/loops/orchestrator/tick.ts +35 -2
- package/src/loops/orchestrator/traffic.ts +2 -0
- package/src/loops/registry.ts +5 -0
- package/src/market/publish.ts +17 -1
- package/src/maturity/feature-maturity.ts +2 -1
- package/src/mss/logging/redaction.ts +17 -0
- package/src/nexus/api/draft-metrics.ts +22 -8
- package/src/nexus/api/graph-peer-edit.ts +77 -0
- package/src/nexus/api/graph-wizard-api.ts +9 -1
- package/src/nexus/api/graphs-api.ts +145 -7
- package/src/nexus/api/http-server.ts +93 -8
- package/src/nexus/api/plugins-market.ts +24 -0
- package/src/nexus/index.ts +3 -2
- package/src/oauth/claude-pty-maturity.ts +24 -0
- package/src/oauth/fallback-chain.ts +27 -14
- package/src/plugins/maker/market-bundle.ts +9 -0
- package/src/plugins/maker/plugin-maker.ts +9 -3
- package/src/plugins/maker/wizard-generate.ts +73 -9
- package/src/policy/llm-policy.ts +11 -6
- package/src/release-loop/checklist-board.ts +27 -0
- package/src/release-loop/checklist.ts +38 -0
- package/src/seat-address/seat-address.ts +1 -0
- package/src/seat-loop/seat-loop.ts +174 -20
- package/src/seat-loop/seat-report.ts +29 -1
- package/src/self-dev/dev-cli.ts +90 -8
- package/src/self-dev/dev-pipeline.ts +20 -1
- package/src/self-dev/draft-sweep.ts +45 -8
- package/src/self-dev/orchestrate.ts +3 -0
- package/src/self-dev/review-provider-fallback.ts +83 -0
- package/src/self-dev/run-supervisor.ts +5 -1
- package/src/self-implement/harness-policy-gates.ts +7 -5
- package/src/self-implement/host-regate.ts +28 -14
- package/src/self-implement/l8-merge-queue.ts +80 -8
- package/src/self-implement/orchestrator.ts +68 -27
- package/src/self-implement/seams.ts +5 -3
- package/src/self-implement/self-implement-cli.ts +4 -0
- package/src/shell-env-bootstrap.ts +19 -4
- package/src/steward/triage.ts +25 -8
- package/src/storage/file-lock.ts +33 -4
- package/src/task-agent/completion-evidence.ts +31 -3
- package/src/task-agent/cover.ts +61 -0
- package/src/task-agent/live-moves.ts +126 -3
- package/src/task-agent/shadow.ts +19 -4
- package/src/task-agent/task-hand.ts +2 -0
- package/src/task-orchestrator/surfaces/pod-pool.ts +138 -15
- package/src/task-orchestrator/surfaces/self-implement.ts +7 -0
- package/src/telegram-commands.ts +8 -0
- package/src/telegram-project-command.ts +6 -0
- package/src/telegram.ts +2 -2
- package/src/typecheck-ratchet.ts +2 -1
- package/src/user-config.ts +35 -4
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/executor.ts +24 -2
- package/src/workflow-runtime/triggers/registry.ts +27 -0
- package/src/workflow-runtime/triggers/schedule-source.ts +30 -3
- package/apps/pwa/out/_next/static/chunks/1307-c97eba9fb72a9a5e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-59fedea50d13c762.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8857-f1762140ef9a96b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9247-7581825d511a64c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9497-b0e758d1cd4ae8d7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-937c92292a4ab4f0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-ea998f7f5d156bea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-33c328b64ca508f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e4bcb2c503dab04b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-300897dcbbd5ed76.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-103fa3191c1b2e08.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-be8b4615c919e36c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-c685b104e0dfa609.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-8d693331cfbb6add.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-cef4f30a4c22fae1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66ea1b54fc83fa15.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-8cc707bd09a497d9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-90b55be1f15ce0e8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cb16606e08be65db.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-16dc64b74ed6a40a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-8d04132dd0ac5e10.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-f23484de467c9ea3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-5456692bda39aafb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-d75f7cd65845ce98.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-5603f5e568b25ce3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-d2cc0f4e89f42028.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-a6eb73b8a4840f1c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c0a3f4d10edb83fa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6c0b88068c143f15.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-141436fd26dd56fc.js +0 -1
- package/apps/pwa/out/_next/static/css/e81df11490b4413b.css +0 -1
- /package/apps/pwa/out/_next/static/{MObU8ujkMciAKr6ljAV05 → aBi6EyMtLsrl7J8eH4kF7}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{MObU8ujkMciAKr6ljAV05 → aBi6EyMtLsrl7J8eH4kF7}/_ssgManifest.js +0 -0
|
@@ -20,6 +20,7 @@ import { parseGraphTemplateYaml, type GraphEdgeSpec, type GraphNodeSpec, type Gr
|
|
|
20
20
|
import { createGraphVariant, type GraphVariantPlan } from '../self-implement/graph-variant.js';
|
|
21
21
|
import { psProcessStartMs, START_TOLERANCE_MS } from '../harness/harness-stop.js';
|
|
22
22
|
import { executeBashNode } from '../workflow-runtime/nodes/bash.js';
|
|
23
|
+
import { interpolate } from '../workflow-runtime/variables.js';
|
|
23
24
|
import type { BashNode, NodeExecContext, WorkflowDeps } from '../workflow-runtime/types.js';
|
|
24
25
|
|
|
25
26
|
type BashRun = WorkflowDeps['runBash'];
|
|
@@ -59,6 +60,8 @@ export interface GraphRunState {
|
|
|
59
60
|
|
|
60
61
|
export interface GraphRunOptions {
|
|
61
62
|
input?: unknown;
|
|
63
|
+
/** Bytes already checked by the tick gate, used for a current-graph start or legacy resume. */
|
|
64
|
+
graphSource?: string;
|
|
62
65
|
dryRun?: boolean;
|
|
63
66
|
variant?: { goal: string; plan: GraphVariantPlan };
|
|
64
67
|
runId?: string;
|
|
@@ -72,7 +75,7 @@ export interface GraphRunOptions {
|
|
|
72
75
|
* its own universe and lands in the tree-derived test universe while the run ledger is prod.
|
|
73
76
|
*/
|
|
74
77
|
pinChildUniverse?: boolean;
|
|
75
|
-
deps?: { root?: string; runBash?: BashRun; log?: (event: string, data: Record<string, unknown>) => void; processStartMs?: (pid: number) => number | null; growthProposer?: GrowthProposer; growthLLM?: (prompt: string) => Promise<string>; classifyGrowthRecipe?: (node: GraphNodeSpec, resolved: { command?: string; approval?: string }) => GrowthRecipeEffect; growthDecision?: GrowthDecisionDeps & { list?: (filters: { status: 'all' }) => DecisionEntry[] } };
|
|
78
|
+
deps?: { root?: string; runBash?: BashRun; callLLM?: (prompt: string) => Promise<string>; log?: (event: string, data: Record<string, unknown>) => void; beforeNode?: () => void; processStartMs?: (pid: number) => number | null; growthProposer?: GrowthProposer; growthLLM?: (prompt: string) => Promise<string>; classifyGrowthRecipe?: (node: GraphNodeSpec, resolved: { command?: string; approval?: string }) => GrowthRecipeEffect; growthDecision?: GrowthDecisionDeps & { list?: (filters: { status: 'all' }) => DecisionEntry[] } };
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
function safeSegment(value: string): string {
|
|
@@ -83,24 +86,29 @@ function safeSegment(value: string): string {
|
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
type CommandRecipe = { command: string; dry_run_command?: string; timeout_ms?: number };
|
|
86
|
-
type
|
|
89
|
+
type PromptRecipe = { prompt: string };
|
|
90
|
+
type Recipe = CommandRecipe | PromptRecipe | ({ approval: string } & Partial<CommandRecipe>);
|
|
87
91
|
|
|
88
92
|
/** 접두 없는 recipe 가 카탈로그 역할이고, 그래프 옆 recipes.yaml 에 같은 키의 `{ command }` 가 있을 때만 cmd 처럼 실행한다. */
|
|
89
93
|
function roleCommand(recipe: string, recipes: Record<string, Recipe>, catalogRoles: ReadonlySet<string>): CommandRecipe | undefined {
|
|
90
94
|
if (!recipe || recipe === 'none' || recipe.includes(':') || !catalogRoles.has(recipe)) return undefined;
|
|
91
95
|
const entry = recipes[recipe];
|
|
92
96
|
// An entry that carries an approval only runs through `approval:` — never as a bare command.
|
|
93
|
-
return entry && !('approval' in entry) && typeof entry.command === 'string' ? { command: entry.command,
|
|
97
|
+
return entry && 'command' in entry && !('approval' in entry) && typeof entry.command === 'string' ? { command: entry.command,
|
|
94
98
|
...(entry.dry_run_command ? { dry_run_command: entry.dry_run_command } : {}),
|
|
95
99
|
...(entry.timeout_ms ? { timeout_ms: entry.timeout_ms } : {}) } : undefined;
|
|
96
100
|
}
|
|
97
101
|
|
|
98
|
-
function resolveNodeRecipe(node: GraphNodeSpec, recipes: Record<string, Recipe>, catalogRoles: ReadonlySet<string>): { command?: CommandRecipe; approval?: { approval: string } } {
|
|
102
|
+
function resolveNodeRecipe(node: GraphNodeSpec, recipes: Record<string, Recipe>, catalogRoles: ReadonlySet<string>): { command?: CommandRecipe; prompt?: PromptRecipe; approval?: { approval: string } } {
|
|
99
103
|
if (node.recipe === 'none') return {};
|
|
104
|
+
if (node.kind === 'prompt' && node.recipe.startsWith('prompt:')) {
|
|
105
|
+
const entry = recipes[node.recipe.slice('prompt:'.length)];
|
|
106
|
+
return entry && 'prompt' in entry ? { prompt: entry } : {};
|
|
107
|
+
}
|
|
100
108
|
if (node.recipe.startsWith('cmd:')) {
|
|
101
109
|
const id = node.recipe.slice(4);
|
|
102
110
|
const entry = recipes[id];
|
|
103
|
-
return entry && !('approval' in entry) && typeof entry.command === 'string' ? { command: { command: entry.command,
|
|
111
|
+
return entry && 'command' in entry && !('approval' in entry) && typeof entry.command === 'string' ? { command: { command: entry.command,
|
|
104
112
|
...(entry.dry_run_command ? { dry_run_command: entry.dry_run_command } : {}),
|
|
105
113
|
...(entry.timeout_ms ? { timeout_ms: entry.timeout_ms } : {}) } } : {};
|
|
106
114
|
}
|
|
@@ -133,7 +141,10 @@ function recipesFor(path: string, recipeSource: string): Record<string, Recipe>
|
|
|
133
141
|
for (const [id, raw] of Object.entries(parsed)) {
|
|
134
142
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) throw new Error(`invalid recipe: ${id}`);
|
|
135
143
|
const item = raw as Record<string, unknown>;
|
|
136
|
-
if (Object.hasOwn(item, '
|
|
144
|
+
if (Object.hasOwn(item, 'prompt')) {
|
|
145
|
+
if (typeof item.prompt !== 'string' || !item.prompt.trim() || Object.keys(item).some(key => key !== 'prompt')) throw new Error(`invalid recipe: ${id}`);
|
|
146
|
+
recipes[id] = { prompt: item.prompt };
|
|
147
|
+
} else if (Object.hasOwn(item, 'approval')) {
|
|
137
148
|
if (typeof item.approval !== 'string' || !item.approval.trim() ||
|
|
138
149
|
(item.command !== undefined && (typeof item.command !== 'string' || !item.command.trim())) ||
|
|
139
150
|
(item.timeout_ms !== undefined && (!Number.isSafeInteger(item.timeout_ms) || (item.timeout_ms as number) <= 0)) ||
|
|
@@ -382,7 +393,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
382
393
|
? listGraphRuns(root).runs.filter((run) => run.runId === options.resumeRunId && run.graphPath === resolve(path)) : [];
|
|
383
394
|
if (matchingRuns.length > 1) throw new Error(`ambiguous run id: ${options.resumeRunId}`);
|
|
384
395
|
const currentSource = options.resumeRunId && (options.resumeGraphId || matchingRuns.length) && !options.useCurrentGraph
|
|
385
|
-
? undefined : readFileSync(path, 'utf8');
|
|
396
|
+
? undefined : options.graphSource ?? readFileSync(path, 'utf8');
|
|
386
397
|
const currentHeader: unknown = options.resumeRunId && !options.resumeGraphId && !matchingRuns.length ? parseYaml(currentSource!) : undefined;
|
|
387
398
|
const resumeGraphId = options.resumeGraphId ?? matchingRuns[0]?.graphId ?? (currentHeader && typeof currentHeader === 'object' && !Array.isArray(currentHeader)
|
|
388
399
|
? (currentHeader as Record<string, unknown>).graph_id : undefined);
|
|
@@ -393,7 +404,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
393
404
|
}
|
|
394
405
|
const snapshotDir = saved ? `${saved.statePath}.graph` : undefined;
|
|
395
406
|
const graphMode: 'snapshot' | 'current' = snapshotDir && saved?.graphSnapshot && !options.useCurrentGraph ? 'snapshot' : 'current';
|
|
396
|
-
const source = graphMode === 'snapshot' ? readFileSync(join(snapshotDir!, 'graph.yaml'), 'utf8') : currentSource ?? readFileSync(path, 'utf8');
|
|
407
|
+
const source = graphMode === 'snapshot' ? readFileSync(join(snapshotDir!, 'graph.yaml'), 'utf8') : currentSource ?? options.graphSource ?? readFileSync(path, 'utf8');
|
|
397
408
|
// The shared YAML parser requires a recipe string. A command-less node in
|
|
398
409
|
// this runner is represented internally as `none`, without changing that parser.
|
|
399
410
|
const raw: unknown = parseYaml(source);
|
|
@@ -451,7 +462,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
451
462
|
for (const node of graph.nodes) {
|
|
452
463
|
if (node.recipe === 'none') continue;
|
|
453
464
|
const resolved = resolveNodeRecipe(node, recipes, catalogRoles);
|
|
454
|
-
if (!resolved.command && !resolved.approval) throw new Error(`unknown command recipe for ${node.nodeId}: ${node.recipe}`);
|
|
465
|
+
if (!resolved.command && !resolved.prompt && !resolved.approval) throw new Error(`unknown command recipe for ${node.nodeId}: ${node.recipe}`);
|
|
455
466
|
}
|
|
456
467
|
const graphId = safeSegment(graph.graphId);
|
|
457
468
|
if (saved && graphId !== saved.graphId) {
|
|
@@ -625,6 +636,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
625
636
|
writeFileSync(join(`${statePath}.graph`, 'recipes.yaml'), recipeSource, { flag: 'wx' });
|
|
626
637
|
}
|
|
627
638
|
while (current !== undefined) {
|
|
639
|
+
options.deps?.beforeNode?.();
|
|
628
640
|
const node = graph.nodes.find((n) => n.nodeId === current);
|
|
629
641
|
if (!node) throw new Error(`undeclared node: ${current}`);
|
|
630
642
|
const resumingPending = state.pending?.nodeId === current;
|
|
@@ -643,6 +655,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
643
655
|
}
|
|
644
656
|
const resolved = resolveNodeRecipe(node, recipes, catalogRoles);
|
|
645
657
|
const command = resolved.command;
|
|
658
|
+
const prompt = resolved.prompt;
|
|
646
659
|
const approval = resolved.approval ? { approval: approvalMessage(resolved.approval.approval, state.input) } : undefined;
|
|
647
660
|
if (resumingPending && (!approval || approval.approval !== state.pending?.message)) {
|
|
648
661
|
throw new Error(`pending approval no longer matches graph: ${current}`);
|
|
@@ -739,6 +752,25 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
739
752
|
error = result.error ? redactCredentials(result.error) : undefined;
|
|
740
753
|
state.executed++;
|
|
741
754
|
}
|
|
755
|
+
if (prompt && !state.dryRun && !resumingCompleted) {
|
|
756
|
+
const outputs: Record<string, { ok: boolean; output: unknown; durationMs: number }> = Object.create(null);
|
|
757
|
+
for (const previous of state.nodes) outputs[previous.nodeId] = { ok: previous.ok, output: lastJsonObject(previous.output) ?? previous.output ?? null, durationMs: 0 };
|
|
758
|
+
try {
|
|
759
|
+
const text = interpolate(prompt.prompt, {
|
|
760
|
+
arguments: typeof state.input === 'string' ? state.input : state.input === undefined ? '' : JSON.stringify(state.input),
|
|
761
|
+
artifactsDir: dirname(statePath), outputs,
|
|
762
|
+
});
|
|
763
|
+
if (text.missing.length) throw new Error(`missing prompt reference: ${text.missing.join(', ')}`);
|
|
764
|
+
// The LLM is injected only; production wiring to a provider is a follow-up slice.
|
|
765
|
+
if (!options.deps?.callLLM) throw new Error('prompt node requires an injected LLM (deps.callLLM)');
|
|
766
|
+
output = await options.deps.callLLM(text.text);
|
|
767
|
+
ok = true;
|
|
768
|
+
} catch (cause) {
|
|
769
|
+
ok = false;
|
|
770
|
+
error = cause instanceof Error ? cause.message : String(cause);
|
|
771
|
+
}
|
|
772
|
+
state.executed++;
|
|
773
|
+
}
|
|
742
774
|
const seconds = Number(((performance.now() - startedAt) / 1000).toFixed(2));
|
|
743
775
|
// A wall clock stepped back mid-node must not record an end before the start.
|
|
744
776
|
const nodeEndedAt = new Date(Math.max(Date.now(), Date.parse(nodeStartedAt))).toISOString();
|
|
@@ -753,7 +785,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
753
785
|
if (approval && !state.dryRun && !command) {
|
|
754
786
|
output = JSON.stringify({ outcome: ok ? 'approved' : 'rejected', ...(state.pending?.decidedBy === undefined ? {} : { decidedBy: state.pending.decidedBy }), decidedAt: state.pending?.decidedAt ?? null });
|
|
755
787
|
}
|
|
756
|
-
state.nodes.push({ nodeId: current, ok, exit, executed: !!command && (!state.dryRun || !!command.dry_run_command), ...((command || approval) && (!state.dryRun || !!command?.dry_run_command) && output !== undefined ? { output } : {}), ...(error ? { error } : {}),
|
|
788
|
+
state.nodes.push({ nodeId: current, ok, exit, executed: (!!command && (!state.dryRun || !!command.dry_run_command)) || (!!prompt && !state.dryRun), ...((command || prompt || approval) && (!state.dryRun || !!command?.dry_run_command) && output !== undefined ? { output } : {}), ...(error ? { error } : {}),
|
|
757
789
|
...(approval && state.pending ? { ...(state.pending.decidedBy === undefined ? {} : { decidedBy: state.pending.decidedBy }), decidedAt: state.pending.decidedAt } : {}),
|
|
758
790
|
startedAt: nodeStartedAt, endedAt: nodeEndedAt, seconds });
|
|
759
791
|
}
|
|
@@ -761,7 +793,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
761
793
|
delete state.pending;
|
|
762
794
|
delete state.approvalSourceHash;
|
|
763
795
|
}
|
|
764
|
-
const reported = command && (!state.dryRun || command.dry_run_command) ? lastJsonObject(resumingCompleted ? completed?.output : output)?.outcome : undefined;
|
|
796
|
+
const reported = (prompt && !state.dryRun) || (command && (!state.dryRun || command.dry_run_command)) ? lastJsonObject(resumingCompleted ? completed?.output : output)?.outcome : undefined;
|
|
765
797
|
const namedOutcome = typeof reported === 'string' ? reported : undefined;
|
|
766
798
|
console.error(`[graph] ${current} ${ok ? 'ok' : 'fail'} (${seconds.toFixed(2)}s)`);
|
|
767
799
|
debug.log('graph.run', 'node', { graphId, runId, nodeId: current, phase: ok ? 'ok' : 'fail', seconds, exit });
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { topoSort } from '../workflow-runtime/schema.js';
|
|
2
|
+
import type { DagNode, WorkflowDefinition } from '../workflow-runtime/types.js';
|
|
3
|
+
import type { GraphEdgeSpec, GraphNodeSpec, GraphTemplateSpec } from '../self-implement/graph-yaml.js';
|
|
4
|
+
import { WORKFLOW_CORE_KINDS } from '../graph-kinds/registry.js';
|
|
5
|
+
|
|
6
|
+
/** In-memory graph projection. The compatibility executor dispatches these
|
|
7
|
+
* workflow recipes; graph-runner cannot execute all workflow node variants yet. */
|
|
8
|
+
export interface WorkflowGraph {
|
|
9
|
+
readonly template: GraphTemplateSpec;
|
|
10
|
+
readonly recipes: Readonly<Record<string, DagNode>>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type WorkflowGraphResult =
|
|
14
|
+
| { readonly ok: true; readonly graph: WorkflowGraph }
|
|
15
|
+
| { readonly ok: false; readonly reason: string };
|
|
16
|
+
|
|
17
|
+
/** Project a validated workflow without changing its YAML or executing any work.
|
|
18
|
+
* Reject constructs whose graph projection would silently change DAG semantics. */
|
|
19
|
+
export function workflowToGraph(workflow: WorkflowDefinition): WorkflowGraphResult {
|
|
20
|
+
if ((workflow.concurrency ?? 1) !== 1) return { ok: false, reason: 'concurrency > 1' };
|
|
21
|
+
if (!workflow.nodes.length) return { ok: false, reason: 'empty workflow' };
|
|
22
|
+
if (workflow.nodes.some(node => node.on_error)) {
|
|
23
|
+
return { ok: false, reason: 'error routing' };
|
|
24
|
+
}
|
|
25
|
+
let order: string[];
|
|
26
|
+
try { order = topoSort(workflow.nodes); }
|
|
27
|
+
catch (error) { return { ok: false, reason: String(error) }; }
|
|
28
|
+
if (order.length !== workflow.nodes.length || new Set(order).size !== order.length) {
|
|
29
|
+
return { ok: false, reason: 'invalid topology' };
|
|
30
|
+
}
|
|
31
|
+
const byId = new Map(workflow.nodes.map(node => [node.id, node] as const));
|
|
32
|
+
const recipes: Record<string, DagNode> = Object.create(null);
|
|
33
|
+
const nodes: GraphNodeSpec[] = [];
|
|
34
|
+
for (const id of order) {
|
|
35
|
+
const node = byId.get(id)!;
|
|
36
|
+
const kind = typeof node.kind === 'string' ? node.kind
|
|
37
|
+
: WORKFLOW_CORE_KINDS.find(key => key !== 'subworkflow' && key in node);
|
|
38
|
+
if (!kind) {
|
|
39
|
+
return { ok: false, reason: `unknown node variant: ${id}` };
|
|
40
|
+
}
|
|
41
|
+
recipes[id] = node;
|
|
42
|
+
nodes.push({ nodeId: id, kind, recipe: `workflow:${id}`, maxVisits: 1 });
|
|
43
|
+
}
|
|
44
|
+
// A single cursor visits each node exactly once in the same stable topological
|
|
45
|
+
// order as the serial workflow executor. Preserve original dependencies and
|
|
46
|
+
// conditions in recipes; do not mistake a when expression for a graph outcome.
|
|
47
|
+
const edges: GraphEdgeSpec[] = order.slice(1).map((id, index) => ({ from: order[index]!, to: id }));
|
|
48
|
+
const template: GraphTemplateSpec = {
|
|
49
|
+
graphId: workflow.name, version: 1, entryNode: order[0]!, terminalNodes: [order.at(-1)!],
|
|
50
|
+
nodes, edges,
|
|
51
|
+
};
|
|
52
|
+
return { ok: true, graph: { template, recipes } };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The workflow compatibility cursor follows the projected graph edges, not
|
|
56
|
+
* the source DAG. The WF event adapter remains the owner of node dispatch. */
|
|
57
|
+
export function walkWorkflowGraph(graph: WorkflowGraph): string[] {
|
|
58
|
+
const order: string[] = [];
|
|
59
|
+
const visited = new Set<string>();
|
|
60
|
+
let current: string | undefined = graph.template.entryNode;
|
|
61
|
+
while (current !== undefined) {
|
|
62
|
+
const spec = graph.template.nodes.find(node => node.nodeId === current);
|
|
63
|
+
if (visited.has(current) || !spec || spec.recipe !== `workflow:${current}` || spec.maxVisits !== 1
|
|
64
|
+
|| !Object.hasOwn(graph.recipes, current)) throw new Error(`invalid workflow graph cursor: ${current}`);
|
|
65
|
+
const recipe = graph.recipes[current]!;
|
|
66
|
+
const kind = typeof recipe.kind === 'string' ? recipe.kind
|
|
67
|
+
: WORKFLOW_CORE_KINDS.find(key => key !== 'subworkflow' && key in recipe);
|
|
68
|
+
if (recipe.id !== current || kind !== spec.kind) throw new Error(`invalid workflow graph recipe: ${current}`);
|
|
69
|
+
visited.add(current);
|
|
70
|
+
order.push(current);
|
|
71
|
+
const outgoing: GraphEdgeSpec[] = graph.template.edges.filter(edge => edge.from === current);
|
|
72
|
+
if (outgoing.length > 1 || outgoing.some(edge => edge.to === undefined)) throw new Error(`invalid workflow graph edge: ${current}`);
|
|
73
|
+
current = outgoing[0]?.to;
|
|
74
|
+
}
|
|
75
|
+
if (order.length !== graph.template.nodes.length || !graph.template.terminalNodes.includes(order.at(-1)!)) {
|
|
76
|
+
throw new Error('incomplete workflow graph path');
|
|
77
|
+
}
|
|
78
|
+
return order;
|
|
79
|
+
}
|
|
@@ -18,6 +18,8 @@ import { validateGraphYaml } from '../nexus/api/graph-kinds.js';
|
|
|
18
18
|
import { defaultGraphsDir } from '../self-implement/graph-templates.js';
|
|
19
19
|
import { WORKFLOW_SYNTH_SYSTEM_PROMPT } from '../workflow-synth/system-prompt.js';
|
|
20
20
|
import { WIZARD_ARCHETYPES } from './archetypes.js';
|
|
21
|
+
import { listInstalledPacks, queryInstalledPack } from '../knowledge/query.js';
|
|
22
|
+
import { parsePackIdString } from '../knowledge/kgs/pack.js';
|
|
21
23
|
import { WIZARD_MAX_RETRIES, WIZARD_STEPS, WIZARD_STEP_IDS, recipesYamlFor, type WizardNodeStep } from './steps.js';
|
|
22
24
|
|
|
23
25
|
export interface GraphWizardTurn { role: 'user' | 'assistant'; text: string }
|
|
@@ -28,6 +30,8 @@ export interface GraphWizardRequest {
|
|
|
28
30
|
/** 있으면 prompt 는 «이 그래프를 고치라»는 지시다 — 결과는 고친 «전체» YAML. */
|
|
29
31
|
currentYaml?: string;
|
|
30
32
|
history?: GraphWizardTurn[];
|
|
33
|
+
/** Installed pack selected for a knowledge (RAG) node. */
|
|
34
|
+
packId?: string;
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
export interface GraphWizardResult {
|
|
@@ -58,10 +62,18 @@ export interface GraphWizardDeps {
|
|
|
58
62
|
/** 이미 쓰인 graph_id(핵심 ⊕ 내 그래프). 기본 = graphs/** ⊕ <state>/graphs. */
|
|
59
63
|
existingIds?: () => ReadonlySet<string>;
|
|
60
64
|
maxAttempts?: number;
|
|
65
|
+
/** Installed pack inventory and retrieval seams for isolated generation. */
|
|
66
|
+
packs?: typeof listInstalledPacks;
|
|
67
|
+
searchPack?: typeof queryInstalledPack;
|
|
61
68
|
}
|
|
62
69
|
|
|
63
70
|
export const GRAPH_WIZARD_MAX_ATTEMPTS = 3;
|
|
64
71
|
|
|
72
|
+
/** Inventory used by the editor's pack picker and the wizard's validation. */
|
|
73
|
+
export function wizardInstalledPacks(deps: GraphWizardDeps = {}): Array<{ id: string; title: string }> {
|
|
74
|
+
return (deps.packs ?? listInstalledPacks)();
|
|
75
|
+
}
|
|
76
|
+
|
|
65
77
|
/** 요청 자체가 틀렸다(400) — LLM 실패(502)와 가른다. */
|
|
66
78
|
export class GraphWizardInputError extends Error { override name = 'GraphWizardInputError'; }
|
|
67
79
|
const GRAPH_ID = /^[a-z0-9-]+$/;
|
|
@@ -422,8 +434,9 @@ async function defaultCallLLM(prompt: string): Promise<string> {
|
|
|
422
434
|
function buildPrompt(input: {
|
|
423
435
|
req: GraphWizardRequest; kind: GraphKind; id: string; base?: TemplateEntry; templates: readonly TemplateEntry[];
|
|
424
436
|
previous?: { yaml: string; issues: string[] };
|
|
437
|
+
evidence?: string;
|
|
425
438
|
}): string {
|
|
426
|
-
const { req, kind, id, base, templates, previous } = input;
|
|
439
|
+
const { req, kind, id, base, templates, previous, evidence } = input;
|
|
427
440
|
const kinds = listNodeKinds(kind).map((k) => k.kind).join(', ');
|
|
428
441
|
const editing = typeof req.currentYaml === 'string' && req.currentYaml.trim() !== '';
|
|
429
442
|
const lines: string[] = [
|
|
@@ -451,6 +464,8 @@ function buildPrompt(input: {
|
|
|
451
464
|
} else {
|
|
452
465
|
lines.push('', `사용자의 말: ${req.prompt}`);
|
|
453
466
|
}
|
|
467
|
+
if (evidence) lines.push('', '선택한 설치 팩의 검색 근거:', evidence,
|
|
468
|
+
`지식(RAG) 노드를 cmd 단계로 넣고 # 지식 검색 | knowledge-rag | ${req.packId} 주석을 붙인다. 다음 산출 단계는 이 노드의 근거를 사용한다. 실행 입력 input.query 가 있으면 질문으로 검색하고 없으면 설치 팩 전체 색인에서 가져온다. 근거에 없는 사실을 만들지 마라.`);
|
|
454
469
|
if (previous) {
|
|
455
470
|
lines.push('', '직전 시도가 검증에 실패했다. 아래 문제를 «전부» 고쳐 전체 YAML 을 다시 써라:', ...previous.issues.map((i) => `- ${i}`),
|
|
456
471
|
'직전 YAML:', '```yaml', previous.yaml.trim(), '```');
|
|
@@ -467,6 +482,15 @@ export async function generateGraphFromPrompt(req: GraphWizardRequest, deps: Gra
|
|
|
467
482
|
const kind: GraphKind = req.kind === 'workflow' ? 'workflow' : 'harness';
|
|
468
483
|
const prompt = typeof req.prompt === 'string' ? req.prompt.trim() : '';
|
|
469
484
|
if (!prompt) throw new GraphWizardInputError('prompt is required');
|
|
485
|
+
if (req.packId !== undefined && kind !== 'harness') throw new GraphWizardInputError('pack selection requires a harness graph');
|
|
486
|
+
let evidence: string | undefined;
|
|
487
|
+
if (req.packId !== undefined) {
|
|
488
|
+
if (!parsePackIdString(req.packId) || !wizardInstalledPacks(deps).some(pack => pack.id === req.packId))
|
|
489
|
+
throw new GraphWizardInputError(`pack not installed: ${req.packId}`);
|
|
490
|
+
const cards = (deps.searchPack ?? queryInstalledPack)(req.packId, prompt);
|
|
491
|
+
if (!cards.length) throw new GraphWizardInputError(`no knowledge found in ${req.packId} for: ${prompt}`);
|
|
492
|
+
evidence = cards.slice(0, 5).map(card => `[${card.ref}] ${card.title}: ${card.body.slice(0, 1200)}`).join('\n');
|
|
493
|
+
}
|
|
470
494
|
const editing = typeof req.currentYaml === 'string' && req.currentYaml.trim() !== '';
|
|
471
495
|
const graphsDir = deps.graphsDir ?? defaultGraphsDir();
|
|
472
496
|
const templates = kind === 'harness' ? listWizardTemplates(graphsDir) : [];
|
|
@@ -497,7 +521,7 @@ export async function generateGraphFromPrompt(req: GraphWizardRequest, deps: Gra
|
|
|
497
521
|
for (attempts = 1; attempts <= maxAttempts; attempts += 1) {
|
|
498
522
|
let text: string;
|
|
499
523
|
try {
|
|
500
|
-
text = await callLLM(buildPrompt({ req: { ...req, prompt }, kind, id, base: keywordBase, templates,
|
|
524
|
+
text = await callLLM(buildPrompt({ req: { ...req, prompt }, kind, id, base: keywordBase, templates, evidence,
|
|
501
525
|
...(attempts > 1 ? { previous: { yaml, issues } } : {}) }));
|
|
502
526
|
} catch (error) {
|
|
503
527
|
debug.log('graph.wizard', 'error', { id, kind, attempt: attempts, base, message: error instanceof Error ? error.message : String(error) });
|
|
@@ -514,6 +538,14 @@ export async function generateGraphFromPrompt(req: GraphWizardRequest, deps: Gra
|
|
|
514
538
|
try { issues = validateWizardYaml(kind, yaml); }
|
|
515
539
|
catch (error) { issues = [`검증 실패: ${error instanceof Error ? error.message : String(error)}`]; }
|
|
516
540
|
steps = kind === 'harness' ? parseNodeAnnotations(yaml) : {};
|
|
541
|
+
if (req.packId && issues.length === 0) {
|
|
542
|
+
const raw = parseYaml(yaml) as { nodes?: Array<{ node_id: string; recipe: string }>; edges?: Array<{ from: string; map?: Record<string, string> }> };
|
|
543
|
+
const rag = (raw.nodes ?? []).find(node => node.recipe.startsWith('cmd:') && steps[node.node_id]?.step === 'knowledge-rag' && steps[node.node_id]?.arg === req.packId);
|
|
544
|
+
if (!rag || !(raw.edges ?? []).some(edge => edge.from === rag.node_id &&
|
|
545
|
+
typeof edge.map?.ok === 'string' && !['done', 'failed'].includes(edge.map.ok) &&
|
|
546
|
+
(raw.nodes ?? []).some(node => node.node_id === edge.map!.ok && node.recipe.startsWith('cmd:'))))
|
|
547
|
+
issues.push(`선택한 팩 ${req.packId} 의 knowledge-rag 노드를 산출 단계에 연결하라`);
|
|
548
|
+
}
|
|
517
549
|
recipes = undefined;
|
|
518
550
|
dryRun = undefined;
|
|
519
551
|
if (kind === 'harness' && issues.length === 0) {
|
|
@@ -9,6 +9,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
9
9
|
import { existsSync, readFileSync } from 'node:fs';
|
|
10
10
|
import { resolve } from 'node:path';
|
|
11
11
|
import { debug } from '../debug/log.js';
|
|
12
|
+
import { queryInstalledPack } from '../knowledge/query.js';
|
|
12
13
|
|
|
13
14
|
export interface WizardStepSpec {
|
|
14
15
|
id: string;
|
|
@@ -28,6 +29,7 @@ export const WIZARD_STEPS: readonly WizardStepSpec[] = [
|
|
|
28
29
|
{ id: 'notify-me', use: '실패·포기 같은 사건을 나(대표)에게 알린다. arg = 알림 문구', outcomes: ['ok', 'fail'], timeoutMs: 60_000 },
|
|
29
30
|
{ id: 'gh-pr-review', use: 'GitHub PR 을 엘라누스 리뷰어로 리뷰한다. arg = PR 번호(없으면 실행 입력 input.pr). 결과 ok(must-fix 없음) · must-fix', outcomes: ['ok', 'must-fix', 'fail'], timeoutMs: 900_000 },
|
|
30
31
|
{ id: 'gh-pr-merge', use: 'GitHub PR 을 squash 머지한다. arg = PR 번호(없으면 input.pr)', outcomes: ['ok', 'fail'], timeoutMs: 300_000 },
|
|
32
|
+
{ id: 'knowledge-rag', use: '설치된 지식 팩만 검색해 인용 가능한 근거를 낸다. arg = pack:<slug>@<version>; 실행 입력 input.query 또는 앞 단계 결과로 검색하며 둘 다 없으면 팩의 색인에서 가져온다', outcomes: ['ok', 'fail'], timeoutMs: 120_000 },
|
|
31
33
|
{ id: 'custom', use: '위 단계로 안 되는 외부 연동(노션·슬랙 업로드 등). 실행하면 «아직 구현되지 않음»으로 실패한다. arg = 무엇을 해야 하는지', outcomes: ['ok', 'fail'], timeoutMs: 60_000 },
|
|
32
34
|
];
|
|
33
35
|
|
|
@@ -140,6 +142,17 @@ export function runWizardStep(step: string, arg: string | undefined, ctx: GraphC
|
|
|
140
142
|
const started = Date.now();
|
|
141
143
|
let result: StepResult;
|
|
142
144
|
switch (step) {
|
|
145
|
+
case 'knowledge-rag': {
|
|
146
|
+
const query = ctx.input && typeof ctx.input === 'object' && typeof (ctx.input as { query?: unknown }).query === 'string'
|
|
147
|
+
? (ctx.input as { query: string }).query : prev.trim();
|
|
148
|
+
if (!arg) { result = { outcome: 'fail', error: 'knowledge-rag: pack id(arg) required' }; break; }
|
|
149
|
+
try {
|
|
150
|
+
const hits = queryInstalledPack(arg, query);
|
|
151
|
+
result = hits.length ? { outcome: 'ok', text: hits.slice(0, 5).map(hit => `[${hit.ref}] ${hit.title}: ${hit.body}`).join('\n') }
|
|
152
|
+
: { outcome: 'fail', error: `knowledge-rag: no matches in ${arg}` };
|
|
153
|
+
} catch (error) { result = { outcome: 'fail', error: error instanceof Error ? error.message : String(error) }; }
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
143
156
|
case 'web-search': {
|
|
144
157
|
const query = arg?.trim() || (ctx.input && typeof ctx.input === 'object' && typeof (ctx.input as { query?: unknown }).query === 'string' ? (ctx.input as { query: string }).query : '');
|
|
145
158
|
if (!query) { result = { outcome: 'fail', error: 'web-search: 검색어(arg)가 없다' }; break; }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execFile, execFileSync, spawn } from 'node:child_process';
|
|
1
|
+
import { execFile, execFileSync, spawn, spawnSync } from 'node:child_process';
|
|
2
2
|
import { promisify } from 'node:util';
|
|
3
3
|
import { dlopen, FFIType } from 'bun:ffi';
|
|
4
4
|
import { createHash } from 'node:crypto';
|
|
@@ -10,6 +10,7 @@ import { LogStore, logsDbPath } from '../mss/logging/log-store.js';
|
|
|
10
10
|
import { loadSelfDevRun } from '../self-dev/run-store.js';
|
|
11
11
|
import { normalizeRunId } from './harness-space.js';
|
|
12
12
|
import { effectiveInstanceRoot, prodInstanceRoot } from '../instance/resolve.js';
|
|
13
|
+
import { stateDirSourceForChild } from '../agent/identity-env.js';
|
|
13
14
|
import { lookupGoal, type GoalLookupOptions, type GoalLookupResult } from '../self-implement/goal-lookup.js';
|
|
14
15
|
import { archiveGoals, type GoalArchiveOptions } from '../self-implement/goal-archive.js';
|
|
15
16
|
import { detectBursts, formatIncidentBurstWarning, incidentLastLines, readRunExits, recentBurst, recordRunExit } from './harness-incidents.js';
|
|
@@ -23,13 +24,15 @@ import { resolveChildLlmEffort, resolveImplementationChildModel } from '../self-
|
|
|
23
24
|
import { resolveHarnessTarget } from '../self-implement/harness-target-options.js';
|
|
24
25
|
import { queryRunningRuns } from '../self-implement/running-runs.js';
|
|
25
26
|
import { DevPipelineError } from '../self-dev/dev-pipeline.js';
|
|
26
|
-
import { collectDraftMetrics, DRAFT_SWEEP_CONCURRENCY, mapBounded, runDraftSweep, sweepFailureReason, type DraftSweepAdapters, type DraftSweepResult, type SweepDraft, type SweepMergedPr, type SweepReviewGate } from '../self-dev/draft-sweep.js';
|
|
27
|
+
import { collectDraftMetrics, collectOverlapMetrics, DRAFT_SWEEP_CONCURRENCY, mapBounded, resolveDraftSweepCloseCap, runDraftSweep, sweepFailureReason, type DraftSweepAdapters, type DraftSweepResult, type OverlapMetricAdapters, type SweepDraft, type SweepMergedPr, type SweepReviewGate } from '../self-dev/draft-sweep.js';
|
|
28
|
+
import { readAskPreflightRowsSince, readSalvagedRowsSince } from '../self-dev/ask-launch-io.js';
|
|
27
29
|
import { debug } from '../debug/log.js';
|
|
28
30
|
import { installHarnessSalvageCommand, installHarnessSalvageRetentionCommand } from './harness-salvage-cli.js';
|
|
29
31
|
import { decideNestedElanousLaunch, readNestedElanousDepth } from './nested-elanous-policy.js';
|
|
30
32
|
import { dispatchTask, type DispatchTaskInput, type DispatchTaskDeps } from '../execution-loop/dispatch-task.js';
|
|
31
33
|
import { launchRequestId, preLaunchGate, type PreLaunchGateDeps } from '../execution-loop/launch-gate.js';
|
|
32
34
|
import { PR_LABELS } from '../github/pr-labels.js';
|
|
35
|
+
import { ghAutomationEnv } from '../autopilot/pr-manager.js';
|
|
33
36
|
import { CODEX_PROVIDER, GROK_PROVIDER, decideBudget, decideLaunchBudget, readBudgetInputsLive, type BudgetDecision, type BudgetInputs } from '../self-implement/budget-gate.js';
|
|
34
37
|
import { DEFAULT_FALLBACK_CHAIN } from '../oauth/fallback-chain.js';
|
|
35
38
|
import { installDeliverableVerifyCliCommand, type InstallDeliverableVerifyCliDeps } from './deliverable-verify-cli.js';
|
|
@@ -1589,6 +1592,28 @@ function readProcessCwd(pid: number): Pick<HarnessProcessRecord, 'cwd' | 'cwdSta
|
|
|
1589
1592
|
}
|
|
1590
1593
|
}
|
|
1591
1594
|
|
|
1595
|
+
/** One `lsof` for many pids (a per-pid lsof costs ~0.1–0.3 s); lsof exits 1 when some pid is gone yet prints the rest. */
|
|
1596
|
+
function batchProcessCwdReader(pids: readonly number[]): (pid: number) => Pick<HarnessProcessRecord, 'cwd' | 'cwdStatus' | 'cwdFailureReason'> {
|
|
1597
|
+
const cwds = new Map<number, string>();
|
|
1598
|
+
let failure: string | undefined;
|
|
1599
|
+
if (pids.length) {
|
|
1600
|
+
const result = spawnSync('lsof', ['-a', '-d', 'cwd', '-Fpn', '-p', pids.join(',')],
|
|
1601
|
+
{ encoding: 'utf8', timeout: 5_000, maxBuffer: 16 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
1602
|
+
if (result.error || typeof result.stdout !== 'string') failure = observationFailureReason(result.error ?? new Error('lsof output missing'));
|
|
1603
|
+
else {
|
|
1604
|
+
let current: number | undefined;
|
|
1605
|
+
for (const line of result.stdout.split('\n')) {
|
|
1606
|
+
if (line.startsWith('p')) current = Number(line.slice(1));
|
|
1607
|
+
else if (line.startsWith('n') && current !== undefined && line.length > 1) cwds.set(current, line.slice(1).trim());
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
return (pid) => {
|
|
1612
|
+
const cwd = cwds.get(pid);
|
|
1613
|
+
return cwd ? { cwd, cwdStatus: 'observed' } : { cwdStatus: 'unknown', cwdFailureReason: failure ?? 'lsof cwd missing' };
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1592
1617
|
const OWNERSHIP_ENV_KEYS = new Set<string>(Object.values(HARNESS_PROCESS_OWNERSHIP_ENV));
|
|
1593
1618
|
|
|
1594
1619
|
function ownershipValueFromEntry(entry: string, key: string): string | undefined {
|
|
@@ -1744,10 +1769,23 @@ export function readProcessOwnership(
|
|
|
1744
1769
|
}
|
|
1745
1770
|
}
|
|
1746
1771
|
|
|
1747
|
-
|
|
1772
|
+
/** Narrows the per-process reads (`lsof` cwd ≈ 0.1 s each · `ps eww`) of `defaultListHarnessProcesses`. */
|
|
1773
|
+
export interface HarnessProcessListOptions {
|
|
1774
|
+
/** Keep only matching records; the others are dropped before any per-process read. */
|
|
1775
|
+
readonly include?: (record: HarnessProcessRecord) => boolean;
|
|
1776
|
+
/** Skip the ownership (`ps eww`) read — for callers that only need the cwd. */
|
|
1777
|
+
readonly cwdOnly?: boolean;
|
|
1778
|
+
/** Wall-clock budget for the per-process reads; records past it keep `cwdStatus: 'unknown'` (reason names the budget). */
|
|
1779
|
+
readonly deadlineMs?: number;
|
|
1780
|
+
readonly readCwd?: (pid: number) => Pick<HarnessProcessRecord, 'cwd' | 'cwdStatus' | 'cwdFailureReason'>;
|
|
1781
|
+
readonly psOutput?: () => string;
|
|
1782
|
+
readonly clock?: () => number;
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
export function defaultListHarnessProcesses(options: HarnessProcessListOptions = {}): HarnessProcessListObservation {
|
|
1748
1786
|
let out: string;
|
|
1749
1787
|
try {
|
|
1750
|
-
out = execFileSync('ps', ['-axo', 'pid=,ppid=,pcpu=,etime=,command='], {
|
|
1788
|
+
out = options.psOutput ? options.psOutput() : execFileSync('ps', ['-axo', 'pid=,ppid=,pcpu=,etime=,command='], {
|
|
1751
1789
|
encoding: 'utf8',
|
|
1752
1790
|
timeout: 5_000,
|
|
1753
1791
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
@@ -1757,11 +1795,21 @@ export function defaultListHarnessProcesses(): HarnessProcessListObservation {
|
|
|
1757
1795
|
}
|
|
1758
1796
|
const parsed = parseHarnessProcessPsOutput(out);
|
|
1759
1797
|
if (parsed.status === 'failed') return parsed;
|
|
1760
|
-
const
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1798
|
+
const clock = options.clock ?? Date.now;
|
|
1799
|
+
const selected = options.include ? parsed.records.filter(options.include) : parsed.records;
|
|
1800
|
+
const readCwd = options.readCwd ?? (options.cwdOnly ? batchProcessCwdReader(selected.map((record) => record.pid)) : readProcessCwd);
|
|
1801
|
+
const deadline = options.deadlineMs === undefined ? Infinity : clock() + options.deadlineMs;
|
|
1802
|
+
const records = selected.map((record) => {
|
|
1803
|
+
// A host with hundreds of harness processes made the serial reads take minutes (10-08 queue tick hang).
|
|
1804
|
+
if (clock() >= deadline) {
|
|
1805
|
+
return { ...record, cwdStatus: 'unknown' as const, cwdFailureReason: `observation deadline ${options.deadlineMs}ms exceeded` };
|
|
1806
|
+
}
|
|
1807
|
+
return {
|
|
1808
|
+
...record,
|
|
1809
|
+
...readCwd(record.pid),
|
|
1810
|
+
...(options.cwdOnly ? {} : { ownership: readProcessOwnership(record.pid) }),
|
|
1811
|
+
};
|
|
1812
|
+
});
|
|
1765
1813
|
if (parsed.status === 'incomplete') {
|
|
1766
1814
|
return {
|
|
1767
1815
|
status: 'incomplete',
|
|
@@ -1877,12 +1925,30 @@ type GithubPull = {
|
|
|
1877
1925
|
|
|
1878
1926
|
type GhExecute = (args: string[]) => string;
|
|
1879
1927
|
|
|
1880
|
-
//
|
|
1881
|
-
//
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1928
|
+
// Explicit env preserves the augmented PATH under Bun (cron may start with a minimal PATH).
|
|
1929
|
+
// Authentication comes from the same automation policy used by the PR manager, not the user's keyring.
|
|
1930
|
+
// Failure details are bounded and redact the token before reaching the sweep result or debug log.
|
|
1931
|
+
function ghFailure(args: string[], error: unknown, token: string | undefined): Error {
|
|
1932
|
+
const failure = error as { status?: number | null; code?: number | string; stderr?: string | Buffer };
|
|
1933
|
+
const exit = failure.status ?? (typeof failure.code === 'number' ? failure.code : 'unknown');
|
|
1934
|
+
const stderr = String(failure.stderr ?? '');
|
|
1935
|
+
const stderrTail = (token ? stderr.replaceAll(token, '[REDACTED]') : stderr).slice(-300).replace(/[\r\n]+/g, ' ').trim();
|
|
1936
|
+
try { debug.log('harness.drafts', 'gh-failed', { args0: args[0], exit, stderrTail }); }
|
|
1937
|
+
catch { /* Logging must not hide the gh failure. */ }
|
|
1938
|
+
// gh never ran (e.g. «Executable not found: gh»): no exit or stderr to report, so keep the spawn error itself.
|
|
1939
|
+
if (typeof failure.status !== 'number' && !stderrTail && error instanceof Error) return error;
|
|
1940
|
+
return new Error(`gh ${args.slice(0, 2).join(' ')} exit=${exit}: ${stderrTail}`);
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
const executeGh: GhExecute = (args) => {
|
|
1944
|
+
const env = ghAutomationEnv(process.env);
|
|
1945
|
+
try {
|
|
1946
|
+
return execFileSync('gh', args, {
|
|
1947
|
+
encoding: 'utf8', timeout: 30_000, maxBuffer: 16 * 1024 * 1024,
|
|
1948
|
+
stdio: ['ignore', 'pipe', 'pipe'], env,
|
|
1949
|
+
});
|
|
1950
|
+
} catch (error) { throw ghFailure(args, error, env.GH_TOKEN); }
|
|
1951
|
+
};
|
|
1886
1952
|
|
|
1887
1953
|
/** Merge-time adapter: reuse the sweep's GitHub inventory and worktree liveness without a second triage implementation. */
|
|
1888
1954
|
export async function supersedeMergedGoalDrafts(
|
|
@@ -1986,9 +2052,14 @@ function deliverResyncMemo(spaceId: string, memo: ControlMemoPayload,
|
|
|
1986
2052
|
}
|
|
1987
2053
|
|
|
1988
2054
|
const execFileAsync = promisify(execFile);
|
|
1989
|
-
const executeGhAsync = async (args: string[]): Promise<string> =>
|
|
1990
|
-
|
|
1991
|
-
|
|
2055
|
+
const executeGhAsync = async (args: string[]): Promise<string> => {
|
|
2056
|
+
const env = ghAutomationEnv(process.env);
|
|
2057
|
+
try {
|
|
2058
|
+
return (await execFileAsync('gh', args, {
|
|
2059
|
+
encoding: 'utf8', timeout: 30_000, maxBuffer: 16 * 1024 * 1024, env,
|
|
2060
|
+
})).stdout;
|
|
2061
|
+
} catch (error) { throw ghFailure(args, error, env.GH_TOKEN); }
|
|
2062
|
+
};
|
|
1992
2063
|
|
|
1993
2064
|
/** RUN-TTL adapters: open self-impl PRs, head commit time (async, bounded by the core), owning run, memo, label. */
|
|
1994
2065
|
export async function githubRunTtlAdapters(repository: string, execute: GhExecute = executeGh,
|
|
@@ -2467,12 +2538,16 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
|
|
|
2467
2538
|
|
|
2468
2539
|
export interface HarnessDraftSweepDeps {
|
|
2469
2540
|
readonly adapters?: DraftSweepAdapters;
|
|
2541
|
+
/** Optional overlap source injection; tests do not read GitHub or logs.db. */
|
|
2542
|
+
readonly overlapAdapters?: OverlapMetricAdapters;
|
|
2470
2543
|
readonly repository?: () => string;
|
|
2471
2544
|
readonly write?: (line: string) => void;
|
|
2472
2545
|
readonly execute?: GhExecute;
|
|
2473
2546
|
readonly now?: () => Date;
|
|
2474
2547
|
/** RUN-TTL step adapters. Omitted with injected `adapters` ⇒ the step is skipped (fakes never reach real gh). */
|
|
2475
2548
|
readonly runTtl?: RunTtlAdapters;
|
|
2549
|
+
/** Test seam for `tools.selfImplement.draftSweepCloseCap`; omitted ⇒ the user config. */
|
|
2550
|
+
readonly closeCapConfig?: () => unknown;
|
|
2476
2551
|
}
|
|
2477
2552
|
|
|
2478
2553
|
/** RUN-TTL step inside the draft sweep — fail-soft: its failure never changes the sweep verdict. */
|
|
@@ -2566,10 +2641,22 @@ function installHarnessDraftSweepCommand(harnessCmd: Command, deps: HarnessDraft
|
|
|
2566
2641
|
const repository = resolveRepositoryName({ repo: opts.repo ?? deps.repository?.(), executeGh: (args) => execute(args) });
|
|
2567
2642
|
if (!DRAFT_SWEEP_REPOSITORY.test(repository) || repository.includes('..')) throw new HarnessCliInputError(`invalid --repo (expected owner/name): ${repository}`);
|
|
2568
2643
|
const started = Date.now();
|
|
2569
|
-
const
|
|
2644
|
+
const adapters = deps.adapters ?? githubDraftSweepAdapters(deps.execute);
|
|
2645
|
+
const now = deps.now?.() ?? new Date();
|
|
2646
|
+
const metrics = await collectDraftMetrics(repository, adapters, now);
|
|
2647
|
+
let overlap: Awaited<ReturnType<typeof collectOverlapMetrics>> | null = null;
|
|
2648
|
+
try {
|
|
2649
|
+
overlap = await collectOverlapMetrics(repository, deps.overlapAdapters ?? {
|
|
2650
|
+
...adapters,
|
|
2651
|
+
listPreflightRows: readAskPreflightRowsSince,
|
|
2652
|
+
listSalvagedRows: readSalvagedRowsSince,
|
|
2653
|
+
}, now);
|
|
2654
|
+
} catch (error) {
|
|
2655
|
+
try { debug.log('self-dev.draft-metrics', 'overlap-failed', { repository, reason: sweepFailureReason(error) }); } catch { /* fail-soft */ }
|
|
2656
|
+
}
|
|
2570
2657
|
try { debug.log('self-dev.draft-metrics', 'collected', { repository, ms: Date.now() - started, ...metrics }); } catch { /* fail-soft */ }
|
|
2571
2658
|
const write = deps.write ?? console.log;
|
|
2572
|
-
if (opts.json) write(JSON.stringify({ repository, ...metrics }));
|
|
2659
|
+
if (opts.json) write(JSON.stringify({ repository, ...metrics, overlap }));
|
|
2573
2660
|
else write(`draft 재고 ${metrics.inventory} · 최장 나이 ${metrics.oldestAgeHours === null ? '해당 없음' : `${metrics.oldestAgeHours.toFixed(1)}h`} · needs-owner ${metrics.needsOwner} · 48h 전환율 ${metrics.conversion48h === null ? '표본 없음' : `${(metrics.conversion48h * 100).toFixed(1)}% (${metrics.converted48h}/${metrics.cohort48h})`}`);
|
|
2574
2661
|
} catch (error) {
|
|
2575
2662
|
console.error(humanErrorLine(error));
|
|
@@ -2580,7 +2667,8 @@ function installHarnessDraftSweepCommand(harnessCmd: Command, deps: HarnessDraft
|
|
|
2580
2667
|
.option('--apply', '판정한 라벨 변경과 종료를 적용')
|
|
2581
2668
|
.option('--json', '판정 결과를 JSON 으로 출력')
|
|
2582
2669
|
.option('--repo <owner/name>', '조회할 GitHub 저장소')
|
|
2583
|
-
.
|
|
2670
|
+
.option('--close-cap <n>', '이번 틱 최대 종료 수 (1~100 · config tools.selfImplement.draftSweepCloseCap 보다 우선 · 기본 10)')
|
|
2671
|
+
.action(async (opts: { apply?: boolean; json?: boolean; repo?: string; closeCap?: string }) => {
|
|
2584
2672
|
try {
|
|
2585
2673
|
const write = deps.write ?? console.log;
|
|
2586
2674
|
const execute = deps.execute ?? executeGh;
|
|
@@ -2593,7 +2681,16 @@ function installHarnessDraftSweepCommand(harnessCmd: Command, deps: HarnessDraft
|
|
|
2593
2681
|
}
|
|
2594
2682
|
// One adapter instance per sweep: its open listing and ledger scan are shared with the RUN-TTL step.
|
|
2595
2683
|
const github = deps.adapters ? undefined : githubDraftSweepAdapters(deps.execute);
|
|
2596
|
-
const
|
|
2684
|
+
const configCap = deps.closeCapConfig
|
|
2685
|
+
? deps.closeCapConfig()
|
|
2686
|
+
: (() => { try { return getUserConfig().tools?.selfImplement?.draftSweepCloseCap; } catch { return undefined; } })();
|
|
2687
|
+
const cap = resolveDraftSweepCloseCap({ flag: opts.closeCap, config: configCap });
|
|
2688
|
+
for (const warning of cap.warnings) {
|
|
2689
|
+
console.error(`⚠ ${warning}`);
|
|
2690
|
+
try { debug.log('self-dev.draft-sweep', 'close-cap-rejected', { warning, closeCap: cap.closeCap, closeCapSource: cap.closeCapSource }); } catch { /* fail-soft */ }
|
|
2691
|
+
}
|
|
2692
|
+
const result: DraftSweepResult = await runDraftSweep({ repository, apply: opts.apply === true, adapters: deps.adapters ?? github!,
|
|
2693
|
+
closeCap: cap.closeCap, closeCapSource: cap.closeCapSource });
|
|
2597
2694
|
const reason = result.error ? sweepFailureReason(result.error) : undefined;
|
|
2598
2695
|
const ttlStarted = Date.now();
|
|
2599
2696
|
const runTtl = await draftSweepRunTtl(repository, deps, github);
|
|
@@ -2944,7 +3041,8 @@ export function installHarnessCliCommand(program: Command, deps: HarnessCliComma
|
|
|
2944
3041
|
harnessQueueReceiptPath(root, item.launchId!), queueDeps.launchCommand ?? join(import.meta.dir, '../../bin/elanous.mjs'),
|
|
2945
3042
|
...(resolve(root) === prodInstanceRoot() ? [] : [`--test=${root}`]), ...args], {
|
|
2946
3043
|
cwd: item.launchCwd ?? resolve(import.meta.dir, '../..'), stdio: 'inherit',
|
|
2947
|
-
env: { ...process.env, ELANOUS_STATE_DIR: root,
|
|
3044
|
+
env: { ...process.env, ELANOUS_STATE_DIR: root,
|
|
3045
|
+
ELANOUS_STATE_DIR_SOURCE: stateDirSourceForChild(root), ELANOUS_HARNESS_SEAT: item.seat,
|
|
2948
3046
|
ELANOUS_HARNESS_QUEUE_LAUNCH: item.launchId! },
|
|
2949
3047
|
});
|
|
2950
3048
|
// Capture the end at spawn time: a child that already closed (or died by a signal) must not leave the parent waiting.
|