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
|
@@ -4,10 +4,23 @@ import { getUserConfig } from '../user-config.js';
|
|
|
4
4
|
import { getSecretAsync } from '../nexus/config/secrets/index.js';
|
|
5
5
|
import { fetchLinearProjectIssues, type LinearProjectIssue } from '../connectors/linear.js';
|
|
6
6
|
import { formatElanousCard } from './elanous-card.js';
|
|
7
|
+
import recordedDateEvidence from './coo-admin-dates.evidence.json';
|
|
8
|
+
|
|
9
|
+
type CooDateEvidence = {
|
|
10
|
+
project: string;
|
|
11
|
+
verifiedIssues: Array<{
|
|
12
|
+
identifier: string;
|
|
13
|
+
title: string;
|
|
14
|
+
url: string;
|
|
15
|
+
officialDeadline: string | null;
|
|
16
|
+
preparationPeriod: string | { start: string } | null;
|
|
17
|
+
representativeActionDate: string | null;
|
|
18
|
+
}>;
|
|
19
|
+
};
|
|
7
20
|
|
|
8
21
|
export const COO_ADMIN_SPEC: LLMToolSpec = {
|
|
9
22
|
name: 'coo_admin',
|
|
10
|
-
description: '읽기 전용 Linear COO 행정 조회 — «행정 뭐 남았어 · COO 할 일 · 마감 다가오는 행정» 질문에 사용. 외부 행정 프로젝트의 열린 일을
|
|
23
|
+
description: '읽기 전용 Linear COO 행정 조회 — «행정 뭐 남았어 · COO 할 일 · 마감 다가오는 행정» 질문에 사용. 외부 행정 프로젝트의 열린 일을 우선순위와 확인된 날짜 근거로 브리핑한다.',
|
|
11
24
|
parameters: { type: 'object', properties: {}, required: [] },
|
|
12
25
|
};
|
|
13
26
|
|
|
@@ -16,6 +29,7 @@ export interface CooAdminDeps {
|
|
|
16
29
|
fetch?: typeof fetch;
|
|
17
30
|
project?: string;
|
|
18
31
|
now?: Date;
|
|
32
|
+
dateEvidence?: CooDateEvidence;
|
|
19
33
|
}
|
|
20
34
|
|
|
21
35
|
/** Reads the configured Linear project without changing any Linear issue. */
|
|
@@ -32,27 +46,47 @@ export async function dispatchCooAdmin(_args: Record<string, unknown>, deps: Coo
|
|
|
32
46
|
const issues = await fetchLinearProjectIssues({ apiKey, project, fetch: deps.fetch ?? fetch });
|
|
33
47
|
const today = deps.now ?? new Date();
|
|
34
48
|
const todayUtc = Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate());
|
|
35
|
-
const daysUntil = (date: string) => Math.round((Date.parse(`${date
|
|
49
|
+
const daysUntil = (date: string) => Math.round((Date.parse(`${date}T00:00:00Z`) - todayUtc) / 86_400_000);
|
|
50
|
+
const dateEvidence = deps.dateEvidence ?? (recordedDateEvidence as CooDateEvidence);
|
|
51
|
+
const verifiedDate = (date: string | null | undefined): string | null => {
|
|
52
|
+
if (!date || !/^\d{4}-\d{2}-\d{2}$/.test(date)) return null;
|
|
53
|
+
const time = Date.parse(`${date}T00:00:00Z`);
|
|
54
|
+
return Number.isFinite(time) && new Date(time).toISOString().slice(0, 10) === date ? date : null;
|
|
55
|
+
};
|
|
56
|
+
const evidenceFor = (item: LinearProjectIssue) => dateEvidence.project === project
|
|
57
|
+
? dateEvidence.verifiedIssues.find(row => row.identifier === item.identifier && row.url === item.url && row.title === item.title)
|
|
58
|
+
: undefined;
|
|
59
|
+
const deadlineFor = (item: LinearProjectIssue) => verifiedDate(evidenceFor(item)?.officialDeadline);
|
|
60
|
+
const rank = (item: LinearProjectIssue) => item.priority === 0 ? 5 : item.priority;
|
|
36
61
|
issues.sort((a, b) => {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
return
|
|
62
|
+
const aDue = deadlineFor(a);
|
|
63
|
+
const bDue = deadlineFor(b);
|
|
64
|
+
return rank(a) - rank(b) || (aDue === null ? (bDue === null ? 0 : 1)
|
|
65
|
+
: bDue === null ? -1 : daysUntil(aDue) - daysUntil(bDue));
|
|
40
66
|
});
|
|
41
67
|
count = issues.length;
|
|
42
68
|
const lines = issues.map((item: LinearProjectIssue) => {
|
|
43
|
-
const
|
|
69
|
+
const evidence = evidenceFor(item);
|
|
70
|
+
const due = deadlineFor(item);
|
|
71
|
+
const days = due ? daysUntil(due) : null;
|
|
44
72
|
if (days !== null && days < 0) overdue++;
|
|
45
|
-
const deadline =
|
|
46
|
-
|
|
73
|
+
const deadline = due ? `확인된 마감일 ${due} (${days !== null && days < 0 ? `지남 ${-days}일` : `D-${days}`})` : '기한 미확인 · 확인 예정일 미정';
|
|
74
|
+
const preparation = verifiedDate(typeof evidence?.preparationPeriod === 'string'
|
|
75
|
+
? evidence.preparationPeriod : evidence?.preparationPeriod?.start);
|
|
76
|
+
const representative = verifiedDate(evidence?.representativeActionDate);
|
|
77
|
+
const dates = [preparation && `준비 시작일 ${preparation}`, representative && `대표 손이 필요한 날 ${representative}`].filter(Boolean);
|
|
78
|
+
return `${deadline} · 우선순위 ${item.priority || '미지정'} · ${item.title} · ${item.state.name} · ${item.assignee?.name ?? '미배정'}${dates.length ? ` · ${dates.join(' · ')}` : ''}\n${item.url}`;
|
|
47
79
|
});
|
|
48
80
|
debug.log('coo.admin', 'read', { count, overdue, reason: 'ok' });
|
|
49
|
-
const scope = issues.truncated ? '최대 250건 중 조회한 항목만 마감 순으로 표시 (이후 항목은 포함되지 않음)\n' : '';
|
|
81
|
+
const scope = issues.truncated ? '최대 250건 중 조회한 항목만 우선순위·확인된 마감 순으로 표시 (이후 항목은 포함되지 않음)\n' : '';
|
|
50
82
|
const prose = lines.length ? `${scope}${lines.join('\n')}` : '남은 행정 0건';
|
|
51
|
-
const card = formatElanousCard({ kind: 'coo-admin', items: issues.map(item =>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
83
|
+
const card = formatElanousCard({ kind: 'coo-admin', items: issues.map(item => {
|
|
84
|
+
const due = deadlineFor(item);
|
|
85
|
+
return {
|
|
86
|
+
title: item.title, due, daysLeft: due ? daysUntil(due) : null,
|
|
87
|
+
state: item.state.name, owner: item.assignee?.name ?? '미배정', url: item.url,
|
|
88
|
+
};
|
|
89
|
+
}) });
|
|
56
90
|
return `${prose}\n${card}`;
|
|
57
91
|
} catch (error) {
|
|
58
92
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -26,6 +26,7 @@ import { loadMandate, type TradeMandate } from './trade-mandate.js';
|
|
|
26
26
|
import { debug, redactSecretText } from '../debug/log.js';
|
|
27
27
|
import { LogStore, logsDbPath, type LogStoreRow } from '../mss/logging/log-store.js';
|
|
28
28
|
import { detectRepeatedFailures, explainFailure } from './repeated-failure.js';
|
|
29
|
+
import { listLoops, loopRecentRuns, type LoopRun } from '../loops/registry.js';
|
|
29
30
|
|
|
30
31
|
/** 예약 레지스트리 조회 관측 — 실패가 0으로 접히지 않게 남기는 창구. */
|
|
31
32
|
export const OPS_STATUS_LOG_CATEGORY = 'ops.status';
|
|
@@ -34,6 +35,29 @@ export const OPS_SCHEDULES_LOOKUP_FAILED_EVENT = 'schedules.lookup-failed';
|
|
|
34
35
|
/** 예약 레지스트리 행 조회 seam — 미지정 시 실 레지스트리 reader. */
|
|
35
36
|
export type ListScheduleRows = () => ScheduleRow[];
|
|
36
37
|
|
|
38
|
+
/** Graph-loop health input — run history is newest first. */
|
|
39
|
+
export type GraphLoopRuns = () => Array<{
|
|
40
|
+
id: string;
|
|
41
|
+
enabled: boolean;
|
|
42
|
+
runs: Array<Pick<LoopRun, 'at' | 'status' | 'failedNodes'>>;
|
|
43
|
+
}>;
|
|
44
|
+
|
|
45
|
+
/** Bounded window per loop: a streak whose oldest read run is already >24h old is flagged; a longer
|
|
46
|
+
* all-failed history than this window can only under-report (never over-report) the streak length. */
|
|
47
|
+
export const GRAPH_LOOP_HEALTH_RUN_WINDOW = 50;
|
|
48
|
+
|
|
49
|
+
function defaultGraphLoopRuns(): ReturnType<GraphLoopRuns> {
|
|
50
|
+
const out: ReturnType<GraphLoopRuns> = [];
|
|
51
|
+
for (const loop of listLoops().filter(entry => entry.enabled)) {
|
|
52
|
+
// One unreadable run history must not hide the other loops.
|
|
53
|
+
try { out.push({ id: loop.id, enabled: loop.enabled, runs: loopRecentRuns(loop.id, {}, GRAPH_LOOP_HEALTH_RUN_WINDOW) }); }
|
|
54
|
+
catch (err) {
|
|
55
|
+
debug.log('ops.health', 'graph-loop-failure-skipped', { loop: loop.id, reason: redactSecretText(err instanceof Error ? err.message : String(err)) });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
37
61
|
/** 조회 seam(테스트) — 미지정 시 실 스토어. */
|
|
38
62
|
export interface OpsStatusOpts {
|
|
39
63
|
now?: Date;
|
|
@@ -47,6 +71,8 @@ export interface OpsStatusOpts {
|
|
|
47
71
|
planDraftFor?: (missionId: string) => string;
|
|
48
72
|
/** Health-only log reader seam; the default reads the current instance's last ten minutes read-only. */
|
|
49
73
|
recentLogs?: () => LogStoreRow[];
|
|
74
|
+
/** Health-only graph-loop registry reader; injection avoids the real state directory. */
|
|
75
|
+
graphLoopRuns?: GraphLoopRuns;
|
|
50
76
|
}
|
|
51
77
|
|
|
52
78
|
export interface CountByStatus { [status: string]: number }
|
|
@@ -341,6 +367,33 @@ export function opsHealth(opts: OpsStatusOpts = {}): OpsHealthReport {
|
|
|
341
367
|
}
|
|
342
368
|
}
|
|
343
369
|
|
|
370
|
+
// Graph-run history is independent of ops_events loops and of log-store failures.
|
|
371
|
+
try {
|
|
372
|
+
const loops = (opts.graphLoopRuns ?? defaultGraphLoopRuns)();
|
|
373
|
+
const nowMs = (opts.now ?? new Date()).getTime();
|
|
374
|
+
let flagged = 0;
|
|
375
|
+
for (const loop of loops) {
|
|
376
|
+
if (!loop.enabled) continue;
|
|
377
|
+
const runs = [...loop.runs].sort((a, b) => Date.parse(b.at) - Date.parse(a.at));
|
|
378
|
+
const failures = [] as typeof runs;
|
|
379
|
+
for (const run of runs) {
|
|
380
|
+
if (run.status !== 'failed') break;
|
|
381
|
+
failures.push(run);
|
|
382
|
+
}
|
|
383
|
+
const first = failures.at(-1);
|
|
384
|
+
if (failures.length < 2 || !first || !Number.isFinite(Date.parse(first.at)) || nowMs - Date.parse(first.at) <= 24 * 3600_000) continue;
|
|
385
|
+
anomalies.push({
|
|
386
|
+
kind: 'repeated_failure', entity: `loop:${loop.id}`,
|
|
387
|
+
detail: `그래프 루프 ${loop.id} 연속 실패 ${failures.length}회 · ${first.at} 부터 · 마지막 실패 노드 ${failures[0]!.failedNodes.join(', ')}`,
|
|
388
|
+
since: first.at,
|
|
389
|
+
});
|
|
390
|
+
flagged++;
|
|
391
|
+
}
|
|
392
|
+
debug.log('ops.health', 'graph-loop-failure', { loops: loops.length, flagged });
|
|
393
|
+
} catch (err) {
|
|
394
|
+
debug.log('ops.health', 'graph-loop-failure-skipped', { reason: redactSecretText(err instanceof Error ? err.message : String(err)) });
|
|
395
|
+
}
|
|
396
|
+
|
|
344
397
|
// Independent log check: an unreadable log store must not hide task/loop/schedule anomalies.
|
|
345
398
|
try {
|
|
346
399
|
const recentLogs = opts.recentLogs ?? (() => {
|
|
@@ -17,6 +17,7 @@ import { getUserConfig, type UserConfig } from '../user-config.js';
|
|
|
17
17
|
import { resolveChannelBotToken } from '../channel-bot-token.js';
|
|
18
18
|
import { explainReportRoute, isOperationalKind, kindRouteTarget, logKindRouteFallback, mainHomeTarget } from './telegram-kind-route.js';
|
|
19
19
|
import { debug } from '../debug/log.js';
|
|
20
|
+
import type { AddBriefItem } from '../briefing/brief-items.js';
|
|
20
21
|
import { registerLogStoreSink, setLogInstanceName } from '../mss/logging/log-store.js';
|
|
21
22
|
import type { LogSink } from '../mss/logging/sink.js';
|
|
22
23
|
import { resolveDaemonEndpoint } from '../nexus/daemon-endpoint.js';
|
|
@@ -633,6 +634,13 @@ export function deliver(text: string, kind = 'alert', onBot?: (bot: string) => v
|
|
|
633
634
|
return false;
|
|
634
635
|
}
|
|
635
636
|
|
|
637
|
+
type UndeliverableRecorder = (item: AddBriefItem) => void;
|
|
638
|
+
let undeliverableRecorder: UndeliverableRecorder | null = null;
|
|
639
|
+
/** Replace the ledger writer in tests; null restores the production ledger. */
|
|
640
|
+
export function setUndeliverableRecorder(recorder: UndeliverableRecorder | null): void {
|
|
641
|
+
undeliverableRecorder = recorder;
|
|
642
|
+
}
|
|
643
|
+
|
|
636
644
|
/** OB8b — both paths failed: say so loudly, with the universe this process resolved, instead of a silent false.
|
|
637
645
|
* The usual cause (10-01 · MK): an ad-hoc `bun -e` or a script from a source tree resolves a cwd-derived test
|
|
638
646
|
* universe, so it finds neither the production daemon nor the production bot token. */
|
|
@@ -647,7 +655,22 @@ function reportUndeliverable(kind: string, daemonPath: DaemonPathClass | 'not-fo
|
|
|
647
655
|
let route: ReturnType<typeof explainReportRoute> | null = null;
|
|
648
656
|
try { if (cfg) route = explainReportRoute(cfg, kind); } catch { /* the report still goes out */ }
|
|
649
657
|
ensureOutboundLogSink();
|
|
650
|
-
|
|
658
|
+
const reason = route?.reason ?? 'unknown';
|
|
659
|
+
try { debug.log('outbound.send', 'undeliverable', { kind: safeObservationLabel(kind), daemonPath, universe, root, route: reason }); } catch { /* fail-soft */ }
|
|
660
|
+
try {
|
|
661
|
+
const record = undeliverableRecorder ?? ((item: AddBriefItem) => {
|
|
662
|
+
const { BriefItemsLedger } = require('../briefing/brief-items.js') as typeof import('../briefing/brief-items.js');
|
|
663
|
+
new BriefItemsLedger().add(item);
|
|
664
|
+
});
|
|
665
|
+
record({
|
|
666
|
+
domain: '운영', priority: 'P1', source: 'outbound.undeliverable',
|
|
667
|
+
text: `미전달: kind ${kind} · 데몬 ${daemonPath} · 경로 ${reason} · 우주 ${universe}`,
|
|
668
|
+
evidence: 'elanous logs --category outbound.send --event undeliverable',
|
|
669
|
+
dedupeKey: `${kind} · ${reason}`, dedupeWithinMs: 60 * 60_000,
|
|
670
|
+
});
|
|
671
|
+
} catch (error) {
|
|
672
|
+
try { debug.log('outbound.send', 'undeliverable-record-failed', { kind: safeObservationLabel(kind), route: reason, error: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ }
|
|
673
|
+
}
|
|
651
674
|
const hint = universe === 'prod'
|
|
652
675
|
? '운영 데몬이 떠 있는지 확인: elanous nexus show'
|
|
653
676
|
: '운영으로 보내려면 설치본 elanous 로 실행하거나 ELANOUS_STATE_DIR=~/.elanous 와 --config-dir ~/.elanous 를 준다';
|
|
@@ -7,6 +7,7 @@ import { defaultGraphsDir } from '../self-implement/graph-templates.js';
|
|
|
7
7
|
import { getElanousConfigDir } from '../elanous-config-dir.js';
|
|
8
8
|
import type { ExecAttachment } from './store.js';
|
|
9
9
|
import { debug } from '../debug/log.js';
|
|
10
|
+
import { peerEditRunGate } from '../nexus/api/graph-peer-edit.js';
|
|
10
11
|
import { OUTPUT_KINDS, pickDefaultOutput, type OutputKind } from './default-outputs.js';
|
|
11
12
|
|
|
12
13
|
export interface InstalledGraph {
|
|
@@ -105,6 +106,11 @@ async function coreAndMineGraphs(coreDir: string, mineDir: string): Promise<Inst
|
|
|
105
106
|
const id = doc?.graph_id;
|
|
106
107
|
if (typeof id !== 'string' || !/^[a-z0-9-]+$/.test(id) || seen.has(id)) continue;
|
|
107
108
|
if (core && doc?.loop?.exec_request !== true) continue;
|
|
109
|
+
// W9c — a «mine» graph a peer changed is not runnable until the owner approves it (fail closed).
|
|
110
|
+
if (!core) {
|
|
111
|
+
const gate = peerEditRunGate(dir, id);
|
|
112
|
+
if (!gate.ok) { debug.log('exec-requests', 'skipped-peer-edit', { id, error: gate.error }); continue; }
|
|
113
|
+
}
|
|
108
114
|
const inputs = doc?.loop?.inputs;
|
|
109
115
|
const inputKeys = core && Array.isArray(inputs)
|
|
110
116
|
? inputs.filter((key): key is string => typeof key === 'string')
|
|
@@ -20,13 +20,13 @@ export interface NodeKindEntry {
|
|
|
20
20
|
run?: NodeKindRun;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export const HARNESS_CORE_KINDS = ['agent', 'gate', 'git', 'judge', 'observe', 'hitl', 'subgraph'] as const;
|
|
23
|
+
export const HARNESS_CORE_KINDS = ['agent', 'gate', 'git', 'judge', 'observe', 'hitl', 'subgraph', 'prompt'] as const;
|
|
24
24
|
|
|
25
25
|
// Workflow validation, the palette, and executor variant labels read the same keys.
|
|
26
26
|
export const WORKFLOW_CORE_KINDS = ['prompt', 'bash', 'skill', 'cft', 'approval', 'if', 'switch', 'iteration', 'classify', 'extract', 'set', 'filter', 'template', 'http', 'showroom', 'task', 'scheduleTrigger', 'webhookTrigger', 'discordTrigger', 'telegramTrigger', 'manualTrigger', 'chatTrigger', 'subworkflow', 'knowledge'] as const;
|
|
27
27
|
|
|
28
28
|
const descriptions: Record<GraphKind, readonly string[]> = {
|
|
29
|
-
harness: ['Run an agent', 'Check an execution gate', 'Perform git operations', 'Judge an outcome', 'Observe a result', 'Request human input', 'Run a child graph'],
|
|
29
|
+
harness: ['Run an agent', 'Check an execution gate', 'Perform git operations', 'Judge an outcome', 'Observe a result', 'Request human input', 'Run a child graph', 'Run an LLM prompt'],
|
|
30
30
|
workflow: ['Run an LLM prompt', 'Run a shell command', 'Invoke a skill', 'Invoke a CFT method', 'Request approval', 'Branch on a condition', 'Select a case', 'Iterate items', 'Classify input', 'Extract structured data', 'Assign variables', 'Filter items', 'Render a template', 'Make an HTTP request', 'Run a showroom', 'Create a task', 'Start on a schedule', 'Start on a webhook', 'Start on Discord', 'Start on Telegram', 'Start manually', 'Start on chat', 'Call another workflow', 'Search the vault for context'],
|
|
31
31
|
};
|
|
32
32
|
|
|
@@ -93,21 +93,21 @@ export function getNodeKind(graph: GraphKind, kind: string): NodeKindEntry | und
|
|
|
93
93
|
|
|
94
94
|
function isNodeKindRun(run: NodeKindRun): boolean {
|
|
95
95
|
if (!run || typeof run !== 'object' || Array.isArray(run)) return false;
|
|
96
|
-
if ('bash' in run) return typeof run.bash === 'string' && run.bash.trim().length > 0 && !('http' in run) && !('skill' in run) && !('mcp' in run);
|
|
96
|
+
if ('bash' in run) return typeof run.bash === 'string' && run.bash.trim().length > 0 && !('prompt' in run) && !('http' in run) && !('skill' in run) && !('mcp' in run);
|
|
97
97
|
if ('http' in run) {
|
|
98
|
-
return !('skill' in run) && !('mcp' in run)
|
|
98
|
+
return !('prompt' in run) && !('skill' in run) && !('mcp' in run)
|
|
99
99
|
&& typeof run.http === 'object' && run.http !== null
|
|
100
100
|
&& typeof run.http.method === 'string' && run.http.method.trim().length > 0
|
|
101
101
|
&& typeof run.http.url === 'string' && run.http.url.trim().length > 0
|
|
102
102
|
&& (run.http.body === undefined || typeof run.http.body === 'string');
|
|
103
103
|
}
|
|
104
|
-
if ('skill' in run) return !('mcp' in run)
|
|
104
|
+
if ('skill' in run) return !('prompt' in run) && !('mcp' in run)
|
|
105
105
|
&& typeof run.skill === 'object' && run.skill !== null
|
|
106
106
|
&& typeof run.skill.name === 'string' && run.skill.name.trim().length > 0
|
|
107
107
|
&& (run.skill.prompt === undefined || typeof run.skill.prompt === 'string');
|
|
108
108
|
if ('mcp' in run) {
|
|
109
109
|
const spec = run.mcp;
|
|
110
|
-
return typeof spec === 'object' && spec !== null && !Array.isArray(spec)
|
|
110
|
+
return !('prompt' in run) && typeof spec === 'object' && spec !== null && !Array.isArray(spec)
|
|
111
111
|
&& typeof spec.server === 'string' && /^[a-z0-9_-]+$/.test(spec.server)
|
|
112
112
|
&& typeof spec.tool === 'string' && /^[a-z0-9_-]+$/.test(spec.tool)
|
|
113
113
|
&& (spec.args === undefined || (typeof spec.args === 'object' && spec.args !== null && !Array.isArray(spec.args)));
|
|
@@ -154,10 +154,12 @@ export function registerGraphCommands(program: Command, runsDeps: { root?: strin
|
|
|
154
154
|
graph.command('tick <file>')
|
|
155
155
|
.description('Advance one graph run, resuming a pending decision or starting only when requested')
|
|
156
156
|
.option('--start', 'Start a new run when the graph is idle')
|
|
157
|
+
.option('--schedule', 'Subscribe graph YAML triggers.schedule to the workflow scheduler and tick on each fire')
|
|
157
158
|
.option('--input <json>', 'JSON object passed to a newly started run')
|
|
158
159
|
.option('--json', 'Print the tick result as JSON')
|
|
159
|
-
.action(async (file: string, opts: { start?: boolean; input?: string; json?: boolean }) => {
|
|
160
|
+
.action(async (file: string, opts: { start?: boolean; schedule?: boolean; input?: string; json?: boolean }) => {
|
|
160
161
|
try {
|
|
162
|
+
if (opts.schedule && (opts.start || opts.input !== undefined || opts.json)) throw new Error('--schedule cannot be combined with --start, --input or --json');
|
|
161
163
|
if (opts.input !== undefined && !opts.start) throw new Error('--input requires --start');
|
|
162
164
|
let input: Record<string, unknown> | undefined;
|
|
163
165
|
if (opts.input !== undefined) {
|
|
@@ -169,11 +171,41 @@ export function registerGraphCommands(program: Command, runsDeps: { root?: strin
|
|
|
169
171
|
}
|
|
170
172
|
await (await import('../domains/standalone-log-sink.js')).registerStandaloneLogSink('graph');
|
|
171
173
|
const { graphTick } = await import('./graph-tick.js');
|
|
174
|
+
if (opts.schedule) {
|
|
175
|
+
const { createScheduleSource } = await import('../workflow-runtime/triggers/schedule-source.js');
|
|
176
|
+
const graphFile = resolve(file);
|
|
177
|
+
const source = createScheduleSource();
|
|
178
|
+
// The CLI tick callback is the graph counterpart of the workflow scheduler's onEmit.
|
|
179
|
+
const onScheduledTick = async () => {
|
|
180
|
+
try {
|
|
181
|
+
const { notifyGraphEvents } = await import('./graph-notify.js');
|
|
182
|
+
const result = await graphTick(graphFile, { startIfIdle: true, deps: { notify: async () => { await notifyGraphEvents(); } } });
|
|
183
|
+
if (result.status === 'refused') console.error(`graph tick: refused: ${result.reason}`);
|
|
184
|
+
else if (result.status === 'failed') console.error(`graph tick: ${result.runId}: failed`);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
debug.log('graph.runner', 'schedule-tick-failed', { file: graphFile, error: error instanceof Error ? error.message : String(error) });
|
|
187
|
+
console.error(`graph tick: ${error instanceof Error ? error.message : String(error)}`);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
source.subscribeGraph(graphFile, onScheduledTick);
|
|
191
|
+
if (!source.subscriptions().length) throw new Error('graph triggers.schedule is missing');
|
|
192
|
+
await source.start();
|
|
193
|
+
if (source.handle()?.skipped.length) {
|
|
194
|
+
const reasons = source.handle()!.skipped.map(item => item.reason).join('; ');
|
|
195
|
+
await source.stop();
|
|
196
|
+
throw new Error(`graph schedule skipped: ${reasons}`);
|
|
197
|
+
}
|
|
198
|
+
console.log(`graph schedule: ${source.subscriptions().length} active`);
|
|
199
|
+
const shutdown = () => { void source.stop().then(() => { process.exitCode = 0; }); };
|
|
200
|
+
process.once('SIGINT', shutdown);
|
|
201
|
+
process.once('SIGTERM', shutdown);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
172
204
|
const result = await graphTick(file, { startIfIdle: opts.start, ...(input === undefined ? {} : { input }),
|
|
173
205
|
deps: { notify: async () => { const { notifyGraphEvents } = await import('./graph-notify.js'); await notifyGraphEvents(); } } });
|
|
174
206
|
if (opts.json) await writeStdoutJson(JSON.stringify(result) + '\n');
|
|
175
207
|
else console.log(`graph tick: ${result.action}${result.runId ? ` ${result.runId}: ${result.status}` : ''}`);
|
|
176
|
-
if (result.status === 'failed') process.exitCode = 1;
|
|
208
|
+
if (result.status === 'failed' || result.status === 'refused') process.exitCode = 1;
|
|
177
209
|
} catch (error) {
|
|
178
210
|
console.error(`graph tick: ${error instanceof Error ? error.message : String(error)}`);
|
|
179
211
|
process.exitCode = 1;
|
|
@@ -11,6 +11,7 @@ import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
|
11
11
|
import { debug } from '../debug/log.js';
|
|
12
12
|
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
13
13
|
import { jsonResponse } from '../nexus/api/json-response.js';
|
|
14
|
+
import { peerEditRefusalBody, peerEditRunGate } from '../nexus/api/graph-peer-edit.js';
|
|
14
15
|
import { defaultGraphsDir } from '../self-implement/graph-templates.js';
|
|
15
16
|
import { runGraph, type GraphRunState } from './runner.js';
|
|
16
17
|
import { readWizardSteps, wizardRecipesFor } from '../graph-wizard/saved-steps.js';
|
|
@@ -143,6 +144,12 @@ export function handleGraphRunStart(rawId: string, deps: GraphRunApiDeps = {}):
|
|
|
143
144
|
if (!id) return jsonResponse({ error: 'bad_request', reason: 'invalid graph id' }, 400);
|
|
144
145
|
const mine = findMine(mineDirOf(deps), id);
|
|
145
146
|
if (!mine) return jsonResponse({ error: 'not-found', id, reason: '내 그래프에만 실행이 있다 — 핵심 그래프는 복제해서 돌린다' }, 404);
|
|
147
|
+
// W9c — a peer «edit» save runs nothing on this machine until the owner approves it. Unreadable marker = refused.
|
|
148
|
+
const peerGate = peerEditRunGate(mineDirOf(deps), id);
|
|
149
|
+
if (!peerGate.ok) {
|
|
150
|
+
debug.log('graphs.run', 'refused-peer-edit', { id, error: peerGate.error }, { level: 'warn' });
|
|
151
|
+
return jsonResponse(peerEditRefusalBody(id, peerGate), 409);
|
|
152
|
+
}
|
|
146
153
|
const recipesFile = deps.recipesFile ?? join(defaultGraphsDir(), 'demo', 'editor-recipes.yaml');
|
|
147
154
|
let recipeText: string;
|
|
148
155
|
let allowed: Set<string>;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { closeSync, mkdirSync, openSync, readFileSync, rmdirSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { join } from 'node:path';
|
|
2
|
+
import { closeSync, mkdirSync, openSync, readFileSync, realpathSync, rmdirSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
4
4
|
import { parse as parseYaml } from 'yaml';
|
|
5
|
+
import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
5
6
|
import { debug } from '../debug/log.js';
|
|
7
|
+
import { peerEditRunGate } from '../nexus/api/graph-peer-edit.js';
|
|
6
8
|
import { observeModelInputTokens } from '../harness/model-input-observation.js';
|
|
7
9
|
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
8
10
|
import { DecisionLedger } from '../decisions/decision-ledger.js';
|
|
@@ -11,23 +13,74 @@ import { latestGraphRun, runGraph, type GraphRunOptions, type GraphRunState } fr
|
|
|
11
13
|
export interface GraphTickOptions {
|
|
12
14
|
startIfIdle?: boolean;
|
|
13
15
|
input?: unknown;
|
|
16
|
+
mineDir?: string;
|
|
14
17
|
deps?: GraphRunOptions['deps'] & { notify?: () => void | Promise<void>; isAlive?: (pid: number) => boolean };
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
export interface GraphTickResult {
|
|
18
|
-
action: 'waiting' | 'resumed' | 'started' | 'idle';
|
|
21
|
+
action: 'waiting' | 'resumed' | 'started' | 'idle' | 'refused';
|
|
19
22
|
runId?: string;
|
|
20
|
-
status?: GraphRunState['status'];
|
|
23
|
+
status?: GraphRunState['status'] | 'refused';
|
|
24
|
+
reason?: 'peer-edit-unapproved' | 'peer-edit-unreadable';
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
/** One tick owns the graph from the latest-state read through notification and the chosen action. */
|
|
24
28
|
export async function graphTick(path: string, options: GraphTickOptions = {}): Promise<GraphTickResult> {
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
29
|
+
const mineDir = options.mineDir ?? join(elanousStateRoot(), 'graphs');
|
|
30
|
+
const inside = (dir: string, file: string): boolean => {
|
|
31
|
+
const contained = relative(dir, file);
|
|
32
|
+
return contained !== '' && contained !== '..' && !contained.startsWith(`..${sep}`) && !isAbsolute(contained);
|
|
33
|
+
};
|
|
34
|
+
const inspect = (): { source: string; graphId: string; ids: string[] } => {
|
|
35
|
+
const source = readFileSync(path, 'utf8');
|
|
36
|
+
const document: unknown = parseYaml(source);
|
|
37
|
+
const graphId = document && typeof document === 'object' && !Array.isArray(document)
|
|
38
|
+
? (document as Record<string, unknown>).graph_id : undefined;
|
|
39
|
+
if (typeof graphId !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(graphId) || graphId === '.' || graphId === '..') {
|
|
40
|
+
throw new Error(`invalid graph id: ${String(graphId)}`);
|
|
41
|
+
}
|
|
42
|
+
const ids: string[] = [];
|
|
43
|
+
const file = resolve(path);
|
|
44
|
+
const realFile = realpathSync(path);
|
|
45
|
+
let realMine: string | undefined;
|
|
46
|
+
try { realMine = realpathSync(mineDir); }
|
|
47
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
|
|
48
|
+
for (const candidate of [file, realFile]) {
|
|
49
|
+
if (inside(resolve(mineDir), candidate) || (realMine && inside(realMine, candidate))) {
|
|
50
|
+
const name = basename(candidate);
|
|
51
|
+
if (/\.ya?ml$/.test(name)) ids.push(name.replace(/\.ya?ml$/, ''));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (ids.length) ids.push(graphId);
|
|
55
|
+
return { source, graphId, ids: [...new Set(ids)] };
|
|
56
|
+
};
|
|
57
|
+
const initial = inspect();
|
|
58
|
+
const graphId = initial.graphId;
|
|
59
|
+
const peerRefusal = (ids: string[]): GraphTickResult | null => {
|
|
60
|
+
for (const id of ids) {
|
|
61
|
+
const gate = peerEditRunGate(mineDir, id);
|
|
62
|
+
if (gate.ok) continue;
|
|
63
|
+
debug.log('graph.runner', 'tick-peer-edit-refused', {
|
|
64
|
+
id,
|
|
65
|
+
editedBy: gate.error === 'peer-edit-unapproved' ? gate.record.editedBy : undefined,
|
|
66
|
+
version: gate.error === 'peer-edit-unapproved' ? gate.record.version : undefined,
|
|
67
|
+
});
|
|
68
|
+
return { action: 'refused', status: 'refused', reason: gate.error };
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
};
|
|
72
|
+
const refused = peerRefusal(initial.ids);
|
|
73
|
+
if (refused) return refused;
|
|
74
|
+
const peerEditRefused = Symbol('tick-peer-edit-refused');
|
|
75
|
+
let nodeRefusal: GraphTickResult | null = null;
|
|
76
|
+
const runDeps: GraphRunOptions['deps'] = {
|
|
77
|
+
...options.deps,
|
|
78
|
+
beforeNode: () => {
|
|
79
|
+
options.deps?.beforeNode?.();
|
|
80
|
+
nodeRefusal = peerRefusal([...new Set([...initial.ids, ...inspect().ids])]);
|
|
81
|
+
if (nodeRefusal) throw peerEditRefused;
|
|
82
|
+
},
|
|
83
|
+
};
|
|
31
84
|
const root = options.deps?.root ?? effectiveInstanceRoot();
|
|
32
85
|
const lock = join(root, 'graph-runs', graphId, '.tick.lock');
|
|
33
86
|
mkdirSync(join(root, 'graph-runs', graphId), { recursive: true });
|
|
@@ -38,6 +91,11 @@ export async function graphTick(path: string, options: GraphTickOptions = {}): P
|
|
|
38
91
|
acquireTickLock(lock, graphId, options.deps?.isAlive ?? processAlive);
|
|
39
92
|
acquired = true;
|
|
40
93
|
await options.deps?.notify?.();
|
|
94
|
+
const current = inspect();
|
|
95
|
+
const refusedAfterNotify = peerRefusal([...new Set([...initial.ids, ...current.ids])]);
|
|
96
|
+
if (refusedAfterNotify) return refusedAfterNotify;
|
|
97
|
+
// The run identity and executed bytes must agree with the ledger and the gate.
|
|
98
|
+
if (current.graphId !== graphId) throw new Error(`graph id changed during tick: ${graphId} -> ${current.graphId}`);
|
|
41
99
|
const latest = latestGraphRun(graphId, root);
|
|
42
100
|
const growthCard = latest?.growthPark
|
|
43
101
|
? (options.deps?.growthDecision?.list ?? ((filters: { status: 'all' }) => new DecisionLedger({ stateDir: root }).list(filters)))({ status: 'all' })
|
|
@@ -49,10 +107,10 @@ export async function graphTick(path: string, options: GraphTickOptions = {}): P
|
|
|
49
107
|
if (latest?.status === 'awaiting-approval' && !latest.pending?.decision && !growthDecided) {
|
|
50
108
|
result = { action: 'waiting', runId: latest.runId, status: latest.status };
|
|
51
109
|
} else if (latest?.status === 'running' || (latest?.status === 'awaiting-approval' && (latest.pending?.decision || growthDecided))) {
|
|
52
|
-
const state = await runGraph(path, { resumeRunId: latest.runId, deps: { ...
|
|
110
|
+
const state = await runGraph(path, { resumeRunId: latest.runId, graphSource: current.source, deps: { ...runDeps, root } });
|
|
53
111
|
result = { action: 'resumed', runId: state.runId, status: state.status };
|
|
54
112
|
} else if (options.startIfIdle) {
|
|
55
|
-
const state = await runGraph(path, { input: options.input, deps: { ...
|
|
113
|
+
const state = await runGraph(path, { input: options.input, graphSource: current.source, deps: { ...runDeps, root } });
|
|
56
114
|
result = { action: 'started', runId: state.runId, status: state.status };
|
|
57
115
|
} else {
|
|
58
116
|
result = { action: 'idle' };
|
|
@@ -60,6 +118,9 @@ export async function graphTick(path: string, options: GraphTickOptions = {}): P
|
|
|
60
118
|
(options.deps?.log ?? ((event: string, data: Record<string, unknown>) => debug.log('graph.runner', event, data)))
|
|
61
119
|
('tick', { graphId, action: result.action, ...(result.runId ? { runId: result.runId } : {}) });
|
|
62
120
|
return result;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (error === peerEditRefused && nodeRefusal) return nodeRefusal;
|
|
123
|
+
throw error;
|
|
63
124
|
} finally {
|
|
64
125
|
try {
|
|
65
126
|
if (acquired) {
|