elanous 0.2.17 → 0.2.19
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/J5H7BvqgSI8DUjtRRcA_S/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1157-cba0e8553d35a980.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{726.83532d69452ee24b.js → 1470.98a6ab61ccb4131c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/171-23a877ed11720de9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2336-e830c777eb6bce04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9247-269e4464cc4b491c.js → 2375-e061bb24cd2b5cde.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{211-9c9090c59e2c5907.js → 2977-e38442e86289f1d3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{f6b7c9ba.64a895b8b2fedbdc.js → 2ea96958.e8c7e112de50b067.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3094.67339318c7f72a3a.js +2 -0
- package/apps/pwa/out/_next/static/chunks/338-ceb956612dcfaa86.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3395-835e4ceaa6335ba4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +5 -0
- package/apps/pwa/out/_next/static/chunks/3441-be82e3595aef3a20.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3689-89905039bf3e7257.js +1 -0
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3822-6f268bc26c6a84ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1268.0840ff91e083fcbd.js → 4323.c5dfb6f503304775.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4457-d7df81c90f9d0268.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4551-77dadae76f6cbace.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6110.1297d4093fdfbb0d.js → 4847.56f5bc454fbcc827.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/4978.6fd93b974441e87f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5012-f746f6549b869f91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7327-2d69fb321ba73389.js → 6062-73e68343a9814f6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/62-5bfed1e23790b745.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6320-9bc8561fde416e07.js +1 -0
- package/apps/pwa/out/_next/static/chunks/649-ca96d1c5d09e02b8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6726-85a7db59b288c53f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6735-33267c4630d0d963.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7633-141682015873beb6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{935.4ddaa99fc1d1126c.js → 7647.2fcd30d894d5ee30.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{f6231284-45726550aba08fca.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8067-143791231c99bd02.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8430-a10cfba8ef282d4e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8870-9614a213b990b10b.js +25 -0
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-12c2f30dea62291c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/976-4a9b4ebdca7c739a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9989-15cdf568bafa9fc8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/ae787e4c-e5b1ef00b2dbceca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-deef5ee191830f14.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-b193da485f9a14b2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-b6e1a79df0e214f4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-e84df4d907f703c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a5fb9a5db89ae4ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1499f481d7fc52d6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-c7cf753aeb648790.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-4118469e514e10fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-e968f7630654332a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-505915052f947022.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-c2f962fb4a3b06c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7b5ed0bd8a589ec4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-cf8193250dd42dd4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-cf3f5dfa35dea8e9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-5b29d9b06ceadade.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a15e7fcc283e9ca7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-d2915e7b70ff3d0e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-b70cd005d9169e25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-2595f0eba29f57e1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-c776322454156e36.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-77bbd17f4435aa10.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-049689e611ecc936.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-85954a4fb09c44eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-e773e552e1068ee6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5633c4bfa7684b4a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d829b92bd01b4994.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-061c317fc771becf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a45ece8865ed4b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-0be990618c0d56bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-bfea4865dc244b73.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d6ba8e337741484.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-76a88b55d816482e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-bba1542df59f61ce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4185c95689bb372e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b699e1a8d5d34b8b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-c225e338c31bf274.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-449353f70507ca1f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-90b889714102476d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-f1ee7fbeb7f96ddc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-d59a95e1434dfaa0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-71491a23414d1a47.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-de7112bdb65fa4fe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-feb4d0d88079660d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f17d887b40dd6b1d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-8da77be2192bcd5d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-72c3b9a5e9582b29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3afe4c8d0c48c39.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-b9308f2d8a931531.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-6f35f9d892fa630b.js +1 -0
- package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
- package/apps/pwa/out/_next/static/css/85f53c6f35d11382.css +1 -0
- package/apps/pwa/out/_next/static/css/d3427996da11e480.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +23 -23
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +23 -23
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +23 -23
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +22 -22
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +22 -22
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +23 -23
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +22 -22
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +23 -23
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +23 -23
- package/apps/pwa/out/decisions/index.html +1 -0
- package/apps/pwa/out/decisions/index.txt +30 -0
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +25 -24
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +23 -23
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +23 -23
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +22 -22
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +24 -23
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +23 -23
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +23 -23
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +28 -27
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +22 -22
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +22 -22
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +23 -23
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +23 -23
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +22 -22
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +22 -22
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +22 -22
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +23 -23
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +23 -23
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +22 -22
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +23 -23
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +23 -23
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +23 -23
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +23 -23
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +23 -23
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +22 -22
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +23 -23
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +23 -23
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +23 -23
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +23 -23
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +23 -23
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +23 -23
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +23 -23
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +23 -23
- package/graphs/ops/guardian.yaml +26 -0
- package/graphs/ops/recipes.yaml +3 -0
- package/package.json +2 -1
- package/samples/workflows/templates/approval-gated-deploy.yaml +30 -0
- package/samples/workflows/templates/chat-driven-research.yaml +35 -0
- package/samples/workflows/templates/discord-listen-and-respond.yaml +29 -0
- package/samples/workflows/templates/http-poll-then-alert.yaml +30 -0
- package/samples/workflows/templates/manual-quickstart.yaml +21 -0
- package/samples/workflows/templates/scheduled-daily-digest.yaml +30 -0
- package/samples/workflows/templates/webhook-deploy-hook.yaml +27 -0
- package/scripts/ci-public-export-import-gate.ts +7 -2
- package/scripts/ci-typecheck-changed.ts +21 -6
- package/scripts/coord-channel-watch.sh +2 -2
- package/scripts/discord-seat-probe.ts +6 -3
- package/scripts/docs/generate-cli-anatomy.ts +162 -0
- package/scripts/goals/authoring-metrics.ts +73 -0
- package/scripts/landing-heal/must-fix-match.ts +7 -7
- package/scripts/landing-heal/run-mission.ts +3 -2
- package/scripts/lib/pch-regress-map.ts +23 -0
- package/scripts/morning-report.ts +18 -4
- package/scripts/pch-regress.ts +88 -0
- package/scripts/public-export-test-run.ts +37 -2
- package/scripts/release-loop/checklist-gate-node.ts +13 -11
- package/scripts/release-loop/cut-branch.ts +2 -1
- package/scripts/release-loop/cutoff.ts +4 -2
- package/scripts/release-loop/docs-land-node.ts +8 -1
- package/scripts/release-loop/docs-node.ts +1 -1
- package/scripts/release-loop/gate-diff.ts +18 -0
- package/scripts/release-loop/gate-node.ts +436 -59
- package/scripts/release-loop/gate-partial.ts +106 -0
- package/scripts/release-loop/gate-timing.ts +120 -0
- package/scripts/release-loop/node-verdict.ts +2 -1
- package/scripts/release-loop/npm-publish-node.ts +11 -7
- package/scripts/release-loop/ops-upgrade-node.ts +8 -1
- package/scripts/release-loop/preflight.ts +246 -0
- package/scripts/release-loop/publish-node.ts +8 -1
- package/scripts/release-loop/release-readiness.ts +11 -6
- package/scripts/release-loop/release-version.ts +48 -0
- package/scripts/release-loop/resume-release.ts +174 -0
- package/scripts/release-loop/shard-plan.ts +65 -0
- package/scripts/release-loop/unattended-release.ts +45 -7
- package/scripts/release-loop/version-node.ts +145 -40
- package/scripts/rhythm/daily.ts +25 -4
- package/scripts/skills/public-check.ts +62 -12
- package/scripts/test-deterministic.ts +51 -33
- package/scripts/test-diet/effectiveness.ts +62 -1
- package/scripts/test-diet/lib.ts +1 -1
- package/scripts/test-diet/node.ts +8 -1
- package/scripts/video/scene-table.ts +159 -0
- package/src/acp/core-turn-bridge.ts +39 -2
- package/src/agent-mission/review-loop.ts +4 -3
- package/src/agent-substrate/pr-comment-meta.ts +20 -0
- package/src/autopilot/build/llm-conflict-merge.ts +28 -8
- package/src/away/presence.ts +40 -0
- package/src/away/release-watch.ts +255 -0
- package/src/briefing/brief-items.ts +86 -17
- package/src/chat/index.ts +2 -1
- package/src/chat/short-question.ts +3 -40
- package/src/cli/autopilot-cli.ts +30 -11
- package/src/cli/away-cli.ts +68 -0
- package/src/cli/brief-cli.ts +36 -2
- package/src/cli/freeze-cli.ts +3 -2
- package/src/cli/guardian-cli.ts +49 -0
- package/src/cli/intake-cli.ts +85 -7
- package/src/cli/log-event-names.ts +1 -0
- package/src/cli/notify-cli.ts +75 -0
- package/src/cli/pod-cli.ts +5 -1
- package/src/cli/provider-cli.ts +12 -2
- package/src/cli/pty-drive-cli.ts +14 -3
- package/src/cli/release-cli.ts +128 -19
- package/src/cli/setup-cli.ts +31 -1
- package/src/cli/tasks-cli.ts +403 -4
- package/src/context-bus/condense.ts +19 -3
- package/src/context-bus/context-day.ts +77 -24
- package/src/context-bus/context-first.ts +33 -0
- package/src/context-bus/context-now-public.ts +1 -1
- package/src/context-bus/context-now-surfaces.ts +16 -6
- package/src/context-bus/context-now.ts +97 -3
- package/src/core-turn/short-question.ts +100 -0
- package/src/dashboard/dashboard-status-lines.ts +24 -4
- package/src/dashboard/index.ts +50 -5
- package/src/dashboard/input/slash-executor.ts +6 -2
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +63 -36
- package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +21 -8
- package/src/dashboard/slash-runtime/help-groups.ts +44 -0
- package/src/dashboard/slash-runtime/model-choices.ts +125 -0
- package/src/discord-self-message.ts +20 -2
- package/src/discord-slash-wire.ts +1 -0
- package/src/domains/guardian-tick.ts +138 -0
- package/src/domains/outbound-alert.ts +244 -40
- package/src/domains/outbound-log-probe.ts +16 -1
- package/src/domains/release-tool.ts +2 -2
- package/src/domains/schedule-registry.ts +23 -0
- package/src/domains/telegram-kind-route.ts +39 -0
- package/src/domains/watch-default-scheduler.ts +95 -0
- package/src/esc-abort-gate.ts +44 -0
- package/src/execution-loop/gates.ts +11 -2
- package/src/flow-loop/split.ts +8 -4
- package/src/git-fs/worktree.ts +23 -3
- package/src/graph-runner/runner.ts +25 -5
- package/src/harness/harness-cli-command.ts +12 -2
- package/src/harness/harness-queue.ts +388 -33
- package/src/harness/harness-salvage-cli.ts +78 -0
- package/src/harness/harness-worktree-add.ts +68 -0
- package/src/harness/helper-scan.ts +10 -3
- package/src/harness/ledger-write-guard.ts +6 -16
- package/src/hq/hq.ts +6 -1
- package/src/hq/seats.ts +369 -0
- package/src/index.ts +185 -18
- package/src/instance/test-write-guard-spy-first.fixture.ts +13 -0
- package/src/instance/test-write-guard.ts +200 -0
- package/src/intake-plane/adopt-gate.ts +49 -0
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/collect-github.ts +57 -25
- package/src/intake-plane/digest.ts +39 -15
- package/src/intake-plane/discord-seat-work.ts +8 -2
- package/src/intake-plane/harness-target.ts +4 -3
- package/src/intake-plane/intake-sources.ts +1 -0
- package/src/intake-plane/items.ts +1 -0
- package/src/intake-plane/requirement-funnel.ts +316 -0
- package/src/knowledge/know-find.ts +10 -1
- package/src/loops/budget.ts +36 -0
- package/src/loops/orchestrator/degradation.ts +57 -0
- package/src/loops/orchestrator/finish-rate.ts +106 -4
- package/src/loops/orchestrator/grid-place.ts +11 -0
- package/src/loops/orchestrator/tick.ts +450 -54
- package/src/maturity/feature-maturity.ts +8 -5
- package/src/maturity/tui-slash-maturity.ts +1 -0
- package/src/mss/logging/log-store.ts +3 -0
- package/src/nexus/api/decisions-route.ts +11 -1
- package/src/nexus/api/http-server.ts +16 -5
- package/src/nexus/api/loop-edges.ts +352 -23
- package/src/nexus/api/ops-api.ts +96 -28
- package/src/nexus/api/ops-seats-sources.ts +2 -2
- package/src/nexus/api/workflow-templates.ts +3 -0
- package/src/nexus/index.ts +7 -0
- package/src/oauth/codex.ts +38 -0
- package/src/onboarding/first-chat-gate.ts +29 -0
- package/src/release-loop/checklist.ts +106 -6
- package/src/release-loop/feature-store.ts +64 -16
- package/src/release-loop/landing-freeze.ts +6 -4
- package/src/release-loop/light-rc.ts +173 -0
- package/src/release-loop/placement.ts +64 -20
- package/src/release-loop/rubric.ts +136 -0
- package/src/seat-dispatch/seat-ask.ts +79 -16
- package/src/seat-loop/seat-loop.ts +12 -6
- package/src/self-dev/ask-launch-flow.ts +24 -3
- package/src/self-dev/ask-path-candidates.ts +68 -0
- package/src/self-dev/dev-cli.ts +36 -0
- package/src/self-dev/dev-pipeline.ts +4 -0
- package/src/self-dev/draft-triage-rules.ts +1 -1
- package/src/self-dev/orchestrate-cli.ts +22 -0
- package/src/self-dev/orchestrate.ts +1 -0
- package/src/self-dev/pr-preview.ts +179 -0
- package/src/self-dev/round-pr-supersede.ts +244 -0
- package/src/self-dev/run-supervisor.ts +14 -0
- package/src/self-dev/shard-run.ts +23 -7
- package/src/self-implement/follow-up-goals.ts +2 -2
- package/src/self-implement/gate-cli.ts +29 -8
- package/src/self-implement/gate-remote.ts +427 -0
- package/src/self-implement/goal-author-clarification.ts +88 -13
- package/src/self-implement/goal-author.ts +8 -3
- package/src/self-implement/goal-clarification-escalation.ts +2 -0
- package/src/self-implement/l8-merge-queue.ts +29 -5
- package/src/self-implement/orchestrator.ts +61 -13
- package/src/self-implement/prior-draft-findings.ts +4 -3
- package/src/self-implement/salvage-branches.ts +254 -0
- package/src/skills/skill-feedback.ts +76 -0
- package/src/task-agent/actions.ts +101 -7
- package/src/task-agent/board.ts +132 -0
- package/src/task-agent/card-evidence.ts +203 -0
- package/src/task-agent/completion-evidence.ts +126 -0
- package/src/task-agent/judge.ts +49 -5
- package/src/task-agent/live-moves.ts +176 -0
- package/src/task-agent/mission.ts +449 -0
- package/src/task-agent/shadow.ts +258 -0
- package/src/task-agent/task-hand.ts +337 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +64 -4
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +10 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +142 -13
- package/src/task-orchestrator/surfaces/pod-pool.ts +13 -5
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +88 -20
- package/src/telegram-away-command.ts +38 -0
- package/src/telegram-commands.ts +11 -0
- package/src/telegram-loops-command.ts +1 -1
- package/src/telegram-report-log-probe.ts +21 -0
- package/src/telegram-report.ts +27 -3
- package/src/telegram.ts +5 -20
- package/src/tui-client/dashboard-session.ts +1 -0
- package/src/user-config.ts +196 -4
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-aac7332576c4d6fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +0 -1
- package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4614-21403aac730d4586.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +0 -2
- package/apps/pwa/out/_next/static/chunks/6877-fe94e76a5841ee9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8401-4ce1c42c8d89903f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
- package/apps/pwa/out/_next/static/chunks/8949-ecb4128a0858b911.js +0 -1
- package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +0 -1
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-3143b07a817d1da9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-17ba086ef82d339b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-7eb661ff23af7da2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-6e5c3a867557298b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-7110f732f45bf944.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-9a41d65355fc25b9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-5e77cd1802567c89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-41afec0b255ed2bb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-4187666132fc98c4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-09328d499162bae4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-29ff3a8f84f03b6a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-80ec7401bc83f511.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ab93b240f687d69a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-fc7d86467291ae38.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-de43a186e5ba5683.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-8b08cafad27759c9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-0e2d6985fb42cd2f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-13e4108999f58d13.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e8296ee284aa1293.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-043a9111e74f6b24.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-23a4601ceb7f452e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-10d958605a77b3bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-166745e51ac0c7b0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a04260b8ccc968d4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-7a1d85e121065cc8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-12916918988ac97e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d8a3347a9d0c3f4a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-5a64cb5208c6a8fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-1407ec83ea64c1f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-1d824510df025d18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-6e878ebbe1737d00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-7f49fe645d5132f7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-81a4e7f8dbfe74cb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-27d71f131acd9320.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-424659bacfb8cdd7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-fc2df261f8a2c7df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-09461366af82ef13.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5d4fef9ccd7d406b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ea90a8ac73ce2749.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +0 -1
- package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +0 -1
- package/apps/pwa/out/_next/static/yRA5ur6DcPl0YHSRheH2O/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{yRA5ur6DcPl0YHSRheH2O → J5H7BvqgSI8DUjtRRcA_S}/_ssgManifest.js +0 -0
|
@@ -37,6 +37,54 @@ export const POD_LITE_IMAGE = 'elanous-harness-lite:local';
|
|
|
37
37
|
export const POD_LITE_MEMORY_LIMIT = '2Gi';
|
|
38
38
|
|
|
39
39
|
/** 요청은 한도를 넘을 수 없다(쿠버네티스가 Job 을 거부한다) — lite 2Gi 처럼 한도가 기본 요청보다 작으면 한도로 맞춘다. */
|
|
40
|
+
/** The command Job container's CPU limit when the caller names none (GATE-SPEED A3①: unchanged default). */
|
|
41
|
+
export const POD_COMMAND_CPU_LIMIT_DEFAULT = '4';
|
|
42
|
+
|
|
43
|
+
/** Container CPU request/limit override. Omitted fields keep the defaults (request `POD_CHILD_REQUESTS.cpu` · limit 4). */
|
|
44
|
+
export interface PodCpu { request?: string; limit?: string }
|
|
45
|
+
|
|
46
|
+
/** Kubernetes CPU quantity → millicores (`1` · `0.5` · `500m`); null when it is not a positive quantity we accept. */
|
|
47
|
+
export function podCpuMillis(value: string): number | null {
|
|
48
|
+
const m = /^(?:(\d+(?:\.\d{1,3})?)|(\d+)m)$/.exec(value.trim());
|
|
49
|
+
if (!m) return null;
|
|
50
|
+
const millis = m[1] !== undefined ? Math.round(Number(m[1]) * 1000) : Number(m[2]);
|
|
51
|
+
return Number.isSafeInteger(millis) && millis > 0 ? millis : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Resolve a CPU override against the defaults; throws when a value is malformed or the request exceeds the limit (k8s would refuse the Job). */
|
|
55
|
+
export function resolvePodCpu(cpu: PodCpu | undefined): { request: string; limit: string } {
|
|
56
|
+
const request = cpu?.request?.trim() || POD_CHILD_REQUESTS.cpu;
|
|
57
|
+
const limit = cpu?.limit?.trim() || POD_COMMAND_CPU_LIMIT_DEFAULT;
|
|
58
|
+
const r = podCpuMillis(request), l = podCpuMillis(limit);
|
|
59
|
+
if (r === null) throw new Error(`pod cpu request is not a positive CPU quantity: ${request}`);
|
|
60
|
+
if (l === null) throw new Error(`pod cpu limit is not a positive CPU quantity: ${limit}`);
|
|
61
|
+
if (r > l) throw new Error(`pod cpu request ${request} exceeds limit ${limit}`);
|
|
62
|
+
return { request, limit };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Config value → CPU override (release gate `release.loop.gatePodCpu`). A scalar (`2` · `"1500m"`) sets request = limit;
|
|
66
|
+
* `{ request, limit }` sets either side. Absent/empty = undefined (request 1 / limit 4). Malformed values throw. */
|
|
67
|
+
export function parsePodCpu(value: unknown, label = 'cpu'): PodCpu | undefined {
|
|
68
|
+
const text = (v: unknown): string | undefined => {
|
|
69
|
+
if (v === undefined || v === null || v === '') return undefined;
|
|
70
|
+
if (typeof v !== 'number' && typeof v !== 'string') throw new Error(`${label} must be a CPU quantity or { request, limit }`);
|
|
71
|
+
return String(v).trim() || undefined;
|
|
72
|
+
};
|
|
73
|
+
let cpu: PodCpu;
|
|
74
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
75
|
+
const { request, limit, ...rest } = value as Record<string, unknown>;
|
|
76
|
+
if (Object.keys(rest).length) throw new Error(`${label} has unknown keys: ${Object.keys(rest).join(', ')}`);
|
|
77
|
+
const r = text(request), l = text(limit);
|
|
78
|
+
cpu = { ...(r ? { request: r } : {}), ...(l ? { limit: l } : {}) };
|
|
79
|
+
} else {
|
|
80
|
+
const both = text(value);
|
|
81
|
+
cpu = both ? { request: both, limit: both } : {};
|
|
82
|
+
}
|
|
83
|
+
if (!cpu.request && !cpu.limit) return undefined;
|
|
84
|
+
resolvePodCpu(cpu);
|
|
85
|
+
return cpu;
|
|
86
|
+
}
|
|
87
|
+
|
|
40
88
|
export function memoryRequestWithin(limit: string | undefined): string {
|
|
41
89
|
const gi = (value: string | undefined) => { const m = /^(\d+)Gi$/.exec(value ?? ''); return m ? Number(m[1]) : null; };
|
|
42
90
|
const request = gi(POD_CHILD_REQUESTS.memory);
|
|
@@ -130,6 +178,8 @@ export interface PodCommandJobInput {
|
|
|
130
178
|
bunCache?: string;
|
|
131
179
|
/** 컨테이너 메모리 한도(기본 16Gi) — 게이트가 파일 하나 격리 Job 에 올린다. */
|
|
132
180
|
memoryLimit?: string;
|
|
181
|
+
/** GATE-SPEED A3①: CPU request/limit override (release gate shards only). Omitted = request 1 / limit 4. */
|
|
182
|
+
cpu?: PodCpu;
|
|
133
183
|
source?: PodSource;
|
|
134
184
|
deadlineSeconds: number;
|
|
135
185
|
runId?: string;
|
|
@@ -147,6 +197,7 @@ export function podCommandJobManifest(o: PodCommandJobInput): Record<string, unk
|
|
|
147
197
|
}
|
|
148
198
|
const grok = o.llm === 'grok';
|
|
149
199
|
const bunCache = o.bunCache ? podBunCacheVolume(o.bunCache) : undefined;
|
|
200
|
+
const cpu = resolvePodCpu(o.cpu);
|
|
150
201
|
const quotedArgs = o.command.map(bashSingleQuote).join(' ');
|
|
151
202
|
const credLines = [
|
|
152
203
|
...(grok ? ['mkdir -p ~/.grok && install -m 600 /creds/grok-auth.json ~/.grok/auth.json'] : []),
|
|
@@ -185,7 +236,7 @@ export function podCommandJobManifest(o: PodCommandJobInput): Record<string, unk
|
|
|
185
236
|
initContainers: [{ name: 'isolation-gate', image: o.image, imagePullPolicy: o.imagePullPolicy ?? 'Never', command: ['bash', '-c'], args: [GATE] }],
|
|
186
237
|
containers: [{
|
|
187
238
|
name: 'child', image: o.image, imagePullPolicy: o.imagePullPolicy ?? 'Never',
|
|
188
|
-
resources: { requests: { ...POD_CHILD_REQUESTS, memory: memoryRequestWithin(o.memoryLimit) }, limits: { memory: o.memoryLimit ?? '16Gi', cpu:
|
|
239
|
+
resources: { requests: { ...POD_CHILD_REQUESTS, cpu: cpu.request, memory: memoryRequestWithin(o.memoryLimit) }, limits: { memory: o.memoryLimit ?? '16Gi', cpu: cpu.limit } },
|
|
189
240
|
command: ['bash', '-c'], args: [script],
|
|
190
241
|
env: [
|
|
191
242
|
...(o.runId ? [{ name: 'ELANOUS_RUN_ID', value: o.runId }] : []),
|
|
@@ -272,6 +323,8 @@ export interface RunPodCommandOptions {
|
|
|
272
323
|
bunCache?: string;
|
|
273
324
|
/** 컨테이너 메모리 한도(기본 16Gi · lite 면 2Gi). */
|
|
274
325
|
memoryLimit?: string;
|
|
326
|
+
/** GATE-SPEED A3①: CPU request/limit override — only the release gate passes it. Omitted = request 1 / limit 4. */
|
|
327
|
+
cpu?: PodCpu;
|
|
275
328
|
/** POD7: 호출부가 «네트워크 스킬만 쓰는 명령»이라고 말할 때만 lite 이미지 ⊕ 2Gi. */
|
|
276
329
|
lite?: boolean;
|
|
277
330
|
source?: PodSource;
|
|
@@ -284,6 +337,7 @@ export interface RunPodCommandOptions {
|
|
|
284
337
|
name?: string;
|
|
285
338
|
pollMs?: number;
|
|
286
339
|
kubectl?: Kubectl;
|
|
340
|
+
/** A caller-owned scheduler is self-capped: acquires count only its own Jobs, never cluster occupancy (POOLGLOBAL-REG). */
|
|
287
341
|
poolScheduler?: PodPoolScheduler;
|
|
288
342
|
checkPool?: typeof checkPodPool;
|
|
289
343
|
syncImages?: typeof syncPoolImages;
|
|
@@ -356,6 +410,8 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
356
410
|
if (hostMirror && !isAbsolute(hostMirror)) throw new Error('pod.hostMirror must be an absolute directory path');
|
|
357
411
|
const bunCache = options.bunCache?.trim() || undefined;
|
|
358
412
|
if (bunCache && !isAbsolute(bunCache)) throw new Error('pod.bunCache must be an absolute directory path');
|
|
413
|
+
// Fail before any Secret/Job is applied — a bad quantity must not reach the cluster.
|
|
414
|
+
if (options.cpu) resolvePodCpu(options.cpu);
|
|
359
415
|
|
|
360
416
|
let member: PodPoolMember | null = null;
|
|
361
417
|
let pool = options.poolScheduler;
|
|
@@ -366,12 +422,16 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
366
422
|
const members = parsePodPool(spec);
|
|
367
423
|
const checked = (options.checkPool ?? checkPodPool)(members, baseKubectl as PoolKubectl);
|
|
368
424
|
if (!checked.ok) throw new Error('pod command: 풀의 노드가 하나도 준비되지 않았다');
|
|
369
|
-
|
|
425
|
+
// Occupancy reads go through the same kubectl as the readiness check (an injected one included).
|
|
426
|
+
pool = new PodPoolScheduler(checked.ready, { kubectl: baseKubectl as PoolKubectl });
|
|
370
427
|
}
|
|
371
428
|
}
|
|
372
429
|
if (pool) {
|
|
430
|
+
// POOLGLOBAL-REG: a caller that hands in its own scheduler (the release gate fans its shards over one) holds its own
|
|
431
|
+
// concurrency cap — its Jobs count against the spec caps, not the cluster's goal Pods (#24253 → 0.2.15 gate ran 1 shard).
|
|
432
|
+
const selfCapped = options.poolScheduler !== undefined;
|
|
373
433
|
for (;;) {
|
|
374
|
-
member = await pool.tryAcquire();
|
|
434
|
+
member = await pool.tryAcquire(undefined, undefined, { selfCapped });
|
|
375
435
|
if (member) break;
|
|
376
436
|
await sleep(options.pollMs ?? 15_000);
|
|
377
437
|
}
|
|
@@ -426,7 +486,7 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
426
486
|
name, namespace, launch, image: imageRef ?? image,
|
|
427
487
|
...(imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}),
|
|
428
488
|
repoUrl, command, skills, ...(llm ? { llm } : {}), ...(options.clone ? { clone: true } : {}),
|
|
429
|
-
...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), ...(bunCache ? { bunCache } : {}), ...(memoryLimit ? { memoryLimit } : {}), deadlineSeconds,
|
|
489
|
+
...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), ...(bunCache ? { bunCache } : {}), ...(memoryLimit ? { memoryLimit } : {}), ...(options.cpu ? { cpu: options.cpu } : {}), deadlineSeconds,
|
|
430
490
|
...(options.runId ? { runId: options.runId } : {}),
|
|
431
491
|
});
|
|
432
492
|
kubectl(['-n', namespace, 'delete', 'job', '-l', own, '--ignore-not-found']);
|
|
@@ -33,6 +33,8 @@ export function extractPodFailureReason(input: {
|
|
|
33
33
|
containerReason?: string | null;
|
|
34
34
|
jobReason?: string;
|
|
35
35
|
deadlineSeconds?: number;
|
|
36
|
+
/** POD-NORESULT: the stage and PR from the child's parsed result row (disposition) — used only when no log line gives a readable reason. */
|
|
37
|
+
result?: { stage?: string | null; prUrl?: string | null; prNumber?: number | null } | null;
|
|
36
38
|
}): string {
|
|
37
39
|
if (input.jobReason === 'DeadlineExceeded') {
|
|
38
40
|
return `DeadlineExceeded: Job 수명 상한 ${input.deadlineSeconds ?? 'unknown'}초 초과`;
|
|
@@ -72,6 +74,14 @@ export function extractPodFailureReason(input: {
|
|
|
72
74
|
const errorLine = [...meaningful].reverse().find((line) => line.terminal || isError(line.text));
|
|
73
75
|
const reason = errorLine?.text.replace(/\/home\/[^/\s]+\//gu, '~/').slice(0, 240);
|
|
74
76
|
if (reason) return reason;
|
|
77
|
+
// POD-NORESULT: an unconverged child (exit 2 → BackoffLimitExceeded under backoffLimit 0) often leaves a result row
|
|
78
|
+
// with a stage and PR but no error text — report that instead of «unreadable».
|
|
79
|
+
const stage = input.result?.stage?.trim();
|
|
80
|
+
if (stage) {
|
|
81
|
+
const prNumber = input.result?.prNumber;
|
|
82
|
+
const pr = Number.isSafeInteger(prNumber) && prNumber! > 0 ? `PR #${prNumber}` : input.result?.prUrl ? `PR ${input.result.prUrl}` : '';
|
|
83
|
+
return pr ? `수확 가능(${stage}) · ${pr}` : `수렴 못 함(단계 ${stage})`;
|
|
84
|
+
}
|
|
75
85
|
const why = input.logTailReason?.trim() || (input.logs.trim() ? '로그에 읽을 수 있는 오류 줄 없음' : '자식 로그 비어 있음');
|
|
76
86
|
return `사유 못 읽음: ${why.replace(/\/home\/[^/\s]+\//gu, '~/').slice(0, 240)}`;
|
|
77
87
|
}
|
|
@@ -7,6 +7,13 @@ import { join } from 'node:path';
|
|
|
7
7
|
import { dlopen } from 'bun:ffi';
|
|
8
8
|
import { hostLeaseBaseDir, type PendingJobIdentity } from '../../pod-lease/host-lease.js';
|
|
9
9
|
|
|
10
|
+
/** The harness Pod's container requests (self-implement-pod Job manifest). Lives here so admission reuses the real request. */
|
|
11
|
+
export const POD_CHILD_REQUESTS = { cpu: '1', memory: '6Gi' } as const;
|
|
12
|
+
/** POD-ADMIT-BY-USAGE — observed usage is scaled by this before it reserves memory. */
|
|
13
|
+
const POD_USAGE_HEADROOM = 1.5;
|
|
14
|
+
/** POD-ADMIT-BY-USAGE — a node keeps at least this share of allocatable memory free (real usage and bookkeeping). */
|
|
15
|
+
const POD_NODE_FREE_FLOOR_RATIO = 0.1;
|
|
16
|
+
|
|
10
17
|
/** Persisted on a Job admitted through the host lease; the file lease remains until its Pod is observed Running or terminal. */
|
|
11
18
|
export const POD_HOST_LEASE_ANNOTATION = 'elanous.dev/host-lease-admitted';
|
|
12
19
|
|
|
@@ -39,6 +46,27 @@ export interface PodLeaseMember {
|
|
|
39
46
|
/** Node-by-node memory left after all assigned Pod requests and harness Running limits. */
|
|
40
47
|
availableMemoryByNodeBytes: number[] | null;
|
|
41
48
|
reason: string | null;
|
|
49
|
+
/**
|
|
50
|
+
* POD-ADMIT-BY-USAGE (only when measured with `measureUsage`): observed memory of Running harness Pods
|
|
51
|
+
* (`kubectl top pods`). null = metrics unreadable (see usageReason) — every Pod is then reserved at its limit, as before.
|
|
52
|
+
*/
|
|
53
|
+
harnessUsageBytes?: number[] | null;
|
|
54
|
+
usageReason?: string | null;
|
|
55
|
+
/** Pool-wide harness reservation: Σ min(limit, max(request, usage×1.5)), or max(request, limit) when the Pod's usage is unmeasured. */
|
|
56
|
+
memoryReservedBytes?: number | null;
|
|
57
|
+
/** Parallel to availableMemoryByNodeBytes: each schedulable node's allocatable memory. */
|
|
58
|
+
allocatableMemoryByNodeBytes?: number[] | null;
|
|
59
|
+
/** Parallel to availableMemoryByNodeBytes: each node's real memory use (`kubectl top nodes`), null when unmeasured. */
|
|
60
|
+
usedMemoryByNodeBytes?: Array<number | null> | null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** POD-ADMIT-BY-USAGE — how much memory one new goal is counted at, and why. */
|
|
64
|
+
export interface PodAdmissionByUsage {
|
|
65
|
+
samples: number;
|
|
66
|
+
p95Bytes: number | null;
|
|
67
|
+
admitBytes: number;
|
|
68
|
+
/** Why admission stayed on the conservative per-goal size; null when usage drove it. */
|
|
69
|
+
fallback: string | null;
|
|
42
70
|
}
|
|
43
71
|
|
|
44
72
|
export interface PoolLeaseMeasure {
|
|
@@ -61,6 +89,28 @@ export interface PoolLeaseRecommendation {
|
|
|
61
89
|
pendingJobs?: PendingJobIdentity[];
|
|
62
90
|
/** Absent only for injected legacy status fixtures; real measurements always report a number or null. */
|
|
63
91
|
unleasedRunning?: number | null;
|
|
92
|
+
/** POD-ADMIT-BY-USAGE — the per-goal size this recommendation used. */
|
|
93
|
+
admission?: PodAdmissionByUsage;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** `kubectl top … --no-headers` → name → memory bytes; null when any line cannot be read (fail conservative). */
|
|
97
|
+
function topMemory(stdout: string, column: number): Map<string, number> | null {
|
|
98
|
+
const out = new Map<string, number>();
|
|
99
|
+
for (const line of stdout.split('\n')) {
|
|
100
|
+
if (!line.trim()) continue;
|
|
101
|
+
const cols = line.trim().split(/\s+/u);
|
|
102
|
+
const bytes = cols.length > column ? quantity(cols[column]!, 'memory') : null;
|
|
103
|
+
if (bytes === null || out.has(cols[0]!)) return null;
|
|
104
|
+
out.set(cols[0]!, bytes);
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Nearest-rank p95. */
|
|
110
|
+
function p95(values: readonly number[]): number | null {
|
|
111
|
+
if (!values.length) return null;
|
|
112
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
113
|
+
return sorted[Math.max(0, Math.ceil(sorted.length * 0.95) - 1)]!;
|
|
64
114
|
}
|
|
65
115
|
|
|
66
116
|
/** Kubernetes quantity to bytes (memory) or millicores (CPU). */
|
|
@@ -238,7 +288,7 @@ function runPoolDnsProbe(context: string, kubectl: PoolKubectl): PoolDnsProbe {
|
|
|
238
288
|
}
|
|
239
289
|
|
|
240
290
|
/** Each cluster reads resource reservations; DNS requires a bounded disposable Pod. */
|
|
241
|
-
export function measurePoolLease(members: readonly PodPoolMember[], deps: { kubectl?: PoolKubectl; dns?: (context: string) => PoolDnsProbe; /** Read-only snapshots must not create the disposable DNS Pod or update its cache. */ skipDnsProbe?: boolean } = {}): PoolLeaseMeasure {
|
|
291
|
+
export function measurePoolLease(members: readonly PodPoolMember[], deps: { kubectl?: PoolKubectl; dns?: (context: string) => PoolDnsProbe; /** Read-only snapshots must not create the disposable DNS Pod or update its cache. */ skipDnsProbe?: boolean; /** POD-ADMIT-BY-USAGE — also read `kubectl top` so Running harness Pods reserve by observed usage. */ measureUsage?: boolean } = {}): PoolLeaseMeasure {
|
|
242
292
|
const kubectl = deps.kubectl ?? leaseKubectl;
|
|
243
293
|
return { members: members.map((member) => {
|
|
244
294
|
const base = ['--context', member.context, '--request-timeout=10s'];
|
|
@@ -249,6 +299,26 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
|
|
|
249
299
|
// All namespaces are necessary: unrelated workloads also reserve memory on these nodes.
|
|
250
300
|
const pods = kubectl([...base, 'get', 'pods', '--all-namespaces', '-o', 'json']);
|
|
251
301
|
const reasons: string[] = [];
|
|
302
|
+
// POD-ADMIT-BY-USAGE: metrics are optional — a failed `top` leaves every reservation at today's limit.
|
|
303
|
+
let podUsage: Map<string, number> | null = null;
|
|
304
|
+
let nodeUsage: Map<string, number> | null = null;
|
|
305
|
+
if (deps.measureUsage) {
|
|
306
|
+
const top = kubectl([...base, '-n', 'elanous-test', 'top', 'pods', '--no-headers']);
|
|
307
|
+
podUsage = top.status === 0 ? topMemory(top.stdout, 2) : null;
|
|
308
|
+
result.usageReason = podUsage ? null
|
|
309
|
+
: `top pods unavailable: ${top.status === 0 ? 'unparseable output' : top.stderr.trim().split('\n').pop() || `rc=${top.status}`}`.slice(0, 160);
|
|
310
|
+
if (podUsage) {
|
|
311
|
+
const topNodes = kubectl([...base, 'top', 'nodes', '--no-headers']);
|
|
312
|
+
nodeUsage = topNodes.status === 0 ? topMemory(topNodes.stdout, 3) : null;
|
|
313
|
+
// The node real-usage floor needs node metrics: without them stay on today's limit reservations.
|
|
314
|
+
if (!nodeUsage) {
|
|
315
|
+
podUsage = null;
|
|
316
|
+
result.usageReason = `top nodes unavailable: ${topNodes.status === 0 ? 'unparseable output' : topNodes.stderr.trim().split('\n').pop() || `rc=${topNodes.status}`}`.slice(0, 160);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const harnessUsage: number[] = [];
|
|
321
|
+
const nodeAllocatable = new Map<string, number>();
|
|
252
322
|
const parsedItems = (response: ReturnType<PoolKubectl>): Record<string, unknown>[] | null => {
|
|
253
323
|
if (response.status !== 0) return null;
|
|
254
324
|
try { return items(JSON.parse(response.stdout)); } catch { return null; }
|
|
@@ -280,12 +350,15 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
|
|
|
280
350
|
}
|
|
281
351
|
nodeNames.add(name);
|
|
282
352
|
const repels = taintList.some((t) => t!.effect === 'NoSchedule' || t!.effect === 'NoExecute');
|
|
283
|
-
if (spec?.unschedulable !== true && !repels && object(ready[0])?.status === 'True') nodeFree.set(name, mem);
|
|
353
|
+
if (spec?.unschedulable !== true && !repels && object(ready[0])?.status === 'True') { nodeFree.set(name, mem); nodeAllocatable.set(name, mem); }
|
|
284
354
|
memory += mem; cpu += cores;
|
|
285
355
|
}
|
|
286
356
|
if (valid) { result.allocatableMemoryBytes = memory; result.allocatableCpuMillicores = cpu; }
|
|
287
|
-
else { nodeFree.clear(); nodeNames.clear(); reasons.push('cluster nodes: invalid name/allocatable memory/cpu/readiness'); }
|
|
357
|
+
else { nodeFree.clear(); nodeAllocatable.clear(); nodeNames.clear(); reasons.push('cluster nodes: invalid name/allocatable memory/cpu/readiness'); }
|
|
288
358
|
}
|
|
359
|
+
// POD-ADMIT-BY-USAGE: every schedulable node needs a real-usage reading, or the member stays on limits.
|
|
360
|
+
const unreadNode = podUsage && nodeUsage ? [...nodeFree.keys()].find((n) => !nodeUsage!.has(n)) : undefined;
|
|
361
|
+
if (unreadNode !== undefined) { podUsage = null; result.usageReason = `top nodes: no reading for ${unreadNode}`.slice(0, 160); }
|
|
289
362
|
const jobItems = parsedItems(jobs);
|
|
290
363
|
const podItems = parsedItems(pods);
|
|
291
364
|
if (!jobItems) reasons.push(`cluster jobs: ${jobs.status === 0 ? 'invalid response' : jobs.stderr.trim().split('\n').pop() || `rc=${jobs.status}`}`);
|
|
@@ -303,7 +376,7 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
|
|
|
303
376
|
if (object(meta.annotations)?.[POD_HOST_LEASE_ANNOTATION] === 'true') leasedJobs.add(meta.name);
|
|
304
377
|
}
|
|
305
378
|
if (!jobsValid) reasons.push('cluster jobs: missing name/labels');
|
|
306
|
-
let running = 0, pending = 0, unleasedRunning = 0, limits = 0, limitsValid = true, reservationsValid = nodeItems !== null && nodeNames.size === nodeItems.length;
|
|
379
|
+
let running = 0, pending = 0, unleasedRunning = 0, limits = 0, reserved = 0, limitsValid = true, reservationsValid = nodeItems !== null && nodeNames.size === nodeItems.length;
|
|
307
380
|
const pendingJobs = new Map<string, PendingJobIdentity>();
|
|
308
381
|
let unknownPhase = false;
|
|
309
382
|
const seenPods = new Set<string>();
|
|
@@ -324,6 +397,7 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
|
|
|
324
397
|
(labels?.['elanous.substrate'] === 'pod' || jobNames.has(String(labels?.['elanous.job'] ?? '')));
|
|
325
398
|
const spec = object(pod.spec);
|
|
326
399
|
let limit: number | null = null;
|
|
400
|
+
let reserve: number | null = null;
|
|
327
401
|
if (harness) {
|
|
328
402
|
if (phase === 'Pending') {
|
|
329
403
|
pending++;
|
|
@@ -338,7 +412,15 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
|
|
|
338
412
|
if (typeof jobName !== 'string' || !leasedJobs.has(jobName)) unleasedRunning++;
|
|
339
413
|
limit = spec ? runningLimit(spec) : null;
|
|
340
414
|
if (limit === null) limitsValid = false;
|
|
341
|
-
else
|
|
415
|
+
else {
|
|
416
|
+
limits += limit;
|
|
417
|
+
// Measured usage: max(request, usage×1.5) capped at the limit. Unmeasured: max(request, limit) (today's node reservation).
|
|
418
|
+
const used = typeof metadata.name === 'string' ? podUsage?.get(metadata.name) : undefined;
|
|
419
|
+
const request = spec ? requestedMemory(spec) : null;
|
|
420
|
+
if (used !== undefined) harnessUsage.push(used);
|
|
421
|
+
reserve = used !== undefined && request !== null ? Math.min(limit, Math.max(request, Math.ceil(used * POD_USAGE_HEADROOM))) : Math.max(request ?? 0, limit);
|
|
422
|
+
reserved += reserve;
|
|
423
|
+
}
|
|
342
424
|
}
|
|
343
425
|
}
|
|
344
426
|
// Pending Pods with no assigned node have no node reservation yet. They already occupy a pool slot.
|
|
@@ -347,13 +429,22 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
|
|
|
347
429
|
requestedMemory(spec) !== null) continue;
|
|
348
430
|
const requested = spec ? requestedMemory(spec) : null;
|
|
349
431
|
if (typeof nodeName !== 'string' || !nodeNames.has(nodeName) || requested === null) { reservationsValid = false; continue; }
|
|
350
|
-
if (nodeFree.has(nodeName)) nodeFree.set(nodeName, nodeFree.get(nodeName)! - Math.max(requested, limit ?? 0));
|
|
432
|
+
if (nodeFree.has(nodeName)) nodeFree.set(nodeName, nodeFree.get(nodeName)! - Math.max(requested, reserve ?? limit ?? 0));
|
|
351
433
|
}
|
|
352
434
|
if (unknownPhase) reasons.push('cluster pods: missing/Unknown phase');
|
|
353
435
|
if (jobsValid && !unknownPhase) { result.running = running; result.pending = pending; result.pendingJobs = [...pendingJobs.values()]; result.unleasedRunning = unleasedRunning; }
|
|
354
|
-
if (limitsValid && jobsValid && !unknownPhase)
|
|
436
|
+
if (limitsValid && jobsValid && !unknownPhase) {
|
|
437
|
+
result.memoryLimitBytes = limits;
|
|
438
|
+
if (deps.measureUsage) { result.memoryReservedBytes = reserved; result.harnessUsageBytes = podUsage ? harnessUsage : null; }
|
|
439
|
+
}
|
|
355
440
|
else if (!limitsValid) reasons.push('cluster pods: Running Pod memory limit missing/invalid');
|
|
356
|
-
if (reservationsValid && limitsValid && jobsValid && !unknownPhase)
|
|
441
|
+
if (reservationsValid && limitsValid && jobsValid && !unknownPhase) {
|
|
442
|
+
result.availableMemoryByNodeBytes = [...nodeFree.values()];
|
|
443
|
+
if (deps.measureUsage) {
|
|
444
|
+
result.allocatableMemoryByNodeBytes = [...nodeFree.keys()].map((n) => nodeAllocatable.get(n)!);
|
|
445
|
+
result.usedMemoryByNodeBytes = [...nodeFree.keys()].map((n) => nodeUsage?.get(n) ?? null);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
357
448
|
else if (!reservationsValid && !unknownPhase) reasons.push('cluster reservations: node assignment or memory request/limit missing/invalid');
|
|
358
449
|
}
|
|
359
450
|
result.reason = reasons.length ? reasons.join('; ').slice(0, 240) : null;
|
|
@@ -366,6 +457,7 @@ export function measurePoolLease(members: readonly PodPoolMember[], deps: { kube
|
|
|
366
457
|
} catch (error) {
|
|
367
458
|
result.running = null; result.pending = null; result.unleasedRunning = null; result.memoryLimitBytes = null;
|
|
368
459
|
result.allocatableMemoryBytes = null; result.allocatableCpuMillicores = null; result.availableMemoryByNodeBytes = null;
|
|
460
|
+
if (deps.measureUsage) { result.memoryReservedBytes = null; result.harnessUsageBytes = null; result.allocatableMemoryByNodeBytes = null; result.usedMemoryByNodeBytes = null; }
|
|
369
461
|
result.reason = `cluster: ${error instanceof Error ? error.message : String(error)}`.split('\n')[0]!.slice(0, 240);
|
|
370
462
|
}
|
|
371
463
|
return result;
|
|
@@ -377,7 +469,9 @@ export function recommendConcurrency(measure: PoolLeaseMeasure, options: { capac
|
|
|
377
469
|
const empty: PoolLeaseRecommendation = { recommended: null, limitedBy: null, reason: null, capacitySlots: null, memorySlots: null, liteMemorySlots: null, placeableSlots: null, accountSlots: null, running: null, pending: null, unleasedRunning: null };
|
|
378
470
|
const goalBytes = quantity(options.perGoalMemory ?? '16Gi', 'memory');
|
|
379
471
|
if (goalBytes === null || goalBytes <= 0) return { ...empty, reason: '측정 불가: perGoalMemory' };
|
|
472
|
+
const admission = admissionByUsage(measure, goalBytes, options.perGoalMemory !== undefined);
|
|
380
473
|
if (!measure.members.length || measure.members.some((m) => m.running === null || m.pending === null || m.unleasedRunning === null || m.allocatableMemoryBytes === null || m.memoryLimitBytes === null || m.allocatableCpuMillicores === null || m.availableMemoryByNodeBytes === null)) {
|
|
474
|
+
debug.log('pod-lease', 'admit-by-usage', { samples: admission.samples, p95Bytes: admission.p95Bytes, admitBytes: admission.admitBytes, fallback: admission.fallback, recommended: null, limitedBy: null });
|
|
381
475
|
return { ...empty, reason: `측정 불가: cluster${measure.members.map((m) => m.reason ? ` ${m.context}: ${m.reason}` : '').join('')}` };
|
|
382
476
|
}
|
|
383
477
|
if (!Number.isSafeInteger(options.capacity) || options.capacity < 0) return { ...empty, reason: '측정 불가: capacity' };
|
|
@@ -392,12 +486,25 @@ export function recommendConcurrency(measure: PoolLeaseMeasure, options: { capac
|
|
|
392
486
|
const eligibleOccupied = measure.members.reduce((sum, m) => sum + (m.reason === 'dns' ? 0 : Math.min(m.capacity, m.running! + m.pending!)), 0);
|
|
393
487
|
const capacitySlots = Math.max(0, Math.min(options.capacity - eligibleOccupied,
|
|
394
488
|
memberFree.reduce((sum, free) => sum + free, 0)));
|
|
395
|
-
|
|
396
|
-
|
|
489
|
+
// The goal size may fall back to the conservative size pool-wide; the node floor is separate and follows each
|
|
490
|
+
// member: once its Running Pods reserve by usage (usage read), its nodes keep 10% free in bookkeeping and real use.
|
|
491
|
+
const admitBytes = admission.admitBytes;
|
|
492
|
+
const usageRead = (m: PodLeaseMember) => Array.isArray(m.harnessUsageBytes);
|
|
493
|
+
const memberMemorySlots = measure.members.map((m) => m.availableMemoryByNodeBytes!.reduce((sum, bytes, i) => {
|
|
494
|
+
if (!usageRead(m)) return sum + Math.max(0, Math.floor(bytes / admitBytes));
|
|
495
|
+
const allocatable = m.allocatableMemoryByNodeBytes?.[i];
|
|
496
|
+
const used = m.usedMemoryByNodeBytes?.[i];
|
|
497
|
+
// A usage-read member always carries both per-node readings; anything missing admits nothing on that node.
|
|
498
|
+
if (typeof allocatable !== 'number' || typeof used !== 'number') return sum;
|
|
499
|
+
const floor = allocatable * POD_NODE_FREE_FLOOR_RATIO;
|
|
500
|
+
const slots = Math.min(Math.floor((bytes - floor) / admitBytes), Math.floor((allocatable - used - floor) / admitBytes));
|
|
501
|
+
return sum + Math.max(0, slots);
|
|
502
|
+
}, 0));
|
|
397
503
|
// Preserve the requested allocatable-minus-harness-limit metric. The node-local
|
|
398
504
|
// placement bound additionally accounts for every namespace's reservations.
|
|
399
|
-
const memorySlots = measure.members.reduce((sum, m) => sum + (m.reason === 'dns' ? 0 : Math.max(0,
|
|
400
|
-
Math.floor((m.allocatableMemoryBytes! - m.memoryLimitBytes!) /
|
|
505
|
+
const memorySlots = measure.members.reduce((sum, m) => sum + (m.reason === 'dns' ? 0 : Math.max(0, usageRead(m)
|
|
506
|
+
? Math.floor((m.allocatableMemoryBytes! * (1 - POD_NODE_FREE_FLOOR_RATIO) - (m.memoryReservedBytes ?? m.memoryLimitBytes!)) / admitBytes)
|
|
507
|
+
: Math.floor((m.allocatableMemoryBytes! - m.memoryLimitBytes!) / admitBytes))), 0);
|
|
401
508
|
const placeableSlots = measure.members.reduce((sum, m, i) => sum + Math.min(
|
|
402
509
|
Math.max(0, m.capacity - m.running! - m.pending!), memberMemorySlots[i]!,
|
|
403
510
|
), 0);
|
|
@@ -411,5 +518,27 @@ export function recommendConcurrency(measure: PoolLeaseMeasure, options: { capac
|
|
|
411
518
|
const recommended = Math.max(0, Math.min(capacitySlots, memoryBound) - eligibleUnleasedRunning);
|
|
412
519
|
const limitedBy = memoryBound < capacitySlots ? 'memory' : 'capacity';
|
|
413
520
|
if (unleasedRunning > 0) debug.log('pod-lease', 'unleased', { unleasedRunning, running, recommended });
|
|
414
|
-
|
|
521
|
+
debug.log('pod-lease', 'admit-by-usage', { samples: admission.samples, p95Bytes: admission.p95Bytes, admitBytes: admission.admitBytes, fallback: admission.fallback, recommended, limitedBy });
|
|
522
|
+
return { recommended, limitedBy, reason: measure.members.some((m) => m.reason === 'dns') ? 'dns' : null, capacitySlots, memorySlots, liteMemorySlots, placeableSlots, accountSlots, running, pending, pendingJobs, unleasedRunning, admission };
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* POD-ADMIT-BY-USAGE — one new goal counts at max(the Pod's memory request, p95 of Running harness Pod usage × 1.5),
|
|
527
|
+
* capped at the per-goal size. No samples (metrics unreadable, nothing running, or an explicit per-goal size such as an
|
|
528
|
+
* OOM retry tier) keeps the conservative per-goal size.
|
|
529
|
+
*/
|
|
530
|
+
function admissionByUsage(measure: PoolLeaseMeasure, goalBytes: number, explicit: boolean): PodAdmissionByUsage {
|
|
531
|
+
const live = measure.members.filter((m) => m.reason !== 'dns');
|
|
532
|
+
const samples = live.flatMap((m) => m.harnessUsageBytes ?? []);
|
|
533
|
+
const p95Bytes = p95(samples);
|
|
534
|
+
const requestBytes = quantity(POD_CHILD_REQUESTS.memory, 'memory')!;
|
|
535
|
+
// Any live member whose usage was not read keeps the whole pool on today's per-goal size (fail conservative).
|
|
536
|
+
const fallback = explicit ? 'explicit perGoalMemory'
|
|
537
|
+
: live.every((m) => m.harnessUsageBytes === undefined) ? 'usage not measured'
|
|
538
|
+
: live.some((m) => m.harnessUsageBytes == null) ? (live.map((m) => m.harnessUsageBytes == null ? `${m.context}: ${m.usageReason ?? 'usage not measured'}` : '').filter(Boolean).join('; '))
|
|
539
|
+
: p95Bytes === null ? 'no Running harness Pod usage samples'
|
|
540
|
+
: null;
|
|
541
|
+
const admitBytes = fallback !== null || p95Bytes === null ? goalBytes
|
|
542
|
+
: Math.min(goalBytes, Math.max(requestBytes, Math.ceil(p95Bytes * POD_USAGE_HEADROOM)));
|
|
543
|
+
return { samples: samples.length, p95Bytes, admitBytes, fallback };
|
|
415
544
|
}
|
|
@@ -193,7 +193,8 @@ export class PodPoolScheduler {
|
|
|
193
193
|
// One lease directory per pool (contexts, not caps) — every CLI process launching into the same clusters shares it.
|
|
194
194
|
this.hostLease = options.hostLease ?? podPoolHostLease(members);
|
|
195
195
|
const measureStatus = options.status ?? (() => {
|
|
196
|
-
|
|
196
|
+
// POD-ADMIT-BY-USAGE: admission reads observed Pod usage (`kubectl top`); tryAcquire's occupancy read does not.
|
|
197
|
+
const measure = measurePoolLease(members, { ...(options.kubectl ? { kubectl: options.kubectl } : {}), ...(options.dns ? { dns: options.dns } : {}), measureUsage: true });
|
|
197
198
|
return recommendConcurrency(measure, { capacity: members.reduce((n, m) => n + m.capacity, 0), accounts: 0, perAccount: 0 });
|
|
198
199
|
});
|
|
199
200
|
this.measure = async () => measureStatus();
|
|
@@ -297,11 +298,18 @@ export class PodPoolScheduler {
|
|
|
297
298
|
* POOL-SPREAD (10-06): comparing absolute free slots always picked the big member (node-b 25 vs node-c 4), so the small
|
|
298
299
|
* member idled until the big one was full. Comparing the free share fills members in proportion to their capacity.
|
|
299
300
|
* A member whose occupancy could not be read falls back to inflight-only (never treated as zero occupied).
|
|
301
|
+
* `selfCapped` (POOLGLOBAL-REG 10-07): a caller with its own concurrency cap (the release gate) counts only its own Jobs
|
|
302
|
+
* — free = capacity − this scheduler's inflight, summed over members as the pool total — and reads no cluster occupancy.
|
|
303
|
+
* #24253 made every acquire subtract all Running+Pending harness goal Pods, so with ~24 goal Pods on the gate's 25-slot
|
|
304
|
+
* member the 0.2.15 gate launched its shards one at a time.
|
|
300
305
|
* Ties keep the configured member order. Returns null when every readable member is full.
|
|
301
306
|
*/
|
|
302
|
-
async tryAcquire(skip?: ReadonlySet<string>, onEmpty?: (reason: string) => void): Promise<PodPoolMember | null> {
|
|
307
|
+
async tryAcquire(skip?: ReadonlySet<string>, onEmpty?: (reason: string) => void, mode: { selfCapped?: boolean } = {}): Promise<PodPoolMember | null> {
|
|
308
|
+
const selfCapped = mode.selfCapped === true;
|
|
303
309
|
let reading: Record<string, MemberOccupancy>;
|
|
304
|
-
|
|
310
|
+
// A self-capped caller's own Jobs are its inflight; other workloads' Pods are not its slots (no cluster read).
|
|
311
|
+
if (selfCapped) reading = Object.fromEntries(this.members.map((m) => [m.context, { occupied: 0 }]));
|
|
312
|
+
else try { reading = await this.occupancy(); }
|
|
305
313
|
catch (error) {
|
|
306
314
|
const reason = error instanceof Error ? error.message : String(error);
|
|
307
315
|
reading = Object.fromEntries(this.members.map((m) => [m.context, { occupied: null, reason }]));
|
|
@@ -329,12 +337,12 @@ export class PodPoolScheduler {
|
|
|
329
337
|
if (better(cand)) best = cand;
|
|
330
338
|
}
|
|
331
339
|
if (!best) {
|
|
332
|
-
onEmpty?.(`no free slot · free=${JSON.stringify(free)} · measured=${JSON.stringify(measured)}${skip?.size ? ` · skipped=${[...skip].join(',')}` : ''}`);
|
|
340
|
+
onEmpty?.(`no free slot · free=${JSON.stringify(free)} · measured=${JSON.stringify(measured)}${selfCapped ? ' · selfCapped' : ''}${skip?.size ? ` · skipped=${[...skip].join(',')}` : ''}`);
|
|
333
341
|
return null;
|
|
334
342
|
}
|
|
335
343
|
this.inflight.set(best.member.context, (this.inflight.get(best.member.context) ?? 0) + 1);
|
|
336
344
|
const share = Object.fromEntries(this.members.map((m) => [m.context, Math.round(Math.max(0, free[m.context] ?? 0) / m.capacity * 100) / 100]));
|
|
337
|
-
debug.log('pod.pool', 'member-selected', { member: best.member.context, free, measured, share });
|
|
345
|
+
debug.log('pod.pool', 'member-selected', { member: best.member.context, free, measured, share, ...(selfCapped ? { selfCapped: true } : {}) });
|
|
338
346
|
return best.member;
|
|
339
347
|
}
|
|
340
348
|
release(member: PodPoolMember): void {
|