elanous 0.2.18 → 0.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/{1307-30dff22c9e1f3073.js → 1307-77fad80cf54477c6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2489-ff55a35b202c0f94.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7748-6e51bfe927019f5c.js +25 -0
- package/apps/pwa/out/_next/static/chunks/8401-35e94e9091f5eb8d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +1 -0
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-33c0689b70ed8cf3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/{page-d92f7ee3994e84c7.js → page-c855c24de7db74fe.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-4733c967b56a4277.js → page-85b316bb4235980b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-d22fd849cc22f104.js → page-9b2b26a33cb2cd42.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-bb742f995c6769e7.js → page-c9955fde67b1bcb5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-02b073a2ac793b83.js → page-7a852956c74015a3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-8a9b3594bc23289b.js → page-4ba4abe096b6c2a8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-575d4a8b4f7becb2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-308111abadf35670.js → page-f5d56ff5f45ad164.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-2260e9eef0357421.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-5d97b2901e0d1ea6.js → page-7c058069c5e7fa63.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-8c3d8e63ae2e1c98.js → page-466bf0ee16ad1432.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-9b72a07d424701c6.js → page-a23bc71713bcc08f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ed4a6f799f005d09.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-a572f58cd3ca5864.js → page-9cd565005b93aca6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/{page-be5ea965a07fbc41.js → page-474cb5888ea0e8f3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-35bfec2e631d3eae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-fa244bd146a00ae8.js → page-9ca21f9ad3208a61.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-44d56d6391965a6a.js → page-f9ccb1b1adbea14d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-83a700d4c754d802.js → page-ec102c9b8b691e37.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-7b3170f374f77b9d.js → not-found-a9c2cfa8dda922a5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-f6055495e9ed7ea8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-1467047104141297.js → page-db03ae3589a81c9e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4a6463b97f0ad94f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-102310930791197a.js → page-75bffecc9aa1004e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-6ed0b73bae8351d8.js → page-31d5b875760bb62d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c26fa10aac6c21c4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-43336055451f6e37.js → page-68793c6e10331d66.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/{page-3cc47dca0922ab9a.js → page-46d5201d2207a511.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-a77ee355a2304ff0.js → page-d81c07a7b4f618ae.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-b17cd46cf0e9b8cf.js → page-be1c4e8697f22360.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-cf0a4b4abead71ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-7bf54c9bb9274af8.js → page-0fd895fab9b62c1b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-c6e4364d9de8f717.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-0acc8e198f7b0dfb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-134da468538baf48.js → page-34882c84f84021c5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-c2782b330447ffb6.js → webpack-c260571513bf43c2.js} +1 -1
- package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
- package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.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 +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 +16 -15
- 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 +16 -15
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +24 -23
- 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/demo/editor-recipes.yaml +22 -0
- package/graphs/ops/guardian.yaml +26 -0
- package/graphs/ops/recipes.yaml +3 -0
- package/graphs/retro/daily.yaml +25 -0
- package/graphs/retro/recipes.yaml +24 -0
- package/package.json +1 -1
- package/scripts/ask-marker-check.ts +3 -1
- package/scripts/coord-channel-watch.sh +2 -2
- package/scripts/discord-seat-probe.ts +3 -1
- package/scripts/land-global-checks.ts +89 -0
- package/scripts/lib/pch-regress-map.ts +24 -0
- package/scripts/morning-report.ts +18 -4
- package/scripts/pch-regress.ts +88 -0
- package/scripts/release-loop/gate-diff.ts +18 -0
- package/scripts/release-loop/gate-node.ts +628 -65
- package/scripts/release-loop/gate-partial.ts +106 -0
- package/scripts/release-loop/gate-timing.ts +229 -0
- package/scripts/release-loop/preflight.ts +6 -4
- package/scripts/release-loop/publish-window.ts +141 -0
- package/scripts/release-loop/release-docs.ts +1 -4
- package/scripts/release-loop/release-universe.ts +38 -0
- package/scripts/release-loop/resume-release.ts +67 -5
- package/scripts/release-loop/shard-plan.ts +65 -0
- package/scripts/release-loop/unattended-release.ts +12 -2
- package/scripts/release-story/draft.ts +5 -1
- package/scripts/retro/shadow-compare.ts +71 -0
- package/scripts/rhythm/daily.ts +6 -1
- package/scripts/video/scene-table.ts +159 -0
- package/src/acp/core-turn-bridge.ts +39 -2
- package/src/agent-mission/driver.ts +34 -6
- package/src/autopilot/build/llm-conflict-merge.ts +132 -7
- package/src/autopilot/build/merge-intent.ts +135 -7
- package/src/away/presence.ts +40 -0
- package/src/away/release-watch.ts +272 -0
- package/src/chat/index.ts +2 -1
- package/src/chat/short-question.ts +3 -40
- package/src/cli/away-cli.ts +68 -0
- package/src/cli/config-drift.ts +513 -0
- package/src/cli/guardian-cli.ts +49 -0
- package/src/cli/intake-cli.ts +13 -7
- package/src/cli/log-event-names.ts +5 -0
- package/src/cli/pod-cli.ts +5 -1
- package/src/cli/pr-cli.ts +20 -0
- package/src/cli/pty-drive-cli.ts +14 -3
- package/src/cli/release-cli.ts +52 -12
- package/src/cli/retro-cli.ts +78 -0
- package/src/cli/setup-cli.ts +31 -1
- package/src/cli/tasks-cli.ts +193 -14
- 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 +68 -40
- package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +62 -23
- package/src/dashboard/slash-runtime/help-groups.ts +44 -0
- package/src/dashboard/slash-runtime/model-choices.ts +125 -0
- package/src/domains/guardian-tick.ts +138 -0
- package/src/domains/outbound-alert.ts +44 -13
- package/src/domains/outbound-log-probe.ts +6 -1
- 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/graph-runner/graph-run-api.ts +205 -0
- package/src/graph-runner/runner.ts +36 -6
- package/src/harness/harness-cli-command.ts +420 -51
- package/src/harness/harness-queue.ts +315 -25
- package/src/harness/harness-salvage-cli.ts +140 -0
- package/src/harness/harness-worktree-add.ts +68 -0
- package/src/harness/ledger-write-guard.ts +6 -16
- package/src/hq/fence-outcomes.ts +63 -0
- package/src/hq/fence-wrapper.ts +33 -0
- package/src/hq/hq.ts +17 -2
- package/src/index.ts +118 -6
- 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/digest.ts +39 -15
- 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/loops/orchestrator/degradation.ts +57 -0
- package/src/loops/orchestrator/finish-rate.ts +33 -3
- package/src/loops/orchestrator/tick.ts +166 -45
- package/src/maturity/feature-maturity.ts +6 -4
- package/src/maturity/tui-slash-maturity.ts +1 -0
- package/src/mss/logging/log-store.ts +3 -0
- package/src/nexus/api/graph-kinds.ts +10 -4
- package/src/nexus/api/graphs-api.ts +178 -6
- package/src/nexus/api/http-server.ts +54 -8
- package/src/nexus/api/loop-edges.ts +292 -17
- package/src/nexus/api/ops-api.ts +112 -29
- package/src/nexus/api/ops-board-route.ts +48 -0
- package/src/nexus/index.ts +7 -0
- package/src/onboarding/entry-hints.ts +5 -0
- package/src/onboarding.ts +24 -6
- package/src/release-loop/checklist.ts +2 -0
- package/src/release-loop/feature-store.ts +68 -18
- package/src/release-loop/gate-shards.ts +158 -0
- package/src/release-loop/light-rc.ts +173 -0
- package/src/release-loop/placement.ts +51 -17
- package/src/release-loop/release-schedule.ts +6 -2
- package/src/release-loop/rubric.ts +57 -1
- package/src/retro/daily.ts +372 -0
- package/src/seat-loop/op-decide.ts +173 -0
- package/src/seat-loop/seat-loop.ts +60 -3
- package/src/seat-loop/seat-report.ts +58 -3
- package/src/self-dev/ask-launch-flow.ts +5 -4
- package/src/self-dev/ask-launch-io.ts +49 -1
- package/src/self-dev/ask-path-candidates.ts +40 -2
- package/src/self-dev/dev-cli.ts +36 -0
- package/src/self-dev/draft-sweep.ts +371 -16
- package/src/self-dev/draft-triage-rules.ts +19 -4
- package/src/self-dev/orchestrate.ts +1 -0
- package/src/self-dev/pr-preview.ts +247 -0
- package/src/self-dev/shard-run.ts +23 -7
- package/src/self-implement/blocked-draft-policy.ts +101 -0
- package/src/self-implement/execution-clarification.ts +11 -0
- package/src/self-implement/gate-cli.ts +29 -8
- package/src/self-implement/gate-env-retry.ts +83 -0
- package/src/self-implement/goal-author-clarification.ts +88 -13
- package/src/self-implement/goal-clarification-escalation.ts +2 -0
- package/src/self-implement/orchestrator.ts +220 -6
- package/src/self-implement/run-stop.ts +123 -0
- package/src/self-implement/run-ttl.ts +258 -0
- package/src/self-implement/salvage-branches.ts +254 -0
- package/src/self-implement/salvage-retention.ts +123 -0
- package/src/self-implement/seams.ts +4 -0
- package/src/self-implement/self-implement-cli.ts +2 -0
- package/src/self-implement/sibling-resync.ts +223 -0
- package/src/self-implement/stop-autoheal.ts +204 -0
- package/src/task-agent/actions.ts +63 -2
- package/src/task-agent/board.ts +132 -0
- package/src/task-agent/card-evidence.ts +292 -0
- package/src/task-agent/completion-evidence.ts +126 -0
- package/src/task-agent/judge.ts +25 -0
- package/src/task-agent/live-moves.ts +276 -0
- package/src/task-agent/mission.ts +33 -7
- package/src/task-agent/shadow.ts +192 -15
- package/src/task-agent/task-hand.ts +124 -17
- package/src/task-orchestrator/surfaces/pod-command-job.ts +82 -8
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +4 -0
- package/src/task-orchestrator/surfaces/pod-install-slots.ts +89 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +233 -21
- package/src/task-orchestrator/surfaces/pod-pool.ts +155 -34
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +21 -5
- package/src/telegram-away-command.ts +38 -0
- package/src/telegram-commands.ts +52 -0
- package/src/telegram-report-log-probe.ts +21 -0
- package/src/telegram-report.ts +27 -3
- package/src/testing/real-cli-spawn.ts +106 -0
- package/src/tui-client/dashboard-session.ts +1 -0
- package/src/user-config.ts +190 -5
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
- package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-a0eb02638b1e6f31.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c39554c321576982.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-c130b63862437814.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-a3c58f5c72a5f3f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-e8e1ecaea7cd71d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-44d5f767491692bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-61553851aa606146.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-a1f585e154e61c59.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-5150ef17b5383a57.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +0 -1
- package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +0 -1
- /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → Bam1YziK2iH9bN4K0utGk}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → Bam1YziK2iH9bN4K0utGk}/_ssgManifest.js +0 -0
|
@@ -7,9 +7,12 @@
|
|
|
7
7
|
* - 기본은 SHADOW: 카드와 명령만 남기고 아무것도 띄우지 않는다. `--live` 일 때만 주입된 launcher 로 띄운다.
|
|
8
8
|
* - 관측: `debug.log('task-agent', 'handed', {taskId, seat, checklistId, mode})`.
|
|
9
9
|
*/
|
|
10
|
+
import { execFileSync } from 'node:child_process';
|
|
10
11
|
import { randomBytes } from 'node:crypto';
|
|
11
|
-
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
12
|
-
import { dirname, join } from 'node:path';
|
|
12
|
+
import { mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { dirname, join, resolve } from 'node:path';
|
|
14
|
+
import { resolveDaemonHarnessTarget } from '../intake-plane/harness-target.js';
|
|
15
|
+
import { HARNESS_RUN_ID_ENV, mintRunId, normalizeRunId } from '../harness/harness-space.js';
|
|
13
16
|
import { debug } from '../debug/log.js';
|
|
14
17
|
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
15
18
|
import { withFileLockSync } from '../storage/file-lock.js';
|
|
@@ -18,17 +21,35 @@ export const TASK_SEATS = ['OP', 'TC', 'MK', 'UX'] as const;
|
|
|
18
21
|
export type TaskSeat = typeof TASK_SEATS[number];
|
|
19
22
|
/** 과제 카드 id 접두 — `task show <id>` 가 이 접두로 TOX 태스크와 갈린다. */
|
|
20
23
|
export const TASK_CARD_PREFIX = 'ta-';
|
|
24
|
+
/**
|
|
25
|
+
* 과제 종결 종류(RFC-loop-agent-map §A4b③) — 비면 code-pr(PR 병합)로 읽는다.
|
|
26
|
+
* code-pr 밖의 종류는 «확인 증거»(`judge.ts` TaskJudgeInput.evidence)로 종결한다 — 증거를 만드는 일은 DEFAULT-HANDS 몫.
|
|
27
|
+
*/
|
|
28
|
+
export const COMPLETION_KINDS = ['code-pr', 'artifact', 'research-report', 'ops-action', 'content', 'watch-brief', 'decision-support'] as const;
|
|
29
|
+
export type CompletionKind = typeof COMPLETION_KINDS[number];
|
|
21
30
|
|
|
22
|
-
|
|
31
|
+
/** `run-bound`·`goal-bound`·`pr-bound` 은 runId(⊕ pr)를 싣는다 — 옛 카드의 칸은 at·event·detail 뿐이다. */
|
|
32
|
+
export interface TaskCardEvent { at: string; event: string; detail?: string; runId?: string; pr?: number }
|
|
23
33
|
export interface TaskCardMove { kind: 'launch' | 'wait'; command?: string[]; reason: string }
|
|
24
34
|
export interface TaskCard {
|
|
25
35
|
id: string;
|
|
26
36
|
text: string;
|
|
27
37
|
seat?: TaskSeat;
|
|
28
38
|
checklistId?: string;
|
|
39
|
+
/** 종결 종류 — 비면 code-pr. */
|
|
40
|
+
completion?: CompletionKind;
|
|
41
|
+
/** 과제 대상 프로젝트(§A4b②) — target 은 절대 경로(git 이 아닐 수 있다). id 는 `task hand --project` 가 싣는다(옛 카드엔 없을 수 있다). */
|
|
42
|
+
project?: { id?: string; target: string };
|
|
43
|
+
/** 보드(§A4b① `board.ts`) 소속 — 목표 id · 이정표 id(`task hand --goal/--milestone`). 조각 카드의 `goalId`(골 sha 16자)와 다른 칸이다. */
|
|
44
|
+
goal?: string;
|
|
45
|
+
milestone?: string;
|
|
29
46
|
createdAt: string;
|
|
30
|
-
/**
|
|
31
|
-
|
|
47
|
+
/**
|
|
48
|
+
* launch-failed = 발사기가 띄우기에 실패했다(사유는 history 마지막 칸) — 다음 수는 다시 발사.
|
|
49
|
+
* failed = 발사된 런이 PR·수확 가지 없이 죽었거나 끝내 안 떴다(TA-JUDGE-DEAD-RUN · `card-evidence.ts`) — 사유
|
|
50
|
+
* (`failed/needs-owner|needs-relaunch — …`)는 history 마지막 칸 · 자동 재발사하지 않는다(사람/판단부 몫).
|
|
51
|
+
*/
|
|
52
|
+
status: 'handed' | 'launched' | 'launch-failed' | 'failed';
|
|
32
53
|
history: TaskCardEvent[];
|
|
33
54
|
/** 미션 카드만 — 조각 카드 id(순서대로) · 쪼갠 출처(`mission.ts` splitMission). */
|
|
34
55
|
pieces?: string[];
|
|
@@ -36,17 +57,48 @@ export interface TaskCard {
|
|
|
36
57
|
/** 조각 카드만 — 소속 미션 카드 id · 먼저 착지해야 하는 조각 id(선후). */
|
|
37
58
|
mission?: string;
|
|
38
59
|
after?: string[];
|
|
39
|
-
/**
|
|
40
|
-
|
|
60
|
+
/**
|
|
61
|
+
* 이 카드 런이 연 PR · 골 id(16자). 두 출처가 한 칸을 쓴다(UX LOOP-INTERACT 계약 — card.runId → goalId):
|
|
62
|
+
* - 수동(② `tasks advance --pr|--goal` · 미션 조각만) — PR 은 번호(number) · `refSource: 'manual'`.
|
|
63
|
+
* - 런 원장 묶기(TA-CARD-RUN-LINK `card-evidence.ts`) — PR 은 `{number, url}` · 비어 있을 때만 «한 번» 채운다 · 수동 근거를 덮지 않는다.
|
|
64
|
+
* 번호는 `cardPrNumber` 로 읽는다(옛 카드의 number 도 그대로 읽힌다).
|
|
65
|
+
*/
|
|
66
|
+
pr?: number | TaskCardPr;
|
|
41
67
|
goalId?: string;
|
|
68
|
+
/** `manual` = pr/goalId 를 사람이 적었다(`recordPieceRef`) — 런 묶기가 건드리지 않는다. */
|
|
69
|
+
refSource?: 'manual';
|
|
42
70
|
/** 조각 카드만 — ② 가 선행 착지 뒤 넘겼다(한 번만 · 같은 잠금 안에서 먼저 적는다). */
|
|
43
71
|
handed?: { at: string; mode: 'shadow' | 'live'; claim?: string; error?: string };
|
|
72
|
+
/**
|
|
73
|
+
* TA-CARD-RUN-LINK — `--live` 발사가 발사기에 넘긴 런 id(`ELANOUS_RUN_ID` 상속 → 하니스 런이 그 id 로 돈다) ⊕ 발사 토큰.
|
|
74
|
+
* 발사기가 그 id 를 «받았다»고 돌려줄 때만 적는다(한 번만). 원장이 아직 없으면 그 런은 시작 전이거나 대기열로 갔다.
|
|
75
|
+
*/
|
|
76
|
+
runId?: string;
|
|
77
|
+
launchId?: string;
|
|
78
|
+
/** Pod 런이면 PR 을 연 자식 런 id(런 묶기가 PR 과 함께 적는다). */
|
|
79
|
+
runChildId?: string;
|
|
44
80
|
/** 미션 카드만 — 모든 조각 착지 → 칸 green «제안»(체크리스트는 손대지 않는다 · OP/주인이 뒤집는다). */
|
|
45
81
|
greenProposal?: { at: string; checklistId: string | null; evidence: Record<string, string> };
|
|
82
|
+
/** TA-JUDGE-LIVE-SAFE — live `review` 수가 리뷰를 요청한 PR 머리들(머리마다 한 번 · 띄우기 실패면 error · 이력은 지우지 않는다). */
|
|
83
|
+
reviewRequests?: Array<{ pr: number; head: string; at: string; error?: string }>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** 런 원장에서 묶은 PR — url 은 원장(pr-opened · Pod job-finished)에 있을 때만(지어내지 않는다). */
|
|
87
|
+
export interface TaskCardPr { number: number; url?: string }
|
|
88
|
+
|
|
89
|
+
/** 카드의 PR 번호 — 수동(number)·묶음(`{number,url}`) 둘 다. */
|
|
90
|
+
export function cardPrNumber(card: Pick<TaskCard, 'pr'>): number | undefined {
|
|
91
|
+
return typeof card.pr === 'number' ? card.pr : card.pr?.number;
|
|
46
92
|
}
|
|
47
93
|
|
|
48
94
|
interface TaskAgentStateFile { tasks?: Record<string, TaskCard>; [key: string]: unknown }
|
|
49
95
|
|
|
96
|
+
function isGitWorkTree(path: string): boolean {
|
|
97
|
+
try {
|
|
98
|
+
return execFileSync('git', ['rev-parse', '--is-inside-work-tree'], { cwd: path, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim() === 'true';
|
|
99
|
+
} catch { return false; }
|
|
100
|
+
}
|
|
101
|
+
|
|
50
102
|
export function taskAgentStatePath(): string {
|
|
51
103
|
return join(effectiveInstanceRoot(), 'task-agent-actions.json');
|
|
52
104
|
}
|
|
@@ -130,6 +182,7 @@ export function nextMoveFor(card: TaskCard, landed?: ReadonlySet<string>): TaskC
|
|
|
130
182
|
if (card.status !== 'launched' && unmet.length > 0) return { kind: 'wait', reason: `after ${unmet.join(', ')}${landed ? '' : ' · 착지 미확인'}` };
|
|
131
183
|
if (card.status === 'handed') return { kind: 'launch', command: launchArgs(card), reason: '넘겨받은 과제 — 첫 발사' };
|
|
132
184
|
if (card.status === 'launch-failed') return { kind: 'launch', command: launchArgs(card), reason: '직전 발사 실패 — 다시 발사' };
|
|
185
|
+
if (card.status === 'failed') return { kind: 'wait', reason: card.history.at(-1)?.detail ?? '런 실패 — PR·수확 가지 없음' };
|
|
133
186
|
return { kind: 'wait', reason: '발사됨 — 런 멈춤을 슈퍼바이저 그림자 판단(task-agent.shadow-move)이 받는다' };
|
|
134
187
|
}
|
|
135
188
|
|
|
@@ -137,12 +190,22 @@ export function shellQuote(arg: string): string {
|
|
|
137
190
|
return /^[A-Za-z0-9_./:=@%+-]+$/.test(arg) ? arg : `'${arg.replaceAll("'", `'\\''`)}'`;
|
|
138
191
|
}
|
|
139
192
|
|
|
140
|
-
|
|
193
|
+
/** 발사기에 넘기는 연결 — env(`ELANOUS_RUN_ID`)를 자식 환경에 얹으면 하니스 런이 `runId` 로 돈다(`resolveRunIdentity` 상속). launchId 는 카드에만 남는 발사 토큰이다. */
|
|
194
|
+
export interface TaskLaunchContext { runId: string; launchId: string; env: Record<string, string> }
|
|
195
|
+
/** 발사기가 «이 런 id 로 띄웠다»고 돌려주는 영수증 — 없으면 카드에 런 id 를 적지 않는다(추측하지 않는다). */
|
|
196
|
+
export interface TaskLaunchReceipt { runId?: string }
|
|
197
|
+
export type TaskLauncher = (args: string[], cwd?: string, context?: TaskLaunchContext) => void | TaskLaunchReceipt | Promise<void | TaskLaunchReceipt>;
|
|
141
198
|
|
|
142
199
|
export interface HandTaskOptions {
|
|
143
200
|
text: string;
|
|
144
201
|
seat?: TaskSeat;
|
|
145
202
|
checklistId?: string;
|
|
203
|
+
completion?: TaskCard['completion'];
|
|
204
|
+
/** `--project <id> --target <dir>` — target 은 존재 확인 뒤 절대 경로로 바꿔 싣는다. */
|
|
205
|
+
project?: { id: string; target: string };
|
|
206
|
+
/** 보드 소속 목표·이정표 id — 카드에 그대로 싣는다. */
|
|
207
|
+
goal?: string;
|
|
208
|
+
milestone?: string;
|
|
146
209
|
live?: boolean;
|
|
147
210
|
statePath?: string;
|
|
148
211
|
/** `--live` 일 때만 불린다. */
|
|
@@ -179,12 +242,31 @@ export class TaskLaunchError extends Error {
|
|
|
179
242
|
}
|
|
180
243
|
}
|
|
181
244
|
|
|
182
|
-
export interface HandTaskResult { card: TaskCard; move: TaskCardMove; mode: 'shadow' | 'live'; launched: boolean }
|
|
245
|
+
export interface HandTaskResult { card: TaskCard; move: TaskCardMove; mode: 'shadow' | 'live'; launched: boolean; cwd?: string }
|
|
183
246
|
|
|
184
247
|
export async function handTask(opts: HandTaskOptions): Promise<HandTaskResult> {
|
|
185
248
|
const text = opts.text.trim();
|
|
186
249
|
if (!text) throw new Error('과제 한 줄이 비어 있다');
|
|
187
250
|
if (opts.seat !== undefined && !TASK_SEATS.includes(opts.seat)) throw new Error(`자리는 ${TASK_SEATS.join('|')} 중 하나다: ${opts.seat}`);
|
|
251
|
+
if (opts.completion !== undefined && !COMPLETION_KINDS.includes(opts.completion)) throw new Error(`종결 종류는 ${COMPLETION_KINDS.join('|')} 중 하나다: ${opts.completion}`);
|
|
252
|
+
for (const [flag, value] of [['--goal', opts.goal], ['--milestone', opts.milestone]] as const) {
|
|
253
|
+
if (value !== undefined && (!value.trim() || /\p{Cc}/u.test(value))) throw new Error(`${flag} id 가 비었거나 제어 문자를 담았다: ${JSON.stringify(value)}`);
|
|
254
|
+
}
|
|
255
|
+
let project: TaskCard['project'];
|
|
256
|
+
let targetIsGit: boolean | null = null;
|
|
257
|
+
if (opts.project) {
|
|
258
|
+
if (!opts.project.id.trim()) throw new Error('--project id 가 비어 있다');
|
|
259
|
+
const target = resolve(opts.project.target);
|
|
260
|
+
let isDir = false;
|
|
261
|
+
try { isDir = statSync(target).isDirectory(); } catch { /* 없음 */ }
|
|
262
|
+
if (!isDir) throw new Error(`대상 디렉터리가 없습니다: ${target}`);
|
|
263
|
+
targetIsGit = isGitWorkTree(target);
|
|
264
|
+
// 코드 종결(code-pr · 비면 code-pr)은 git 대상만 — 그 밖의 종결 종류는 git 이 아닌 폴더도 받는다(같은 판정을 harness-target 과 공유).
|
|
265
|
+
const codeCompletion = (opts.completion ?? 'code-pr') === 'code-pr';
|
|
266
|
+
const selection = resolveDaemonHarnessTarget({ configured: target, cwd: target, isGitRepo: () => targetIsGit === true, allowNonGit: !codeCompletion });
|
|
267
|
+
if (!selection.ok) throw new Error(`코드 종결은 git 대상이 필요 — --completion 을 고르거나 git init: ${target}`);
|
|
268
|
+
project = { id: opts.project.id, target: selection.repo };
|
|
269
|
+
}
|
|
188
270
|
const now = (opts.now ?? (() => new Date()))();
|
|
189
271
|
const id = opts.id ?? `${TASK_CARD_PREFIX}${now.toISOString().slice(0, 10).replaceAll('-', '')}-${randomBytes(3).toString('hex')}`;
|
|
190
272
|
const mode = opts.live ? 'live' : 'shadow';
|
|
@@ -193,6 +275,10 @@ export async function handTask(opts: HandTaskOptions): Promise<HandTaskResult> {
|
|
|
193
275
|
id, text,
|
|
194
276
|
...(opts.seat ? { seat: opts.seat } : {}),
|
|
195
277
|
...(opts.checklistId ? { checklistId: opts.checklistId } : {}),
|
|
278
|
+
...(opts.completion ? { completion: opts.completion } : {}),
|
|
279
|
+
...(project ? { project } : {}),
|
|
280
|
+
...(opts.goal ? { goal: opts.goal } : {}),
|
|
281
|
+
...(opts.milestone ? { milestone: opts.milestone } : {}),
|
|
196
282
|
createdAt: now.toISOString(),
|
|
197
283
|
status: 'handed',
|
|
198
284
|
history: [],
|
|
@@ -210,17 +296,21 @@ export async function handTask(opts: HandTaskOptions): Promise<HandTaskResult> {
|
|
|
210
296
|
if (!guard(current)) return undefined;
|
|
211
297
|
owned.ok = true;
|
|
212
298
|
// next 의 옛 근거는 버리고 디스크의 근거로 «교체»한다 — 정정된 근거 옆에 옛 근거가 되살아나지 않게.
|
|
213
|
-
const { pr: _stalePr, goalId: _staleGoal, ...base } = next;
|
|
214
|
-
return { ...base, ...(current?.pr !== undefined ? { pr: current.pr } : {}), ...(current?.goalId !== undefined ? { goalId: current.goalId } : {}) };
|
|
299
|
+
const { pr: _stalePr, goalId: _staleGoal, refSource: _staleSource, ...base } = next;
|
|
300
|
+
return { ...base, ...(current?.pr !== undefined ? { pr: current.pr } : {}), ...(current?.goalId !== undefined ? { goalId: current.goalId } : {}), ...(current?.refSource ? { refSource: current.refSource } : {}) };
|
|
215
301
|
});
|
|
216
302
|
if (!owned.ok) throw new TaskCardSupersededError(next.id);
|
|
217
303
|
};
|
|
218
304
|
writeCard(path, card);
|
|
219
|
-
try { debug.log('task-agent', 'handed', { taskId: id, seat: card.seat ?? null, checklistId: card.checklistId ?? null, mode }); } catch { /* fail-soft */ }
|
|
220
|
-
if (mode === 'shadow') return { card, move, mode, launched: false };
|
|
305
|
+
try { debug.log('task-agent', 'handed', { taskId: id, seat: card.seat ?? null, checklistId: card.checklistId ?? null, mode, projectId: project?.id ?? null, targetIsGit }); } catch { /* fail-soft */ }
|
|
306
|
+
if (mode === 'shadow') return { card, move, mode, launched: false, ...(project ? { cwd: project.target } : {}) };
|
|
221
307
|
if (!opts.launcher) throw new Error('--live 인데 launcher 가 없다');
|
|
308
|
+
const runId = mintRunId();
|
|
309
|
+
const launchId = `tl-${randomBytes(6).toString('hex')}`;
|
|
310
|
+
const context: TaskLaunchContext = { runId, launchId, env: { [HARNESS_RUN_ID_ENV]: runId } };
|
|
311
|
+
let receipt: void | TaskLaunchReceipt;
|
|
222
312
|
try {
|
|
223
|
-
await opts.launcher(move.command
|
|
313
|
+
receipt = await opts.launcher(move.command!, project?.target, context);
|
|
224
314
|
} catch (error) {
|
|
225
315
|
const reason = error instanceof Error ? error.message : String(error);
|
|
226
316
|
const failed: TaskCard = { ...card, status: 'launch-failed', history: [{ at: (opts.now ?? (() => new Date()))().toISOString(), event: 'launch-failed', detail: reason }] };
|
|
@@ -228,8 +318,25 @@ export async function handTask(opts: HandTaskOptions): Promise<HandTaskResult> {
|
|
|
228
318
|
try { debug.log('task-agent', 'launch-failed', { taskId: id, seat: card.seat ?? null, reason }); } catch { /* fail-soft */ }
|
|
229
319
|
throw new TaskLaunchError(failed, reason);
|
|
230
320
|
}
|
|
231
|
-
const
|
|
321
|
+
const launchedAt = (opts.now ?? (() => new Date()))().toISOString();
|
|
322
|
+
const receivedRunId = receipt && typeof receipt.runId === 'string' ? receipt.runId.trim() : '';
|
|
323
|
+
// 넘긴 런 id 와 «같은» 영수증만 — 다른 id 면 env 로 넘긴 런과 카드가 어긋난다(묶지 않는다).
|
|
324
|
+
const boundRunId = receivedRunId && receivedRunId === runId && normalizeRunId(receivedRunId) === receivedRunId ? receivedRunId : undefined;
|
|
325
|
+
if (receivedRunId && !boundRunId) {
|
|
326
|
+
try { debug.log('task-agent', 'card-run-unbound', { card: id, launchId, expected: runId, received: receivedRunId }); } catch { /* fail-soft */ }
|
|
327
|
+
}
|
|
328
|
+
const launched: TaskCard = {
|
|
329
|
+
...card, status: 'launched',
|
|
330
|
+
...(boundRunId ? { runId: boundRunId, launchId } : {}),
|
|
331
|
+
history: [
|
|
332
|
+
{ at: launchedAt, event: 'launch', detail: move.command!.join(' ') },
|
|
333
|
+
...(boundRunId ? [{ at: launchedAt, event: 'run-bound', detail: boundRunId, runId: boundRunId }] : []),
|
|
334
|
+
],
|
|
335
|
+
};
|
|
232
336
|
writeCard(path, launched);
|
|
233
|
-
try { debug.log('task-agent', 'launched', { taskId: id, seat: card.seat ?? null }); } catch { /* fail-soft */ }
|
|
234
|
-
|
|
337
|
+
try { debug.log('task-agent', 'launched', { taskId: id, seat: card.seat ?? null, runId: boundRunId ?? null }); } catch { /* fail-soft */ }
|
|
338
|
+
if (boundRunId) {
|
|
339
|
+
try { debug.log('task-agent', 'card-run-bound', { card: id, runId: boundRunId, launchId }); } catch { /* fail-soft */ }
|
|
340
|
+
}
|
|
341
|
+
return { card: launched, move, mode, launched: true, ...(project ? { cwd: project.target } : {}) };
|
|
235
342
|
}
|
|
@@ -14,6 +14,7 @@ import { debug } from '../../debug/log.js';
|
|
|
14
14
|
import { effectiveInstanceRoot } from '../../instance/resolve.js';
|
|
15
15
|
import { collectPodArtifacts } from './pod-artifact-return.js';
|
|
16
16
|
import { podBunCacheVolume } from './pod-bun-cache.js';
|
|
17
|
+
import { podHostDirsInit, podInstallSlotsVolume } from './pod-install-slots.js';
|
|
17
18
|
import { type PodPoolMember, PodPoolScheduler, resolvePodPoolSpec, parsePodPool, checkPodPool, syncPoolImages, type PoolKubectl } from './pod-pool.js';
|
|
18
19
|
import { podSkillsDigest, readSkillEnvFiles, resolvePodSkills } from './pod-skills.js';
|
|
19
20
|
import { podSourceScript, type PodSource } from './pod-source-receive.js';
|
|
@@ -37,6 +38,54 @@ export const POD_LITE_IMAGE = 'elanous-harness-lite:local';
|
|
|
37
38
|
export const POD_LITE_MEMORY_LIMIT = '2Gi';
|
|
38
39
|
|
|
39
40
|
/** 요청은 한도를 넘을 수 없다(쿠버네티스가 Job 을 거부한다) — lite 2Gi 처럼 한도가 기본 요청보다 작으면 한도로 맞춘다. */
|
|
41
|
+
/** The command Job container's CPU limit when the caller names none (GATE-SPEED A3①: unchanged default). */
|
|
42
|
+
export const POD_COMMAND_CPU_LIMIT_DEFAULT = '4';
|
|
43
|
+
|
|
44
|
+
/** Container CPU request/limit override. Omitted fields keep the defaults (request `POD_CHILD_REQUESTS.cpu` · limit 4). */
|
|
45
|
+
export interface PodCpu { request?: string; limit?: string }
|
|
46
|
+
|
|
47
|
+
/** Kubernetes CPU quantity → millicores (`1` · `0.5` · `500m`); null when it is not a positive quantity we accept. */
|
|
48
|
+
export function podCpuMillis(value: string): number | null {
|
|
49
|
+
const m = /^(?:(\d+(?:\.\d{1,3})?)|(\d+)m)$/.exec(value.trim());
|
|
50
|
+
if (!m) return null;
|
|
51
|
+
const millis = m[1] !== undefined ? Math.round(Number(m[1]) * 1000) : Number(m[2]);
|
|
52
|
+
return Number.isSafeInteger(millis) && millis > 0 ? millis : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Resolve a CPU override against the defaults; throws when a value is malformed or the request exceeds the limit (k8s would refuse the Job). */
|
|
56
|
+
export function resolvePodCpu(cpu: PodCpu | undefined): { request: string; limit: string } {
|
|
57
|
+
const request = cpu?.request?.trim() || POD_CHILD_REQUESTS.cpu;
|
|
58
|
+
const limit = cpu?.limit?.trim() || POD_COMMAND_CPU_LIMIT_DEFAULT;
|
|
59
|
+
const r = podCpuMillis(request), l = podCpuMillis(limit);
|
|
60
|
+
if (r === null) throw new Error(`pod cpu request is not a positive CPU quantity: ${request}`);
|
|
61
|
+
if (l === null) throw new Error(`pod cpu limit is not a positive CPU quantity: ${limit}`);
|
|
62
|
+
if (r > l) throw new Error(`pod cpu request ${request} exceeds limit ${limit}`);
|
|
63
|
+
return { request, limit };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Config value → CPU override (release gate `release.loop.gatePodCpu`). A scalar (`2` · `"1500m"`) sets request = limit;
|
|
67
|
+
* `{ request, limit }` sets either side. Absent/empty = undefined (request 1 / limit 4). Malformed values throw. */
|
|
68
|
+
export function parsePodCpu(value: unknown, label = 'cpu'): PodCpu | undefined {
|
|
69
|
+
const text = (v: unknown): string | undefined => {
|
|
70
|
+
if (v === undefined || v === null || v === '') return undefined;
|
|
71
|
+
if (typeof v !== 'number' && typeof v !== 'string') throw new Error(`${label} must be a CPU quantity or { request, limit }`);
|
|
72
|
+
return String(v).trim() || undefined;
|
|
73
|
+
};
|
|
74
|
+
let cpu: PodCpu;
|
|
75
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
76
|
+
const { request, limit, ...rest } = value as Record<string, unknown>;
|
|
77
|
+
if (Object.keys(rest).length) throw new Error(`${label} has unknown keys: ${Object.keys(rest).join(', ')}`);
|
|
78
|
+
const r = text(request), l = text(limit);
|
|
79
|
+
cpu = { ...(r ? { request: r } : {}), ...(l ? { limit: l } : {}) };
|
|
80
|
+
} else {
|
|
81
|
+
const both = text(value);
|
|
82
|
+
cpu = both ? { request: both, limit: both } : {};
|
|
83
|
+
}
|
|
84
|
+
if (!cpu.request && !cpu.limit) return undefined;
|
|
85
|
+
resolvePodCpu(cpu);
|
|
86
|
+
return cpu;
|
|
87
|
+
}
|
|
88
|
+
|
|
40
89
|
export function memoryRequestWithin(limit: string | undefined): string {
|
|
41
90
|
const gi = (value: string | undefined) => { const m = /^(\d+)Gi$/.exec(value ?? ''); return m ? Number(m[1]) : null; };
|
|
42
91
|
const request = gi(POD_CHILD_REQUESTS.memory);
|
|
@@ -128,8 +177,12 @@ export interface PodCommandJobInput {
|
|
|
128
177
|
clone?: boolean;
|
|
129
178
|
hostMirror?: string;
|
|
130
179
|
bunCache?: string;
|
|
180
|
+
/** GATE-INSTALL-CACHE — hostPath of the node-wide install tokens (release gate shards only · `pod-install-slots.ts`). */
|
|
181
|
+
installSlots?: string;
|
|
131
182
|
/** 컨테이너 메모리 한도(기본 16Gi) — 게이트가 파일 하나 격리 Job 에 올린다. */
|
|
132
183
|
memoryLimit?: string;
|
|
184
|
+
/** GATE-SPEED A3①: CPU request/limit override (release gate shards only). Omitted = request 1 / limit 4. */
|
|
185
|
+
cpu?: PodCpu;
|
|
133
186
|
source?: PodSource;
|
|
134
187
|
deadlineSeconds: number;
|
|
135
188
|
runId?: string;
|
|
@@ -147,6 +200,10 @@ export function podCommandJobManifest(o: PodCommandJobInput): Record<string, unk
|
|
|
147
200
|
}
|
|
148
201
|
const grok = o.llm === 'grok';
|
|
149
202
|
const bunCache = o.bunCache ? podBunCacheVolume(o.bunCache) : undefined;
|
|
203
|
+
const installSlots = o.installSlots ? podInstallSlotsVolume(o.installSlots) : undefined;
|
|
204
|
+
// hostPath directories the uid-1000 child writes: a root init container hands them over (DirectoryOrCreate is root 0755).
|
|
205
|
+
const hostDirMounts = [...(bunCache ? [bunCache.volumeMount] : []), ...(installSlots ? [installSlots.volumeMount] : [])];
|
|
206
|
+
const cpu = resolvePodCpu(o.cpu);
|
|
150
207
|
const quotedArgs = o.command.map(bashSingleQuote).join(' ');
|
|
151
208
|
const credLines = [
|
|
152
209
|
...(grok ? ['mkdir -p ~/.grok && install -m 600 /creds/grok-auth.json ~/.grok/auth.json'] : []),
|
|
@@ -182,26 +239,30 @@ export function podCommandJobManifest(o: PodCommandJobInput): Record<string, unk
|
|
|
182
239
|
spec: {
|
|
183
240
|
restartPolicy: 'Never',
|
|
184
241
|
securityContext: { runAsUser: 1000, fsGroup: 1000 },
|
|
185
|
-
initContainers: [
|
|
242
|
+
initContainers: [
|
|
243
|
+
{ name: 'isolation-gate', image: o.image, imagePullPolicy: o.imagePullPolicy ?? 'Never', command: ['bash', '-c'], args: [GATE] },
|
|
244
|
+
...(hostDirMounts.length ? [podHostDirsInit(o.image, o.imagePullPolicy ?? 'Never', hostDirMounts)] : []),
|
|
245
|
+
],
|
|
186
246
|
containers: [{
|
|
187
247
|
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:
|
|
248
|
+
resources: { requests: { ...POD_CHILD_REQUESTS, cpu: cpu.request, memory: memoryRequestWithin(o.memoryLimit) }, limits: { memory: o.memoryLimit ?? '16Gi', cpu: cpu.limit } },
|
|
189
249
|
command: ['bash', '-c'], args: [script],
|
|
190
250
|
env: [
|
|
191
251
|
...(o.runId ? [{ name: 'ELANOUS_RUN_ID', value: o.runId }] : []),
|
|
192
252
|
{ name: 'ELANOUS_SUBSTRATE', value: 'pod' },
|
|
193
253
|
{ name: 'ELANOUS_POD_NAME', valueFrom: { fieldRef: { fieldPath: 'metadata.name' } } },
|
|
194
254
|
],
|
|
195
|
-
...(secretKeys || o.hostMirror ||
|
|
255
|
+
...(secretKeys || o.hostMirror || hostDirMounts.length ? { volumeMounts: [
|
|
196
256
|
...(secretKeys ? [{ name: 'creds', mountPath: '/creds', readOnly: true }] : []),
|
|
197
257
|
...(o.hostMirror ? [{ name: 'host-mirror', mountPath: '/host-mirror', readOnly: true }] : []),
|
|
198
|
-
...
|
|
258
|
+
...hostDirMounts,
|
|
199
259
|
] } : {}),
|
|
200
260
|
}],
|
|
201
|
-
...(secretKeys || o.hostMirror ||
|
|
261
|
+
...(secretKeys || o.hostMirror || hostDirMounts.length ? { volumes: [
|
|
202
262
|
...(secretKeys ? [{ name: 'creds', secret: { secretName: `${o.name}-creds`, defaultMode: 0o400 } }] : []),
|
|
203
263
|
...(o.hostMirror ? [{ name: 'host-mirror', hostPath: { path: o.hostMirror, type: 'Directory' } }] : []),
|
|
204
264
|
...(bunCache ? [bunCache.volume] : []),
|
|
265
|
+
...(installSlots ? [installSlots.volume] : []),
|
|
205
266
|
] } : {}),
|
|
206
267
|
},
|
|
207
268
|
},
|
|
@@ -270,8 +331,12 @@ export interface RunPodCommandOptions {
|
|
|
270
331
|
clone?: boolean;
|
|
271
332
|
hostMirror?: string;
|
|
272
333
|
bunCache?: string;
|
|
334
|
+
/** GATE-INSTALL-CACHE — hostPath of the node-wide install tokens (absolute). */
|
|
335
|
+
installSlots?: string;
|
|
273
336
|
/** 컨테이너 메모리 한도(기본 16Gi · lite 면 2Gi). */
|
|
274
337
|
memoryLimit?: string;
|
|
338
|
+
/** GATE-SPEED A3①: CPU request/limit override — only the release gate passes it. Omitted = request 1 / limit 4. */
|
|
339
|
+
cpu?: PodCpu;
|
|
275
340
|
/** POD7: 호출부가 «네트워크 스킬만 쓰는 명령»이라고 말할 때만 lite 이미지 ⊕ 2Gi. */
|
|
276
341
|
lite?: boolean;
|
|
277
342
|
source?: PodSource;
|
|
@@ -284,6 +349,7 @@ export interface RunPodCommandOptions {
|
|
|
284
349
|
name?: string;
|
|
285
350
|
pollMs?: number;
|
|
286
351
|
kubectl?: Kubectl;
|
|
352
|
+
/** A caller-owned scheduler is self-capped: acquires count only its own Jobs, never cluster occupancy (POOLGLOBAL-REG). */
|
|
287
353
|
poolScheduler?: PodPoolScheduler;
|
|
288
354
|
checkPool?: typeof checkPodPool;
|
|
289
355
|
syncImages?: typeof syncPoolImages;
|
|
@@ -356,6 +422,10 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
356
422
|
if (hostMirror && !isAbsolute(hostMirror)) throw new Error('pod.hostMirror must be an absolute directory path');
|
|
357
423
|
const bunCache = options.bunCache?.trim() || undefined;
|
|
358
424
|
if (bunCache && !isAbsolute(bunCache)) throw new Error('pod.bunCache must be an absolute directory path');
|
|
425
|
+
const installSlots = options.installSlots?.trim() || undefined;
|
|
426
|
+
if (installSlots && !isAbsolute(installSlots)) throw new Error('pod installSlots must be an absolute directory path');
|
|
427
|
+
// Fail before any Secret/Job is applied — a bad quantity must not reach the cluster.
|
|
428
|
+
if (options.cpu) resolvePodCpu(options.cpu);
|
|
359
429
|
|
|
360
430
|
let member: PodPoolMember | null = null;
|
|
361
431
|
let pool = options.poolScheduler;
|
|
@@ -366,12 +436,16 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
366
436
|
const members = parsePodPool(spec);
|
|
367
437
|
const checked = (options.checkPool ?? checkPodPool)(members, baseKubectl as PoolKubectl);
|
|
368
438
|
if (!checked.ok) throw new Error('pod command: 풀의 노드가 하나도 준비되지 않았다');
|
|
369
|
-
|
|
439
|
+
// Occupancy reads go through the same kubectl as the readiness check (an injected one included).
|
|
440
|
+
pool = new PodPoolScheduler(checked.ready, { kubectl: baseKubectl as PoolKubectl });
|
|
370
441
|
}
|
|
371
442
|
}
|
|
372
443
|
if (pool) {
|
|
444
|
+
// POOLGLOBAL-REG: a caller that hands in its own scheduler (the release gate fans its shards over one) holds its own
|
|
445
|
+
// concurrency cap — its Jobs count against the spec caps, not the cluster's goal Pods (#24253 → 0.2.15 gate ran 1 shard).
|
|
446
|
+
const selfCapped = options.poolScheduler !== undefined;
|
|
373
447
|
for (;;) {
|
|
374
|
-
member = await pool.tryAcquire();
|
|
448
|
+
member = await pool.tryAcquire(undefined, undefined, { selfCapped });
|
|
375
449
|
if (member) break;
|
|
376
450
|
await sleep(options.pollMs ?? 15_000);
|
|
377
451
|
}
|
|
@@ -426,7 +500,7 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
426
500
|
name, namespace, launch, image: imageRef ?? image,
|
|
427
501
|
...(imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}),
|
|
428
502
|
repoUrl, command, skills, ...(llm ? { llm } : {}), ...(options.clone ? { clone: true } : {}),
|
|
429
|
-
...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), ...(bunCache ? { bunCache } : {}), ...(memoryLimit ? { memoryLimit } : {}), deadlineSeconds,
|
|
503
|
+
...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), ...(bunCache ? { bunCache } : {}), ...(installSlots ? { installSlots } : {}), ...(memoryLimit ? { memoryLimit } : {}), ...(options.cpu ? { cpu: options.cpu } : {}), deadlineSeconds,
|
|
430
504
|
...(options.runId ? { runId: options.runId } : {}),
|
|
431
505
|
});
|
|
432
506
|
kubectl(['-n', namespace, 'delete', 'job', '-l', own, '--ignore-not-found']);
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/** STOP-RECORD stop_class values used by the card terminal classifier. */
|
|
2
|
+
export const STOP_CLASS_POD_FAILURE = 'pod-failure' as const;
|
|
3
|
+
export const STOP_CLASS_NO_LAUNCH = 'no-launch' as const;
|
|
4
|
+
|
|
1
5
|
/** Executor kinds of the dev child's `--json` result line (`src/index.ts` writeStdoutJson · DevExecutor.kind). */
|
|
2
6
|
const CHILD_RESULT_KINDS = new Set(['self', 'external']);
|
|
3
7
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// GATE-INSTALL-CACHE (0.2.20) — a node-wide throttle for the release-gate shards' `bun install`.
|
|
2
|
+
//
|
|
3
|
+
// Cause, measured on the 0.2.19 gate (`elanous logs --category release-loop.gate --event pod-shard --all --include-test`):
|
|
4
|
+
// · no cache is «not» the cause — the harness image carries a warm Bun cache, and a Pod that installed alone
|
|
5
|
+
// (10-07 10:48–10:55 · 12:13 · 12:16) took 2.4–5.0 s with `pod-bun-cache source=none`;
|
|
6
|
+
// · the same install took 150–340 s when 24 shard Pods (or ~20 isolation Pods) started together on the one 32-CPU
|
|
7
|
+
// node-b node (09:3x round 1: 221–337 s · 11:4x round 3: 150–240 s · 10:34 isolation wave: 124–188 s).
|
|
8
|
+
// ⇒ concurrent installs thrash the node (24 × copying node_modules out of the image cache + CPU), so a shared
|
|
9
|
+
// hostPath Bun cache would not help (the bytes are already local) — fewer installs «at once» does.
|
|
10
|
+
//
|
|
11
|
+
// Concurrency safety: the hostPath holds only throttle tokens (`slot-<n>` directories), never packages. Each Pod still
|
|
12
|
+
// installs into its own container (its own node_modules, its own image-local Bun cache), so correctness never depends on
|
|
13
|
+
// the token: a lost token (Pod killed mid-install) only lowers throughput until it goes stale (`staleSeconds`), a doubled
|
|
14
|
+
// token (two waiters reclaiming one stale slot in the same instant) only lets one extra install run, and a waiter that
|
|
15
|
+
// cannot get a token within `waitSeconds` — or finds the directory unwritable — installs anyway (fail-open).
|
|
16
|
+
// `mkdir` is atomic on one filesystem, and every Pod on a node sees the same hostPath directory.
|
|
17
|
+
|
|
18
|
+
/** Default hostPath of the install tokens on each gate node. */
|
|
19
|
+
export const POD_INSTALL_SLOTS_HOST_PATH = '/var/lib/elanous/gate-install-slots';
|
|
20
|
+
/** Default concurrent installs per node (`release.loop.gatePodInstallSlots` · 0 = off). Alone ≈ 3–5 s ⇒ 24 shards ≈ 6 waves. */
|
|
21
|
+
export const POD_INSTALL_SLOTS_DEFAULT = 4;
|
|
22
|
+
export const POD_INSTALL_SLOTS_MOUNT = '/gate-install-slots';
|
|
23
|
+
|
|
24
|
+
/** hostPath volume ⊕ mount for the tokens. `DirectoryOrCreate` makes a root-owned 0755 directory, so the Pod also needs
|
|
25
|
+
* {@link podHostDirsInit} (the child runs as uid 1000). */
|
|
26
|
+
export function podInstallSlotsVolume(hostPath: string = POD_INSTALL_SLOTS_HOST_PATH) {
|
|
27
|
+
return {
|
|
28
|
+
volume: { name: 'install-slots', hostPath: { path: hostPath, type: 'DirectoryOrCreate' } },
|
|
29
|
+
volumeMount: { name: 'install-slots', mountPath: POD_INSTALL_SLOTS_MOUNT, readOnly: false },
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A root init container that hands the mounted hostPath directories to the child's uid. Without it a `DirectoryOrCreate`
|
|
35
|
+
* hostPath stays root:root 0755 and the child's `[ -w … ]` guard silently skips it (the Bun cache option was a no-op).
|
|
36
|
+
*/
|
|
37
|
+
export function podHostDirsInit(image: string, imagePullPolicy: string, mounts: ReadonlyArray<{ name: string; mountPath: string; readOnly: boolean }>) {
|
|
38
|
+
const paths = mounts.map((mount) => `'${mount.mountPath.replace(/'/g, `'\\''`)}'`).join(' ');
|
|
39
|
+
return {
|
|
40
|
+
name: 'host-dirs', image, imagePullPolicy,
|
|
41
|
+
securityContext: { runAsUser: 0 },
|
|
42
|
+
command: ['sh', '-c'],
|
|
43
|
+
// Never fails the Pod: an unchanged directory only means the child falls back (no cache · no throttle).
|
|
44
|
+
args: [`for d in ${paths}; do mkdir -p "$d" && chown 1000:1000 "$d" && chmod 0775 "$d" || echo "[host-dirs] could not prepare $d"; done; exit 0`],
|
|
45
|
+
volumeMounts: mounts.map((mount) => ({ ...mount })),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const int = (value: number, label: string) => {
|
|
50
|
+
if (!Number.isSafeInteger(value) || value < 1) throw new Error(`invalid ${label}: ${value}`);
|
|
51
|
+
return value;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Bash that defines `install_slot_acquire` / `install_slot_release`. Acquire waits for one of `slots` tokens under `dir`
|
|
56
|
+
* (polling every `pollSeconds`, default 2 s), reclaims a token older than `staleSeconds` (minute granularity), and gives up after `waitSeconds` (install runs anyway).
|
|
57
|
+
* It prints `[gate] install-slot <n> waited <s>s` or `[gate] install-slot none …` for the shard log.
|
|
58
|
+
*/
|
|
59
|
+
export function installSlotScript(o: { dir?: string; slots: number; waitSeconds?: number; staleSeconds?: number; pollSeconds?: number }): string {
|
|
60
|
+
const dir = `'${(o.dir ?? POD_INSTALL_SLOTS_MOUNT).replace(/'/g, `'\\''`)}'`;
|
|
61
|
+
const slots = int(o.slots, 'install slots');
|
|
62
|
+
const wait = int(o.waitSeconds ?? 600, 'install slot wait');
|
|
63
|
+
const poll = o.pollSeconds ?? 2;
|
|
64
|
+
if (!Number.isFinite(poll) || poll <= 0 || poll > 60) throw new Error(`invalid install slot poll: ${poll}`);
|
|
65
|
+
const staleMinutes = Math.max(1, Math.ceil(int(o.staleSeconds ?? 900, 'install slot stale age') / 60));
|
|
66
|
+
return [
|
|
67
|
+
'install_slot=',
|
|
68
|
+
'install_slot_acquire() {',
|
|
69
|
+
` local d=${dir} t0 now n`,
|
|
70
|
+
' t0=$(date +%s)',
|
|
71
|
+
' if [ ! -d "$d" ] || [ ! -w "$d" ]; then echo "[gate] install-slot none (directory not writable)"; return 0; fi',
|
|
72
|
+
' while :; do',
|
|
73
|
+
` for n in $(seq 0 ${slots - 1}); do`,
|
|
74
|
+
' if mkdir "$d/slot-$n" 2>/dev/null; then',
|
|
75
|
+
' install_slot="$d/slot-$n"',
|
|
76
|
+
' echo "[gate] install-slot $n waited $(( $(date +%s) - t0 ))s"; return 0',
|
|
77
|
+
' fi',
|
|
78
|
+
// A token whose holder died (directory mtime older than the stale age · `find -mmin` is the same on GNU and BSD) is
|
|
79
|
+
// renamed away first (rename is atomic: one reclaimer wins) and the slot is tried again on the next pass.
|
|
80
|
+
` if [ -n "$(find "$d/slot-$n" -maxdepth 0 -mmin +${staleMinutes} 2>/dev/null)" ]; then mv "$d/slot-$n" "$d/stale-$n-$$" 2>/dev/null && rm -rf "$d/stale-$n-$$"; fi`,
|
|
81
|
+
' done',
|
|
82
|
+
' now=$(date +%s)',
|
|
83
|
+
` if [ $(( now - t0 )) -ge ${wait} ]; then echo "[gate] install-slot none (waited $(( now - t0 ))s)"; return 0; fi`,
|
|
84
|
+
` sleep ${poll}`,
|
|
85
|
+
' done',
|
|
86
|
+
'}',
|
|
87
|
+
'install_slot_release() { if [ -n "$install_slot" ]; then rm -rf "$install_slot"; install_slot=; fi; }',
|
|
88
|
+
].join('\n');
|
|
89
|
+
}
|