elanous 0.2.18 → 0.2.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → J5H7BvqgSI8DUjtRRcA_S}/_buildManifest.js +1 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1157-cba0e8553d35a980.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{726.83532d69452ee24b.js → 1470.98a6ab61ccb4131c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/171-23a877ed11720de9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2336-e830c777eb6bce04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9247-269e4464cc4b491c.js → 2375-e061bb24cd2b5cde.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{211-9c9090c59e2c5907.js → 2977-e38442e86289f1d3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{f6b7c9ba.64a895b8b2fedbdc.js → 2ea96958.e8c7e112de50b067.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3094.67339318c7f72a3a.js +2 -0
- package/apps/pwa/out/_next/static/chunks/338-ceb956612dcfaa86.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3395-835e4ceaa6335ba4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +5 -0
- package/apps/pwa/out/_next/static/chunks/3441-be82e3595aef3a20.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3689-89905039bf3e7257.js +1 -0
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3822-6f268bc26c6a84ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1268.0840ff91e083fcbd.js → 4323.c5dfb6f503304775.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4457-d7df81c90f9d0268.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4551-77dadae76f6cbace.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6110.1297d4093fdfbb0d.js → 4847.56f5bc454fbcc827.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/4978.6fd93b974441e87f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5012-f746f6549b869f91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7327-2d69fb321ba73389.js → 6062-73e68343a9814f6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/62-5bfed1e23790b745.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6320-9bc8561fde416e07.js +1 -0
- package/apps/pwa/out/_next/static/chunks/649-ca96d1c5d09e02b8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6726-85a7db59b288c53f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6735-33267c4630d0d963.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7633-141682015873beb6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{935.4ddaa99fc1d1126c.js → 7647.2fcd30d894d5ee30.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{f6231284-45726550aba08fca.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8067-143791231c99bd02.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8430-a10cfba8ef282d4e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8870-9614a213b990b10b.js +25 -0
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-12c2f30dea62291c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/976-4a9b4ebdca7c739a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9989-15cdf568bafa9fc8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/ae787e4c-e5b1ef00b2dbceca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-deef5ee191830f14.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-b193da485f9a14b2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-b6e1a79df0e214f4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-e84df4d907f703c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a5fb9a5db89ae4ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1499f481d7fc52d6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-c7cf753aeb648790.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-4118469e514e10fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-e968f7630654332a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-505915052f947022.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-c2f962fb4a3b06c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7b5ed0bd8a589ec4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-cf8193250dd42dd4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-cf3f5dfa35dea8e9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-5b29d9b06ceadade.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a15e7fcc283e9ca7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-d2915e7b70ff3d0e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-b70cd005d9169e25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-2595f0eba29f57e1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-c776322454156e36.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-77bbd17f4435aa10.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-049689e611ecc936.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-85954a4fb09c44eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-e773e552e1068ee6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5633c4bfa7684b4a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d829b92bd01b4994.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-061c317fc771becf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a45ece8865ed4b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-0be990618c0d56bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-bfea4865dc244b73.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d6ba8e337741484.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-76a88b55d816482e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-bba1542df59f61ce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4185c95689bb372e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b699e1a8d5d34b8b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-c225e338c31bf274.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-449353f70507ca1f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-90b889714102476d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-f1ee7fbeb7f96ddc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-d59a95e1434dfaa0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-71491a23414d1a47.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-de7112bdb65fa4fe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-feb4d0d88079660d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f17d887b40dd6b1d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-8da77be2192bcd5d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-72c3b9a5e9582b29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3afe4c8d0c48c39.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-b9308f2d8a931531.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-6f35f9d892fa630b.js +1 -0
- package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
- package/apps/pwa/out/_next/static/css/85f53c6f35d11382.css +1 -0
- package/apps/pwa/out/_next/static/css/d3427996da11e480.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +23 -23
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +23 -23
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +23 -23
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +22 -22
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +22 -22
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +23 -23
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +22 -22
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +23 -23
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +23 -23
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +22 -22
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +25 -24
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +23 -23
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +23 -23
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +22 -22
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +24 -23
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +23 -23
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +23 -23
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +28 -27
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +22 -22
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +22 -22
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +23 -23
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +23 -23
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +22 -22
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +22 -22
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +22 -22
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +23 -23
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +23 -23
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +22 -22
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +23 -23
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +23 -23
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +23 -23
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +23 -23
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +23 -23
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +22 -22
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +23 -23
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +23 -23
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +23 -23
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +23 -23
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +23 -23
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +23 -23
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +23 -23
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +23 -23
- package/graphs/ops/guardian.yaml +26 -0
- package/graphs/ops/recipes.yaml +3 -0
- package/package.json +1 -1
- package/scripts/coord-channel-watch.sh +2 -2
- package/scripts/discord-seat-probe.ts +3 -1
- package/scripts/lib/pch-regress-map.ts +23 -0
- package/scripts/morning-report.ts +18 -4
- package/scripts/pch-regress.ts +88 -0
- package/scripts/release-loop/gate-diff.ts +18 -0
- package/scripts/release-loop/gate-node.ts +338 -46
- package/scripts/release-loop/gate-partial.ts +106 -0
- package/scripts/release-loop/gate-timing.ts +120 -0
- package/scripts/release-loop/resume-release.ts +64 -5
- package/scripts/release-loop/shard-plan.ts +65 -0
- package/scripts/release-loop/unattended-release.ts +2 -0
- package/scripts/video/scene-table.ts +159 -0
- package/src/acp/core-turn-bridge.ts +39 -2
- package/src/away/presence.ts +40 -0
- package/src/away/release-watch.ts +255 -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/guardian-cli.ts +49 -0
- package/src/cli/intake-cli.ts +13 -7
- package/src/cli/log-event-names.ts +1 -0
- package/src/cli/pod-cli.ts +5 -1
- package/src/cli/pty-drive-cli.ts +14 -3
- package/src/cli/release-cli.ts +32 -7
- package/src/cli/setup-cli.ts +31 -1
- package/src/cli/tasks-cli.ts +104 -11
- package/src/context-bus/context-now-public.ts +1 -1
- package/src/context-bus/context-now-surfaces.ts +16 -6
- package/src/context-bus/context-now.ts +97 -3
- package/src/core-turn/short-question.ts +100 -0
- package/src/dashboard/dashboard-status-lines.ts +24 -4
- package/src/dashboard/index.ts +50 -5
- package/src/dashboard/input/slash-executor.ts +6 -2
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +63 -36
- package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +21 -8
- package/src/dashboard/slash-runtime/help-groups.ts +44 -0
- package/src/dashboard/slash-runtime/model-choices.ts +125 -0
- package/src/domains/guardian-tick.ts +138 -0
- package/src/domains/outbound-alert.ts +44 -13
- 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/runner.ts +25 -5
- package/src/harness/harness-cli-command.ts +2 -0
- package/src/harness/harness-salvage-cli.ts +78 -0
- package/src/harness/harness-worktree-add.ts +68 -0
- package/src/harness/ledger-write-guard.ts +6 -16
- package/src/hq/hq.ts +6 -1
- package/src/index.ts +52 -3
- 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/http-server.ts +16 -5
- package/src/nexus/api/loop-edges.ts +290 -17
- package/src/nexus/api/ops-api.ts +96 -28
- package/src/nexus/index.ts +7 -0
- package/src/release-loop/checklist.ts +2 -0
- package/src/release-loop/feature-store.ts +54 -7
- package/src/release-loop/light-rc.ts +173 -0
- package/src/release-loop/placement.ts +51 -17
- package/src/release-loop/rubric.ts +57 -1
- package/src/self-dev/dev-cli.ts +36 -0
- package/src/self-dev/orchestrate.ts +1 -0
- package/src/self-dev/pr-preview.ts +179 -0
- package/src/self-dev/shard-run.ts +23 -7
- package/src/self-implement/gate-cli.ts +29 -8
- 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 +2 -1
- package/src/self-implement/salvage-branches.ts +254 -0
- package/src/task-agent/actions.ts +63 -2
- package/src/task-agent/board.ts +132 -0
- package/src/task-agent/card-evidence.ts +203 -0
- package/src/task-agent/completion-evidence.ts +126 -0
- package/src/task-agent/judge.ts +18 -0
- package/src/task-agent/live-moves.ts +176 -0
- package/src/task-agent/mission.ts +32 -6
- package/src/task-agent/shadow.ts +183 -15
- package/src/task-agent/task-hand.ts +117 -15
- package/src/task-orchestrator/surfaces/pod-command-job.ts +64 -4
- package/src/task-orchestrator/surfaces/pod-lease.ts +142 -13
- package/src/task-orchestrator/surfaces/pod-pool.ts +13 -5
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +2 -2
- package/src/telegram-away-command.ts +38 -0
- package/src/telegram-commands.ts +11 -0
- package/src/telegram-report-log-probe.ts +21 -0
- package/src/telegram-report.ts +27 -3
- package/src/tui-client/dashboard-session.ts +1 -0
- package/src/user-config.ts +134 -2
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-30dff22c9e1f3073.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
- package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +0 -1
- package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.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/6158.af2e93b7adff709b.js +0 -2
- package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
- package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +0 -1
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d92f7ee3994e84c7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-4733c967b56a4277.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-d22fd849cc22f104.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-bb742f995c6769e7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-02b073a2ac793b83.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/chat/page-8a9b3594bc23289b.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/control/page-308111abadf35670.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/design-check/page-5d97b2901e0d1ea6.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/exec/page-8c3d8e63ae2e1c98.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-9b72a07d424701c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a572f58cd3ca5864.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-be5ea965a07fbc41.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/market/page-fa244bd146a00ae8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-44d56d6391965a6a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-83a700d4c754d802.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-7b3170f374f77b9d.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/reflection/page-1467047104141297.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/sessions/page-102310930791197a.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/settings/page-6ed0b73bae8351d8.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/setup/layout-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-43336055451f6e37.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-3cc47dca0922ab9a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a77ee355a2304ff0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-b17cd46cf0e9b8cf.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/trace/page-7bf54c9bb9274af8.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/workspace/page-134da468538baf48.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-c2782b330447ffb6.js +0 -1
- package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +0 -1
- /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → J5H7BvqgSI8DUjtRRcA_S}/_ssgManifest.js +0 -0
package/src/task-agent/shadow.ts
CHANGED
|
@@ -5,14 +5,18 @@
|
|
|
5
5
|
* - 입력은 슈퍼바이저 결과에 «이미 실려 있는» 값만 쓴다(네트워크 호출 0): 종료 어휘 ·
|
|
6
6
|
* PR 번호/병합 여부(`prNumber`·`merged`). 리뷰 판정·must-fix 는 결과에 없으므로 비운다
|
|
7
7
|
* («통과»로 읽지 않는다 — 판단부가 review 수를 고른다).
|
|
8
|
-
* -
|
|
9
|
-
* 설정 `taskAgent.mode=live`
|
|
8
|
+
* - 슈퍼바이저의 기본 호출은 shadow: «했을 수»만 기록하고 명령·복사는 하나도 돌리지 않는다.
|
|
9
|
+
* 전달 과제의 명시적 live 호출만 파일을 복사한다. 설정 `taskAgent.mode=live` 는 기본 호출에 먹지 않는다.
|
|
10
10
|
* - 관측: `debug.log('task-agent', 'shadow-move', …)` → `elanous logs --category task-agent`.
|
|
11
11
|
*/
|
|
12
|
+
import { lstatSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
|
|
13
|
+
import { extname, join, relative, resolve } from 'node:path';
|
|
12
14
|
import { debug } from '../debug/log.js';
|
|
15
|
+
import { readTaskAgentState, readTaskCard, taskAgentStatePath, type CompletionKind, type TaskCard } from './task-hand.js';
|
|
13
16
|
import type { SupervisorJobResult, SupervisorStopReason } from '../self-dev/run-supervisor.js';
|
|
14
|
-
import { executeNextAction, type NextAction } from './actions.js';
|
|
17
|
+
import { executeNextAction, type DeliveryKind, type NextAction } from './actions.js';
|
|
15
18
|
import { judgeNextMove, type TaskJudgeInput, type TaskJudgement } from './judge.js';
|
|
19
|
+
import { configuredTaskAgentLiveMoves, executeLiveGreenProposal, executeLiveReview, type LiveMoveDeps, type LiveMoveResult, type TaskAgentLiveMove } from './live-moves.js';
|
|
16
20
|
|
|
17
21
|
export interface TaskAgentShadowInput {
|
|
18
22
|
runId: string | null;
|
|
@@ -27,23 +31,42 @@ export interface TaskAgentShadowMove {
|
|
|
27
31
|
executorKind: TaskJudgement['executorKind'] | null;
|
|
28
32
|
variant: TaskJudgement['executorVariant'] | null;
|
|
29
33
|
reason: string;
|
|
30
|
-
/** 실행부 결과 — shadow 경로에서는
|
|
34
|
+
/** 실행부 결과 — 기본 shadow 경로에서는 'shadow'(실행부 밖의 수면 null). */
|
|
31
35
|
executorResult: string | null;
|
|
32
36
|
/** 실행부가 남긴 «했을 수» 문면. */
|
|
33
37
|
wouldDo: string | null;
|
|
34
38
|
pr: number | null;
|
|
39
|
+
/** TA-JUDGE-LIVE-SAFE — `taskAgent.liveMoves` 가 이 수를 실제로 했을 때만 실린다(없으면 종전 그대로 그림자). */
|
|
40
|
+
liveMove?: LiveMoveResult;
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
export interface TaskAgentShadowDeps {
|
|
38
44
|
log?: (category: string, event: string, data: Record<string, unknown>) => void;
|
|
39
45
|
/** 시험용 — shadow 실행부가 명령을 부르면 여기로 온다(정상이면 0회). */
|
|
40
46
|
command?: (args: string[]) => Promise<{ status: number; stdout: string; stderr?: string }>;
|
|
47
|
+
readCard?: (taskId: string) => TaskCard | undefined;
|
|
48
|
+
/** Explicit live invocation only; supervisor's default hook always uses shadow. */
|
|
49
|
+
mode?: 'shadow' | 'live';
|
|
50
|
+
/** TA-JUDGE-LIVE-SAFE — 실제로 할 안전한 수(`review`·`propose-green`). 비면 설정 `taskAgent.liveMoves` 를 읽는다. */
|
|
51
|
+
liveMoves?: ReadonlySet<TaskAgentLiveMove>;
|
|
52
|
+
/** live 수의 부작용(리뷰 띄우기·PR 머리 조회·상태 파일) — 시험은 주입한다. */
|
|
53
|
+
live?: LiveMoveDeps;
|
|
41
54
|
}
|
|
42
55
|
|
|
43
|
-
/**
|
|
44
|
-
|
|
56
|
+
/** 파일 산출물로 전달(elanous-out)하는 종결 종류 — 그 밖의 code-pr 밖 종류는 확인 증거를 판단부에서 기다린다. */
|
|
57
|
+
const DELIVERY_KINDS: ReadonlySet<CompletionKind> = new Set<CompletionKind>(['research-report', 'artifact', 'content']);
|
|
58
|
+
function isDeliveryKind(kind: CompletionKind | undefined): kind is DeliveryKind {
|
|
59
|
+
return kind !== undefined && DELIVERY_KINDS.has(kind);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 슈퍼바이저 결과에서 판단부 입력을 만든다 — 추가 조회 없이 이미 실린 값만.
|
|
64
|
+
* `completion` 은 카드의 종결 종류(§A4b③) — 있으면 판단부 입력에 싣는다(비면 종전 입력 그대로).
|
|
65
|
+
*/
|
|
66
|
+
export function shadowJudgeInput(stopReason: SupervisorStopReason, results: readonly SupervisorJobResult[], completion?: CompletionKind): TaskJudgeInput {
|
|
45
67
|
const withPr = results.filter((result) => typeof result.prNumber === 'number' && result.prNumber > 0);
|
|
46
68
|
const input: TaskJudgeInput = { stopReason };
|
|
69
|
+
if (completion !== undefined) input.completion = completion;
|
|
47
70
|
if (withPr.length > 0) {
|
|
48
71
|
input.pr = withPr[0]!.prNumber!;
|
|
49
72
|
// 병합 여부만 결과에 실린다 — 닫힘(CLOSED)은 여기서 모른다. 병합이 아니면 OPEN 으로 둔다(판단부는 MERGED 만 본다).
|
|
@@ -52,24 +75,117 @@ export function shadowJudgeInput(stopReason: SupervisorStopReason, results: read
|
|
|
52
75
|
return input;
|
|
53
76
|
}
|
|
54
77
|
|
|
78
|
+
export function selectDeliveryCandidates(worktreePath: string, kind: DeliveryKind): string[] {
|
|
79
|
+
const allowed = kind === 'research-report' ? new Set(['.md']) : new Set(['.md', '.html', '.pdf', '.png', '.pptx']);
|
|
80
|
+
try {
|
|
81
|
+
const root = realpathSync(worktreePath);
|
|
82
|
+
const gitMarker = join(root, '.git');
|
|
83
|
+
let gitPointer = '';
|
|
84
|
+
try { if (lstatSync(gitMarker).isFile()) gitPointer = readFileSync(gitMarker, 'utf8').trim(); }
|
|
85
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
|
|
86
|
+
const gitDir = gitPointer.startsWith('gitdir: ') ? resolve(root, gitPointer.slice(8)) : gitMarker;
|
|
87
|
+
const commonPointer = join(gitDir, 'commondir');
|
|
88
|
+
const sharedDir = (() => { try { return resolve(gitDir, readFileSync(commonPointer, 'utf8').trim()); } catch { return gitDir; } })();
|
|
89
|
+
let index: Buffer;
|
|
90
|
+
try { index = readFileSync(join(gitDir, 'index')); }
|
|
91
|
+
catch (error) {
|
|
92
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
93
|
+
index = Buffer.alloc(12);
|
|
94
|
+
index.write('DIRC');
|
|
95
|
+
index.writeUInt32BE(2, 4);
|
|
96
|
+
}
|
|
97
|
+
const tracked = new Set<string>();
|
|
98
|
+
if (index.toString('ascii', 0, 4) !== 'DIRC' || ![2, 3].includes(index.readUInt32BE(4))) return [];
|
|
99
|
+
let offset = 12;
|
|
100
|
+
const count = index.readUInt32BE(8);
|
|
101
|
+
for (let i = 0; i < count; i++) {
|
|
102
|
+
const start = offset;
|
|
103
|
+
const flags = index.readUInt16BE(start + 60);
|
|
104
|
+
offset = start + 62;
|
|
105
|
+
if (flags & 0x4000) offset += 2;
|
|
106
|
+
const end = index.indexOf(0, offset);
|
|
107
|
+
if (end < 0) return [];
|
|
108
|
+
tracked.add(index.toString('utf8', offset, end));
|
|
109
|
+
offset = start + Math.ceil((end + 1 - start) / 8) * 8;
|
|
110
|
+
}
|
|
111
|
+
const ignored = (() => {
|
|
112
|
+
try { return readFileSync(join(root, '.gitignore'), 'utf8').split(/\r?\n/).map(line => line.trim()).filter(line => line && !line.startsWith('#')); }
|
|
113
|
+
catch { return [] as string[]; }
|
|
114
|
+
})();
|
|
115
|
+
const excluded = (() => {
|
|
116
|
+
try { return readFileSync(join(sharedDir, 'info', 'exclude'), 'utf8').split(/\r?\n/).map(line => line.trim()).filter(line => line && !line.startsWith('#')); }
|
|
117
|
+
catch { return [] as string[]; }
|
|
118
|
+
})();
|
|
119
|
+
const isIgnored = (name: string): boolean => [...ignored, ...excluded].some(pattern => {
|
|
120
|
+
const rule = pattern.replace(/^\//, '').replace(/\/$/, '');
|
|
121
|
+
if (rule.includes('*')) return new RegExp(`^${rule.split('*').map(part => part.replace(/[|\\{}()[\]^$+?.]/g, '\\$&')).join('.*')}$`).test(name);
|
|
122
|
+
return name === rule || name.startsWith(rule + '/') || name.split('/').includes(rule);
|
|
123
|
+
});
|
|
124
|
+
const candidates: string[] = [];
|
|
125
|
+
const walk = (dir: string): void => {
|
|
126
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
127
|
+
if (entry.name === '.git' || entry.name === 'node_modules') continue;
|
|
128
|
+
const file = join(dir, entry.name);
|
|
129
|
+
const name = relative(root, file);
|
|
130
|
+
if (isIgnored(name)) continue;
|
|
131
|
+
if (entry.isDirectory()) { walk(file); continue; }
|
|
132
|
+
if (!entry.isFile()) continue;
|
|
133
|
+
if (allowed.has(extname(name).toLowerCase()) && !tracked.has(name) && lstatSync(file).isFile() && realpathSync(file) === file) candidates.push(name);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
walk(root);
|
|
137
|
+
return candidates.sort();
|
|
138
|
+
} catch { return []; }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** shadow-move 관측의 종결 종류 — 읽은 카드만 «비면 code-pr» 로 풀고, 조회 실패는 'unmeasured', 카드를 안 봤으면 null. */
|
|
142
|
+
function shadowCompletion(card: TaskCard | undefined, lookupFailed: boolean): CompletionKind | 'unmeasured' | null {
|
|
143
|
+
if (card) return card.completion ?? 'code-pr';
|
|
144
|
+
return lookupFailed ? 'unmeasured' : null;
|
|
145
|
+
}
|
|
146
|
+
|
|
55
147
|
export async function recordTaskAgentShadowMove(input: TaskAgentShadowInput, deps: TaskAgentShadowDeps = {}): Promise<TaskAgentShadowMove> {
|
|
56
|
-
const
|
|
57
|
-
|
|
148
|
+
const successful = input.results.find(result => result.ok === true && result.worktreePath);
|
|
149
|
+
let card: TaskCard | undefined;
|
|
150
|
+
let cardLookupFailed = false;
|
|
151
|
+
try {
|
|
152
|
+
card = successful ? (deps.readCard ?? readTaskCard)(successful.taskId) : undefined;
|
|
153
|
+
if (!card && successful && !deps.readCard) {
|
|
154
|
+
const tasks = readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(taskAgentStatePath()).tasks ?? {};
|
|
155
|
+
const matches = Object.values(tasks).filter(candidate => candidate.status === 'launched' && candidate.text === successful.feature);
|
|
156
|
+
if (matches.length === 1) card = matches[0];
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
// Card lookup is optional; preserve the original PR judgement when unavailable — but mark the completion kind as unmeasured.
|
|
160
|
+
cardLookupFailed = true;
|
|
161
|
+
}
|
|
162
|
+
const kind = card?.completion;
|
|
163
|
+
const judgeInput = shadowJudgeInput(input.stopReason, input.results, kind);
|
|
164
|
+
const delivery = successful?.worktreePath && card?.project?.target && isDeliveryKind(kind)
|
|
165
|
+
? { kind, worktreePath: successful.worktreePath, projectTarget: card.project.target, files: selectDeliveryCandidates(successful.worktreePath, kind) }
|
|
166
|
+
: undefined;
|
|
167
|
+
const judgement: TaskJudgement = delivery
|
|
168
|
+
? delivery.files.length
|
|
169
|
+
? { move: 'wait', stage: 'observing', reason: '전달 후보 확인 — 산출물 판독 대기', executorKind: 'deliver' }
|
|
170
|
+
: { move: 'wait', stage: 'waiting', reason: '전달 후보 없음' }
|
|
171
|
+
: judgeNextMove(judgeInput);
|
|
172
|
+
if (cardLookupFailed) judgement.reason = `${judgement.reason} · 종결 종류 미확인(카드 조회 실패)`;
|
|
58
173
|
let executorResult: string | null = null;
|
|
59
174
|
let wouldDo: string | null = null;
|
|
60
175
|
if (judgement.executorKind) {
|
|
61
176
|
const action: NextAction = {
|
|
62
177
|
kind: judgement.executorKind,
|
|
63
178
|
...(judgement.executorVariant ? { variant: judgement.executorVariant } : {}),
|
|
64
|
-
taskId: input.runId ?? 'run-unknown',
|
|
179
|
+
taskId: delivery ? card!.id : input.runId ?? 'run-unknown',
|
|
65
180
|
rationale: judgement.reason,
|
|
66
|
-
pr: typeof judgeInput.pr === 'number' ? judgeInput.pr : 0,
|
|
181
|
+
pr: delivery ? 0 : typeof judgeInput.pr === 'number' ? judgeInput.pr : 0,
|
|
67
182
|
runId: input.runId ?? '',
|
|
68
183
|
original: [...new Set(input.results.map((result) => result.feature))].join(' · '),
|
|
69
|
-
checklistId: '',
|
|
184
|
+
checklistId: delivery ? card?.checklistId ?? '' : '',
|
|
185
|
+
...(delivery ? { delivery } : {}),
|
|
70
186
|
};
|
|
71
187
|
executorResult = await executeNextAction(action, {
|
|
72
|
-
mode: 'shadow',
|
|
188
|
+
mode: delivery ? (deps.mode ?? 'shadow') : 'shadow',
|
|
73
189
|
command: deps.command ?? (async () => { throw new Error('task-agent shadow path must not run commands'); }),
|
|
74
190
|
observe: (_event, data) => { wouldDo = data.reason; },
|
|
75
191
|
});
|
|
@@ -83,8 +199,60 @@ export async function recordTaskAgentShadowMove(input: TaskAgentShadowInput, dep
|
|
|
83
199
|
reason: judgement.reason,
|
|
84
200
|
executorResult,
|
|
85
201
|
wouldDo,
|
|
86
|
-
pr: typeof judgeInput.pr === 'number' ? judgeInput.pr : null,
|
|
202
|
+
pr: delivery ? null : typeof judgeInput.pr === 'number' ? judgeInput.pr : null,
|
|
87
203
|
};
|
|
88
|
-
|
|
89
|
-
|
|
204
|
+
if (delivery && deps.mode !== 'live') {
|
|
205
|
+
move.wouldDo = `${move.wouldDo ?? 'would wait'} → ${join(delivery.projectTarget, 'elanous-out', card!.id)} (${delivery.files.join(', ')})`;
|
|
206
|
+
}
|
|
207
|
+
if (delivery && deps.mode === 'live') {
|
|
208
|
+
move.move = executorResult === 'done' ? 'propose-green' : 'wait';
|
|
209
|
+
move.reason = wouldDo ?? judgement.reason;
|
|
210
|
+
move.wouldDo = null;
|
|
211
|
+
}
|
|
212
|
+
const liveKind: TaskAgentLiveMove | null = move.move === 'review' || move.move === 'propose-green' ? move.move : null;
|
|
213
|
+
// 판단마다 해석한다 — 안전하지 않은 수를 판단할 때도 모르는 항목 경고가 남게(실행은 아래 두 수뿐).
|
|
214
|
+
const liveMoves = deps.liveMoves ?? await configuredTaskAgentLiveMoves();
|
|
215
|
+
const live = liveKind !== null && liveMoves.has(liveKind);
|
|
216
|
+
// 카드 id 는 모든 판단 관측에 싣는다(그림자 일치율을 카드로 잇는다) — 읽기 전용 조회.
|
|
217
|
+
// live 수는 «근거 결과»에 묶는다 — PR 수는 그 PR 을 낸 결과의 카드·작업 트리, 전달은 전달한 카드(다중 결과에서 남의 카드에 쓰지 않게).
|
|
218
|
+
const prResult = typeof judgeInput.pr === 'number' ? input.results.find((result) => result.prNumber === judgeInput.pr) : undefined;
|
|
219
|
+
const boundCard = delivery
|
|
220
|
+
? card
|
|
221
|
+
: prResult
|
|
222
|
+
? (card && successful?.taskId === prResult.taskId ? card : liveCardFor([prResult], deps))
|
|
223
|
+
: undefined;
|
|
224
|
+
// PR 에 묶인 수는 그 PR 의 카드만 관측에 싣는다(못 찾으면 null — 다른 결과의 카드로 대체하지 않는다).
|
|
225
|
+
const liveCard = prResult || delivery ? boundCard : card ?? (cardLookupFailed ? undefined : liveCardFor(input.results, deps));
|
|
226
|
+
let liveMove: LiveMoveResult | undefined;
|
|
227
|
+
if (live) {
|
|
228
|
+
if (liveKind === 'review') {
|
|
229
|
+
liveMove = await executeLiveReview(boundCard, prResult?.prNumber,
|
|
230
|
+
{ runId: input.runId, stopReason: input.stopReason, ...(prResult?.worktreePath ? { cwd: prResult.worktreePath } : {}) }, { ...(deps.log ? { log: deps.log } : {}), ...deps.live });
|
|
231
|
+
} else {
|
|
232
|
+
const evidence: Record<string, string> = {};
|
|
233
|
+
if (boundCard && !delivery && judgeInput.prState === 'MERGED' && prResult) evidence[boundCard.id] = `#${prResult.prNumber}`;
|
|
234
|
+
else if (boundCard && delivery && deps.mode === 'live' && executorResult === 'done') evidence[boundCard.id] = join(delivery.projectTarget, 'elanous-out', boundCard.id);
|
|
235
|
+
liveMove = executeLiveGreenProposal(boundCard, evidence, { ...(deps.log ? { log: deps.log } : {}), ...deps.live });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// 판단한 수마다 live/shadow 를 남긴다 — OP 가 «오판 건수»·«그림자 일치율»을 카드·수 종류로 잰다.
|
|
239
|
+
(deps.log ?? ((category, event, data) => debug.log(category, event, data)))('task-agent', 'shadow-move', {
|
|
240
|
+
...move, completion: shadowCompletion(card, cardLookupFailed), card: liveCard?.id ?? null, live, liveExecuted: liveMove?.executed ?? false, ...(liveMove ? { liveOk: liveMove.ok } : {}),
|
|
241
|
+
});
|
|
242
|
+
return liveMove ? { ...move, liveMove } : move;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** live 수의 카드 — 성공한 결과가 없어도(수확 가능 런은 ok 가 아닐 수 있다) 결과의 taskId · 유일한 발사 문장으로 찾는다. */
|
|
246
|
+
function liveCardFor(results: readonly SupervisorJobResult[], deps: TaskAgentShadowDeps): TaskCard | undefined {
|
|
247
|
+
try {
|
|
248
|
+
for (const result of results) {
|
|
249
|
+
const found = deps.readCard ? deps.readCard(result.taskId) : readTaskCard(result.taskId, deps.live?.statePath);
|
|
250
|
+
if (found) return found;
|
|
251
|
+
}
|
|
252
|
+
if (deps.readCard) return undefined;
|
|
253
|
+
const tasks = readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(deps.live?.statePath ?? taskAgentStatePath()).tasks ?? {};
|
|
254
|
+
const features = new Set(results.map((result) => result.feature));
|
|
255
|
+
const matches = Object.values(tasks).filter(candidate => candidate.status === 'launched' && features.has(candidate.text));
|
|
256
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
257
|
+
} catch { return undefined; }
|
|
90
258
|
}
|
|
@@ -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,14 +21,28 @@ 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
47
|
/** launch-failed = 발사기가 띄우기에 실패했다(사유는 history 마지막 칸) — 다음 수는 다시 발사. */
|
|
31
48
|
status: 'handed' | 'launched' | 'launch-failed';
|
|
@@ -36,17 +53,48 @@ export interface TaskCard {
|
|
|
36
53
|
/** 조각 카드만 — 소속 미션 카드 id · 먼저 착지해야 하는 조각 id(선후). */
|
|
37
54
|
mission?: string;
|
|
38
55
|
after?: string[];
|
|
39
|
-
/**
|
|
40
|
-
|
|
56
|
+
/**
|
|
57
|
+
* 이 카드 런이 연 PR · 골 id(16자). 두 출처가 한 칸을 쓴다(UX LOOP-INTERACT 계약 — card.runId → goalId):
|
|
58
|
+
* - 수동(② `tasks advance --pr|--goal` · 미션 조각만) — PR 은 번호(number) · `refSource: 'manual'`.
|
|
59
|
+
* - 런 원장 묶기(TA-CARD-RUN-LINK `card-evidence.ts`) — PR 은 `{number, url}` · 비어 있을 때만 «한 번» 채운다 · 수동 근거를 덮지 않는다.
|
|
60
|
+
* 번호는 `cardPrNumber` 로 읽는다(옛 카드의 number 도 그대로 읽힌다).
|
|
61
|
+
*/
|
|
62
|
+
pr?: number | TaskCardPr;
|
|
41
63
|
goalId?: string;
|
|
64
|
+
/** `manual` = pr/goalId 를 사람이 적었다(`recordPieceRef`) — 런 묶기가 건드리지 않는다. */
|
|
65
|
+
refSource?: 'manual';
|
|
42
66
|
/** 조각 카드만 — ② 가 선행 착지 뒤 넘겼다(한 번만 · 같은 잠금 안에서 먼저 적는다). */
|
|
43
67
|
handed?: { at: string; mode: 'shadow' | 'live'; claim?: string; error?: string };
|
|
68
|
+
/**
|
|
69
|
+
* TA-CARD-RUN-LINK — `--live` 발사가 발사기에 넘긴 런 id(`ELANOUS_RUN_ID` 상속 → 하니스 런이 그 id 로 돈다) ⊕ 발사 토큰.
|
|
70
|
+
* 발사기가 그 id 를 «받았다»고 돌려줄 때만 적는다(한 번만). 원장이 아직 없으면 그 런은 시작 전이거나 대기열로 갔다.
|
|
71
|
+
*/
|
|
72
|
+
runId?: string;
|
|
73
|
+
launchId?: string;
|
|
74
|
+
/** Pod 런이면 PR 을 연 자식 런 id(런 묶기가 PR 과 함께 적는다). */
|
|
75
|
+
runChildId?: string;
|
|
44
76
|
/** 미션 카드만 — 모든 조각 착지 → 칸 green «제안»(체크리스트는 손대지 않는다 · OP/주인이 뒤집는다). */
|
|
45
77
|
greenProposal?: { at: string; checklistId: string | null; evidence: Record<string, string> };
|
|
78
|
+
/** TA-JUDGE-LIVE-SAFE — live `review` 수가 리뷰를 요청한 PR 머리들(머리마다 한 번 · 띄우기 실패면 error · 이력은 지우지 않는다). */
|
|
79
|
+
reviewRequests?: Array<{ pr: number; head: string; at: string; error?: string }>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** 런 원장에서 묶은 PR — url 은 원장(pr-opened · Pod job-finished)에 있을 때만(지어내지 않는다). */
|
|
83
|
+
export interface TaskCardPr { number: number; url?: string }
|
|
84
|
+
|
|
85
|
+
/** 카드의 PR 번호 — 수동(number)·묶음(`{number,url}`) 둘 다. */
|
|
86
|
+
export function cardPrNumber(card: Pick<TaskCard, 'pr'>): number | undefined {
|
|
87
|
+
return typeof card.pr === 'number' ? card.pr : card.pr?.number;
|
|
46
88
|
}
|
|
47
89
|
|
|
48
90
|
interface TaskAgentStateFile { tasks?: Record<string, TaskCard>; [key: string]: unknown }
|
|
49
91
|
|
|
92
|
+
function isGitWorkTree(path: string): boolean {
|
|
93
|
+
try {
|
|
94
|
+
return execFileSync('git', ['rev-parse', '--is-inside-work-tree'], { cwd: path, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim() === 'true';
|
|
95
|
+
} catch { return false; }
|
|
96
|
+
}
|
|
97
|
+
|
|
50
98
|
export function taskAgentStatePath(): string {
|
|
51
99
|
return join(effectiveInstanceRoot(), 'task-agent-actions.json');
|
|
52
100
|
}
|
|
@@ -137,12 +185,22 @@ export function shellQuote(arg: string): string {
|
|
|
137
185
|
return /^[A-Za-z0-9_./:=@%+-]+$/.test(arg) ? arg : `'${arg.replaceAll("'", `'\\''`)}'`;
|
|
138
186
|
}
|
|
139
187
|
|
|
140
|
-
|
|
188
|
+
/** 발사기에 넘기는 연결 — env(`ELANOUS_RUN_ID`)를 자식 환경에 얹으면 하니스 런이 `runId` 로 돈다(`resolveRunIdentity` 상속). launchId 는 카드에만 남는 발사 토큰이다. */
|
|
189
|
+
export interface TaskLaunchContext { runId: string; launchId: string; env: Record<string, string> }
|
|
190
|
+
/** 발사기가 «이 런 id 로 띄웠다»고 돌려주는 영수증 — 없으면 카드에 런 id 를 적지 않는다(추측하지 않는다). */
|
|
191
|
+
export interface TaskLaunchReceipt { runId?: string }
|
|
192
|
+
export type TaskLauncher = (args: string[], cwd?: string, context?: TaskLaunchContext) => void | TaskLaunchReceipt | Promise<void | TaskLaunchReceipt>;
|
|
141
193
|
|
|
142
194
|
export interface HandTaskOptions {
|
|
143
195
|
text: string;
|
|
144
196
|
seat?: TaskSeat;
|
|
145
197
|
checklistId?: string;
|
|
198
|
+
completion?: TaskCard['completion'];
|
|
199
|
+
/** `--project <id> --target <dir>` — target 은 존재 확인 뒤 절대 경로로 바꿔 싣는다. */
|
|
200
|
+
project?: { id: string; target: string };
|
|
201
|
+
/** 보드 소속 목표·이정표 id — 카드에 그대로 싣는다. */
|
|
202
|
+
goal?: string;
|
|
203
|
+
milestone?: string;
|
|
146
204
|
live?: boolean;
|
|
147
205
|
statePath?: string;
|
|
148
206
|
/** `--live` 일 때만 불린다. */
|
|
@@ -179,12 +237,31 @@ export class TaskLaunchError extends Error {
|
|
|
179
237
|
}
|
|
180
238
|
}
|
|
181
239
|
|
|
182
|
-
export interface HandTaskResult { card: TaskCard; move: TaskCardMove; mode: 'shadow' | 'live'; launched: boolean }
|
|
240
|
+
export interface HandTaskResult { card: TaskCard; move: TaskCardMove; mode: 'shadow' | 'live'; launched: boolean; cwd?: string }
|
|
183
241
|
|
|
184
242
|
export async function handTask(opts: HandTaskOptions): Promise<HandTaskResult> {
|
|
185
243
|
const text = opts.text.trim();
|
|
186
244
|
if (!text) throw new Error('과제 한 줄이 비어 있다');
|
|
187
245
|
if (opts.seat !== undefined && !TASK_SEATS.includes(opts.seat)) throw new Error(`자리는 ${TASK_SEATS.join('|')} 중 하나다: ${opts.seat}`);
|
|
246
|
+
if (opts.completion !== undefined && !COMPLETION_KINDS.includes(opts.completion)) throw new Error(`종결 종류는 ${COMPLETION_KINDS.join('|')} 중 하나다: ${opts.completion}`);
|
|
247
|
+
for (const [flag, value] of [['--goal', opts.goal], ['--milestone', opts.milestone]] as const) {
|
|
248
|
+
if (value !== undefined && (!value.trim() || /\p{Cc}/u.test(value))) throw new Error(`${flag} id 가 비었거나 제어 문자를 담았다: ${JSON.stringify(value)}`);
|
|
249
|
+
}
|
|
250
|
+
let project: TaskCard['project'];
|
|
251
|
+
let targetIsGit: boolean | null = null;
|
|
252
|
+
if (opts.project) {
|
|
253
|
+
if (!opts.project.id.trim()) throw new Error('--project id 가 비어 있다');
|
|
254
|
+
const target = resolve(opts.project.target);
|
|
255
|
+
let isDir = false;
|
|
256
|
+
try { isDir = statSync(target).isDirectory(); } catch { /* 없음 */ }
|
|
257
|
+
if (!isDir) throw new Error(`대상 디렉터리가 없습니다: ${target}`);
|
|
258
|
+
targetIsGit = isGitWorkTree(target);
|
|
259
|
+
// 코드 종결(code-pr · 비면 code-pr)은 git 대상만 — 그 밖의 종결 종류는 git 이 아닌 폴더도 받는다(같은 판정을 harness-target 과 공유).
|
|
260
|
+
const codeCompletion = (opts.completion ?? 'code-pr') === 'code-pr';
|
|
261
|
+
const selection = resolveDaemonHarnessTarget({ configured: target, cwd: target, isGitRepo: () => targetIsGit === true, allowNonGit: !codeCompletion });
|
|
262
|
+
if (!selection.ok) throw new Error(`코드 종결은 git 대상이 필요 — --completion 을 고르거나 git init: ${target}`);
|
|
263
|
+
project = { id: opts.project.id, target: selection.repo };
|
|
264
|
+
}
|
|
188
265
|
const now = (opts.now ?? (() => new Date()))();
|
|
189
266
|
const id = opts.id ?? `${TASK_CARD_PREFIX}${now.toISOString().slice(0, 10).replaceAll('-', '')}-${randomBytes(3).toString('hex')}`;
|
|
190
267
|
const mode = opts.live ? 'live' : 'shadow';
|
|
@@ -193,6 +270,10 @@ export async function handTask(opts: HandTaskOptions): Promise<HandTaskResult> {
|
|
|
193
270
|
id, text,
|
|
194
271
|
...(opts.seat ? { seat: opts.seat } : {}),
|
|
195
272
|
...(opts.checklistId ? { checklistId: opts.checklistId } : {}),
|
|
273
|
+
...(opts.completion ? { completion: opts.completion } : {}),
|
|
274
|
+
...(project ? { project } : {}),
|
|
275
|
+
...(opts.goal ? { goal: opts.goal } : {}),
|
|
276
|
+
...(opts.milestone ? { milestone: opts.milestone } : {}),
|
|
196
277
|
createdAt: now.toISOString(),
|
|
197
278
|
status: 'handed',
|
|
198
279
|
history: [],
|
|
@@ -210,17 +291,21 @@ export async function handTask(opts: HandTaskOptions): Promise<HandTaskResult> {
|
|
|
210
291
|
if (!guard(current)) return undefined;
|
|
211
292
|
owned.ok = true;
|
|
212
293
|
// next 의 옛 근거는 버리고 디스크의 근거로 «교체»한다 — 정정된 근거 옆에 옛 근거가 되살아나지 않게.
|
|
213
|
-
const { pr: _stalePr, goalId: _staleGoal, ...base } = next;
|
|
214
|
-
return { ...base, ...(current?.pr !== undefined ? { pr: current.pr } : {}), ...(current?.goalId !== undefined ? { goalId: current.goalId } : {}) };
|
|
294
|
+
const { pr: _stalePr, goalId: _staleGoal, refSource: _staleSource, ...base } = next;
|
|
295
|
+
return { ...base, ...(current?.pr !== undefined ? { pr: current.pr } : {}), ...(current?.goalId !== undefined ? { goalId: current.goalId } : {}), ...(current?.refSource ? { refSource: current.refSource } : {}) };
|
|
215
296
|
});
|
|
216
297
|
if (!owned.ok) throw new TaskCardSupersededError(next.id);
|
|
217
298
|
};
|
|
218
299
|
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 };
|
|
300
|
+
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 */ }
|
|
301
|
+
if (mode === 'shadow') return { card, move, mode, launched: false, ...(project ? { cwd: project.target } : {}) };
|
|
221
302
|
if (!opts.launcher) throw new Error('--live 인데 launcher 가 없다');
|
|
303
|
+
const runId = mintRunId();
|
|
304
|
+
const launchId = `tl-${randomBytes(6).toString('hex')}`;
|
|
305
|
+
const context: TaskLaunchContext = { runId, launchId, env: { [HARNESS_RUN_ID_ENV]: runId } };
|
|
306
|
+
let receipt: void | TaskLaunchReceipt;
|
|
222
307
|
try {
|
|
223
|
-
await opts.launcher(move.command
|
|
308
|
+
receipt = await opts.launcher(move.command!, project?.target, context);
|
|
224
309
|
} catch (error) {
|
|
225
310
|
const reason = error instanceof Error ? error.message : String(error);
|
|
226
311
|
const failed: TaskCard = { ...card, status: 'launch-failed', history: [{ at: (opts.now ?? (() => new Date()))().toISOString(), event: 'launch-failed', detail: reason }] };
|
|
@@ -228,8 +313,25 @@ export async function handTask(opts: HandTaskOptions): Promise<HandTaskResult> {
|
|
|
228
313
|
try { debug.log('task-agent', 'launch-failed', { taskId: id, seat: card.seat ?? null, reason }); } catch { /* fail-soft */ }
|
|
229
314
|
throw new TaskLaunchError(failed, reason);
|
|
230
315
|
}
|
|
231
|
-
const
|
|
316
|
+
const launchedAt = (opts.now ?? (() => new Date()))().toISOString();
|
|
317
|
+
const receivedRunId = receipt && typeof receipt.runId === 'string' ? receipt.runId.trim() : '';
|
|
318
|
+
// 넘긴 런 id 와 «같은» 영수증만 — 다른 id 면 env 로 넘긴 런과 카드가 어긋난다(묶지 않는다).
|
|
319
|
+
const boundRunId = receivedRunId && receivedRunId === runId && normalizeRunId(receivedRunId) === receivedRunId ? receivedRunId : undefined;
|
|
320
|
+
if (receivedRunId && !boundRunId) {
|
|
321
|
+
try { debug.log('task-agent', 'card-run-unbound', { card: id, launchId, expected: runId, received: receivedRunId }); } catch { /* fail-soft */ }
|
|
322
|
+
}
|
|
323
|
+
const launched: TaskCard = {
|
|
324
|
+
...card, status: 'launched',
|
|
325
|
+
...(boundRunId ? { runId: boundRunId, launchId } : {}),
|
|
326
|
+
history: [
|
|
327
|
+
{ at: launchedAt, event: 'launch', detail: move.command!.join(' ') },
|
|
328
|
+
...(boundRunId ? [{ at: launchedAt, event: 'run-bound', detail: boundRunId, runId: boundRunId }] : []),
|
|
329
|
+
],
|
|
330
|
+
};
|
|
232
331
|
writeCard(path, launched);
|
|
233
|
-
try { debug.log('task-agent', 'launched', { taskId: id, seat: card.seat ?? null }); } catch { /* fail-soft */ }
|
|
234
|
-
|
|
332
|
+
try { debug.log('task-agent', 'launched', { taskId: id, seat: card.seat ?? null, runId: boundRunId ?? null }); } catch { /* fail-soft */ }
|
|
333
|
+
if (boundRunId) {
|
|
334
|
+
try { debug.log('task-agent', 'card-run-bound', { card: id, runId: boundRunId, launchId }); } catch { /* fail-soft */ }
|
|
335
|
+
}
|
|
336
|
+
return { card: launched, move, mode, launched: true, ...(project ? { cwd: project.target } : {}) };
|
|
235
337
|
}
|
|
@@ -37,6 +37,54 @@ export const POD_LITE_IMAGE = 'elanous-harness-lite:local';
|
|
|
37
37
|
export const POD_LITE_MEMORY_LIMIT = '2Gi';
|
|
38
38
|
|
|
39
39
|
/** 요청은 한도를 넘을 수 없다(쿠버네티스가 Job 을 거부한다) — lite 2Gi 처럼 한도가 기본 요청보다 작으면 한도로 맞춘다. */
|
|
40
|
+
/** The command Job container's CPU limit when the caller names none (GATE-SPEED A3①: unchanged default). */
|
|
41
|
+
export const POD_COMMAND_CPU_LIMIT_DEFAULT = '4';
|
|
42
|
+
|
|
43
|
+
/** Container CPU request/limit override. Omitted fields keep the defaults (request `POD_CHILD_REQUESTS.cpu` · limit 4). */
|
|
44
|
+
export interface PodCpu { request?: string; limit?: string }
|
|
45
|
+
|
|
46
|
+
/** Kubernetes CPU quantity → millicores (`1` · `0.5` · `500m`); null when it is not a positive quantity we accept. */
|
|
47
|
+
export function podCpuMillis(value: string): number | null {
|
|
48
|
+
const m = /^(?:(\d+(?:\.\d{1,3})?)|(\d+)m)$/.exec(value.trim());
|
|
49
|
+
if (!m) return null;
|
|
50
|
+
const millis = m[1] !== undefined ? Math.round(Number(m[1]) * 1000) : Number(m[2]);
|
|
51
|
+
return Number.isSafeInteger(millis) && millis > 0 ? millis : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Resolve a CPU override against the defaults; throws when a value is malformed or the request exceeds the limit (k8s would refuse the Job). */
|
|
55
|
+
export function resolvePodCpu(cpu: PodCpu | undefined): { request: string; limit: string } {
|
|
56
|
+
const request = cpu?.request?.trim() || POD_CHILD_REQUESTS.cpu;
|
|
57
|
+
const limit = cpu?.limit?.trim() || POD_COMMAND_CPU_LIMIT_DEFAULT;
|
|
58
|
+
const r = podCpuMillis(request), l = podCpuMillis(limit);
|
|
59
|
+
if (r === null) throw new Error(`pod cpu request is not a positive CPU quantity: ${request}`);
|
|
60
|
+
if (l === null) throw new Error(`pod cpu limit is not a positive CPU quantity: ${limit}`);
|
|
61
|
+
if (r > l) throw new Error(`pod cpu request ${request} exceeds limit ${limit}`);
|
|
62
|
+
return { request, limit };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Config value → CPU override (release gate `release.loop.gatePodCpu`). A scalar (`2` · `"1500m"`) sets request = limit;
|
|
66
|
+
* `{ request, limit }` sets either side. Absent/empty = undefined (request 1 / limit 4). Malformed values throw. */
|
|
67
|
+
export function parsePodCpu(value: unknown, label = 'cpu'): PodCpu | undefined {
|
|
68
|
+
const text = (v: unknown): string | undefined => {
|
|
69
|
+
if (v === undefined || v === null || v === '') return undefined;
|
|
70
|
+
if (typeof v !== 'number' && typeof v !== 'string') throw new Error(`${label} must be a CPU quantity or { request, limit }`);
|
|
71
|
+
return String(v).trim() || undefined;
|
|
72
|
+
};
|
|
73
|
+
let cpu: PodCpu;
|
|
74
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
75
|
+
const { request, limit, ...rest } = value as Record<string, unknown>;
|
|
76
|
+
if (Object.keys(rest).length) throw new Error(`${label} has unknown keys: ${Object.keys(rest).join(', ')}`);
|
|
77
|
+
const r = text(request), l = text(limit);
|
|
78
|
+
cpu = { ...(r ? { request: r } : {}), ...(l ? { limit: l } : {}) };
|
|
79
|
+
} else {
|
|
80
|
+
const both = text(value);
|
|
81
|
+
cpu = both ? { request: both, limit: both } : {};
|
|
82
|
+
}
|
|
83
|
+
if (!cpu.request && !cpu.limit) return undefined;
|
|
84
|
+
resolvePodCpu(cpu);
|
|
85
|
+
return cpu;
|
|
86
|
+
}
|
|
87
|
+
|
|
40
88
|
export function memoryRequestWithin(limit: string | undefined): string {
|
|
41
89
|
const gi = (value: string | undefined) => { const m = /^(\d+)Gi$/.exec(value ?? ''); return m ? Number(m[1]) : null; };
|
|
42
90
|
const request = gi(POD_CHILD_REQUESTS.memory);
|
|
@@ -130,6 +178,8 @@ export interface PodCommandJobInput {
|
|
|
130
178
|
bunCache?: string;
|
|
131
179
|
/** 컨테이너 메모리 한도(기본 16Gi) — 게이트가 파일 하나 격리 Job 에 올린다. */
|
|
132
180
|
memoryLimit?: string;
|
|
181
|
+
/** GATE-SPEED A3①: CPU request/limit override (release gate shards only). Omitted = request 1 / limit 4. */
|
|
182
|
+
cpu?: PodCpu;
|
|
133
183
|
source?: PodSource;
|
|
134
184
|
deadlineSeconds: number;
|
|
135
185
|
runId?: string;
|
|
@@ -147,6 +197,7 @@ export function podCommandJobManifest(o: PodCommandJobInput): Record<string, unk
|
|
|
147
197
|
}
|
|
148
198
|
const grok = o.llm === 'grok';
|
|
149
199
|
const bunCache = o.bunCache ? podBunCacheVolume(o.bunCache) : undefined;
|
|
200
|
+
const cpu = resolvePodCpu(o.cpu);
|
|
150
201
|
const quotedArgs = o.command.map(bashSingleQuote).join(' ');
|
|
151
202
|
const credLines = [
|
|
152
203
|
...(grok ? ['mkdir -p ~/.grok && install -m 600 /creds/grok-auth.json ~/.grok/auth.json'] : []),
|
|
@@ -185,7 +236,7 @@ export function podCommandJobManifest(o: PodCommandJobInput): Record<string, unk
|
|
|
185
236
|
initContainers: [{ name: 'isolation-gate', image: o.image, imagePullPolicy: o.imagePullPolicy ?? 'Never', command: ['bash', '-c'], args: [GATE] }],
|
|
186
237
|
containers: [{
|
|
187
238
|
name: 'child', image: o.image, imagePullPolicy: o.imagePullPolicy ?? 'Never',
|
|
188
|
-
resources: { requests: { ...POD_CHILD_REQUESTS, memory: memoryRequestWithin(o.memoryLimit) }, limits: { memory: o.memoryLimit ?? '16Gi', cpu:
|
|
239
|
+
resources: { requests: { ...POD_CHILD_REQUESTS, cpu: cpu.request, memory: memoryRequestWithin(o.memoryLimit) }, limits: { memory: o.memoryLimit ?? '16Gi', cpu: cpu.limit } },
|
|
189
240
|
command: ['bash', '-c'], args: [script],
|
|
190
241
|
env: [
|
|
191
242
|
...(o.runId ? [{ name: 'ELANOUS_RUN_ID', value: o.runId }] : []),
|
|
@@ -272,6 +323,8 @@ export interface RunPodCommandOptions {
|
|
|
272
323
|
bunCache?: string;
|
|
273
324
|
/** 컨테이너 메모리 한도(기본 16Gi · lite 면 2Gi). */
|
|
274
325
|
memoryLimit?: string;
|
|
326
|
+
/** GATE-SPEED A3①: CPU request/limit override — only the release gate passes it. Omitted = request 1 / limit 4. */
|
|
327
|
+
cpu?: PodCpu;
|
|
275
328
|
/** POD7: 호출부가 «네트워크 스킬만 쓰는 명령»이라고 말할 때만 lite 이미지 ⊕ 2Gi. */
|
|
276
329
|
lite?: boolean;
|
|
277
330
|
source?: PodSource;
|
|
@@ -284,6 +337,7 @@ export interface RunPodCommandOptions {
|
|
|
284
337
|
name?: string;
|
|
285
338
|
pollMs?: number;
|
|
286
339
|
kubectl?: Kubectl;
|
|
340
|
+
/** A caller-owned scheduler is self-capped: acquires count only its own Jobs, never cluster occupancy (POOLGLOBAL-REG). */
|
|
287
341
|
poolScheduler?: PodPoolScheduler;
|
|
288
342
|
checkPool?: typeof checkPodPool;
|
|
289
343
|
syncImages?: typeof syncPoolImages;
|
|
@@ -356,6 +410,8 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
356
410
|
if (hostMirror && !isAbsolute(hostMirror)) throw new Error('pod.hostMirror must be an absolute directory path');
|
|
357
411
|
const bunCache = options.bunCache?.trim() || undefined;
|
|
358
412
|
if (bunCache && !isAbsolute(bunCache)) throw new Error('pod.bunCache must be an absolute directory path');
|
|
413
|
+
// Fail before any Secret/Job is applied — a bad quantity must not reach the cluster.
|
|
414
|
+
if (options.cpu) resolvePodCpu(options.cpu);
|
|
359
415
|
|
|
360
416
|
let member: PodPoolMember | null = null;
|
|
361
417
|
let pool = options.poolScheduler;
|
|
@@ -366,12 +422,16 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
366
422
|
const members = parsePodPool(spec);
|
|
367
423
|
const checked = (options.checkPool ?? checkPodPool)(members, baseKubectl as PoolKubectl);
|
|
368
424
|
if (!checked.ok) throw new Error('pod command: 풀의 노드가 하나도 준비되지 않았다');
|
|
369
|
-
|
|
425
|
+
// Occupancy reads go through the same kubectl as the readiness check (an injected one included).
|
|
426
|
+
pool = new PodPoolScheduler(checked.ready, { kubectl: baseKubectl as PoolKubectl });
|
|
370
427
|
}
|
|
371
428
|
}
|
|
372
429
|
if (pool) {
|
|
430
|
+
// POOLGLOBAL-REG: a caller that hands in its own scheduler (the release gate fans its shards over one) holds its own
|
|
431
|
+
// concurrency cap — its Jobs count against the spec caps, not the cluster's goal Pods (#24253 → 0.2.15 gate ran 1 shard).
|
|
432
|
+
const selfCapped = options.poolScheduler !== undefined;
|
|
373
433
|
for (;;) {
|
|
374
|
-
member = await pool.tryAcquire();
|
|
434
|
+
member = await pool.tryAcquire(undefined, undefined, { selfCapped });
|
|
375
435
|
if (member) break;
|
|
376
436
|
await sleep(options.pollMs ?? 15_000);
|
|
377
437
|
}
|
|
@@ -426,7 +486,7 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
426
486
|
name, namespace, launch, image: imageRef ?? image,
|
|
427
487
|
...(imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}),
|
|
428
488
|
repoUrl, command, skills, ...(llm ? { llm } : {}), ...(options.clone ? { clone: true } : {}),
|
|
429
|
-
...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), ...(bunCache ? { bunCache } : {}), ...(memoryLimit ? { memoryLimit } : {}), deadlineSeconds,
|
|
489
|
+
...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), ...(bunCache ? { bunCache } : {}), ...(memoryLimit ? { memoryLimit } : {}), ...(options.cpu ? { cpu: options.cpu } : {}), deadlineSeconds,
|
|
430
490
|
...(options.runId ? { runId: options.runId } : {}),
|
|
431
491
|
});
|
|
432
492
|
kubectl(['-n', namespace, 'delete', 'job', '-l', own, '--ignore-not-found']);
|