elanous 0.2.14 → 0.2.16
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-6e7e0377c1db0bdc.js → 1307-b46d45b705e8c9ae.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
- package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9103-7a3ae7f60af3b99f.js → 9103-60fbef33e66f0a46.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-7b2b4f378354869a.js → page-3cb52e5ea8fc2fa5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-c1e20d7d338713f1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-1512d91c7491ed62.js → page-482c5dbde699947c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1e962b31d1f11081.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-a627db94ec6904a0.js → page-8c2d9526164f5d09.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-e6b84ab07d3567d5.js → page-a0e2a6d1bfb52fc5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-c53598cfce6156f2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-fed69f9759d248d0.js → page-13a63953f53c6a23.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-15374909d8f1aebd.js → page-5d3c8429d04f3831.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-012a742f81218b01.js → page-3193bdc0bbab047a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-76270efaee638f17.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-99effd9d893a43dc.js → page-6107ca8ddd5e3bdd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-938de57ae0315e46.js → layout-dce686e582c23937.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d3d53b146416e422.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/{page-160089123a40dd91.js → page-1a61c7ca1ceb3ae7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/market/{page-629e1ede482ec6eb.js → page-51f811fa119308b6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-dacff46049e1557e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-27e99b4e54a849d4.js → page-4167348049166852.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-de52e8fdf9095f7a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-36a7d994f4ca0299.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-32cde713be47cddb.js → page-ca3952cd78a83ca2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-e7e10cd3386222e5.js → page-5b042736aa113756.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-acb749f1e432bcf3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-e64caff084f7ce21.js → page-1ebfe049c640e171.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-db07eaffba0df7e3.js → page-57924393e3e4e539.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-31fa1ce4c4aff9ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-86f91034bd5cabe5.js → page-0f5cd57ca928f42b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-abe8e3b74bea1ffc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-ce4d8a1215ce53a3.js → page-d28573d6792b4275.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-58043db78ba519ae.js → page-59f03fda76c03d22.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-357750dfb91bf960.js → page-d751de5f8062ff5e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/{page-274703bc41b71333.js → page-fe1759f94003b32b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-d64c984e855c8c68.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-3b7e821dc492bdfd.js → page-9afdee66b820c76e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d4aef67abf264874.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-f8da5dc6f3752276.js → page-1ea64acadd704ace.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-2f810b18d89ecba6.js → page-3544f154c87b43ee.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-30c2073bfc21c004.js +1 -0
- package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +1 -0
- package/apps/pwa/out/_next/static/css/{04dabcbcc4bfa6db.css → 085673d826b98b62.css} +1 -1
- package/apps/pwa/out/_next/static/rv9cierV0jSOvDBm1g0XB/_buildManifest.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +15 -15
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +15 -15
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +15 -15
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +15 -15
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +15 -15
- package/apps/pwa/out/ceo/index.html +1 -0
- package/apps/pwa/out/ceo/index.txt +33 -0
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +15 -15
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +15 -15
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +15 -15
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +15 -15
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +15 -15
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +15 -15
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +15 -15
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +15 -15
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +15 -15
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +15 -15
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +15 -15
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +15 -15
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +15 -15
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +15 -15
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +15 -15
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +15 -15
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +15 -15
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +15 -15
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +15 -15
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +15 -15
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +16 -16
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +15 -15
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +15 -15
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +15 -15
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +15 -15
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +15 -15
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +15 -15
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +15 -15
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +15 -15
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +15 -15
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +15 -15
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +15 -15
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +15 -15
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +15 -15
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +15 -15
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +15 -15
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +15 -15
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +15 -15
- package/graphs/doc-gen/doc-gen.yaml +24 -0
- package/graphs/doc-gen/recipes.yaml +3 -0
- package/graphs/heal/heal-loop.yaml +1 -1
- package/graphs/implement-loop.yaml +1 -1
- package/graphs/orchestrator/orchestrator.yaml +7 -0
- package/graphs/orchestrator/recipes.yaml +3 -0
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +4 -2
- package/graphs/steward/recipes.yaml +2 -1
- package/package.json +1 -1
- package/scripts/brand/check.ts +7 -5
- package/scripts/ci-public-export-import-gate.ts +154 -0
- package/scripts/ci-test-interference-gate.ts +5 -1
- package/scripts/ci-typecheck-changed.ts +82 -31
- package/scripts/docs/doc-rot.ts +164 -0
- package/scripts/docs/generate-graph-anatomy.ts +437 -0
- package/scripts/docs-cli-check.ts +4 -4
- package/scripts/expose-rubric-suggest.ts +18 -12
- package/scripts/goals/goal-census.ts +343 -0
- package/scripts/lecture-note/node.ts +31 -11
- package/scripts/lessons/handbook-page.ts +30 -3
- package/scripts/marketing/generate-ax-brief.ts +140 -0
- package/scripts/marketing/generate-feature-map.ts +118 -0
- package/scripts/measure/author-section-contribution.ts +84 -0
- package/scripts/release-loop/checklist-gate-node.ts +53 -14
- package/scripts/release-loop/cut-branch.ts +43 -13
- package/scripts/release-loop/gate-node.ts +82 -14
- package/scripts/release-loop/vault-note-node.ts +257 -8
- package/scripts/release-story/draft.ts +57 -9
- package/scripts/rhythm/daily.ts +263 -76
- package/scripts/rhythm/monthly.ts +97 -0
- package/scripts/rhythm/weekly.ts +30 -9
- package/scripts/smart-merge/resync-shadow.ts +147 -0
- package/src/acp/client.ts +17 -2
- package/src/agent-env/profile.ts +161 -0
- package/src/agent-mission/driver.ts +51 -1
- package/src/agent-mission/supervisor-metrics-cli.ts +40 -0
- package/src/agent-mission/supervisor-metrics.ts +218 -0
- package/src/agent-substrate/pr-reviewer.ts +2 -0
- package/src/ask-user-question/pending-questions.ts +5 -0
- package/src/auth/github-app-token.ts +112 -13
- package/src/autopilot/build/llm-conflict-merge.ts +32 -6
- package/src/autopilot/track-agent-shadow.ts +2 -1
- package/src/briefing/brief-items.ts +276 -0
- package/src/budget/usage-outcomes.ts +204 -0
- package/src/claims/claims-render.ts +14 -5
- package/src/cli/agent-env-cli.ts +21 -0
- package/src/cli/brief-cli.ts +73 -0
- package/src/cli/decisions-cli.ts +9 -0
- package/src/cli/doctor-cli.ts +23 -1
- package/src/cli/doctor-node-modules.ts +147 -0
- package/src/cli/intake-cli.ts +46 -17
- package/src/cli/know-cli.ts +20 -0
- package/src/cli/logs-cli.ts +43 -1
- package/src/cli/pod-cli.ts +2 -0
- package/src/cli/pr-cli.ts +80 -12
- package/src/cli/release-cli.ts +66 -29
- package/src/cli/seat-requests-cli.ts +52 -0
- package/src/cli/self-update.ts +200 -1
- package/src/cli/usage-cli.ts +26 -0
- package/src/context-bus/context-now-public.ts +16 -6
- package/src/context-bus/context-now.ts +3 -1
- package/src/context-bus/coord-events.ts +6 -4
- package/src/decisions/decision-cards.ts +33 -7
- package/src/decisions/proact-meter.ts +286 -0
- package/src/decisions/proact1-lite.ts +86 -0
- package/src/decisions/web-push-decision-cards.ts +1 -1
- package/src/discord-self-message.ts +9 -0
- package/src/domains/alert-audit.ts +271 -0
- package/src/domains/core-tools.ts +4 -3
- package/src/domains/ops-facts-tool.ts +31 -1
- package/src/flow-loop/split.ts +24 -0
- package/src/harness/harness-cli-command.ts +147 -17
- package/src/harness/harness-incidents.ts +2 -0
- package/src/harness/harness-pod-dispatch.ts +67 -8
- package/src/harness/harness-queue.ts +380 -50
- package/src/harness/harness-seams.ts +13 -3
- package/src/harness/helper-repair.ts +218 -0
- package/src/harness/helper-scan.ts +27 -4
- package/src/harness/launch-stamp.ts +236 -0
- package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
- package/src/harness/ledger-write-guard.ts +35 -0
- package/src/harness/nested-elanous-policy.ts +114 -0
- package/src/harness/pod-memory-policy.ts +15 -0
- package/src/harness/repository-name.ts +55 -0
- package/src/hq/fence-audit.ts +90 -0
- package/src/hq/hq.ts +70 -6
- package/src/hq/lease.ts +9 -7
- package/src/hq/move-plan.ts +126 -0
- package/src/hq/promote.ts +31 -0
- package/src/hq/transfer.ts +185 -0
- package/src/index.ts +189 -13
- package/src/intake-plane/check.ts +63 -0
- package/src/intake-plane/digest.ts +65 -21
- package/src/intake-plane/front-classifier.ts +6 -0
- package/src/intake-plane/items.ts +3 -2
- package/src/intake-plane/lens.ts +177 -0
- package/src/intake-plane/route.ts +145 -1
- package/src/knowledge/know-find.ts +105 -0
- package/src/llm.ts +75 -19
- package/src/loops/budget.ts +36 -5
- package/src/loops/checker.ts +2 -2
- package/src/loops/orchestrator/author-depth.ts +65 -9
- package/src/loops/orchestrator/author-input-check.ts +8 -3
- package/src/loops/orchestrator/author-ledger.ts +3 -1
- package/src/loops/orchestrator/finish-rate.ts +14 -3
- package/src/loops/orchestrator/seat-share.ts +56 -0
- package/src/loops/orchestrator/tick.ts +107 -29
- package/src/loops/orchestrator/traffic.ts +117 -11
- package/src/loops/registry.ts +142 -61
- package/src/maturity/bot-slash-catalog.ts +11 -2
- package/src/maturity/feature-maturity.ts +4 -3
- package/src/maturity/tui-slash-availability.ts +40 -0
- package/src/mss/logging/log-store.ts +35 -4
- package/src/nexus/api/http-server.ts +28 -14
- package/src/nexus/api/loop-edges.ts +173 -0
- package/src/nexus/api/schedules-read.ts +13 -0
- package/src/nexus/api/seat-requests.ts +79 -56
- package/src/oauth/overload-failover-launch.ts +75 -0
- package/src/oauth/overload-failover.ts +137 -0
- package/src/prompt-enhance/enhance.ts +6 -1
- package/src/release-loop/auto-start.ts +6 -3
- package/src/release-loop/checklist.ts +19 -5
- package/src/release-loop/feature-store.ts +59 -31
- package/src/release-loop/merged-pr-checklist.ts +125 -0
- package/src/release-loop/placement.ts +98 -29
- package/src/seat-dispatch/seat-ask.ts +33 -6
- package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
- package/src/seat-dispatch/seat-request-ledger.ts +133 -0
- package/src/seat-loop/seat-loop.ts +215 -12
- package/src/self-dev/ask-launch-flow.ts +22 -1
- package/src/self-dev/authored-goal-trace.ts +64 -0
- package/src/self-dev/dev-cli.ts +19 -1
- package/src/self-dev/dev-pipeline.ts +14 -9
- package/src/self-dev/draft-sweep.ts +90 -10
- package/src/self-dev/draft-triage-rules.ts +25 -8
- package/src/self-dev/entrance-registry.ts +10 -6
- package/src/self-dev/old-door.ts +73 -0
- package/src/self-dev/orchestrate.ts +15 -1
- package/src/self-dev/review-provider-fallback.ts +6 -1
- package/src/self-dev/run-supervisor.ts +49 -2
- package/src/self-implement/app-api-budget.ts +166 -0
- package/src/self-implement/budget-gate.ts +7 -5
- package/src/self-implement/follow-up-goals.ts +289 -0
- package/src/self-implement/frozen-merges.ts +3 -2
- package/src/self-implement/goal-author.ts +50 -6
- package/src/self-implement/goal-clarification-escalation.ts +5 -0
- package/src/self-implement/heal-intake.ts +120 -2
- package/src/self-implement/heal-triage.ts +44 -0
- package/src/self-implement/host-regate.ts +9 -2
- package/src/self-implement/l8-merge-queue.ts +132 -0
- package/src/self-implement/orchestrator.ts +325 -47
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/revert-guard.ts +42 -7
- package/src/self-implement/run-ledger.ts +6 -0
- package/src/self-implement/run-status-mapping.ts +2 -0
- package/src/self-implement/seams.ts +45 -16
- package/src/self-implement/self-implement-cli.ts +1 -0
- package/src/self-implement/speed-claims.ts +338 -0
- package/src/session-runtime/index.ts +2 -0
- package/src/session-runtime/retry-api.ts +87 -0
- package/src/session-runtime/retry-policy.ts +107 -6
- package/src/steward/triage.ts +74 -3
- package/src/steward/working-backwards.ts +20 -2
- package/src/task-cards/card-cli.ts +25 -0
- package/src/task-cards/card-store.ts +5 -3
- package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
- package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +4 -3
- package/src/task-orchestrator/surfaces/pod-ledger-prod-sink.ts +75 -0
- package/src/task-orchestrator/surfaces/pod-pool.ts +70 -9
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +37 -1
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +166 -46
- package/src/task-orchestrator/surfaces/self-implement.ts +7 -1
- package/src/telegram-commands.ts +5 -0
- package/src/telegram.ts +72 -4
- package/src/user-config.ts +64 -7
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/nodes/bash.ts +9 -1
- package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-2df489622a52a33a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-1abfe3aec3be6bdc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2ce5471926761fdd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e1e47d8d940aba3d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +0 -1
- package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +0 -1
- package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{x6mjI6vqOkVVu0jFuDkz1 → rv9cierV0jSOvDBm1g0XB}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { debug } from '../debug/log.js';
|
|
5
|
+
import { releasePathHold, releasePrFilePaths } from '../self-dev/release-path-guard.js';
|
|
6
|
+
import { getUserConfig, type UserConfig } from '../user-config.js';
|
|
7
|
+
import type { QueueItem, QueueSeat } from './harness-queue.js';
|
|
8
|
+
import type { RunGh, ScannedStoppedPr, StoppedPrCategory } from './helper-scan.js';
|
|
9
|
+
|
|
10
|
+
export interface RepairGoalInput {
|
|
11
|
+
pr: number;
|
|
12
|
+
category: StoppedPrCategory;
|
|
13
|
+
/** The original user's request, not the supervisor's stop report or a paraphrase. */
|
|
14
|
+
originalRequest: string;
|
|
15
|
+
/** Remaining reviewer must-fix items, in their original wording and order. */
|
|
16
|
+
mustFix: readonly string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Author a single follow-up goal without changing the original ask or reviewer findings. */
|
|
20
|
+
export function authorRepairGoal({ pr, category, originalRequest, mustFix }: RepairGoalInput): string | null {
|
|
21
|
+
if ((category !== 'review-budget' && category !== 'review-oscillation') || !originalRequest.trim() || !mustFix.length || mustFix.some((item) => !item.trim())) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const guidance = category === 'review-oscillation'
|
|
26
|
+
? '기존 시험 설계를 보존하고 리뷰 진동·설계 역행을 확인한 뒤 남은 must-fix를 수리하라.'
|
|
27
|
+
: '남은 must-fix를 수리하라. 원래 요청의 범위와 기존 시험 설계를 보존하라.';
|
|
28
|
+
return `${originalRequest}${originalRequest.endsWith('\n') ? '' : '\n'}\n## PR #${pr} 수리\n${guidance}\n\n## 남은 must-fix (원문)\n${mustFix.join('\n')}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** PR 본문에서 원 요청(## 요청 절)과 남은 must-fix(## 마지막 리뷰 must-fix · ## Follow-up must-fix 의 «- » 항목)를 원문 그대로 뽑는다. */
|
|
32
|
+
export function repairInputsFromBody(body: string): { originalRequest: string; mustFix: string[] } {
|
|
33
|
+
const section = (title: RegExp) => {
|
|
34
|
+
const lines = body.split('\n');
|
|
35
|
+
const start = lines.findIndex((line) => title.test(line));
|
|
36
|
+
if (start < 0) return [] as string[];
|
|
37
|
+
const end = lines.findIndex((line, index) => index > start && /^## /.test(line));
|
|
38
|
+
return lines.slice(start + 1, end < 0 ? undefined : end);
|
|
39
|
+
};
|
|
40
|
+
const requestLines = section(/^## 요청\s*$/);
|
|
41
|
+
while (requestLines[0] === '') requestLines.shift();
|
|
42
|
+
while (requestLines.length > 1 && requestLines.at(-1) === '' && requestLines.at(-2) === '') requestLines.pop();
|
|
43
|
+
if (requestLines.length === 2 && requestLines[1] === '') requestLines.pop();
|
|
44
|
+
const originalRequest = requestLines.join('\n');
|
|
45
|
+
const mustFix: string[] = [];
|
|
46
|
+
for (const lines of [section(/^## 마지막 리뷰 must-fix/), section(/^## Follow-up must-fix/)]) {
|
|
47
|
+
let item: string[] = [];
|
|
48
|
+
const flush = () => {
|
|
49
|
+
while (item.at(-1) === '') item.pop();
|
|
50
|
+
if (item.length) mustFix.push(item.join('\n'));
|
|
51
|
+
item = [];
|
|
52
|
+
};
|
|
53
|
+
for (const line of lines) {
|
|
54
|
+
if (/^- /.test(line)) {
|
|
55
|
+
flush();
|
|
56
|
+
item.push(line);
|
|
57
|
+
} else if (item.length) {
|
|
58
|
+
item.push(line);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
flush();
|
|
62
|
+
}
|
|
63
|
+
return { originalRequest, mustFix };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type RepairShadowRow = { pr: number; category: StoppedPrCategory; goalHash: string; at: string };
|
|
67
|
+
|
|
68
|
+
type RepairLedgerResult = 'shadow' | 'launched' | 'cap' | 'needs-human' | 'launch-failed';
|
|
69
|
+
|
|
70
|
+
type RepairLedgerLine = {
|
|
71
|
+
pr: number; category: StoppedPrCategory; goalHash: string; at: string;
|
|
72
|
+
mode: 'shadow' | 'live'; goal: string; result: RepairLedgerResult;
|
|
73
|
+
/** Harness queue item id — the run itself starts on a later queue tick. */ queueId?: string; reason?: string;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const RELEASE_FILE_PAGE = 100;
|
|
77
|
+
|
|
78
|
+
/** Changed files of the original PR. A failed read is a reason to hold, never a reason to launch. */
|
|
79
|
+
async function changedFiles(pr: number, runGh: RunGh): Promise<string[]> {
|
|
80
|
+
const pages: unknown[] = [];
|
|
81
|
+
for (let page = 1; ; page++) {
|
|
82
|
+
const raw = await runGh(['api', '--method', 'GET', '--raw-field', `per_page=${RELEASE_FILE_PAGE}`, '--raw-field', `page=${page}`, `repos/{owner}/{repo}/pulls/${pr}/files`]);
|
|
83
|
+
const parsed: unknown = JSON.parse(raw);
|
|
84
|
+
if (!Array.isArray(parsed)) throw new Error('gh api PR files returned an invalid file list');
|
|
85
|
+
pages.push(parsed);
|
|
86
|
+
if (parsed.length < RELEASE_FILE_PAGE) break;
|
|
87
|
+
}
|
|
88
|
+
return releasePrFilePaths(pages);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const QUEUE_SEATS = ['OP', 'TC', 'MK', 'UX'] as const;
|
|
92
|
+
|
|
93
|
+
/** 원 PR 의 자리 라벨(elanous:seat-<자리> 또는 자리 이름 라벨). 없거나 둘 이상이면 MK. */
|
|
94
|
+
function seatOf(labels: readonly { name?: string }[] | undefined): QueueSeat {
|
|
95
|
+
const found = new Set<QueueSeat>();
|
|
96
|
+
for (const label of labels ?? []) {
|
|
97
|
+
const name = label.name ?? '';
|
|
98
|
+
const prefixed = /^elanous:seat-(OP|TC|MK|UX)$/.exec(name)?.[1];
|
|
99
|
+
const bare = (QUEUE_SEATS as readonly string[]).includes(name) ? name : undefined;
|
|
100
|
+
const seat = prefixed ?? bare;
|
|
101
|
+
if (seat) found.add(seat as QueueSeat);
|
|
102
|
+
}
|
|
103
|
+
return found.size === 1 ? [...found][0]! : 'MK';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function helperSettings(config: UserConfig | undefined): { mode: 'shadow' | 'live'; perDay: number } {
|
|
107
|
+
const helper = config?.harness?.helper;
|
|
108
|
+
return {
|
|
109
|
+
mode: helper?.repair === 'live' ? 'live' : 'shadow',
|
|
110
|
+
perDay: typeof helper?.repairPerDay === 'number' && Number.isSafeInteger(helper.repairPerDay) && helper.repairPerDay >= 0
|
|
111
|
+
? helper.repairPerDay : 3,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** 그림자(기본): 수리 골을 원장에만 남긴다. live: 같은 함수로 대기열에 넣고 원 PR 을 superseded 로 표시한다. 같은 PR 은 평생 한 번. */
|
|
116
|
+
export async function recordRepairShadows(rows: readonly ScannedStoppedPr[], deps: {
|
|
117
|
+
runGh: RunGh; root: string; now?: Date;
|
|
118
|
+
config?: UserConfig;
|
|
119
|
+
/** 대기열 추가. 생략하면 addHarnessQueue — 실제 발사는 기존 대기열 틱이 한다. */
|
|
120
|
+
enqueue?: (input: { seat: QueueSeat; say: string; idempotencyKey: string }) => Promise<Pick<QueueItem, 'id'>>;
|
|
121
|
+
}): Promise<RepairShadowRow[]> {
|
|
122
|
+
const ledger = join(deps.root, 'helper', 'repairs.jsonl');
|
|
123
|
+
const prior: RepairLedgerLine[] = [];
|
|
124
|
+
if (existsSync(ledger)) for (const line of readFileSync(ledger, 'utf8').split('\n')) {
|
|
125
|
+
if (!line.trim()) continue;
|
|
126
|
+
try { prior.push(JSON.parse(line) as RepairLedgerLine); } catch { /* malformed line is skipped */ }
|
|
127
|
+
}
|
|
128
|
+
const { mode, perDay } = helperSettings(deps.config ?? getUserConfig());
|
|
129
|
+
const day = (deps.now ?? new Date()).toISOString().slice(0, 10);
|
|
130
|
+
// Terminal results end a PR for good. A shadow line blocks only while still in shadow (live must still launch it),
|
|
131
|
+
// and a cap or failed launch blocks only for the same UTC day so the PR is retried tomorrow (ACP must-fix).
|
|
132
|
+
// Ledger lines written before results existed have no `result`; they were shadow records.
|
|
133
|
+
for (const line of prior) if (!line.result) line.result = 'shadow';
|
|
134
|
+
const seen = new Set(prior.filter((line) => line.result === 'launched' || line.result === 'needs-human'
|
|
135
|
+
|| (line.result === 'shadow' && mode === 'shadow')
|
|
136
|
+
|| ((line.result === 'cap' || line.result === 'launch-failed') && line.at.slice(0, 10) === day)).map((line) => line.pr));
|
|
137
|
+
// Distinct PRs: a follow-up repair line for an already launched PR is not a second launch.
|
|
138
|
+
let launchedToday = new Set(prior.filter((line) => line.result === 'launched' && line.at.slice(0, 10) === day).map((line) => line.pr)).size;
|
|
139
|
+
// A launched line whose label/comment failed is repaired here — the follow-up only, never a second enqueue.
|
|
140
|
+
if (mode === 'live') {
|
|
141
|
+
const latest = new Map<number, RepairLedgerLine>();
|
|
142
|
+
for (const line of prior) latest.set(line.pr, line);
|
|
143
|
+
for (const line of latest.values()) {
|
|
144
|
+
if (line.result !== 'launched' || !line.reason?.startsWith('후속 실패') || !line.queueId) continue;
|
|
145
|
+
const followups: string[] = [];
|
|
146
|
+
if (line.reason.includes('label ·')) {
|
|
147
|
+
try { await deps.runGh(['pr', 'edit', String(line.pr), '--add-label', 'elanous:superseded']); }
|
|
148
|
+
catch (error) { followups.push(`label · ${error instanceof Error ? error.message : String(error)}`); }
|
|
149
|
+
}
|
|
150
|
+
if (line.reason.includes('comment ·')) {
|
|
151
|
+
try { await deps.runGh(['pr', 'comment', String(line.pr), '--body', `수리 대기열 항목 ${line.queueId} 로 대체(런은 대기열 틱에서 시작)`]); }
|
|
152
|
+
catch (error) { followups.push(`comment · ${error instanceof Error ? error.message : String(error)}`); }
|
|
153
|
+
}
|
|
154
|
+
const repaired: RepairLedgerLine = { ...line, at: (deps.now ?? new Date()).toISOString(), ...(followups.length ? { reason: `후속 실패 · ${followups.join(' · ')}` } : { reason: '후속 복구' }) };
|
|
155
|
+
appendFileSync(ledger, JSON.stringify(repaired) + '\n');
|
|
156
|
+
debug.log('harness.helper', 'repair-followup-retried', { pr: line.pr, queueId: line.queueId, remaining: followups.length });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const enqueue = deps.enqueue ?? (async (input) => {
|
|
160
|
+
const { addHarnessQueue } = await import('./harness-queue.js');
|
|
161
|
+
return addHarnessQueue(input, { root: deps.root });
|
|
162
|
+
});
|
|
163
|
+
const written: RepairShadowRow[] = [];
|
|
164
|
+
for (const row of rows) {
|
|
165
|
+
if ((row.category !== 'review-budget' && row.category !== 'review-oscillation') || seen.has(row.pr)) continue;
|
|
166
|
+
const view = JSON.parse(await deps.runGh(['pr', 'view', String(row.pr), '--json', 'body'])) as { body: string };
|
|
167
|
+
// 자리 라벨은 본문 조회와 분리한다 — 기존 본문 조회 계약(--json body)을 그대로 둔다. 못 읽으면 MK.
|
|
168
|
+
let labels: { name?: string }[] | undefined;
|
|
169
|
+
if (mode === 'live') {
|
|
170
|
+
try { labels = (JSON.parse(await deps.runGh(['pr', 'view', String(row.pr), '--json', 'labels'])) as { labels?: { name?: string }[] }).labels; }
|
|
171
|
+
catch { labels = undefined; }
|
|
172
|
+
}
|
|
173
|
+
const goal = authorRepairGoal({ pr: row.pr, category: row.category, ...repairInputsFromBody(view.body) });
|
|
174
|
+
if (!goal) continue;
|
|
175
|
+
const at = (deps.now ?? new Date()).toISOString();
|
|
176
|
+
const base = { pr: row.pr, category: row.category, goalHash: createHash('sha256').update(goal).digest('hex').slice(0, 16), at, goal };
|
|
177
|
+
let record: RepairLedgerLine = { ...base, mode: 'shadow', result: 'shadow' };
|
|
178
|
+
if (mode === 'live') {
|
|
179
|
+
let files: string[] | undefined;
|
|
180
|
+
try { files = await changedFiles(row.pr, deps.runGh); }
|
|
181
|
+
catch (error) { files = undefined; debug.log('harness.helper', 'repair-files-unreadable', { pr: row.pr, reason: String(error) }); }
|
|
182
|
+
const release = files && releasePathHold(files);
|
|
183
|
+
// 상한은 «발사한 수»의 상한이다. 사람 승인이 필요한 PR 은 그 수를 쓰지 않으므로 상한보다 먼저 가른다.
|
|
184
|
+
if (files && !release && launchedToday >= perDay) {
|
|
185
|
+
record = { ...base, mode: 'live', result: 'cap', reason: '상한' };
|
|
186
|
+
} else if (!files) {
|
|
187
|
+
// An unreadable file list is transient: hold today, retry on a later day (never a permanent needs-human) (ACP must-fix).
|
|
188
|
+
record = { ...base, mode: 'live', result: 'launch-failed', reason: '변경 파일 조회 실패 · 다음 날 재시도' };
|
|
189
|
+
} else if (release) {
|
|
190
|
+
record = { ...base, mode: 'live', result: 'needs-human', reason: '사람 승인 필요' };
|
|
191
|
+
} else {
|
|
192
|
+
let item: Pick<QueueItem, 'id'> | undefined;
|
|
193
|
+
try {
|
|
194
|
+
item = await enqueue({ seat: seatOf(labels), say: goal, idempotencyKey: `helper-repair-${row.pr}` });
|
|
195
|
+
// The queue item exists from here on: a later label/comment failure is recorded on the launched line,
|
|
196
|
+
// never as launch-failed (which would let the next scan enqueue a second repair) (ACP must-fix).
|
|
197
|
+
const followups: string[] = [];
|
|
198
|
+
try { await deps.runGh(['pr', 'edit', String(row.pr), '--add-label', 'elanous:superseded']); }
|
|
199
|
+
catch (error) { followups.push(`label · ${error instanceof Error ? error.message : String(error)}`); }
|
|
200
|
+
try { await deps.runGh(['pr', 'comment', String(row.pr), '--body', `수리 대기열 항목 ${item.id} 로 대체(런은 대기열 틱에서 시작)`]); }
|
|
201
|
+
catch (error) { followups.push(`comment · ${error instanceof Error ? error.message : String(error)}`); }
|
|
202
|
+
record = { ...base, mode: 'live', result: 'launched', queueId: item.id, ...(followups.length ? { reason: `후속 실패 · ${followups.join(' · ')}` } : {}) };
|
|
203
|
+
launchedToday += 1;
|
|
204
|
+
debug.log('harness.helper', 'repair-launched', { pr: row.pr, queueId: item.id, mode: 'live', followupFailures: followups.length });
|
|
205
|
+
} catch (error) {
|
|
206
|
+
record = { ...base, mode: 'live', result: 'launch-failed', reason: `발사 실패 · ${error instanceof Error ? error.message : String(error)}` };
|
|
207
|
+
debug.log('harness.helper', 'repair-launch-failed', { pr: row.pr, reason: record.reason });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
mkdirSync(dirname(ledger), { recursive: true });
|
|
212
|
+
appendFileSync(ledger, JSON.stringify(record) + '\n');
|
|
213
|
+
if (record.result === 'shadow') debug.log('harness.helper', 'repair-authored', { pr: row.pr, category: row.category, mode: 'shadow' });
|
|
214
|
+
seen.add(row.pr);
|
|
215
|
+
written.push({ pr: base.pr, category: base.category, goalHash: base.goalHash, at: base.at });
|
|
216
|
+
}
|
|
217
|
+
return written;
|
|
218
|
+
}
|
|
@@ -125,12 +125,35 @@ export async function scanStoppedPrs({ runGh }: { runGh: RunGh }): Promise<Scann
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
if (import.meta.main) {
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
const args = process.argv.slice(2);
|
|
129
|
+
const json = args.includes('--json');
|
|
130
|
+
const recordShadows = args.includes('--record-shadows');
|
|
131
|
+
if (args.some((arg) => arg !== '--json' && arg !== '--record-shadows') || (json && recordShadows) || args.length > 1) {
|
|
132
|
+
console.error('사용법: bun src/harness/helper-scan.ts [--json | --record-shadows]');
|
|
130
133
|
process.exitCode = 1;
|
|
131
134
|
} else {
|
|
132
|
-
|
|
133
|
-
|
|
135
|
+
// 열린 PR 본문 전체는 기본 버퍼(1MB)를 넘는다(10-05 운영 ENOBUFS) — 넉넉히 준다.
|
|
136
|
+
const runGh = (args: readonly string[]) => execFileSync('gh', [...args], { encoding: 'utf8', maxBuffer: 256 * 1024 * 1024 });
|
|
137
|
+
const rows = await scanStoppedPrs({ runGh });
|
|
138
|
+
if (recordShadows) {
|
|
139
|
+
// 명시적 그림자 기록에서만 추가 PR 조회와 헬퍼 원장 쓰기를 수행한다(발사 0).
|
|
140
|
+
const { recordRepairShadows } = await import('./helper-repair.js');
|
|
141
|
+
const { effectiveInstanceRoot } = await import('../instance/resolve.js');
|
|
142
|
+
const { getUserConfig } = await import('../user-config.js');
|
|
143
|
+
const root = effectiveInstanceRoot();
|
|
144
|
+
const config = getUserConfig();
|
|
145
|
+
const live = config.harness?.helper?.repair === 'live';
|
|
146
|
+
const shadows = await recordRepairShadows(rows, {
|
|
147
|
+
runGh, root, config,
|
|
148
|
+
// live 만 대기열에 넣는다. 실제 발사는 기존 대기열 틱이 한다(새 발사 경로 0).
|
|
149
|
+
...(live ? { enqueue: async (input) => {
|
|
150
|
+
const { addHarnessQueue } = await import('./harness-queue.js');
|
|
151
|
+
return addHarnessQueue(input, { root });
|
|
152
|
+
} } : {}),
|
|
153
|
+
});
|
|
154
|
+
if (shadows.length) console.log(live ? `수리 골 ${shadows.length}건 → helper/repairs.jsonl` : `수리 골 그림자 ${shadows.length}건 → helper/repairs.jsonl`);
|
|
155
|
+
}
|
|
156
|
+
if (json) console.log(JSON.stringify(rows, null, 2));
|
|
134
157
|
else for (const row of rows) {
|
|
135
158
|
const gate = row.blockerCount ? ` · Gate 차단 ${row.blockerCount}(${row.hasPublicExportLeak ? row.blockers.find((line) => /public-export-leak/i.test(line))?.trim() ?? '✗ public-export-leak (6번째 이후)' : row.blockers[0]!.trim()})` : '';
|
|
136
159
|
console.log(`#${row.pr} ${row.category}${gate} → ${row.firstAction}`);
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ONEDOOR-1 — every harness run stamps which door it came through,
|
|
3
|
+
* who launched it, and whether it went through the queue.
|
|
4
|
+
*
|
|
5
|
+
* The stamp lives on the run ledger's first row (`run-origin`). A ledger
|
|
6
|
+
* written before this stamp, or a launch that named no door, reads as
|
|
7
|
+
* `unknown` — that is distinct from a count of zero.
|
|
8
|
+
*/
|
|
9
|
+
import { readdirSync } from 'node:fs';
|
|
10
|
+
import { loadRunLedger, runLedgerDir, type RunLedgerEntry } from '../self-implement/run-ledger.js';
|
|
11
|
+
|
|
12
|
+
export const LAUNCH_DOORS = [
|
|
13
|
+
'harness-say',
|
|
14
|
+
'harness-ask',
|
|
15
|
+
'dev-ask',
|
|
16
|
+
'self-implement',
|
|
17
|
+
'self-orchestrate',
|
|
18
|
+
'queue-tick',
|
|
19
|
+
'agent-mission',
|
|
20
|
+
'daemon-tool',
|
|
21
|
+
'telegram',
|
|
22
|
+
'pwa',
|
|
23
|
+
'unknown',
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
export type LaunchDoor = (typeof LAUNCH_DOORS)[number];
|
|
27
|
+
|
|
28
|
+
export const LAUNCH_SEATS = ['OP', 'TC', 'MK', 'UX'] as const;
|
|
29
|
+
export type LaunchSeat = (typeof LAUNCH_SEATS)[number];
|
|
30
|
+
|
|
31
|
+
export interface LaunchStamp {
|
|
32
|
+
readonly entrance: LaunchDoor;
|
|
33
|
+
/** Seat, human session, or loop name — whichever the launch actually named. */
|
|
34
|
+
readonly actor: string;
|
|
35
|
+
readonly viaQueue: boolean;
|
|
36
|
+
readonly queueId?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ENTRANCE_TO_DOOR: Readonly<Record<string, LaunchDoor>> = {
|
|
40
|
+
'cli-harness-say': 'harness-say',
|
|
41
|
+
'cli-harness-ask': 'harness-ask',
|
|
42
|
+
'cli-dev-ask': 'dev-ask',
|
|
43
|
+
'cli-drive': 'dev-ask',
|
|
44
|
+
'tui-slash-ask': 'dev-ask',
|
|
45
|
+
'tui-slash-dev': 'dev-ask',
|
|
46
|
+
'cli-self-implement': 'self-implement',
|
|
47
|
+
'nl-self-implement': 'self-implement',
|
|
48
|
+
'daemon-self-implement': 'daemon-tool',
|
|
49
|
+
'cli-self-orchestrate': 'self-orchestrate',
|
|
50
|
+
'nl-self-orchestrate': 'self-orchestrate',
|
|
51
|
+
'cli-harness-orchestrate': 'self-orchestrate',
|
|
52
|
+
'cli-harness-mission': 'agent-mission',
|
|
53
|
+
'agent-mission': 'agent-mission',
|
|
54
|
+
'daemon-harness-ask': 'daemon-tool',
|
|
55
|
+
'daemon-tool': 'daemon-tool',
|
|
56
|
+
telegram: 'telegram',
|
|
57
|
+
pwa: 'pwa',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const DOOR_SET = new Set<string>(LAUNCH_DOORS);
|
|
61
|
+
|
|
62
|
+
export function isLaunchDoor(value: string): value is LaunchDoor {
|
|
63
|
+
return DOOR_SET.has(value);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Reuse an existing entrance tag. Anything unnamed or unrecognized is `unknown`, never a zero. */
|
|
67
|
+
export function doorFromEntrance(entrance: string | undefined | null): LaunchDoor {
|
|
68
|
+
if (!entrance) return 'unknown';
|
|
69
|
+
const trimmed = entrance.trim();
|
|
70
|
+
if (!trimmed) return 'unknown';
|
|
71
|
+
if (ENTRANCE_TO_DOOR[trimmed]) return ENTRANCE_TO_DOOR[trimmed];
|
|
72
|
+
if (isLaunchDoor(trimmed)) return trimmed;
|
|
73
|
+
return 'unknown';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface LaunchStampInput {
|
|
77
|
+
readonly entrance?: string | null;
|
|
78
|
+
readonly seat?: string | null;
|
|
79
|
+
readonly session?: string | null;
|
|
80
|
+
readonly loopName?: string | null;
|
|
81
|
+
readonly viaQueue?: boolean;
|
|
82
|
+
readonly queueId?: string | null;
|
|
83
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function named(value: string | null | undefined): string | undefined {
|
|
87
|
+
const trimmed = value?.trim();
|
|
88
|
+
return trimmed ? trimmed : undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Seat wins, then the human session, then the loop name. A blank actor stays blank. */
|
|
92
|
+
export function resolveLaunchActor(input: LaunchStampInput, env: NodeJS.ProcessEnv = process.env): string {
|
|
93
|
+
const seat = named(input.seat) ?? named(env.ELANOUS_HARNESS_SEAT);
|
|
94
|
+
if (seat && (LAUNCH_SEATS as readonly string[]).includes(seat)) return seat;
|
|
95
|
+
return named(input.session)
|
|
96
|
+
?? named(env.ELANOUS_ORIGIN_SESSION)
|
|
97
|
+
?? named(input.loopName)
|
|
98
|
+
?? named(env.ELANOUS_LOOP_NAME)
|
|
99
|
+
?? '';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function resolveLaunchStamp(input: LaunchStampInput = {}, env: NodeJS.ProcessEnv = input.env ?? process.env): LaunchStamp {
|
|
103
|
+
const queueId = named(input.queueId) ?? named(env.ELANOUS_HARNESS_QUEUE_LAUNCH);
|
|
104
|
+
const viaQueue = input.viaQueue === true || queueId !== undefined;
|
|
105
|
+
const door = viaQueue ? 'queue-tick' : doorFromEntrance(input.entrance ?? env.ELANOUS_HARNESS_ENTRANCE);
|
|
106
|
+
return {
|
|
107
|
+
entrance: door,
|
|
108
|
+
actor: resolveLaunchActor(input, env),
|
|
109
|
+
viaQueue,
|
|
110
|
+
...(queueId ? { queueId } : {}),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Read the stamp off a ledger's first row. Missing or unreadable stamps are `unknown`. */
|
|
115
|
+
export function launchStampFromLedger(entries: readonly RunLedgerEntry[] | null | undefined): LaunchStamp {
|
|
116
|
+
const first = entries?.[0];
|
|
117
|
+
const raw = first?.data?.launch;
|
|
118
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
119
|
+
return { entrance: 'unknown', actor: '', viaQueue: false };
|
|
120
|
+
}
|
|
121
|
+
const launch = raw as Record<string, unknown>;
|
|
122
|
+
const entrance = typeof launch.entrance === 'string' && isLaunchDoor(launch.entrance) ? launch.entrance : 'unknown';
|
|
123
|
+
const queueId = typeof launch.queueId === 'string' && launch.queueId.trim() ? launch.queueId.trim() : undefined;
|
|
124
|
+
return {
|
|
125
|
+
entrance,
|
|
126
|
+
actor: typeof launch.actor === 'string' ? launch.actor : '',
|
|
127
|
+
viaQueue: launch.viaQueue === true || queueId !== undefined,
|
|
128
|
+
...(queueId ? { queueId } : {}),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface DoorRow {
|
|
133
|
+
readonly entrance: LaunchDoor;
|
|
134
|
+
readonly runs: number;
|
|
135
|
+
readonly viaQueue: number;
|
|
136
|
+
readonly outsideQueue: number;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface DoorTable {
|
|
140
|
+
readonly sinceMs: number;
|
|
141
|
+
readonly runs: number;
|
|
142
|
+
readonly unknown: number;
|
|
143
|
+
readonly outsideQueue: number;
|
|
144
|
+
readonly viaQueue: number;
|
|
145
|
+
readonly rows: readonly DoorRow[];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface DoorTableOptions {
|
|
149
|
+
readonly dir?: string;
|
|
150
|
+
readonly sinceMs?: number;
|
|
151
|
+
readonly now?: number;
|
|
152
|
+
readonly list?: (dir: string) => string[];
|
|
153
|
+
readonly load?: (runId: string, dir: string) => RunLedgerEntry[] | null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
157
|
+
|
|
158
|
+
function entryTime(entry: RunLedgerEntry | undefined): number | undefined {
|
|
159
|
+
if (!entry?.timestamp) return undefined;
|
|
160
|
+
const parsed = Date.parse(entry.timestamp);
|
|
161
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** One row per door that actually fired, plus the period totals. Untagged ledgers count as `unknown`. */
|
|
165
|
+
export function queryLaunchDoors(options: DoorTableOptions = {}): DoorTable {
|
|
166
|
+
const dir = options.dir ?? runLedgerDir();
|
|
167
|
+
const now = options.now ?? Date.now();
|
|
168
|
+
const sinceMs = options.sinceMs ?? DAY_MS;
|
|
169
|
+
const cutoff = now - sinceMs;
|
|
170
|
+
const list = options.list ?? ((path: string) => {
|
|
171
|
+
try { return readdirSync(path); } catch { return []; }
|
|
172
|
+
});
|
|
173
|
+
const load = options.load ?? ((runId: string, ledgerDir: string) => {
|
|
174
|
+
try { return loadRunLedger(runId, ledgerDir); } catch { return null; }
|
|
175
|
+
});
|
|
176
|
+
const counts = new Map<LaunchDoor, { runs: number; viaQueue: number }>();
|
|
177
|
+
let runs = 0;
|
|
178
|
+
let unknown = 0;
|
|
179
|
+
let outsideQueue = 0;
|
|
180
|
+
let viaQueue = 0;
|
|
181
|
+
for (const file of list(dir)) {
|
|
182
|
+
if (!file.endsWith('.jsonl')) continue;
|
|
183
|
+
const runId = file.slice(0, -'.jsonl'.length);
|
|
184
|
+
const entries = load(runId, dir);
|
|
185
|
+
if (!entries || entries.length === 0) continue;
|
|
186
|
+
const started = entryTime(entries[0]);
|
|
187
|
+
if (started !== undefined && started < cutoff) continue;
|
|
188
|
+
const stamp = launchStampFromLedger(entries);
|
|
189
|
+
runs += 1;
|
|
190
|
+
if (stamp.entrance === 'unknown') unknown += 1;
|
|
191
|
+
if (stamp.viaQueue) viaQueue += 1;
|
|
192
|
+
else outsideQueue += 1;
|
|
193
|
+
const row = counts.get(stamp.entrance) ?? { runs: 0, viaQueue: 0 };
|
|
194
|
+
row.runs += 1;
|
|
195
|
+
if (stamp.viaQueue) row.viaQueue += 1;
|
|
196
|
+
counts.set(stamp.entrance, row);
|
|
197
|
+
}
|
|
198
|
+
const rows = [...counts.entries()]
|
|
199
|
+
.map(([entrance, row]) => ({
|
|
200
|
+
entrance,
|
|
201
|
+
runs: row.runs,
|
|
202
|
+
viaQueue: row.viaQueue,
|
|
203
|
+
outsideQueue: row.runs - row.viaQueue,
|
|
204
|
+
}))
|
|
205
|
+
.sort((left, right) => right.runs - left.runs || left.entrance.localeCompare(right.entrance));
|
|
206
|
+
return { sinceMs, runs, unknown, outsideQueue, viaQueue, rows };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function renderLaunchDoors(table: DoorTable): string {
|
|
210
|
+
const lines = [
|
|
211
|
+
`doors runs=${table.runs} outside-queue=${table.outsideQueue} via-queue=${table.viaQueue} unknown=${table.unknown}`,
|
|
212
|
+
'entrance runs via-queue outside-queue',
|
|
213
|
+
];
|
|
214
|
+
for (const row of table.rows) {
|
|
215
|
+
lines.push(`${row.entrance.padEnd(16)} ${String(row.runs).padStart(4)} ${String(row.viaQueue).padStart(9)} ${String(row.outsideQueue).padStart(13)}`);
|
|
216
|
+
}
|
|
217
|
+
if (table.rows.length === 0) lines.push('(no runs)');
|
|
218
|
+
const summed = table.rows.reduce((sum, row) => sum + row.runs, 0);
|
|
219
|
+
lines.push(`total ${String(summed).padStart(4)}`);
|
|
220
|
+
return lines.join('\n');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const SINCE_PATTERN = /^(\d+)(m|h|d)$/;
|
|
224
|
+
|
|
225
|
+
/** `--since 1d` (also `30m`, `12h`). Anything else is refused rather than guessed. */
|
|
226
|
+
export function parseDoorSince(value: string | undefined, now = Date.now()): { sinceMs: number } | { error: string } {
|
|
227
|
+
if (value === undefined || value === '') return { sinceMs: DAY_MS };
|
|
228
|
+
const match = SINCE_PATTERN.exec(value.trim());
|
|
229
|
+
if (!match) return { error: `--since 는 30m · 12h · 1d 형태여야 합니다: ${value}` };
|
|
230
|
+
const amount = Number(match[1]);
|
|
231
|
+
const unit = match[2] === 'm' ? 60_000 : match[2] === 'h' ? 3_600_000 : DAY_MS;
|
|
232
|
+
if (!Number.isSafeInteger(amount) || amount < 1) return { error: `--since 는 1 이상이어야 합니다: ${value}` };
|
|
233
|
+
const sinceMs = amount * unit;
|
|
234
|
+
if (sinceMs > now) return { sinceMs };
|
|
235
|
+
return { sinceMs };
|
|
236
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { effectiveInstanceRoot, prodInstanceRoot } from '../instance/resolve.js';
|
|
3
|
+
import { recordRunExit } from './harness-incidents.js';
|
|
4
|
+
import { appendRunLedgerEntry } from '../self-implement/run-ledger.js';
|
|
5
|
+
|
|
6
|
+
const root = prodInstanceRoot();
|
|
7
|
+
if (effectiveInstanceRoot() !== root) throw new Error('not the effective production root');
|
|
8
|
+
const before = existsSync(root) ? readdirSync(root) : null;
|
|
9
|
+
recordRunExit({ runId: 'run-test', reason: 'signal', status: 1, signal: null, at: new Date().toISOString() }, process.argv[2] ?? root);
|
|
10
|
+
appendRunLedgerEntry({ runId: 'run-test', event: 'start', data: {} }, process.argv[3] ?? `${root}/run-ledger`);
|
|
11
|
+
const after = existsSync(root) ? readdirSync(root) : null;
|
|
12
|
+
console.log(JSON.stringify({ env: { NODE_ENV: process.env.NODE_ENV, ELANOUS_TEST_HOME: process.env.ELANOUS_TEST_HOME, HOME: process.env.HOME, ELANOUS_STATE_DIR: process.env.ELANOUS_STATE_DIR }, root, effectiveInstanceRoot: effectiveInstanceRoot(), before, after }));
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { realpathSync } from 'node:fs';
|
|
2
|
+
import { dirname, isAbsolute, relative, resolve, sep } from 'node:path';
|
|
3
|
+
import { debug } from '../debug/log.js';
|
|
4
|
+
import { effectiveInstanceRoot, prodInstanceRoot } from '../instance/resolve.js';
|
|
5
|
+
|
|
6
|
+
function physicalPath(path: string): string {
|
|
7
|
+
const absolute = resolve(path);
|
|
8
|
+
try {
|
|
9
|
+
return realpathSync(absolute);
|
|
10
|
+
} catch (error) {
|
|
11
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
12
|
+
const parent = dirname(absolute);
|
|
13
|
+
if (parent === absolute) throw error;
|
|
14
|
+
return resolve(physicalPath(parent), relative(parent, absolute));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Refuse a test's write to the production instance, including an explicitly supplied ledger directory. */
|
|
19
|
+
export function refuseProductionLedgerWriteInTest(
|
|
20
|
+
root: string,
|
|
21
|
+
store: string,
|
|
22
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
23
|
+
productionRoot = prodInstanceRoot(),
|
|
24
|
+
instanceRoot = effectiveInstanceRoot(),
|
|
25
|
+
): boolean {
|
|
26
|
+
if (env.NODE_ENV !== 'test' && !env.ELANOUS_TEST_HOME) return false;
|
|
27
|
+
const target = physicalPath(root);
|
|
28
|
+
const production = physicalPath(productionRoot);
|
|
29
|
+
const withinProduction = relative(production, target);
|
|
30
|
+
if (withinProduction === '..' || withinProduction.startsWith(`..${sep}`) || isAbsolute(withinProduction)) return false;
|
|
31
|
+
try {
|
|
32
|
+
debug.log('harness.incidents', 'write-refused', { store, root: target, effectiveInstanceRoot: instanceRoot });
|
|
33
|
+
} catch { /* Observation cannot reopen a closed write boundary. */ }
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Nested elanous launch policy.
|
|
2
|
+
//
|
|
3
|
+
// Codex children drop parent env under shell_environment_policy inherit=core, so a
|
|
4
|
+
// depth marker that only lives in the environment never arrives. Depth is therefore
|
|
5
|
+
// carried on the codex argv (`-c shell_environment_policy.set.ELANOUS_NESTED_DEPTH=<n>`).
|
|
6
|
+
//
|
|
7
|
+
// Unset ELANOUS_NESTED_DEPTH is depth 0 (the outer launcher). A missing marker is
|
|
8
|
+
// not "depth unknown" and does not refuse an ordinary mission spawn.
|
|
9
|
+
// Refusal for a backend that cannot carry the marker applies only when a nested
|
|
10
|
+
// elanous launch was actually requested (allow flag or config allow).
|
|
11
|
+
//
|
|
12
|
+
// Only depth 0 (the outer launcher) may allow a nested elanous launch. At depth >= 1
|
|
13
|
+
// an allow flag is ignored and the launch stays refused.
|
|
14
|
+
|
|
15
|
+
import { debug } from '../debug/log.js';
|
|
16
|
+
|
|
17
|
+
export const ELANOUS_NESTED_DEPTH_ENV = 'ELANOUS_NESTED_DEPTH';
|
|
18
|
+
|
|
19
|
+
/** Codex config key that sets the depth marker inside the child's shell policy. */
|
|
20
|
+
export const CODEX_NESTED_DEPTH_CONFIG_KEY = `shell_environment_policy.set.${ELANOUS_NESTED_DEPTH_ENV}`;
|
|
21
|
+
|
|
22
|
+
export type NestedElanousBackendName = 'codex' | 'claude' | 'gemini' | 'grok' | 'aside' | (string & {});
|
|
23
|
+
|
|
24
|
+
export interface NestedElanousDecision {
|
|
25
|
+
readonly allowed: boolean;
|
|
26
|
+
readonly depth: number;
|
|
27
|
+
/** True when depth >= 1 supplied allow and it was ignored. */
|
|
28
|
+
readonly allowIgnored: boolean;
|
|
29
|
+
readonly reason: 'depth-0' | 'depth-0-allow' | 'depth-unknown' | 'nested-refused' | 'allow-ignored';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Current process depth from the nested-depth marker. Unset or unreadable = 0 (outer launcher). */
|
|
33
|
+
export function readNestedElanousDepth(env: Record<string, string | undefined> = process.env): number {
|
|
34
|
+
const raw = env[ELANOUS_NESTED_DEPTH_ENV];
|
|
35
|
+
if (raw === undefined || raw.trim() === '') return 0;
|
|
36
|
+
if (!/^\d+$/.test(raw.trim())) return 0;
|
|
37
|
+
return Number(raw);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** True when this backend can carry the depth marker to its child. Only codex can. */
|
|
41
|
+
export function backendCarriesNestedDepth(backendName: string): boolean {
|
|
42
|
+
return backendName === 'codex';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Codex argv fragment that sets the child's depth marker.
|
|
47
|
+
* `depth` is the child's depth (parent + 1). Non-codex backends return [] — they cannot carry it.
|
|
48
|
+
*/
|
|
49
|
+
export function nestedDepthCodexArgs(backendName: string, childDepth: number): string[] {
|
|
50
|
+
if (backendName !== 'codex') return [];
|
|
51
|
+
if (!Number.isInteger(childDepth) || childDepth < 0) return [];
|
|
52
|
+
return ['-c', `${CODEX_NESTED_DEPTH_CONFIG_KEY}=${childDepth}`];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Allow decision. Depth 0 allows only when `allow` is true.
|
|
57
|
+
* Depth >= 1 always refuses; a supplied allow is ignored and logged.
|
|
58
|
+
*/
|
|
59
|
+
export function decideNestedElanousLaunch(input: {
|
|
60
|
+
readonly depth: number;
|
|
61
|
+
readonly allow?: boolean;
|
|
62
|
+
}): NestedElanousDecision {
|
|
63
|
+
const depth = Number.isInteger(input.depth) && input.depth >= 0 ? input.depth : 0;
|
|
64
|
+
if (depth >= 1) {
|
|
65
|
+
if (input.allow === true) {
|
|
66
|
+
debug.log('agent-mission.nested', 'allow-ignored', { depth });
|
|
67
|
+
return { allowed: false, depth, allowIgnored: true, reason: 'allow-ignored' };
|
|
68
|
+
}
|
|
69
|
+
return { allowed: false, depth, allowIgnored: false, reason: 'nested-refused' };
|
|
70
|
+
}
|
|
71
|
+
if (input.allow === true) {
|
|
72
|
+
return { allowed: true, depth, allowIgnored: false, reason: 'depth-0-allow' };
|
|
73
|
+
}
|
|
74
|
+
return { allowed: false, depth, allowIgnored: false, reason: 'depth-0' };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Config `harness.nestedElanous`. Only `'allow'` counts, and only at depth 0. */
|
|
78
|
+
export function configAllowsNestedElanous(
|
|
79
|
+
config: { harness?: { nestedElanous?: string } } | undefined,
|
|
80
|
+
depth: number,
|
|
81
|
+
): boolean {
|
|
82
|
+
if (depth !== 0) return false;
|
|
83
|
+
return config?.harness?.nestedElanous === 'allow';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Gate a nested elanous launch.
|
|
88
|
+
*
|
|
89
|
+
* Ordinary mission spawn is not this gate: call it only when a nested elanous
|
|
90
|
+
* launch was requested. A backend that cannot carry the depth marker is then
|
|
91
|
+
* refused (`depth-unknown`) — including depth 0 with allow — because the child
|
|
92
|
+
* would not know its own depth. Unset depth is 0, not unknown.
|
|
93
|
+
*
|
|
94
|
+
* `allow` is the flag. Config allow counts only at depth 0 and never overrides
|
|
95
|
+
* a depth >= 1 refusal.
|
|
96
|
+
*/
|
|
97
|
+
export function gateNestedElanousLaunch(input: {
|
|
98
|
+
readonly backendName: string;
|
|
99
|
+
readonly depth: number;
|
|
100
|
+
/** True only when this call is gating a requested nested elanous launch. */
|
|
101
|
+
readonly nestedLaunchRequested: boolean;
|
|
102
|
+
readonly allow?: boolean;
|
|
103
|
+
readonly configAllow?: boolean;
|
|
104
|
+
}): NestedElanousDecision {
|
|
105
|
+
const depth = Number.isInteger(input.depth) && input.depth >= 0 ? input.depth : 0;
|
|
106
|
+
if (!input.nestedLaunchRequested) {
|
|
107
|
+
return decideNestedElanousLaunch({ depth, allow: false });
|
|
108
|
+
}
|
|
109
|
+
if (!backendCarriesNestedDepth(input.backendName)) {
|
|
110
|
+
return { allowed: false, depth, allowIgnored: false, reason: 'depth-unknown' };
|
|
111
|
+
}
|
|
112
|
+
const allow = input.allow === true || (depth === 0 && input.configAllow === true);
|
|
113
|
+
return decideNestedElanousLaunch({ depth, allow });
|
|
114
|
+
}
|