elanous 0.2.31 → 0.2.32
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/{1834-50625904996a8fa8.js → 1834-98b64172bc1efe77.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/69-0a2c8209c6d4a158.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8097-9530c03bcc550f02.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-504204b58c82c253.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-3535a4a69f1fafaa.js → page-a9717a67c2eecbaf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-5c9ec30aee62dfbf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-584191533721fa16.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-73c4b4bb3ac7ab5f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/{page-5a503541353e60a8.js → page-c0f1ca371f710b06.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-6025101384e361a5.js → page-46430d45a9e04eac.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-3b2ebf1dc64ff0be.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-a645f00a06483e9a.js → page-a27b1e5a0d1b308f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-25654b82094f7d22.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-e9bab7413b028b28.js → page-8ea49a4c6dd2c6e7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-ce5113aefea0a8d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-e547e6ce55f1fb33.js → page-0e5a41fac1c75396.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-d27e1b9850fd54bf.js → page-31aaf1ed37c6df9a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-3dcc1fa411e1f4e0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-0fec29c7ba01894b.js → page-c69247e7134d329d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-339a07410cd6f36b.js → layout-66dc5cd3a0999abe.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-26ff7ff651a7217d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-617b3976981efa96.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/{page-2fd93ed8c9f16580.js → page-d256f5d5d9552823.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/market/{page-9b564dbff0fefc35.js → page-ce8f1907b8a6101c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-33e182a316263116.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-814ba51385225776.js → page-32866485913d6a04.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-397d479626e5a459.js → not-found-7607cca9b84950c6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/{page-af1bba4e3251b728.js → page-eeab86176b03dc3b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-3ad3be5e7d3fdce9.js → page-5b93dbae3432b996.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-77a6cb90a9cf2b10.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-4da93ad35d4a8d28.js → page-f47e768b5b242b5a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-eb41aa3367fc2505.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-4d13ab0b8a267e3f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-84dad32ff0fc0831.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-3a658d5fa6d16a25.js → page-95306f892aa2d0ee.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-0157de6e85f8bb6b.js → page-940fb6c217f6b470.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-0a07363b130182a1.js → page-8c66a3b24a745630.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-841d73d4ac44ed49.js → page-7b675dfd35c36fc9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-6f43a3d1146e6377.js → page-8bd0d3cf7665bcfc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-521ee23ac8c0abfa.js → page-c219f2ed171131be.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/{page-a9992ee285efaa83.js → page-40f8fa21bdae955b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-3d48e85fa89ec04f.js → page-468f8313e4f1fbd9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-7bbb8fa353a246c4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-04d00ae9b0890349.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/{page-233c71cd5f68f0d7.js → page-4214b855d9233ea8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-70151d78de2f375e.js → page-936752c7aa58762e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-dcac33a3e2cb72c0.js → page-ec052ad359df1797.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-8305570551091338.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-0448145ccd5c45c1.js → page-fad650e4793adb65.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-d90e64743826e1d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-a16bcfd49af86a32.js +1 -0
- package/apps/pwa/out/_next/static/css/b5d88eba78a3d7bf.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +14 -14
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +14 -14
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +17 -14
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +14 -14
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +14 -14
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/graphs/implement-loop.yaml +5 -0
- package/package.json +1 -1
- package/scripts/ci-typecheck-changed.ts +26 -1
- package/scripts/measure-si-task-cpu.ts +143 -0
- package/scripts/ratchets/test-interference-patterns.allowlist.txt +0 -2
- package/scripts/release-loop/prepare-node.ts +102 -4
- package/src/acp/core-turn-bridge.ts +16 -4
- package/src/acp/elanous-extensions.ts +3 -0
- package/src/acp/server.ts +3 -0
- package/src/acp/tool-trace.ts +186 -11
- package/src/agent/self-ambient-cache.ts +156 -0
- package/src/agent/self-ambient.ts +8 -1
- package/src/agent/task-notification.ts +5 -0
- package/src/agent-substrate/pr-reviewer.ts +3 -1
- package/src/ask-user-question/tool.ts +22 -0
- package/src/autopilot/build/llm-conflict-merge.ts +56 -10
- package/src/autopilot/pr-manager.ts +23 -5
- package/src/capture/frame-state-detect.ts +39 -0
- package/src/capture/tui-self-report.ts +134 -6
- package/src/chat/index.ts +61 -15
- package/src/chat/pickers/modal-runtime.ts +6 -0
- package/src/chat/pickers/modals.ts +8 -2
- package/src/cli/ask-bare.ts +1 -0
- package/src/cli/import-cli.ts +12 -0
- package/src/cli/log-event-names.ts +10 -0
- package/src/cli/msg-cli.ts +1 -0
- package/src/cli/pod-cli.ts +14 -3
- package/src/cli/pr-cli.ts +67 -32
- package/src/cli/pr-land-base-read.ts +34 -0
- package/src/cli/pr-land-heavy-remote.ts +81 -25
- package/src/cli/pr-land-overlap-evidence.ts +46 -2
- package/src/cli/pr-land-pod-shadow-job.ts +26 -3
- package/src/cli/pr-land-refusal.ts +20 -5
- package/src/cli/pty-takeover-cli.ts +88 -14
- package/src/cli/sim-cli.ts +18 -0
- package/src/context-bus/ops-events.ts +6 -2
- package/src/core-turn/run-core-turn.ts +1 -0
- package/src/core-turn/run-goal-loop.ts +21 -0
- package/src/core-turn/types.ts +3 -0
- package/src/dashboard/acp-active-model.ts +42 -1
- package/src/dashboard/background-jobs-status.ts +66 -0
- package/src/dashboard/index.ts +117 -44
- package/src/dashboard/input/agent-wake.ts +242 -0
- package/src/dashboard/input/chat-main-entry-runtime.ts +5 -0
- package/src/dashboard/input/external-input-host.ts +23 -9
- package/src/dashboard/input/mouse-wiring.ts +108 -11
- package/src/dashboard/log-click-runtime.ts +118 -2
- package/src/dashboard/turn-preamble.ts +116 -40
- package/src/dashboard/turn-session-layers.ts +4 -2
- package/src/dashboard/turn-start-segments.ts +222 -0
- package/src/display/prompt-row-owner.ts +146 -0
- package/src/display/resize-listener.ts +33 -2
- package/src/fold-stack.ts +4 -2
- package/src/git-fs/index.ts +263 -36
- package/src/harness/gate-reserve.ts +87 -0
- package/src/harness/harness-incidents.ts +13 -0
- package/src/harness/harness-queue.ts +15 -0
- package/src/index.ts +8 -6
- package/src/input/external-input-queue.ts +34 -9
- package/src/llm/route-decision.ts +36 -0
- package/src/llm/tool-loop-steered-rejection.ts +8 -0
- package/src/llm-request-shape.ts +209 -0
- package/src/llm.ts +166 -267
- package/src/loops/orchestrator/tick.ts +24 -2
- package/src/maturity/feature-maturity.ts +1 -1
- package/src/mss/logging/render-categories.ts +5 -0
- package/src/ops/overview.ts +59 -2
- package/src/pr-tool/census.ts +19 -4
- package/src/pr-tool/inflight-registry.ts +143 -0
- package/src/pty-shell/pty-manifest.ts +12 -3
- package/src/pty-shell/tui-run-state.ts +34 -3
- package/src/seat-loop/traffic-signal.ts +2 -1
- package/src/self-dev/ask-launch-flow.ts +13 -0
- package/src/self-dev/draft-residue.ts +243 -24
- package/src/self-dev/shard-plan.ts +11 -3
- package/src/self-dev/shard-run.ts +103 -10
- package/src/self-implement/elanous-tui-spawn.ts +14 -0
- package/src/self-implement/follow-up-placement.ts +79 -0
- package/src/self-implement/gate-artifacts.ts +15 -4
- package/src/self-implement/gate-baseline.ts +96 -5
- package/src/self-implement/gate-cli.ts +7 -2
- package/src/self-implement/gate-remote.ts +26 -1
- package/src/self-implement/host-regate.ts +36 -4
- package/src/self-implement/orchestrator.ts +101 -11
- package/src/self-implement/run-ledger.ts +16 -14
- package/src/session-runtime/index.ts +22 -267
- package/src/shell-runner/background-surface.ts +15 -5
- package/src/skills/tools/agent.ts +39 -6
- package/src/skills/tools/list-dir.ts +11 -0
- package/src/skills/tools/schedule-wakeup.ts +408 -0
- package/src/skills/tools/shell.ts +245 -3
- package/src/status/bar.ts +26 -0
- package/src/steward/digest-delivery.ts +118 -0
- package/src/steward/hold-hygiene.ts +120 -0
- package/src/steward/triage.ts +10 -0
- package/src/surface/shell-runner-boot.ts +5 -2
- package/src/task-agent/task-hand.ts +24 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +65 -11
- package/src/task-orchestrator/surfaces/pod-lease.ts +98 -0
- package/src/task-orchestrator/surfaces/pod-ledger-events.ts +1 -1
- package/src/task-orchestrator/surfaces/pod-pool.ts +3 -1
- package/src/task-orchestrator/surfaces/pod-reuse-cleanup.ts +10 -6
- package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +525 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-runner.ts +90 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +156 -2
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +7 -5
- package/src/tui/chat/execution-badge.ts +5 -1
- package/src/tui/input-lag-probe.ts +230 -0
- package/src/tui.ts +4 -0
- package/src/ux-sim/scenario.ts +255 -0
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/8097-92f87bd108322f66.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-bf3634a117322df7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b9f51447f757fbf2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-2ebb8fd59b9461f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a2fc4e49a4fa4ba8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-f4b630cc8b5273cd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-61ac22cbab1ee4ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c992eafe028b642e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-805ac2e137b7a121.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-349f2752d8cd37ad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-d5c6dcf31c25cecc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-62f4475d3c38fb9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-f219ccdbc0bf27df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-456666766e5ce6fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-7404e377e8adb8c5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-14456240ed2a36ad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-d2ced71132fac9b2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-f08358bdf251adb9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-149dc1caba411bb9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-9cb0c444903da65d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-17e504c396e0e389.js +0 -1
- package/apps/pwa/out/_next/static/css/3f5fc6f96e2cf7b2.css +0 -1
- /package/apps/pwa/out/_next/static/{LSXfY0u9QtfDbejY0Ukf_ → IVKr3AlRUVLKsw2fh7eXF}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{LSXfY0u9QtfDbejY0Ukf_ → IVKr3AlRUVLKsw2fh7eXF}/_ssgManifest.js +0 -0
|
@@ -34,6 +34,8 @@ import {
|
|
|
34
34
|
|
|
35
35
|
/** CLI `--deadline` 생략 시 — 기존 Pod Job 수명 상한과 같다. */
|
|
36
36
|
export const POD_COMMAND_DEADLINE_SECONDS = POD_JOB_DEADLINE_SECONDS;
|
|
37
|
+
/** An opt-in shadow cannot let a synchronous kubectl call hide its host deadline for two minutes. */
|
|
38
|
+
export const POD_COMMAND_CANCEL_KUBECTL_TIMEOUT_MS = 15_000;
|
|
37
39
|
|
|
38
40
|
export const POD_NETWORK_SKILLS = ['omni-crawl', 'omni-digest'] as const;
|
|
39
41
|
export const POD_LITE_IMAGE = 'elanous-harness-lite:local';
|
|
@@ -414,6 +416,12 @@ export interface RunPodCommandOptions {
|
|
|
414
416
|
* elsewhere. Omitted = poll without a bound (unchanged for every caller but the release gate).
|
|
415
417
|
*/
|
|
416
418
|
statusUnreadableMs?: number;
|
|
419
|
+
/** Optional caller cancellation. Omitted callers keep their existing polling behavior. */
|
|
420
|
+
signal?: AbortSignal;
|
|
421
|
+
/** Optional host wall-clock deadline; checked even when a synchronous kubectl delayed the abort timer. */
|
|
422
|
+
hostDeadlineAtMs?: number;
|
|
423
|
+
/** Clock seam for deadline checks. */
|
|
424
|
+
nowMs?: () => number;
|
|
417
425
|
kubectl?: Kubectl;
|
|
418
426
|
/** A caller-owned scheduler is self-capped: acquires count only its own Jobs, never cluster occupancy (POOLGLOBAL-REG). */
|
|
419
427
|
poolScheduler?: PodPoolScheduler;
|
|
@@ -486,14 +494,35 @@ export function podCommandTargetCommit(o: { remote: boolean; gitHead: () => stri
|
|
|
486
494
|
return o.localImageCommit();
|
|
487
495
|
}
|
|
488
496
|
|
|
497
|
+
/** Only opt-in callers can stop waiting on a pre-Job async operation. Late rejections stay observed. */
|
|
498
|
+
function awaitPodCommandAbort<T>(pending: Promise<T>, signal?: AbortSignal): Promise<T> {
|
|
499
|
+
if (!signal) return pending;
|
|
500
|
+
return new Promise<T>((resolve, reject) => {
|
|
501
|
+
const onAbort = () => { signal.removeEventListener('abort', onAbort); reject(signal.reason ?? new Error('pod command aborted')); };
|
|
502
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
503
|
+
pending.then(
|
|
504
|
+
(value) => { signal.removeEventListener('abort', onAbort); resolve(value); },
|
|
505
|
+
(error) => { signal.removeEventListener('abort', onAbort); reject(error); },
|
|
506
|
+
);
|
|
507
|
+
if (signal.aborted) onAbort();
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
489
511
|
/** 풀 자리 · 이미지 판 · Job 적용 · 완료 대기 · 산출 회수 · Secret 정리. */
|
|
490
512
|
export async function runPodCommand(options: RunPodCommandOptions): Promise<PodCommandResult> {
|
|
513
|
+
const nowMs = options.nowMs ?? Date.now;
|
|
514
|
+
const ensureLive = () => {
|
|
515
|
+
if (options.signal?.aborted) throw options.signal.reason ?? new Error('pod command aborted');
|
|
516
|
+
if (options.hostDeadlineAtMs !== undefined && nowMs() >= options.hostDeadlineAtMs) throw new Error('pod shadow: host timeout');
|
|
517
|
+
};
|
|
518
|
+
ensureLive();
|
|
491
519
|
const command = [...options.command];
|
|
492
520
|
if (command.length === 0) throw new Error('pod command: 명령이 비었다');
|
|
493
521
|
const skills = [...new Set((options.skills ?? []).map((s) => s.trim()).filter(Boolean))];
|
|
494
522
|
const llm = options.llm;
|
|
495
523
|
const log = options.log ?? ((c, e, d) => debug.log(c, e, d));
|
|
496
|
-
const baseKubectl = options.kubectl ??
|
|
524
|
+
const baseKubectl = options.kubectl ?? ((args: readonly string[], input?: string) =>
|
|
525
|
+
defaultKubectl(args, input, options.signal ? POD_COMMAND_CANCEL_KUBECTL_TIMEOUT_MS : undefined));
|
|
497
526
|
const sleep = options.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)));
|
|
498
527
|
const namespace = options.namespace ?? 'elanous-test';
|
|
499
528
|
const image = options.image ?? podCommandImageFor(skills, options.clone === true, options.lite === true);
|
|
@@ -534,18 +563,32 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
534
563
|
// concurrency cap — its Jobs count against the spec caps, not the cluster's goal Pods (#24253 → 0.2.15 gate ran 1 shard).
|
|
535
564
|
const selfCapped = options.poolScheduler !== undefined;
|
|
536
565
|
for (;;) {
|
|
537
|
-
|
|
566
|
+
ensureLive();
|
|
567
|
+
const acquiring = pool.tryAcquire(undefined, undefined, { selfCapped, signal: options.signal });
|
|
568
|
+
try { member = await awaitPodCommandAbort(acquiring, options.signal); }
|
|
569
|
+
catch (error) {
|
|
570
|
+
// If cancellation won the await at the same instant acquisition completed, return that lease.
|
|
571
|
+
void acquiring.then((late) => { if (late) pool!.release(late); }, () => {});
|
|
572
|
+
throw error;
|
|
573
|
+
}
|
|
538
574
|
if (member) break;
|
|
539
|
-
await sleep(options.pollMs ?? 15_000);
|
|
575
|
+
await awaitPodCommandAbort(sleep(options.pollMs ?? 15_000), options.signal);
|
|
540
576
|
}
|
|
541
577
|
}
|
|
578
|
+
if (options.signal?.aborted) {
|
|
579
|
+
if (member && pool) pool.release(member);
|
|
580
|
+
ensureLive();
|
|
581
|
+
}
|
|
542
582
|
const currentContext = member || options.kubectl ? null : baseKubectl(['config', 'current-context']);
|
|
543
583
|
const context = member?.context ?? (currentContext?.status === 0 ? currentContext.stdout.trim() : '');
|
|
544
|
-
const
|
|
545
|
-
const
|
|
584
|
+
const kubectlRaw: Kubectl = (args, stdin) => baseKubectl(context ? ['--context', context, ...args] : [...args], stdin);
|
|
585
|
+
const kubectl: Kubectl = (args, stdin) => { ensureLive(); const result = kubectlRaw(args, stdin); ensureLive(); return result; };
|
|
586
|
+
const cleanupSecret = () => { kubectlRaw(['-n', namespace, 'delete', 'secret', '-l', own, '--ignore-not-found']); };
|
|
546
587
|
const artifactRoot = options.artifactsRoot ?? `${effectiveInstanceRoot()}/pod-artifacts`;
|
|
547
588
|
const artifactsDir = `${artifactRoot}/${name}`;
|
|
589
|
+
let jobApplied = false;
|
|
548
590
|
try {
|
|
591
|
+
ensureLive();
|
|
549
592
|
if (!context && !options.kubectl) throw new Error('pod command: kubectl context 를 확인할 수 없다');
|
|
550
593
|
let grokAuth: string | undefined;
|
|
551
594
|
if (llm === 'grok') {
|
|
@@ -573,7 +616,7 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
573
616
|
const localSkillsDigest = options.kubectl ? null : image === POD_LITE_IMAGE
|
|
574
617
|
? podSkillsDigest(POD_NETWORK_SKILLS, resolve(import.meta.dir, '../../../skills')).digest
|
|
575
618
|
: podSkillsDigest(resolvePodSkills(env).skills).digest;
|
|
576
|
-
const syncs = await (options.syncImages ?? syncPoolImages)([member], image, imageCommit, { localSkillsDigest });
|
|
619
|
+
const syncs = await awaitPodCommandAbort((options.syncImages ?? syncPoolImages)([member], image, imageCommit, { localSkillsDigest }), options.signal);
|
|
577
620
|
const synced = syncs.get(member.context);
|
|
578
621
|
if (synced && !synced.ok) throw new Error(`pod command: 이미지 판을 못 맞췄다: ${synced.detail}`);
|
|
579
622
|
if (synced?.imageRef) member = { ...member, imageRef: synced.imageRef };
|
|
@@ -586,8 +629,8 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
586
629
|
const seedMirror = options.seedMirror ?? (options.kubectl ? undefined : seedMirrorOnce);
|
|
587
630
|
if (seedMirror && options.clone && options.source?.kind === 'commit' && hostMirror && member?.sshHost) {
|
|
588
631
|
// Worst case added to a cold start: the push limit (GATE_MIRROR_SEED_LIMIT_MS), once per host·commit.
|
|
589
|
-
const seed = await seedMirror({ host: member.sshHost, commit: options.source.sha, cwd: options.mirrorSeedCwd ?? process.cwd() })
|
|
590
|
-
.catch((error: unknown) => ({ host: member!.sshHost!, ok: false, ms: 0, detail: String(error).slice(0, 300) }));
|
|
632
|
+
const seed = await awaitPodCommandAbort(seedMirror({ host: member.sshHost, commit: options.source.sha, cwd: options.mirrorSeedCwd ?? process.cwd() })
|
|
633
|
+
.catch((error: unknown) => ({ host: member!.sshHost!, ok: false, ms: 0, detail: String(error).slice(0, 300) })), options.signal);
|
|
591
634
|
log('pod.command-job', 'mirror-seed', { host: member.sshHost, commit: options.source.sha, ok: seed?.ok ?? false, ms: seed?.ms ?? 0, ...(seed?.detail ? { detail: seed.detail } : {}) });
|
|
592
635
|
}
|
|
593
636
|
// GATE-WARM-BOOT-NO-INIT (10-15): the hostPath dirs the uid-1000 child writes are handed over on the node itself, once per
|
|
@@ -600,14 +643,15 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
600
643
|
if (!member) log('pod.command-job', 'host-dirs-prep', { ok: false, reason: 'no-pool-member', paths: hostDirPaths });
|
|
601
644
|
else if (!prepareHostDirs) log('pod.command-job', 'host-dirs-prep', { ok: false, reason: 'prep-disabled', paths: hostDirPaths });
|
|
602
645
|
else {
|
|
603
|
-
const prep = await prepareHostDirs({ member, hostPaths: hostDirPaths })
|
|
604
|
-
.catch((error: unknown) => ({ host: member!.sshHost ?? 'local', ok: false, ms: 0, detail: String(error).slice(0, 300) }));
|
|
646
|
+
const prep = await awaitPodCommandAbort(prepareHostDirs({ member, hostPaths: hostDirPaths })
|
|
647
|
+
.catch((error: unknown) => ({ host: member!.sshHost ?? 'local', ok: false, ms: 0, detail: String(error).slice(0, 300) })), options.signal);
|
|
605
648
|
hostDirsPrepared = prep?.ok === true;
|
|
606
649
|
log('pod.command-job', 'host-dirs-prep', { host: prep?.host ?? member.sshHost ?? 'local', ok: prep?.ok ?? false, ms: prep?.ms ?? 0, paths: hostDirPaths, ...(prep?.detail ? { detail: prep.detail } : {}) });
|
|
607
650
|
}
|
|
608
651
|
}
|
|
609
652
|
// Any path where the node-side hand-over did not succeed keeps the old in-Pod hand-over (review mf: uid 1000 could not write).
|
|
610
653
|
const hostDirsInit = hostDirPaths.length > 0 && !hostDirsPrepared;
|
|
654
|
+
ensureLive();
|
|
611
655
|
const ghToken = options.clone ? (options.ghToken ?? defaultGhToken)() : undefined;
|
|
612
656
|
const secret = commandJobSecret({ name, namespace, launch, skills, skillEnvText, ...(grokAuth !== undefined ? { grokAuth } : {}), ...(ghToken !== undefined ? { ghToken } : {}) });
|
|
613
657
|
if (secret) {
|
|
@@ -623,6 +667,8 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
623
667
|
...(hostDirsInit ? { hostDirsInit: true } : {}),
|
|
624
668
|
});
|
|
625
669
|
kubectl(['-n', namespace, 'delete', 'job', '-l', own, '--ignore-not-found']);
|
|
670
|
+
// A wall deadline can elapse inside the synchronous apply; cleanup by this launch's name then remains safe.
|
|
671
|
+
jobApplied = true;
|
|
626
672
|
const applied = kubectl(['apply', '-f', '-'], JSON.stringify(manifest));
|
|
627
673
|
if (applied.status !== 0) {
|
|
628
674
|
cleanupSecret();
|
|
@@ -633,6 +679,7 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
633
679
|
let markerRc: number | null = null;
|
|
634
680
|
const appliedAt = Date.now();
|
|
635
681
|
for (;;) {
|
|
682
|
+
ensureLive();
|
|
636
683
|
const got = kubectl(['-n', namespace, 'get', 'job', name, '-o', 'jsonpath={.status.conditions[*].type}']);
|
|
637
684
|
const finished = jobFinished(got.stdout);
|
|
638
685
|
if (finished) { state = finished; break; }
|
|
@@ -655,8 +702,9 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
655
702
|
throw new Error(`pod command: Job 상태를 ${Math.round(options.statusUnreadableMs / 1000)}초 동안 못 읽었다(${context}) — ${(got.stderr ?? '').trim().split('\n').pop()?.slice(0, 200) ?? ''}`);
|
|
656
703
|
}
|
|
657
704
|
} else unreadableSince = null;
|
|
658
|
-
await sleep(options.pollMs ?? 15_000);
|
|
705
|
+
await awaitPodCommandAbort(sleep(options.pollMs ?? 15_000), options.signal);
|
|
659
706
|
}
|
|
707
|
+
ensureLive();
|
|
660
708
|
let logsResult = kubectl(['-n', namespace, 'logs', `job/${name}`, '-c', 'child']);
|
|
661
709
|
// Marker path returns before the Job condition — a full-log read can hit the same transient window the tail read
|
|
662
710
|
// already survived (container log rotation · API hiccup). Retry it a little before calling the artifacts lost.
|
|
@@ -721,6 +769,12 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
721
769
|
});
|
|
722
770
|
return { exitCode, artifactsDir, job: name, image: imageRef ?? image, artifacts };
|
|
723
771
|
} catch (err) {
|
|
772
|
+
if (jobApplied && options.signal) {
|
|
773
|
+
// Opt-in bounded callers remove their own failed/Pending Job, including status-unreadable exits.
|
|
774
|
+
// On an unreachable API this is best effort; the local lease is always released in finally.
|
|
775
|
+
try { kubectlRaw(['-n', namespace, 'delete', 'job', name, '--ignore-not-found', '--wait=false']); }
|
|
776
|
+
catch { /* preserve the cancellation reason */ }
|
|
777
|
+
}
|
|
724
778
|
cleanupSecret();
|
|
725
779
|
throw err;
|
|
726
780
|
} finally {
|
|
@@ -230,6 +230,13 @@ function runningLimit(spec: Record<string, unknown>): number | null {
|
|
|
230
230
|
|
|
231
231
|
/** A short-lived Pod proves cluster DNS from inside the same namespace as harness Jobs. Image pull failures are not DNS failures. */
|
|
232
232
|
export type PoolDnsProbe = 'ready' | 'dns' | 'image' | 'unknown';
|
|
233
|
+
/** Separate, opt-in observation. null means the probe could not measure that step. */
|
|
234
|
+
export interface ExternalGitHubProbe {
|
|
235
|
+
externalDns: boolean | null;
|
|
236
|
+
githubHttps: boolean | null;
|
|
237
|
+
measuredAt: string | null;
|
|
238
|
+
reason: string | null;
|
|
239
|
+
}
|
|
233
240
|
const DNS_CACHE_MS = 10 * 60_000;
|
|
234
241
|
const DNS_STALE_OK_MS = 30 * 60_000;
|
|
235
242
|
const DNS_LOCK_WAIT_MS = 6 * 60_000;
|
|
@@ -240,6 +247,97 @@ const dnsFlock = process.platform === 'linux' || process.platform === 'darwin'
|
|
|
240
247
|
flock: { args: ['int', 'int'], returns: 'int' },
|
|
241
248
|
}).symbols.flock : null;
|
|
242
249
|
|
|
250
|
+
/** A disposable Pod in the Job namespace measures GitHub reachability without changing admission. */
|
|
251
|
+
export function probePoolGitHub(context: string, kubectl: PoolKubectl = leaseKubectl, options: { dir?: string; now?: () => number } = {}): ExternalGitHubProbe {
|
|
252
|
+
const unknown = (reason: string): ExternalGitHubProbe => ({ externalDns: null, githubHttps: null, measuredAt: null, reason });
|
|
253
|
+
const now = options.now ?? Date.now;
|
|
254
|
+
const dir = options.dir ?? hostLeaseBaseDir();
|
|
255
|
+
const key = createHash('sha256').update(context).digest('hex');
|
|
256
|
+
const cache = join(dir, `github-egress-${key}.cache`);
|
|
257
|
+
const lock = join(dir, `github-egress-${key}.flock`);
|
|
258
|
+
const cached = (): ExternalGitHubProbe | null => {
|
|
259
|
+
try {
|
|
260
|
+
const record = object(JSON.parse(readFileSync(cache, 'utf8')));
|
|
261
|
+
if (typeof record?.at !== 'number' || now() < record.at || now() - record.at >= 60_000) return null;
|
|
262
|
+
if (typeof record.measuredAt !== 'string' || ![true, false].includes(record.externalDns as boolean) ||
|
|
263
|
+
![true, false, null].includes(record.githubHttps as boolean | null) ||
|
|
264
|
+
!(record.reason === null || typeof record.reason === 'string')) return null;
|
|
265
|
+
return { externalDns: record.externalDns as boolean, githubHttps: record.githubHttps as boolean | null,
|
|
266
|
+
measuredAt: record.measuredAt, reason: record.reason as string | null };
|
|
267
|
+
} catch { return null; }
|
|
268
|
+
};
|
|
269
|
+
const fresh = cached();
|
|
270
|
+
if (fresh) return fresh;
|
|
271
|
+
try { mkdirSync(dir, { recursive: true, mode: 0o700 }); } catch { return unknown('probe cache unavailable'); }
|
|
272
|
+
if (!dnsFlock) return unknown('probe lock unavailable');
|
|
273
|
+
let fd: number;
|
|
274
|
+
try { fd = openSync(lock, 'a', 0o600); } catch { return unknown('probe lock unavailable'); }
|
|
275
|
+
let held = false;
|
|
276
|
+
try {
|
|
277
|
+
const deadline = Date.now() + 2_000;
|
|
278
|
+
while (!held) {
|
|
279
|
+
const shared = cached();
|
|
280
|
+
if (shared) return shared;
|
|
281
|
+
if (dnsFlock(fd, 2 | 4) === 0) { held = true; break; }
|
|
282
|
+
if (Date.now() >= deadline) return unknown('probe busy');
|
|
283
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
|
|
284
|
+
}
|
|
285
|
+
const shared = cached();
|
|
286
|
+
if (shared) return shared;
|
|
287
|
+
const result = runPoolGitHubProbe(context, kubectl, new Date(now()).toISOString());
|
|
288
|
+
if (result.measuredAt !== null) {
|
|
289
|
+
const temp = `${cache}.${randomUUID()}.tmp`;
|
|
290
|
+
try { writeFileSync(temp, JSON.stringify({ ...result, at: now() }), { mode: 0o600 }); renameSync(temp, cache); }
|
|
291
|
+
catch { try { rmSync(temp, { force: true }); } catch { /* observation remains valid without cache */ } }
|
|
292
|
+
}
|
|
293
|
+
return result;
|
|
294
|
+
} finally {
|
|
295
|
+
if (held) dnsFlock(fd, 8);
|
|
296
|
+
closeSync(fd);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function runPoolGitHubProbe(context: string, kubectl: PoolKubectl, measuredAt: string): ExternalGitHubProbe {
|
|
301
|
+
const name = `elanous-egress-${randomUUID().slice(0, 12)}`;
|
|
302
|
+
const base = ['--context', context, '--request-timeout=15s'];
|
|
303
|
+
const unknown = (reason: string): ExternalGitHubProbe => ({ externalDns: null, githubHttps: null, measuredAt: null, reason });
|
|
304
|
+
let created = false;
|
|
305
|
+
try {
|
|
306
|
+
const manifest = JSON.stringify({ apiVersion: 'v1', kind: 'Pod', metadata: { name, namespace: 'elanous-test', labels: { 'elanous.probe': 'github-egress' } }, spec: {
|
|
307
|
+
restartPolicy: 'Never', activeDeadlineSeconds: 35, automountServiceAccountToken: false,
|
|
308
|
+
containers: [{ name: 'probe', image: 'busybox:1.36', command: ['sh', '-c',
|
|
309
|
+
'if nslookup github.com >/dev/null 2>&1; then echo EXTERNAL_DNS_OK; if wget -q -T 8 -O /dev/null https://github.com/; then echo GITHUB_HTTPS_OK; else echo GITHUB_HTTPS_FAILED; fi; else echo EXTERNAL_DNS_FAILED; fi'],
|
|
310
|
+
resources: { requests: { cpu: '0', memory: '16Mi' }, limits: { cpu: '100m', memory: '64Mi' } } }],
|
|
311
|
+
} });
|
|
312
|
+
if (kubectl([...base, 'create', '-f', '-'], manifest).status !== 0) return unknown('probe create failed');
|
|
313
|
+
created = true;
|
|
314
|
+
const waited = kubectl([...base, '-n', 'elanous-test', 'wait', '--for=jsonpath={.status.phase}=Succeeded', `pod/${name}`, '--timeout=25s']);
|
|
315
|
+
const podResult = kubectl([...base, '-n', 'elanous-test', 'get', `pod/${name}`, '-o', 'json']);
|
|
316
|
+
if (podResult.status !== 0) return unknown('probe status unavailable');
|
|
317
|
+
const pod = object(JSON.parse(podResult.stdout));
|
|
318
|
+
const statuses = object(pod?.status)?.containerStatuses;
|
|
319
|
+
if (Array.isArray(statuses) && statuses.some((c) =>
|
|
320
|
+
['ErrImagePull', 'ImagePullBackOff', 'InvalidImageName'].includes(String(object(object(object(c)?.state)?.waiting)?.reason))))
|
|
321
|
+
return unknown('probe image unavailable');
|
|
322
|
+
const logs = kubectl([...base, '-n', 'elanous-test', 'logs', `pod/${name}`]);
|
|
323
|
+
if (logs.status !== 0) return unknown(waited.status === 0 ? 'probe output unavailable' : 'probe timeout');
|
|
324
|
+
const markers = logs.stdout.trim().split(/\r?\n/u);
|
|
325
|
+
const dnsOk = markers.includes('EXTERNAL_DNS_OK');
|
|
326
|
+
const dnsFail = markers.includes('EXTERNAL_DNS_FAILED');
|
|
327
|
+
const httpsOk = markers.includes('GITHUB_HTTPS_OK');
|
|
328
|
+
const httpsFail = markers.includes('GITHUB_HTTPS_FAILED');
|
|
329
|
+
if (dnsFail && !dnsOk && !httpsOk && !httpsFail)
|
|
330
|
+
return { externalDns: false, githubHttps: null, measuredAt, reason: 'external DNS failed' };
|
|
331
|
+
if (dnsOk && !dnsFail && httpsOk !== httpsFail)
|
|
332
|
+
return { externalDns: true, githubHttps: httpsOk, measuredAt, reason: httpsFail ? 'GitHub HTTPS failed' : null };
|
|
333
|
+
return unknown(waited.status === 0 ? 'probe output unavailable' : 'probe timeout');
|
|
334
|
+
} catch { return unknown('probe failed'); }
|
|
335
|
+
finally {
|
|
336
|
+
if (created) try { kubectl([...base, '-n', 'elanous-test', 'delete', `pod/${name}`, '--ignore-not-found=true', '--wait=false']); }
|
|
337
|
+
catch { /* best effort; Kubernetes activeDeadlineSeconds also bounds the Pod */ }
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
243
341
|
/** The cache and lock are shared by CLI processes, keyed by context rather than pool composition. */
|
|
244
342
|
export function probePoolDns(context: string, kubectl: PoolKubectl = leaseKubectl, options: { dir?: string; now?: () => number } = {}): PoolDnsProbe {
|
|
245
343
|
const dir = options.dir ?? hostLeaseBaseDir();
|
|
@@ -41,7 +41,7 @@ export function ledgerLineToLogEvent(line: string, runId: string): {
|
|
|
41
41
|
const sources = [entry, details].filter((source): source is Record<string, unknown> => source !== undefined);
|
|
42
42
|
const knownSecrets = secretValues(entry);
|
|
43
43
|
const containsCredential = (value: string) => SECRET_VALUE.test(value) || knownSecrets.some((secret) => value.includes(secret));
|
|
44
|
-
if (containsCredential(entry.event) || !EVENT_ALLOWED.test(entry.event)) return undefined;
|
|
44
|
+
if (containsCredential(entry.event) || (!EVENT_ALLOWED.test(entry.event) && entry.event !== 'ask-auto-target-candidates' && entry.event !== 'ask-auto-reauthor')) return undefined;
|
|
45
45
|
|
|
46
46
|
const data: Record<string, unknown> = { runId };
|
|
47
47
|
let fields = 0;
|
|
@@ -474,7 +474,7 @@ export class PodPoolScheduler {
|
|
|
474
474
|
* member the 0.2.15 gate launched its shards one at a time.
|
|
475
475
|
* Ties keep the configured member order. Returns null when every readable member is full.
|
|
476
476
|
*/
|
|
477
|
-
async tryAcquire(skip?: ReadonlySet<string>, onEmpty?: (reason: string) => void, mode: { selfCapped?: boolean; kind?: PodPlacementKind | null; onPlacement?: (placement: PodPlacement) => void } = {}): Promise<PodPoolMember | null> {
|
|
477
|
+
async tryAcquire(skip?: ReadonlySet<string>, onEmpty?: (reason: string) => void, mode: { selfCapped?: boolean; kind?: PodPlacementKind | null; onPlacement?: (placement: PodPlacement) => void; signal?: AbortSignal } = {}): Promise<PodPoolMember | null> {
|
|
478
478
|
const selfCapped = mode.selfCapped === true;
|
|
479
479
|
const kind = mode.kind ?? null;
|
|
480
480
|
// POOL-LITE: lite goals try the small member first, implementation goals the big one; full → overflow by free share.
|
|
@@ -487,6 +487,8 @@ export class PodPoolScheduler {
|
|
|
487
487
|
const reason = error instanceof Error ? error.message : String(error);
|
|
488
488
|
reading = Object.fromEntries(this.members.map((m) => [m.context, { occupied: null, reason }]));
|
|
489
489
|
}
|
|
490
|
+
// A caller may abandon a slow occupancy read. Never acquire its lease after cancellation.
|
|
491
|
+
if (mode.signal?.aborted) throw mode.signal.reason ?? new Error('pod pool acquisition aborted');
|
|
490
492
|
const free: Record<string, number> = {};
|
|
491
493
|
const measured: Record<string, boolean> = {};
|
|
492
494
|
let best: { member: PodPoolMember; slots: number; measured: boolean; preferred: boolean } | null = null;
|
|
@@ -17,6 +17,8 @@ export interface PodReuseCleanupDecisionInput {
|
|
|
17
17
|
statusExitCode: number | null;
|
|
18
18
|
statusPorcelain: string;
|
|
19
19
|
remainingPaths?: readonly string[];
|
|
20
|
+
additionalCleanExclusions?: readonly { name: string; pattern: string }[];
|
|
21
|
+
statusExclusions?: readonly string[];
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export interface PodReuseCleanupDecision {
|
|
@@ -30,19 +32,21 @@ export interface PodReuseCleanupDecision {
|
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
/**
|
|
33
|
-
* Pure
|
|
34
|
-
*
|
|
35
|
-
*
|
|
35
|
+
* Pure policy: return fixed argv and decide whether the worker can be reused
|
|
36
|
+
* from actual command results. WarmWorkerRuntime owns execution and lifecycle
|
|
37
|
+
* event emission at the serialized request boundary.
|
|
36
38
|
*/
|
|
37
39
|
export function decidePodReuseCleanup(input: PodReuseCleanupDecisionInput): PodReuseCleanupDecision {
|
|
38
40
|
const remainingPaths = (input.remainingPaths ?? []).slice(0, 5);
|
|
39
41
|
const resetArgs = ['reset', '--hard', input.base] as const;
|
|
40
42
|
const cleanArgs = [
|
|
41
43
|
'clean', '-ffdx',
|
|
42
|
-
...POD_REUSE_CLEAN_EXCLUSIONS
|
|
44
|
+
...[...POD_REUSE_CLEAN_EXCLUSIONS, ...(input.additionalCleanExclusions ?? [])]
|
|
45
|
+
.flatMap(({ pattern }) => pattern ? ['-e', pattern] : []),
|
|
43
46
|
] as const;
|
|
44
|
-
const statusArgs = ['status', '--porcelain'
|
|
45
|
-
|
|
47
|
+
const statusArgs = ['status', '--porcelain', '-z', '--', '.', ...(input.statusExclusions ?? [])
|
|
48
|
+
.map((path) => `:(exclude,top,literal)${path}`)] as const;
|
|
49
|
+
const preserved = [...POD_REUSE_CLEAN_EXCLUSIONS, ...(input.additionalCleanExclusions ?? [])].map(({ name }) => name);
|
|
46
50
|
|
|
47
51
|
let reason: PodReuseCleanupDecision['reason'];
|
|
48
52
|
if (!/^[0-9a-f]{40}$/i.test(input.base)) reason = 'invalid-base';
|