elanous 0.2.4 → 0.2.6
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/README.md +7 -6
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/1169-696263ac7af8c4d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1883.c91a2648af4842fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1947-9d64872eeb9cfdec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2808-dc06482994b40f70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 2984-977cfa339fcd65a5.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/3165-95515ca5f299a120.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8142-011e1f474efb8b33.js → 3190-e9282753e7ebde85.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3995-bee512a7063036ce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 4240-7153881b0b377306.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4259-99614149577e77cb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4283-329eabeaf233e6cd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4328-cb5058b1acc2de5e.js +25 -0
- package/apps/pwa/out/_next/static/chunks/4801-ecee6ef62b349b18.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5110-8058791dd25a2e14.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3403.4bc97b4066945a94.js → 5329.8b7e4bca684b6eec.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/5330-c41e9670f594c38f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5423-e95fd125d1794349.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5478-b5eae07dc79ea619.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5787-96ee6642b9a661e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6448-79d72368589f98bc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6522-9866c002b5531836.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6729-b4a558b7aa891e0c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6969-cb03f9410b7d2e03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7007-3b9861091bb8dd21.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5791-bb82743fa6299562.js → 763-7d0e1913b5074e98.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 7633.0654fe6ea259c1ee.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7930-8dc7e20fc6fc19ca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8071-187edbc22a86741f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8198-d4101ad70cb9a579.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8250.4a2c5553a07cdf47.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8293-123b91e7b0330dc0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8296-d6d5d87dfa911d41.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8305-603963c33ee8eec3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8612-4a0066f20951b8fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2ea96958-e8c7e112de50b067.js → 8d0d9faf-97887ff99713b84e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9181-cf4e1b61c4783f92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → 91ff6dda-3b33d42d20657429.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9262-26c2ecbd5a67e4a2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9328-45abdebce4fbecb7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9355-dbc67205adf8745f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9763.255b90dd69875689.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9933-66d31ec25c0231af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-bd06fb55f4da0381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-4e586142da455a25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-889ad5cddcf45b0d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-47ba401d56572c27.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-0b54f9c83458e038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-4fcc296ef6c8ed90.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-fe9fda494a87f4e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-f9642e25ce129590.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e1063e8ba5f3e2e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-9d0df30027d8aa9d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-bd06fb55f4da0381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-d97adf2c9455b8d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-236b80d202110362.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-21a30f35f1a276ad.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-b1a5be357c4fa25d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1705a60303f7f573.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-76b337c11ad0e237.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-7c0f85fbb56f64c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-65ca16b20617b44d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-e7f9b8fc29411c1d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-92ea6213d1aeec04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a913a005aacb5476.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-2d78804a40dfdef6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-08a99148e780ac00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-deb674e7ed36f815.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-49f8de0534d192d7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-bd06fb55f4da0381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-ba162a28d2176e18.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-1ff1a37b550ab41a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-ccaa698f56c0fbdb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-53b944082de1d7db.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-9b70adf1552789bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-30d482aa63241fe8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-aecf786f54651d8a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6ab9b62c7b6e08f2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-08949735455e0c9a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-a1479005234a46c2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3890e90bd232732.js +1 -0
- package/apps/pwa/out/_next/static/chunks/beb5cf10-9adcc10b83930090.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-d68d41c88e05c801.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-11c63971b7a9593b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-d09452353bd0d9cc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-184d727cebeca767.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-21bbbbac99b720b1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-e5bea36f183b25e6.js → webpack-37b8bf78a3c6eec5.js} +1 -1
- package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +1 -0
- package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → qMI0jnlZaBjjm4aIDYNGM}/_buildManifest.js +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +29 -27
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +29 -27
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +29 -27
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +26 -24
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +26 -24
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +26 -24
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +29 -27
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +29 -27
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +29 -27
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/icons/elanous-16.png +0 -0
- package/apps/pwa/out/icons/elanous-180.png +0 -0
- package/apps/pwa/out/icons/elanous-192.png +0 -0
- package/apps/pwa/out/icons/elanous-32.png +0 -0
- package/apps/pwa/out/icons/elanous-512.png +0 -0
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +26 -24
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +29 -27
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +29 -27
- package/apps/pwa/out/manifest.webmanifest +5 -2
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +26 -24
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +26 -24
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +29 -27
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +29 -27
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +27 -25
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +29 -27
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +26 -24
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +29 -27
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +29 -27
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +29 -27
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +29 -27
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +29 -27
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +26 -24
- package/apps/pwa/out/sw-strategies.js +44 -0
- package/apps/pwa/out/sw.js +6 -44
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +29 -27
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +29 -27
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +29 -27
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +29 -27
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +29 -27
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +29 -27
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +29 -27
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +29 -27
- package/catalog/resources.yaml +6 -6
- package/docs/goal-context/steward-directives-2026-09-28.md +35 -37
- package/graphs/landing/landing-heal.yaml +1 -1
- package/graphs/release/recipes.yaml +11 -1
- package/graphs/release/release-loop.yaml +9 -2
- package/graphs/video/README.md +1 -0
- package/graphs/video/explainer-line.yaml +58 -0
- package/package.json +3 -1
- package/plugins/card-followup/README.md +33 -0
- package/plugins/card-followup/examples/card-sample.json +7 -0
- package/plugins/card-followup/graphs/card-followup.yaml +16 -0
- package/plugins/card-followup/graphs/recipes.yaml +12 -0
- package/plugins/card-followup/graphs/run-step.ts +172 -0
- package/plugins/card-followup/plugin.json +25 -0
- package/plugins/geo-check/README.md +13 -0
- package/plugins/geo-check/examples/brand-sample.json +8 -0
- package/plugins/geo-check/graphs/geo-check.yaml +20 -0
- package/plugins/geo-check/graphs/recipes.yaml +18 -0
- package/plugins/geo-check/graphs/run-step.ts +177 -0
- package/plugins/geo-check/plugin.json +24 -0
- package/plugins/job-coach/.codex-plugin/plugin.json +18 -1
- package/plugins/job-coach/README.md +42 -0
- package/plugins/job-coach/connectors/ncs/server.ts +4 -3
- package/plugins/job-coach/graphs/gap-judge.ts +44 -0
- package/plugins/job-coach/graphs/recipes.yaml +27 -0
- package/plugins/job-coach/graphs/report-enterprise.yaml +28 -0
- package/plugins/job-coach/graphs/run-step.ts +211 -17
- package/plugins/job-coach/plugin.json +16 -3
- package/plugins/job-coach/skills/enterprise-ai-fit/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-competency/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-needs/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-roadmap/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-task-analysis/SKILL.md +8 -0
- package/plugins/job-coach/skills/interview-to-profile/SKILL.md +18 -1
- package/scripts/backup/elanous-backup.sh +4 -1
- package/scripts/build-docs-site.ts +4 -3
- package/scripts/coord-channel-watch.sh +23 -4
- package/scripts/coord-post.sh +49 -12
- package/scripts/coord-tracks.json +5 -5
- package/scripts/daemon-port-baseline.txt +1 -2
- package/scripts/eval-bot-vs-grok.ts +79 -0
- package/scripts/eval-grok-live-baseline.ts +36 -0
- package/scripts/fixtures/front-route-corpus.sample.json +17 -0
- package/scripts/fleet/node-setup.sh +46 -7
- package/scripts/landing-heal/must-fix-match.ts +26 -0
- package/scripts/landing-heal/run-mission.ts +114 -20
- package/scripts/lib/android-test-device.ts +32 -0
- package/scripts/lib/front-route-measurement.ts +131 -0
- package/scripts/lib/tui-sim-keys.ts +42 -0
- package/scripts/measure-front-route-classifier.ts +56 -0
- package/scripts/media/ocr-leak-scan.sh +21 -0
- package/scripts/public-export.ts +2 -3
- package/scripts/public-import-pr.ts +80 -6
- package/scripts/publish-internal-dist.ts +2 -3
- package/scripts/red-sweep-diff.ts +1 -1
- package/scripts/release-loop/auto-approve-node.ts +72 -0
- package/scripts/release-loop/checklist-gate-node.ts +35 -0
- package/scripts/release-loop/gate-diff.ts +29 -0
- package/scripts/release-loop/gate-node.ts +366 -45
- package/scripts/release-loop/publish-node.ts +7 -3
- package/scripts/release-loop/release-docs.ts +20 -13
- package/scripts/release-loop/shard-plan.ts +47 -0
- package/scripts/release-loop/test-durations.ts +63 -0
- package/scripts/release-loop/upgrade-node.ts +3 -2
- package/scripts/test-audit-coverage-per-file.sh +16 -0
- package/scripts/test-audit-meaningless.ts +88 -0
- package/scripts/test-audit-overlap-and-weak.ts +73 -0
- package/scripts/test-deterministic.ts +27 -5
- package/scripts/tui-sim.ts +59 -14
- package/scripts/ux/bump-installed.sh +20 -0
- package/scripts/ux/lib/cdp.ts +72 -0
- package/scripts/ux/record-page.ts +53 -0
- package/skills/explainer-video/SKILL.md +117 -0
- package/skills/explainer-video/engine/brand/elanous-mark-on-dark.svg +1 -0
- package/skills/explainer-video/engine/build.mjs +431 -0
- package/skills/explainer-video/engine/reel.mjs +128 -0
- package/skills/explainer-video/engine/reel.sh +8 -0
- package/skills/explainer-video/engine/vo.ts +79 -0
- package/skills/explainer-video/examples/elanous-0.2.5/script.json +155 -0
- package/src/agent/nested-agent-env.ts +1 -0
- package/src/agent-mission/driver.ts +95 -16
- package/src/agent-mission/mission-cli.ts +6 -0
- package/src/agent-mission/resource-ladder.ts +73 -25
- package/src/auth/acp-token.ts +16 -0
- package/src/auth/github-app-token.ts +112 -0
- package/src/autopilot/gh-rest-fallback.ts +177 -0
- package/src/autopilot/mission-origin.ts +20 -4
- package/src/autopilot/mission-phase-track.ts +11 -1
- package/src/autopilot/pr-manager.ts +51 -4
- package/src/autopilot/track-agent.ts +3 -3
- package/src/autopilot/track-courier.ts +2 -2
- package/src/boot/acp-server.ts +2 -16
- package/src/boot/daemon-tools/web-search.ts +2 -1
- package/src/boot/ws-bridge.ts +11 -0
- package/src/chat/index.ts +37 -8
- package/src/chat/log-scroll-key.ts +12 -0
- package/src/chat/submit-trace.ts +30 -0
- package/src/cli/agent-cli.ts +7 -2
- package/src/cli/decisions-cli.ts +94 -0
- package/src/cli/doctor-cli.ts +40 -54
- package/src/cli/doctor-readiness.ts +16 -24
- package/src/cli/intake-cli.ts +1 -1
- package/src/cli/mcp-call.ts +9 -0
- package/src/cli/mcp-gateway-cli.ts +7 -3
- package/src/cli/nexus-ios-bind.ts +25 -8
- package/src/cli/phone-link.ts +117 -0
- package/src/cli/plugin-cli.ts +136 -11
- package/src/cli/pr-lineage.ts +2 -1
- package/src/cli/private-ledger-commands.ts +31 -0
- package/src/cli/pwa-start.ts +3 -0
- package/src/cli/release-cli.ts +97 -7
- package/src/cli/self-update.ts +7 -4
- package/src/cli/start-cli.ts +33 -14
- package/src/dashboard/acp-boot.ts +3 -0
- package/src/dashboard/index.ts +62 -13
- package/src/dashboard/input/no-provider-reply.ts +13 -0
- package/src/dashboard/slash-runtime/unknown-slash-reply.ts +78 -0
- package/src/dashboard/stale-install-check.ts +77 -0
- package/src/decisions/decision-ledger.ts +404 -0
- package/src/domains/outbound-alert.ts +13 -7
- package/src/domains/telegram-kind-route.ts +21 -0
- package/src/execution-loop/dispatch-task.ts +139 -0
- package/src/execution-loop/gate-decision-summary.ts +29 -0
- package/src/execution-loop/gates.ts +197 -0
- package/src/field/field-media.ts +245 -0
- package/src/graph-kinds/installed-plugin-nodes.ts +95 -0
- package/src/graph-kinds/plugin-nodes.ts +10 -2
- package/src/graph-runner/runner.ts +35 -5
- package/src/grounding/token.ts +12 -6
- package/src/harness/harness-cli-command.ts +68 -9
- package/src/harness/harness-pod-dispatch.ts +8 -2
- package/src/harness/harness-stop.ts +147 -18
- package/src/harness/self-send-decide.ts +26 -17
- package/src/harness/self-send-target.ts +22 -1
- package/src/harness/worktree-branch-prefix.ts +4 -3
- package/src/index.ts +50 -13
- package/src/instance/resolve.ts +6 -0
- package/src/intake-plane/collect-telegram-saved.ts +5 -0
- package/src/intake-plane/digest.ts +11 -2
- package/src/live/detail-switch.ts +8 -3
- package/src/llm/usable-llm.ts +32 -0
- package/src/loops/registry.ts +27 -5
- package/src/market/publish.ts +59 -3
- package/src/native-tool-catalog.ts +2 -2
- package/src/nexus/api/field-uploads.ts +143 -0
- package/src/nexus/api/graph-approvals.ts +174 -0
- package/src/nexus/api/harness-api.ts +219 -5
- package/src/nexus/api/http-server.ts +69 -6
- package/src/nexus/api/log-fabric.ts +10 -1
- package/src/nexus/api/mcp-oauth-hint.ts +10 -9
- package/src/nexus/api/merge-approvals.ts +2 -1
- package/src/nexus/api/meta-api.ts +14 -0
- package/src/nexus/api/plugins-credentials-api.ts +47 -0
- package/src/nexus/api/plugins-market.ts +87 -3
- package/src/nexus/api/pod-credential-api.ts +68 -14
- package/src/nexus/api/public-routes.ts +7 -0
- package/src/nexus/api/trace.ts +57 -6
- package/src/nexus/boot/register-mcp-clients.ts +99 -14
- package/src/nexus/index.ts +33 -32
- package/src/nexus/outbound/router.ts +1 -1
- package/src/notify/llm-budget-alert.ts +119 -0
- package/src/onboarding.ts +25 -2
- package/src/plugins/core/host.ts +14 -0
- package/src/plugins/install/market-fetch.ts +4 -3
- package/src/plugins/install/plugin-credentials.ts +106 -0
- package/src/plugins/install/plugin-install.ts +163 -32
- package/src/plugins/maker/plugin-maker.ts +208 -0
- package/src/prompt-library/universal-preamble.ts +8 -2
- package/src/relay/channel.ts +184 -0
- package/src/relay/mux.ts +182 -0
- package/src/relay/test-vectors.json +29 -0
- package/src/release-loop/checklist.ts +217 -0
- package/src/release-loop/release-note.ts +6 -3
- package/src/seat-address/seat-address.ts +34 -0
- package/src/self-dev/dev-cli.ts +40 -20
- package/src/self-dev/dev-pipeline.ts +77 -1
- package/src/self-implement/gate-baseline.ts +3 -1
- package/src/self-implement/goal-author.ts +18 -1
- package/src/self-implement/host-regate.ts +6 -2
- package/src/self-implement/orchestrator.ts +7 -2
- package/src/self-implement/run-ledger-compact.ts +1 -1
- package/src/self-implement/seams.ts +4 -2
- package/src/session/index.ts +23 -10
- package/src/skills/tools/dev-harness.ts +42 -0
- package/src/steward/steward-cards.ts +45 -0
- package/src/steward/triage.ts +50 -14
- package/src/task-orchestrator/surfaces/pod-artifact-return.ts +61 -3
- package/src/task-orchestrator/surfaces/pod-bun-cache.ts +18 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +36 -6
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +39 -9
- package/src/task-orchestrator/surfaces/pod-source-bundle.ts +16 -1
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +51 -13
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +172 -28
- package/src/telegram-report.ts +12 -6
- package/src/telegram.ts +108 -1
- package/src/tui-client/acp-transport-ws-client.ts +72 -5
- package/src/tui.ts +3 -2
- package/src/user-config.ts +32 -2
- package/src/util/spawn-sync-output.ts +15 -0
- package/src/version/code-revision.ts +1 -1
- package/src/version/packed-revision.json +1 -1
- package/src/video-pipeline/recipes/explainer.ts +213 -0
- package/src/video-pipeline/walk-line.ts +3 -2
- package/src/web-search/free.ts +77 -0
- package/src/web-search/index.ts +2 -0
- package/src/workflow-runtime/deps-bridge.ts +4 -3
- package/src/workflow-runtime/plugin-kind-node.ts +32 -4
- package/src/workflow-runtime/types.ts +2 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1191-7b13a24117a689fa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2375-3fcae5186c64fe6b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +0 -25
- package/apps/pwa/out/_next/static/chunks/319-d37df1b6e0cbeff4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4836-3c93795ab834381e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6090-1bd6077591603947.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7457-f5cb1cc8db9f7453.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8519-0059d82bdb4812bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9740-52467f244755917f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +0 -1
- package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-21c3261af6ab377c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-1991445b8ea86274.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-4ebfaee2369fe248.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-9fa879ebf5959096.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-ca7c3cc305c3285a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-9951930add3d2051.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-4472ddf1e2458e69.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-4de08d4b766f134b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7a975e2b9db1dfb7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-49270714ef35bb96.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-33fbdf353e2a5982.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-259f122e2dd64317.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-65b82726753658e1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-4b8df3748dc06285.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-112f7e477bda77fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-542a5c0006da637c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-63ffb347831262ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-98e4e359d97263b4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-97e24ac675ce576e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-ac6391f26391d89d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9f45c9d45ffc43bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-36995b9cb5d98218.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-0c795b577988d63f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b4311e2f71c6735a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-eb2381074df20dba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-b01336e64eba01ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-7af7f21c1f2858ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-a98ecd364586c89b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-57f0502dfbfa089b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-3467d2e1abd8a14a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-7842ca23d53ba682.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-ae9ac882912b0198.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-3a0d72e3f783bcef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-f3b39fe8358c2a97.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-5a22af3181ac2afb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-1457627bb302a22c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
- package/apps/pwa/out/_next/static/css/e66b7f5efc8df3f3.css +0 -1
- /package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → qMI0jnlZaBjjm4aIDYNGM}/_ssgManifest.js +0 -0
|
@@ -125,13 +125,18 @@ export function createPodLedgerFollower(opts: {
|
|
|
125
125
|
let disabled = false;
|
|
126
126
|
let lastProgressAt = now();
|
|
127
127
|
let lastProgressEvent: string | null = null;
|
|
128
|
+
let lastActivityMeasuredAt = lastProgressAt;
|
|
129
|
+
let lastActivityLogAt = -Infinity;
|
|
128
130
|
let nextStallMinute = stallMinutes;
|
|
129
131
|
let stalled = false;
|
|
130
|
-
function checkStall() {
|
|
132
|
+
function checkStall(ledgerRead: boolean, activityMeasured: boolean) {
|
|
131
133
|
const idleMinutes = Math.max(0, (now() - lastProgressAt) / 60_000);
|
|
132
134
|
if (idleMinutes >= nextStallMinute) {
|
|
133
135
|
log('self-implement.pod', 'stalled', { runId: opts.runId, lastProgressEvent, idleMinutes });
|
|
134
|
-
|
|
136
|
+
const observation = ledgerRead
|
|
137
|
+
? (activityMeasured ? '원장·작업 트리 모두 조용함' : '원장 조용함')
|
|
138
|
+
: '원장 못 잼';
|
|
139
|
+
onStall(`[pod] 진행 없음 ${Math.floor(idleMinutes)}분 — ${observation} · 마지막 진행 ${lastProgressEvent ?? '없음'}${activityMeasured ? '' : ' (작업 트리 못 잼)'}`);
|
|
135
140
|
stalled = true;
|
|
136
141
|
nextStallMinute = (Math.floor(idleMinutes / stallMinutes) + 1) * stallMinutes;
|
|
137
142
|
}
|
|
@@ -145,13 +150,38 @@ export function createPodLedgerFollower(opts: {
|
|
|
145
150
|
log('self-implement.pod', 'ledger-live-skipped', { runId: opts.runId, reason: 'host-ledger-exists' });
|
|
146
151
|
return;
|
|
147
152
|
}
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
offset
|
|
152
|
-
|
|
153
|
+
const since = `${Math.floor(lastActivityMeasuredAt / 1000)}.${String(lastActivityMeasuredAt % 1000).padStart(3, '0')}`;
|
|
154
|
+
const measurementStartedAt = now();
|
|
155
|
+
const until = `${Math.floor(measurementStartedAt / 1000)}.${String(measurementStartedAt % 1000).padStart(3, '0')}`;
|
|
156
|
+
const r = opts.exec(`s="\${ELANOUS_STATE_DIR:-$HOME/.elanous}"; f="$s/run-ledger/${opts.runId}.jsonl"; w="$s/worktrees"; if [ -f "$f" ]; then tail -c +${offset + 1} "$f" || exit 1; fi; target=''; matches=0; for tree in "$w"/*/*.worktrees/*; do [ -e "$tree/.git" ] || continue; owner=$(git -C "$tree" config --worktree --get elanous.harness.owner 2>/dev/null) || continue; [ "$owner" = 'dev:${opts.runId}' ] || continue; target="$tree"; matches=$((matches + 1)); done; if [ "$matches" -eq 1 ]; then activity=$(find "$target" -type d \\( -name .git -o -name node_modules \\) -prune -o -type f -newermt '@${since}' ! -newermt '@${until}' -printf . 2>/dev/null) && printf '\\nELANOUS_ACTIVITY %s\\n' "\${#activity}" || printf '\\nELANOUS_ACTIVITY_ERROR\\n'; else printf '\\nELANOUS_ACTIVITY_ERROR\\n'; fi`);
|
|
157
|
+
if (r.status !== 0) { log('self-implement.pod', 'ledger-live-unavailable', { runId: opts.runId, status: r.status, stderr: r.stderr.trim().slice(0, 200) }); checkStall(false, false); return; }
|
|
158
|
+
const marker = r.stdout.lastIndexOf('\nELANOUS_ACTIVITY');
|
|
159
|
+
const activityLine = marker < 0 ? '' : r.stdout.slice(marker + 1);
|
|
160
|
+
const activityMatch = /^ELANOUS_ACTIVITY (0|[1-9]\d*)\n$/.exec(activityLine);
|
|
161
|
+
const activityMeasured = !!activityMatch && Number.isSafeInteger(Number(activityMatch[1]));
|
|
162
|
+
// A missing measurement trailer is still a usable ledger response unless it contains activity protocol bytes.
|
|
163
|
+
const ledgerBytes = marker < 0 ? (r.stdout.includes('ELANOUS_ACTIVITY') ? '' : r.stdout) : r.stdout.slice(0, marker);
|
|
164
|
+
if (activityMeasured) {
|
|
165
|
+
lastActivityMeasuredAt = measurementStartedAt;
|
|
166
|
+
const files = Number(activityMatch![1]);
|
|
167
|
+
if (files > 0) {
|
|
168
|
+
const idleMinutes = Math.max(0, (now() - lastProgressAt) / 60_000);
|
|
169
|
+
if (stalled) log('self-implement.pod', 'stall-cleared', { runId: opts.runId, idleMinutes });
|
|
170
|
+
stalled = false;
|
|
171
|
+
lastProgressAt = now();
|
|
172
|
+
lastProgressEvent = 'worktree-activity';
|
|
173
|
+
nextStallMinute = stallMinutes;
|
|
174
|
+
if (now() - lastActivityLogAt >= 5 * 60_000) {
|
|
175
|
+
log('self-implement.pod', 'worktree-activity', { runId: opts.runId, files });
|
|
176
|
+
lastActivityLogAt = now();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (!ledgerBytes) { checkStall(true, activityMeasured); return; }
|
|
181
|
+
offset += Buffer.byteLength(ledgerBytes);
|
|
182
|
+
const text = pending + ledgerBytes;
|
|
153
183
|
const cut = text.lastIndexOf('\n');
|
|
154
|
-
if (cut < 0) { pending = text; checkStall(); return; }
|
|
184
|
+
if (cut < 0) { pending = text; checkStall(true, activityMeasured); return; }
|
|
155
185
|
const complete = text.slice(0, cut + 1);
|
|
156
186
|
pending = text.slice(cut + 1);
|
|
157
187
|
mkdirSync(dir, { recursive: true });
|
|
@@ -171,7 +201,7 @@ export function createPodLedgerFollower(opts: {
|
|
|
171
201
|
lastProgressEvent = event;
|
|
172
202
|
nextStallMinute = stallMinutes;
|
|
173
203
|
}
|
|
174
|
-
checkStall();
|
|
204
|
+
checkStall(true, activityMeasured);
|
|
175
205
|
},
|
|
176
206
|
};
|
|
177
207
|
}
|
|
@@ -13,6 +13,7 @@ type PackOptions = {
|
|
|
13
13
|
paths?: string[];
|
|
14
14
|
outDir: string;
|
|
15
15
|
maxSizeBytes?: number;
|
|
16
|
+
mirrorHead?: string;
|
|
16
17
|
log?: (category: string, event: string, data: Record<string, unknown>) => void;
|
|
17
18
|
};
|
|
18
19
|
|
|
@@ -108,7 +109,21 @@ export function packSourceBundle(options: PackOptions): PackedBundle {
|
|
|
108
109
|
ref = `refs/elanous/pod-source/${randomUUID()}`;
|
|
109
110
|
git(root, env, 'update-ref', ref, headCommit);
|
|
110
111
|
createdBundle = true;
|
|
111
|
-
|
|
112
|
+
let bundleRevision = ref;
|
|
113
|
+
if (options.mirrorHead !== undefined) {
|
|
114
|
+
// A thin bundle is usable only when its excluded history belongs to the packed base's lineage.
|
|
115
|
+
const mirrorHead = options.mirrorHead;
|
|
116
|
+
let sharedHistory = false;
|
|
117
|
+
if (/^[0-9a-f]{40,64}$/.test(mirrorHead)) {
|
|
118
|
+
try {
|
|
119
|
+
git(root, env, 'cat-file', '-e', `${mirrorHead}^{commit}`);
|
|
120
|
+
git(root, env, 'merge-base', '--is-ancestor', mirrorHead, baseCommit);
|
|
121
|
+
sharedHistory = true;
|
|
122
|
+
} catch { /* The mirror is unavailable or unrelated; include all history instead. */ }
|
|
123
|
+
}
|
|
124
|
+
bundleRevision = sharedHistory ? `${mirrorHead}..${ref}` : ref;
|
|
125
|
+
}
|
|
126
|
+
git(root, env, 'bundle', 'create', bundlePath, bundleRevision);
|
|
112
127
|
git(root, env, 'update-ref', '-d', ref, headCommit);
|
|
113
128
|
ref = undefined;
|
|
114
129
|
const sizeBytes = statSync(bundlePath).size;
|
|
@@ -30,11 +30,33 @@ function assertSize(value: number): number {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/** Job 스크립트의 원천 단계. 성공 갈래는 `ELANOUS_POD_SOURCE <kind> <HEAD>` 로 끝난다. */
|
|
33
|
-
export function podSourceScript(source: PodSource, repoUrl: string): string {
|
|
34
|
-
const
|
|
33
|
+
export function podSourceScript(source: PodSource, repoUrl: string, mirrorPath = '/host-mirror'): string {
|
|
34
|
+
const quotedUrl = `'${repoUrl.replace(/'/g, `'\\''`)}'`;
|
|
35
|
+
const quotedMirror = `'${mirrorPath.replace(/'/g, `'\\''`)}'`;
|
|
36
|
+
const clone = `git clone -q --depth 50 ${quotedUrl} repo`;
|
|
37
|
+
const start = [
|
|
38
|
+
'if [ -e repo ]; then exit 5; fi',
|
|
39
|
+
`if [ -d ${quotedMirror} ] && git -C ${quotedMirror} rev-parse --verify HEAD >/dev/null 2>&1 && git clone -q --shared -- ${quotedMirror} repo && git -C repo rev-parse --verify HEAD >/dev/null 2>&1; then`,
|
|
40
|
+
` git -C repo remote set-url origin ${quotedUrl} || exit 5`,
|
|
41
|
+
' source_via=mirror',
|
|
42
|
+
'else',
|
|
43
|
+
' if [ -e repo ]; then rm -r -- repo || exit 5; fi',
|
|
44
|
+
` ${clone} || exit 5`,
|
|
45
|
+
' source_via=github',
|
|
46
|
+
'fi',
|
|
47
|
+
"printf 'ELANOUS_POD_SOURCE_VIA %s\\n' \"$source_via\"",
|
|
48
|
+
'cd repo || exit 5',
|
|
49
|
+
];
|
|
35
50
|
if (source.kind === 'default') {
|
|
36
51
|
return [
|
|
37
|
-
|
|
52
|
+
...start,
|
|
53
|
+
'if [ "$source_via" = mirror ]; then',
|
|
54
|
+
' remote_head=$(git ls-remote origin HEAD | cut -f1) || exit 5',
|
|
55
|
+
' if ! [[ "$remote_head" =~ ^[0-9a-f]{40}$ ]]; then exit 5; fi',
|
|
56
|
+
' if [ "$remote_head" != "$(git rev-parse HEAD)" ]; then',
|
|
57
|
+
' git fetch --depth 50 origin HEAD && git reset --hard FETCH_HEAD || exit 5',
|
|
58
|
+
' fi',
|
|
59
|
+
'fi',
|
|
38
60
|
'head=$(git rev-parse HEAD)',
|
|
39
61
|
"printf 'ELANOUS_POD_SOURCE default %s\\n' \"$head\"",
|
|
40
62
|
].join('\n');
|
|
@@ -42,9 +64,9 @@ export function podSourceScript(source: PodSource, repoUrl: string): string {
|
|
|
42
64
|
if (source.kind === 'commit') {
|
|
43
65
|
const sha = assertSha(source.sha, 'sha');
|
|
44
66
|
return [
|
|
45
|
-
|
|
46
|
-
`git fetch --depth 1 origin ${sha}`,
|
|
47
|
-
`git checkout --detach ${sha}`,
|
|
67
|
+
...start,
|
|
68
|
+
`if ! git cat-file -e '${sha}^{commit}' 2>/dev/null; then git fetch --depth 1 origin ${sha} || exit 5; fi`,
|
|
69
|
+
`git checkout --detach ${sha} || exit 5`,
|
|
48
70
|
'head=$(git rev-parse HEAD)',
|
|
49
71
|
"printf 'ELANOUS_POD_SOURCE commit %s\\n' \"$head\"",
|
|
50
72
|
].join('\n');
|
|
@@ -52,9 +74,11 @@ export function podSourceScript(source: PodSource, repoUrl: string): string {
|
|
|
52
74
|
if (source.kind === 'pr') {
|
|
53
75
|
const n = assertPositiveInt(source.number);
|
|
54
76
|
return [
|
|
55
|
-
|
|
56
|
-
`git
|
|
57
|
-
|
|
77
|
+
...start,
|
|
78
|
+
`pr_sha=$(git ls-remote origin refs/pull/${n}/head | cut -f1) || exit 5`,
|
|
79
|
+
'if ! [[ "$pr_sha" =~ ^[0-9a-f]{40}$ ]]; then exit 5; fi',
|
|
80
|
+
`if ! git cat-file -e "$pr_sha^{commit}" 2>/dev/null; then git fetch --depth 50 origin pull/${n}/head || exit 5; fi`,
|
|
81
|
+
`git checkout -B pr-${n} "$pr_sha" || exit 5`,
|
|
58
82
|
'head=$(git rev-parse HEAD)',
|
|
59
83
|
"printf 'ELANOUS_POD_SOURCE pr %s\\n' \"$head\"",
|
|
60
84
|
].join('\n');
|
|
@@ -82,10 +106,24 @@ export function podSourceScript(source: PodSource, repoUrl: string): string {
|
|
|
82
106
|
" printf 'ELANOUS_POD_SOURCE_MISMATCH sha256\\n'",
|
|
83
107
|
' exit 3',
|
|
84
108
|
'fi',
|
|
85
|
-
'
|
|
86
|
-
`git -C
|
|
87
|
-
|
|
109
|
+
'if [ -e repo ]; then exit 5; fi',
|
|
110
|
+
`if [ -d ${quotedMirror} ] && git -C ${quotedMirror} rev-parse --verify HEAD >/dev/null 2>&1 && git clone -q --shared -- ${quotedMirror} repo && git -C repo rev-parse --verify HEAD >/dev/null 2>&1; then`,
|
|
111
|
+
` git -C repo remote set-url origin ${quotedUrl} || exit 5`,
|
|
112
|
+
' if git -C repo bundle verify /tmp/source.bundle >/dev/null 2>&1; then',
|
|
113
|
+
' git -C repo fetch /tmp/source.bundle "refs/elanous/pod-source/*:refs/remotes/bundle/*" || exit 5',
|
|
114
|
+
' else',
|
|
115
|
+
' rm -r -- repo || exit 5',
|
|
116
|
+
' git clone /tmp/source.bundle repo || exit 5',
|
|
117
|
+
' git -C repo fetch /tmp/source.bundle "refs/elanous/pod-source/*:refs/remotes/bundle/*" || exit 5',
|
|
118
|
+
' fi',
|
|
119
|
+
'else',
|
|
120
|
+
' if [ -e repo ]; then rm -r -- repo || exit 5; fi',
|
|
121
|
+
' git clone /tmp/source.bundle repo || exit 5',
|
|
122
|
+
' git -C repo fetch /tmp/source.bundle "refs/elanous/pod-source/*:refs/remotes/bundle/*" || exit 5',
|
|
123
|
+
'fi',
|
|
124
|
+
`git -C repo checkout --detach ${headCommit} || exit 5`,
|
|
125
|
+
'head=$(git -C repo rev-parse HEAD) || exit 5',
|
|
88
126
|
"printf 'ELANOUS_POD_SOURCE bundle %s\\n' \"$head\"",
|
|
89
|
-
'cd repo',
|
|
127
|
+
'cd repo || exit 5',
|
|
90
128
|
].join('\n');
|
|
91
129
|
}
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
import { podSkillsDigest, readSkillEnvFiles, resolvePodSkills } from './pod-skills.js';
|
|
15
15
|
import { podSourceScript, type PodSource } from './pod-source-receive.js';
|
|
16
16
|
import { GROUNDING_TOKEN_ENV, GROUNDING_URL_ENV, mintGroundingToken, revokeGroundingRun, type GroundingTokenScope } from '../../grounding/token.js';
|
|
17
|
-
import { POD_CREDENTIAL_GROK_PATH } from '../../nexus/api/pod-credential-api.js';
|
|
17
|
+
import { POD_CREDENTIAL_GROK_PATH, POD_CREDENTIAL_GITHUB_PATH, POD_GITHUB_CREDENTIAL_TOKEN_ENV, POD_GITHUB_CREDENTIAL_URL_ENV, installationRepositories } from '../../nexus/api/pod-credential-api.js';
|
|
18
|
+
import { githubInstallationCredential } from '../../auth/github-app-token.js';
|
|
18
19
|
import { POD_CREDENTIAL_TOKEN_ENV, POD_CREDENTIAL_URL_ENV } from '../../grok/credential.js';
|
|
19
20
|
import { collectPodLedgers, createPodLedgerFollower } from './pod-ledger-collect.js';
|
|
20
21
|
import { collectPodArtifacts } from './pod-artifact-return.js';
|
|
@@ -47,6 +48,7 @@ import { homedir } from 'node:os';
|
|
|
47
48
|
import { isAbsolute, join, normalize, relative, resolve, sep } from 'node:path';
|
|
48
49
|
import { findGitDir } from '../../git-fs/locate.js';
|
|
49
50
|
import { debug } from '../../debug/log.js';
|
|
51
|
+
import { selectLiveDetail } from '../../live/detail-switch.js';
|
|
50
52
|
import { authStorePath } from '../../oauth/store.js';
|
|
51
53
|
import { grokAuthFilePath, resolveGrokCredential } from '../../grok/credential.js';
|
|
52
54
|
import { defaultGrokModel } from '../../grok/models.js';
|
|
@@ -58,6 +60,14 @@ import { codexQuotaPolicyFromConfig } from '../../oauth/codex-account-store.js';
|
|
|
58
60
|
|
|
59
61
|
export type Kubectl = (args: readonly string[], input?: string) => { status: number | null; stdout: string; stderr: string };
|
|
60
62
|
|
|
63
|
+
// gh auth login may reject installation tokens when validating /user. Its config is
|
|
64
|
+
// still the credential source for gh and setup-git's helper. Install it privately
|
|
65
|
+
// on that failure; rename keeps concurrent readers from seeing a partial token.
|
|
66
|
+
const APP_GH_CONFIG_PATH = '$HOME/.config/gh';
|
|
67
|
+
const APP_GH_AUTH_SCRIPT = 'const fs=require("node:fs"),path=require("node:path"),crypto=require("node:crypto");const token=fs.readFileSync(0,"utf8").trim();if(!/^[A-Za-z0-9_]+$/.test(token))process.exit(1);const dir=process.env.GH_CONFIG_DIR||path.join(process.env.HOME,".config/gh");fs.mkdirSync(dir,{recursive:true,mode:0o700});const dest=path.join(dir,"hosts.yml"),temp=path.join(dir,".hosts.yml."+crypto.randomUUID());try{fs.writeFileSync(temp,"github.com:\\n oauth_token: "+token+"\\n git_protocol: https\\n",{mode:0o600,flag:"wx"});fs.renameSync(temp,dest);fs.chmodSync(dest,0o600)}finally{try{fs.unlinkSync(temp)}catch{}}';
|
|
68
|
+
const APP_GH_AUTH_COMMAND = `bun -e '${APP_GH_AUTH_SCRIPT}'`;
|
|
69
|
+
const APP_GH_EXEC = `unset GH_TOKEN GITHUB_TOKEN; GH_CONFIG_DIR="${APP_GH_CONFIG_PATH}" exec "$@"`;
|
|
70
|
+
|
|
61
71
|
export interface PodSpawnOptions {
|
|
62
72
|
/** 이미지 판(라벨 elanous.commit) — 주입(시험·감독기가 이미 잰 값). 없으면 docker 로 잰다. */
|
|
63
73
|
imageCommit?: string | null;
|
|
@@ -75,6 +85,10 @@ export interface PodSpawnOptions {
|
|
|
75
85
|
/** 클러스터 안 이미지(`docker/harness/run.sh` 가 만드는 `elanous-harness:local`). */
|
|
76
86
|
image?: string;
|
|
77
87
|
repoUrl?: string;
|
|
88
|
+
/** 호스트 Git 미러 디렉터리 — 명시 옵션 > 환경변수 > pod.hostMirror. */
|
|
89
|
+
hostMirror?: string;
|
|
90
|
+
/** 설정 읽기(시험 주입). kubectl 주입 시험에서는 기본 사용자 설정을 읽지 않는다. */
|
|
91
|
+
configHostMirror?: () => string | undefined;
|
|
78
92
|
/** 원천 — 없으면 종전 `git clone --depth 50`. bundle 이면 apply 뒤 kubectl cp 로 싣는다. */
|
|
79
93
|
source?: PodSource;
|
|
80
94
|
/** 호스트 환경에서 읽어 Pod env 로 넣을 키 이름(예: OPENROUTER_API_KEY · ANTHROPIC_API_KEY) — 벤치마크 과금 경로. */
|
|
@@ -101,15 +115,22 @@ export interface PodSpawnOptions {
|
|
|
101
115
|
pool?: PodPoolScheduler;
|
|
102
116
|
/** ☸️ 원격 그라운딩 주소(P13) — 없으면 호스트 env ELANOUS_GROUNDING_URL → 설정 pod.groundingUrl. 셋 다 없으면 토큰을 안 만든다. */
|
|
103
117
|
groundingUrl?: string;
|
|
104
|
-
/** 시험 심 — 토큰 발급·회수.
|
|
105
|
-
mintGrounding?: (claims: { runId: string; job: string; ttlMs: number; scope?: GroundingTokenScope }) => Promise<{ token: string; exp: number }>;
|
|
118
|
+
/** 시험 심 — 토큰 발급·회수. GitHub credential 토큰에는 호스트가 검증한 저장소를 서명한다. */
|
|
119
|
+
mintGrounding?: (claims: { runId: string; job: string; ttlMs: number; scope?: GroundingTokenScope; repository?: string }) => Promise<{ token: string; exp: number }>;
|
|
106
120
|
revokeGrounding?: (runId: string) => void;
|
|
121
|
+
/** Test seam for the host's repository-scoped GitHub App installation token. */
|
|
122
|
+
githubInstallation?: (repository: string) => { token: string; expires_at?: string; expiresAt?: string | number } | null;
|
|
123
|
+
/** GitHub installation repository lookup; required to prove the scoped token matches owner/name. */
|
|
124
|
+
githubRepositories?: (token: string) => Promise<readonly string[] | null>;
|
|
125
|
+
now?: () => number;
|
|
107
126
|
sleep?: (ms: number) => Promise<void>;
|
|
108
127
|
credentials?: (account: string) => { elanousAuth: string; codexAuth: string; ghToken: string };
|
|
109
128
|
grokCredentials?: () => { grokAuth?: string; grokApiKey?: string; ghToken: string };
|
|
110
129
|
/** 호스트 키 캐시(`~/.cache/<소문자 이름>`)에서 키를 읽는다(시험 주입) — env 에 없을 때. */
|
|
111
130
|
readKeyCache?: (name: string) => string | undefined;
|
|
112
131
|
env?: NodeJS.ProcessEnv;
|
|
132
|
+
/** Detail switch file (test injection; defaults to the host's local/production selection). */
|
|
133
|
+
liveDetailFile?: string;
|
|
113
134
|
}
|
|
114
135
|
|
|
115
136
|
export function defaultKubectl(args: readonly string[], input?: string): { status: number | null; stdout: string; stderr: string } {
|
|
@@ -188,6 +209,14 @@ export function defaultGhToken(): string {
|
|
|
188
209
|
return r.stdout.trim();
|
|
189
210
|
}
|
|
190
211
|
|
|
212
|
+
/** Bind credential issuance to the repository the host actually supplied to the Pod. */
|
|
213
|
+
function githubRepositoryFromUrl(repoUrl: string): string | null {
|
|
214
|
+
const match = /^(?:https:\/\/github\.com\/|git@github\.com:)([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+?)(?:\.git)?\/?$/.exec(repoUrl);
|
|
215
|
+
const owner = match?.[1];
|
|
216
|
+
const name = match?.[2];
|
|
217
|
+
return owner && name && ![owner, name].some((part) => part === '.' || part === '..') ? `${owner}/${name}` : null;
|
|
218
|
+
}
|
|
219
|
+
|
|
191
220
|
/** Job 스크립트의 salvage 단계 — 자식 rc 가 0 이 아닐 때만, 변경·미푸시 커밋이 있는 격리 워크트리를 `salvage/<job>/<worktree>` 로 push.
|
|
192
221
|
* main·self-impl/ 로는 절대 안 민다. 실패해도 제어 흐름을 바꾸지 않는다(호출자는 이 뒤에 `exit $rc`). */
|
|
193
222
|
export function podSalvageScript(): string {
|
|
@@ -297,13 +326,13 @@ export function podRunLabels(o: { runId?: string; parentRunId?: string }): Recor
|
|
|
297
326
|
|
|
298
327
|
/** Pod 메모리 «등급» — 대표 09-27: PWA Pod 골이 16Gi 에서 OOM 으로 죽는다 ⇒ «high» 등급을 옵션으로 고른다.
|
|
299
328
|
* 등급 = `standard`(`ELANOUS_POD_MEMORY` 또는 16Gi) · `high`(`ELANOUS_POD_MEMORY_HIGH` 또는 32Gi · standard 보다 작아지지 않는다).
|
|
300
|
-
* 고르는 순서(앞이 이긴다): ① 발사 옵션 `--pod-memory <등급>`(→ `ELANOUS_POD_MEMORY_TIER`) ②
|
|
329
|
+
* 고르는 순서(앞이 이긴다): ① 발사 옵션 `--pod-memory <등급>`(→ `ELANOUS_POD_MEMORY_TIER`) ② 조각 문면 한 줄 `Pod 메모리: high|standard` ③ 조각의 `apps/pwa/` ④ 부모 골 본문의 같은 두 규칙 ⑤ standard.
|
|
301
330
|
* node-b 노드 할당 가능 약 251Gi. */
|
|
302
331
|
export const POD_MEMORY_DEFAULT = '16Gi';
|
|
303
332
|
export const POD_MEMORY_HIGH_DEFAULT = '32Gi';
|
|
304
333
|
export const POD_MEMORY_TIERS = ['standard', 'high'] as const;
|
|
305
334
|
export type PodMemoryTier = (typeof POD_MEMORY_TIERS)[number];
|
|
306
|
-
export type PodMemorySource = 'option' | 'goal-line' | 'pwa-auto' | 'default';
|
|
335
|
+
export type PodMemorySource = 'option' | 'goal-line' | 'pwa-auto' | 'default' | 'parent-goal-line' | 'parent-pwa-auto';
|
|
307
336
|
const PWA_PATH = /(?:^|[\s`'"(,·])apps\/pwa\//m;
|
|
308
337
|
const GOAL_LINE = /^\s*(?:Pod 메모리|pod-memory)\s*:\s*(standard|high)\s*$/im;
|
|
309
338
|
|
|
@@ -322,12 +351,15 @@ function memoryGi(v: string): number | null {
|
|
|
322
351
|
return m[2] === 'Gi' ? Number(m[1]) : Number(m[1]) / 1024;
|
|
323
352
|
}
|
|
324
353
|
|
|
325
|
-
export function podMemoryLimitFor(feature: string, env: NodeJS.ProcessEnv | Readonly<Record<string, string | undefined
|
|
354
|
+
export function podMemoryLimitFor(feature: string, env: NodeJS.ProcessEnv | Readonly<Record<string, string | undefined>>, parentGoal?: string): { limit: string; tier: PodMemoryTier; source: PodMemorySource } {
|
|
326
355
|
const option = parsePodMemoryTier(env.ELANOUS_POD_MEMORY_TIER);
|
|
327
356
|
const line = parsePodMemoryTier(GOAL_LINE.exec(feature)?.[1]);
|
|
357
|
+
const parentLine = !option && !line && !goalTouchesPwa(feature) ? parsePodMemoryTier(GOAL_LINE.exec(parentGoal ?? '')?.[1]) : null;
|
|
328
358
|
const [tier, source]: [PodMemoryTier, PodMemorySource] = option ? [option, 'option']
|
|
329
359
|
: line ? [line, 'goal-line']
|
|
330
360
|
: goalTouchesPwa(feature) ? ['high', 'pwa-auto']
|
|
361
|
+
: parentLine ? [parentLine, 'parent-goal-line']
|
|
362
|
+
: parentGoal && goalTouchesPwa(parentGoal) ? ['high', 'parent-pwa-auto']
|
|
331
363
|
: ['standard', 'default'];
|
|
332
364
|
const base = env.ELANOUS_POD_MEMORY?.trim() || POD_MEMORY_DEFAULT;
|
|
333
365
|
if (tier === 'standard') return { limit: base, tier, source };
|
|
@@ -336,7 +368,7 @@ export function podMemoryLimitFor(feature: string, env: NodeJS.ProcessEnv | Read
|
|
|
336
368
|
return { limit: a !== null && b !== null && a > b ? base : high, tier, source };
|
|
337
369
|
}
|
|
338
370
|
|
|
339
|
-
export function podJobManifest(o: { name: string; namespace: string; image: string; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; args: readonly string[]; passEnv: readonly string[]; deadlineSeconds: number; runId?: string; parentRunId?: string; armEnv?: Readonly<Record<string, string>>; hostId?: string; imageCommit?: string | null; skillEnvs?: readonly string[]; memoryLimit?: string; goalDoc?: string; grokCredential?: 'subscription' | 'api_key'; codexAccounts?: readonly string[] }): Record<string, unknown> {
|
|
371
|
+
export function podJobManifest(o: { name: string; namespace: string; image: string; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; hostMirror?: string; args: readonly string[]; passEnv: readonly string[]; deadlineSeconds: number; runId?: string; parentRunId?: string; armEnv?: Readonly<Record<string, string>>; hostId?: string; imageCommit?: string | null; skillEnvs?: readonly string[]; memoryLimit?: string; goalDoc?: string; grokCredential?: 'subscription' | 'api_key'; codexAccounts?: readonly string[]; appCredential?: boolean }): Record<string, unknown> {
|
|
340
372
|
const quoted = o.args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(' ');
|
|
341
373
|
const goalPath = o.goalDoc ? `'${(o.goalDoc.startsWith('-') ? `./${o.goalDoc}` : o.goalDoc).replace(/'/g, `'\\''`)}'` : undefined;
|
|
342
374
|
const askBaseIndex = o.goalDoc ? o.args.indexOf('--base') : -1;
|
|
@@ -351,7 +383,7 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
|
|
|
351
383
|
: o.grokCredential === 'api_key'
|
|
352
384
|
? 'mkdir -p ~/.grok && install -m 600 /creds/grok-api-key ~/.grok/api-key && export XAI_API_KEY="$(cat ~/.grok/api-key)" && export ELANOUS_LLM_PROVIDER=grok'
|
|
353
385
|
: o.codexAccounts ? podCodexAccountScript(o.codexAccounts) : `mkdir -p ~/.elanous ~/.codex && cp /creds/elanous-auth.json ~/.elanous/auth.json && cp /creds/codex-auth.json ~/.codex/auth.json && chmod 600 ~/.elanous/auth.json ~/.codex/auth.json\n${podQuotaPolicyExport()}`,
|
|
354
|
-
'export GH_TOKEN="$(cat /creds/gh-token)"',
|
|
386
|
+
o.appCredential ? `unset GH_TOKEN GITHUB_TOKEN; export GH_CONFIG_DIR="${APP_GH_CONFIG_PATH}"; mkdir -p "$GH_CONFIG_DIR" && chmod 700 "$GH_CONFIG_DIR" && { gh auth login --with-token < /creds/gh-token >/dev/null 2>&1 || ${APP_GH_AUTH_COMMAND} < /creds/gh-token; } && chmod 600 "$GH_CONFIG_DIR/hosts.yml" && chmod 700 "$GH_CONFIG_DIR" || exit 7` : 'export GH_TOKEN="$(cat /creds/gh-token)"',
|
|
355
387
|
// 🔑 스킬 키(.env) — 이미지엔 없다. 이 런의 Secret 에서 각 스킬 폴더로 0600 복사(값은 로그에 안 나온다).
|
|
356
388
|
...(o.skillEnvs?.length ? [`for n in ${o.skillEnvs.join(' ')}; do [ -d ~/.claude/skills/$n ] && install -m 600 /creds/skillenv-$n ~/.claude/skills/$n/.env; done; echo "[pod] skill env: ${o.skillEnvs.join(',')}"`] : []),
|
|
357
389
|
'git config --global user.name "elanous pod child" && git config --global user.email "noreply@anthropic.com" && gh auth setup-git',
|
|
@@ -465,9 +497,9 @@ if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
|
|
|
465
497
|
...Object.entries(o.armEnv ?? {}).map(([name, value]) => ({ name, value })),
|
|
466
498
|
...o.passEnv.map((key) => ({ name: key, valueFrom: { secretKeyRef: { name: `${o.name}-creds`, key: `env-${key}` } } })),
|
|
467
499
|
],
|
|
468
|
-
volumeMounts: [{ name: 'creds', mountPath: '/creds', readOnly: true }],
|
|
500
|
+
volumeMounts: [{ name: 'creds', mountPath: '/creds', readOnly: true }, ...(o.hostMirror ? [{ name: 'host-mirror', mountPath: '/host-mirror', readOnly: true }] : [])],
|
|
469
501
|
}],
|
|
470
|
-
volumes: [{ name: 'creds', secret: { secretName: `${o.name}-creds`, defaultMode: 0o400 } }],
|
|
502
|
+
volumes: [{ name: 'creds', secret: { secretName: `${o.name}-creds`, defaultMode: 0o400 } }, ...(o.hostMirror ? [{ name: 'host-mirror', hostPath: { path: o.hostMirror, type: 'Directory' } }] : [])],
|
|
471
503
|
},
|
|
472
504
|
},
|
|
473
505
|
},
|
|
@@ -495,12 +527,34 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
495
527
|
const namespace = options.namespace ?? 'elanous-test';
|
|
496
528
|
const image = options.image ?? 'elanous-harness:local';
|
|
497
529
|
const repoUrl = options.repoUrl ?? 'https://github.com/ElanvitalAI/elanous';
|
|
530
|
+
const repository = githubRepositoryFromUrl(repoUrl);
|
|
498
531
|
const env = options.env ?? process.env;
|
|
532
|
+
const hostMirror = (options.hostMirror ?? env.ELANOUS_POD_HOST_MIRROR ?? (options.configHostMirror ?? (options.kubectl ? () => undefined : configHostMirror))())?.trim() || undefined;
|
|
533
|
+
if (hostMirror && !isAbsolute(hostMirror)) throw new Error('pod.hostMirror must be an absolute directory path');
|
|
499
534
|
const hostSupervised = options.hostSupervised ?? true;
|
|
500
535
|
return (input) => {
|
|
501
536
|
const name = podJobName(input.spaceId);
|
|
502
|
-
const
|
|
503
|
-
|
|
537
|
+
const featureMemory = podMemoryLimitFor(input.feature, env);
|
|
538
|
+
let parentGoal: string | undefined;
|
|
539
|
+
let parentGoalUnreadable = false;
|
|
540
|
+
let inheritedFrom: string | undefined;
|
|
541
|
+
if (featureMemory.source === 'default' && env.ELANOUS_POD_GOAL_DOC) {
|
|
542
|
+
try {
|
|
543
|
+
const requestedPath = normalize(env.ELANOUS_POD_GOAL_DOC);
|
|
544
|
+
if (isAbsolute(requestedPath) || requestedPath === '..' || requestedPath.startsWith(`..${sep}`) || requestedPath === '.') throw new Error('invalid goal path');
|
|
545
|
+
const root = findGitDir(process.cwd())?.root;
|
|
546
|
+
if (!root) throw new Error('no repository');
|
|
547
|
+
const realRoot = realpathSync(root);
|
|
548
|
+
const goalFile = realpathSync(resolve(realRoot, requestedPath));
|
|
549
|
+
const withinRoot = relative(realRoot, goalFile);
|
|
550
|
+
if (withinRoot === '..' || withinRoot.startsWith(`..${sep}`) || isAbsolute(withinRoot)) throw new Error('outside repository');
|
|
551
|
+
parentGoal = readFileSync(goalFile, 'utf8').replace(/\r\n/g, '\n').split(/^## 실행 기록(?:\n|$)/m, 1)[0];
|
|
552
|
+
inheritedFrom = withinRoot;
|
|
553
|
+
} catch { parentGoalUnreadable = true; }
|
|
554
|
+
}
|
|
555
|
+
const { limit: memoryLimit, tier: memoryTier, source: memorySource } = parentGoal === undefined ? featureMemory : podMemoryLimitFor(input.feature, env, parentGoal);
|
|
556
|
+
debug.log('self-implement.pod', 'memory-limit', { spaceId: input.spaceId, memoryLimit, tier: memoryTier, source: memorySource,
|
|
557
|
+
...(memorySource.startsWith('parent-') ? { inheritedFrom } : {}), ...(parentGoalUnreadable ? { parentGoal: 'unreadable' } : {}) });
|
|
504
558
|
const childRunId = mintRunId();
|
|
505
559
|
const parentRunId = env.ELANOUS_RUN_ID?.trim();
|
|
506
560
|
const address = `self-impl:${input.spaceId}`;
|
|
@@ -548,6 +602,8 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
548
602
|
debug.log('self-implement.pod', unavailableEvent, { job: name, reason: error instanceof Error ? error.message : String(error) });
|
|
549
603
|
return { status: 'unavailable', childLedgerIncomplete: false, samples: [] };
|
|
550
604
|
}
|
|
605
|
+
const via = /^ELANOUS_POD_SOURCE_VIA (mirror|github)\r?$/m.exec(full.stdout)?.[1];
|
|
606
|
+
if (via) debug.log('self-implement.pod', 'source-via', { job: name, via });
|
|
551
607
|
let ledgerIncomplete = false;
|
|
552
608
|
let childLedgerIncomplete = false;
|
|
553
609
|
let artifactIncomplete = false;
|
|
@@ -604,9 +660,9 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
604
660
|
}
|
|
605
661
|
}
|
|
606
662
|
// Grok 자격은 전용 Secret 키 하나로만 보낸다 — passEnv 의 API 키 중복 전달은 막는다.
|
|
607
|
-
const passKeys =
|
|
608
|
-
|
|
609
|
-
|
|
663
|
+
const passKeys = (options.passEnv ?? []).filter((key) => key !== 'ELANOUS_LIVE_DETAIL_UNTIL'
|
|
664
|
+
&& key !== POD_GITHUB_CREDENTIAL_TOKEN_ENV && key !== POD_GITHUB_CREDENTIAL_URL_ENV
|
|
665
|
+
&& (!grok || !['XAI_API_KEY', 'GROK_API_KEY', 'GROK_CODE_XAI_API_KEY'].includes(key)));
|
|
610
666
|
const skillEnvs: Record<string, string> = options.skillEnv
|
|
611
667
|
? (options.readSkillEnv ?? (() => readSkillEnvFiles(resolvePodSkills(env).skills)))()
|
|
612
668
|
: {};
|
|
@@ -632,6 +688,34 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
632
688
|
} else if (grokSubscription && !groundingUrl) {
|
|
633
689
|
debug.log('self-implement.pod', 'credential-relay-skipped', { reason: 'no-host-url' });
|
|
634
690
|
}
|
|
691
|
+
const now = options.now ?? Date.now;
|
|
692
|
+
const issueVerifiedAppCredential = async (): Promise<{ token: string; expiresAt: number } | null> => {
|
|
693
|
+
if (!repository) return null;
|
|
694
|
+
// This is the same repository-scoped, contents+pull_requests installation request on launch and refresh.
|
|
695
|
+
const candidate = (options.githubInstallation ?? ((repo) => githubInstallationCredential({ scope: { repository: repo.split('/')[1]! } })))(repository);
|
|
696
|
+
if (!candidate?.token) return null;
|
|
697
|
+
const expiry = candidate.expires_at ?? ('expiresAt' in candidate ? candidate.expiresAt : undefined);
|
|
698
|
+
const expiresAt = typeof expiry === 'number' ? expiry : Date.parse(expiry ?? '');
|
|
699
|
+
if (!Number.isFinite(expiresAt) || expiresAt <= now()) return null;
|
|
700
|
+
const available = await (options.githubRepositories ?? installationRepositories)(candidate.token);
|
|
701
|
+
return available?.length === 1 && available[0]?.toLowerCase() === repository.toLowerCase()
|
|
702
|
+
? { token: candidate.token, expiresAt } : null;
|
|
703
|
+
};
|
|
704
|
+
let appCredential: Awaited<ReturnType<typeof issueVerifiedAppCredential>> = null;
|
|
705
|
+
try { appCredential = await issueVerifiedAppCredential(); }
|
|
706
|
+
catch { debug.log('self-implement.pod', 'github-app-unavailable', { job: name, reason: 'mint-or-verification-failed' }); }
|
|
707
|
+
const githubRelay = appCredential && groundingUrl && repository
|
|
708
|
+
? await mint({ runId: groundingRunId, job: name, ttlMs: groundingTtlMs, scope: 'gh-credential', repository })
|
|
709
|
+
: null;
|
|
710
|
+
if (githubRelay) {
|
|
711
|
+
groundingMinted = groundingRunId;
|
|
712
|
+
debug.log('self-implement.pod', 'github-credential-relay', { job: name, runId: groundingRunId, exp: githubRelay.exp });
|
|
713
|
+
}
|
|
714
|
+
// The host polling loop is a refresh path even when the Pod cannot reach the relay.
|
|
715
|
+
const hostRefresh = Boolean(appCredential);
|
|
716
|
+
const podGhToken = hostRefresh ? appCredential!.token : creds.ghToken;
|
|
717
|
+
const githubPassKeys = hostRefresh ? passKeys.filter((key) => !['GH_TOKEN', 'GITHUB_TOKEN', 'GH_CONFIG_DIR'].includes(key)) : passKeys;
|
|
718
|
+
let ghTokenExpiresAt = hostRefresh ? appCredential!.expiresAt : null;
|
|
635
719
|
const requestedGoalDoc = env.ELANOUS_POD_GOAL_DOC;
|
|
636
720
|
const requestedPath = requestedGoalDoc ? normalize(requestedGoalDoc) : undefined;
|
|
637
721
|
if (requestedPath && (isAbsolute(requestedPath) || requestedPath === '..' || requestedPath.startsWith(`..${sep}`) || requestedPath === '.')) {
|
|
@@ -700,17 +784,18 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
700
784
|
: codexCredentials
|
|
701
785
|
? Object.fromEntries(codexCredentials.map((candidate, i) => [`codex-${i}.json`, JSON.stringify({ elanousAuth: candidate.elanousAuth, codexAuth: candidate.codexAuth })]))
|
|
702
786
|
: { 'elanous-auth.json': (creds as ReturnType<typeof hostCredentials>).elanousAuth, 'codex-auth.json': (creds as ReturnType<typeof hostCredentials>).codexAuth }),
|
|
703
|
-
'gh-token':
|
|
787
|
+
'gh-token': podGhToken, feature: input.feature,
|
|
704
788
|
...(goalDocument !== undefined ? { 'goal-doc': goalDocument } : {}),
|
|
705
789
|
...Object.fromEntries(Object.entries(skillEnvs).map(([n, text]) => [`skillenv-${n}`, text])),
|
|
706
790
|
...(grounding ? { [`env-${GROUNDING_TOKEN_ENV}`]: grounding.token } : {}),
|
|
707
791
|
...(credentialRelay ? { [`env-${POD_CREDENTIAL_TOKEN_ENV}`]: credentialRelay.token } : {}),
|
|
708
|
-
...
|
|
792
|
+
...(githubRelay ? { [`env-${POD_GITHUB_CREDENTIAL_TOKEN_ENV}`]: githubRelay.token } : {}),
|
|
793
|
+
...Object.fromEntries(githubPassKeys.map((k) => [k, env[k] ?? (options.readKeyCache ?? defaultReadKeyCache)(k)] as const).filter(([, v]) => v).map(([k, v]) => [`env-${k}`, v!])),
|
|
709
794
|
},
|
|
710
795
|
};
|
|
711
796
|
const hostKey = (k: string): string | undefined => env[k] ?? (options.readKeyCache ?? defaultReadKeyCache)(k);
|
|
712
|
-
const passEnv =
|
|
713
|
-
const missing =
|
|
797
|
+
const passEnv = githubPassKeys.filter((k) => hostKey(k));
|
|
798
|
+
const missing = githubPassKeys.filter((k) => !hostKey(k));
|
|
714
799
|
if (missing.length) debug.log('self-implement.pod', 'pass-env-missing', { job: name, missing }, { level: 'warn' });
|
|
715
800
|
// ☸️ 재개(P3 · 2026-09-26): 같은 이름의 Job 이 이미 있으면 «지우지 않고» 붙는다 — 호스트가 끊긴 동안에도 원격은 계속 돌았다.
|
|
716
801
|
// 🩸 종전엔 여기서 delete 부터 해서, 재개가 원격에서 멀쩡히 돌던 Job 을 죽였다. 실패로 끝난 Job 만 지우고 다시 만든다.
|
|
@@ -726,9 +811,12 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
726
811
|
const existingJob = kubectl(['-n', namespace, 'get', 'job', name, '-o', 'json']);
|
|
727
812
|
if (existingJob.status === 0) {
|
|
728
813
|
try {
|
|
729
|
-
const job = JSON.parse(existingJob.stdout) as { spec?: { template?: { spec?: { containers?: Array<{ name?: string; env?: Array<{ name: string; value?: string }> }> } } } };
|
|
730
|
-
const
|
|
814
|
+
const job = JSON.parse(existingJob.stdout) as { spec?: { template?: { spec?: { containers?: Array<{ name?: string; args?: string[]; env?: Array<{ name: string; value?: string }> }> } } } };
|
|
815
|
+
const child = job.spec?.template?.spec?.containers?.find((c) => c.name === 'child');
|
|
816
|
+
const inherited = child?.env?.find((e) => e.name === 'ELANOUS_RUN_ID')?.value;
|
|
731
817
|
if (inherited && inherited !== parentRunId && inherited === normalizeRunId(inherited)) liveChildRunId = inherited;
|
|
818
|
+
// A resumed App Job has an unknown remaining lifetime; refresh its gh config on the first live poll.
|
|
819
|
+
ghTokenExpiresAt = child?.args?.[0]?.includes('gh auth login --with-token < /creds/gh-token') ? now() : null;
|
|
732
820
|
} catch { /* The existing Job's identity must be verified before following it. */ }
|
|
733
821
|
}
|
|
734
822
|
if (liveChildRunId === launchRunId) {
|
|
@@ -737,6 +825,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
737
825
|
}
|
|
738
826
|
}
|
|
739
827
|
if (reattach) debug.log('self-implement.pod', 'job-reattach', { job: name, conditions: existingConditions.join(' ') || 'running', spaceId: input.spaceId });
|
|
828
|
+
if (!hostRefresh && ghTokenExpiresAt === null && !githubRelay) debug.log('self-implement.pod', 'gh-token-app-skipped', { job: name, reason: 'no-verified-app-or-refresh-path' });
|
|
740
829
|
if (!reattach) {
|
|
741
830
|
if (/Failed|FailureTarget/.test(existingConditions.join(' ')) && collectFullLogs('failed-job-logs-unavailable').status === 'incomplete') {
|
|
742
831
|
return { exitCode: 1, output: '', error: { code: 'pod-collection-incomplete', message: `Job ${name} recovery incomplete — old Job retained` } };
|
|
@@ -748,15 +837,23 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
748
837
|
...passEnv,
|
|
749
838
|
...(grounding ? [GROUNDING_TOKEN_ENV] : []),
|
|
750
839
|
...(credentialRelay ? [POD_CREDENTIAL_TOKEN_ENV] : []),
|
|
840
|
+
...(githubRelay ? [POD_GITHUB_CREDENTIAL_TOKEN_ENV] : []),
|
|
751
841
|
];
|
|
752
|
-
const
|
|
842
|
+
const detail = selectLiveDetail(options.liveDetailFile ? { path: options.liveDetailFile } : {}).state;
|
|
843
|
+
const detailUntil = detail && (detail.scope === 'all' || detail.scope === parentRunId || detail.scope === launchRunId) && detail.until > Date.now()
|
|
844
|
+
? String(detail.until) : undefined;
|
|
845
|
+
const armEnv = Object.fromEntries(Object.entries(options.armEnv ?? {}).filter(([key]) => key !== 'ELANOUS_LIVE_DETAIL_UNTIL' && key !== POD_GITHUB_CREDENTIAL_TOKEN_ENV && key !== POD_GITHUB_CREDENTIAL_URL_ENV && (!hostRefresh || !['GH_TOKEN', 'GITHUB_TOKEN', 'GH_CONFIG_DIR'].includes(key))));
|
|
846
|
+
const jobArmEnv = grounding || credentialRelay || githubRelay || env.ELANOUS_DISPATCH_RECORDED === '1' || options.armEnv || detailUntil
|
|
753
847
|
? {
|
|
754
|
-
...
|
|
848
|
+
...armEnv,
|
|
849
|
+
...(detailUntil ? { ELANOUS_LIVE_DETAIL_UNTIL: detailUntil } : {}),
|
|
850
|
+
...(env.ELANOUS_DISPATCH_RECORDED === '1' ? { ELANOUS_DISPATCH_RECORDED: '1' } : {}),
|
|
755
851
|
...(grounding ? { [GROUNDING_URL_ENV]: groundingUrl! } : {}),
|
|
756
852
|
...(credentialRelay ? { [POD_CREDENTIAL_URL_ENV]: `${new URL(groundingUrl!).origin}${POD_CREDENTIAL_GROK_PATH}` } : {}),
|
|
853
|
+
...(githubRelay ? { [POD_GITHUB_CREDENTIAL_URL_ENV]: `${new URL(groundingUrl!).origin}${POD_CREDENTIAL_GITHUB_PATH}` } : {}),
|
|
757
854
|
}
|
|
758
|
-
:
|
|
759
|
-
const job = podJobManifest({ name, namespace, image: jobImage, ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}), repoUrl, ...(options.source ? { source: options.source } : {}), args, passEnv: jobPassEnv, deadlineSeconds: options.deadlineSeconds ?? POD_JOB_DEADLINE_SECONDS, ...(goalDoc ? { goalDoc } : {}), runId: launchRunId, ...(parentRunId ? { parentRunId } : {}), ...(jobArmEnv ? { armEnv: jobArmEnv } : {}), hostId: resolveHostId(env), skillEnvs: Object.keys(skillEnvs), memoryLimit, imageCommit: options.imageCommit !== undefined ? options.imageCommit : options.kubectl ? null : podImageFreshness({ image }).imageCommit, ...(grok ? { grokCredential: 'grokAuth' in creds && creds.grokAuth ? 'subscription' as const : 'api_key' as const } : {}), ...(codexAccounts ? { codexAccounts } : {}) }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
|
|
855
|
+
: undefined;
|
|
856
|
+
const job = podJobManifest({ name, namespace, image: jobImage, ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}), repoUrl, ...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), args, passEnv: jobPassEnv, deadlineSeconds: options.deadlineSeconds ?? POD_JOB_DEADLINE_SECONDS, ...(goalDoc ? { goalDoc } : {}), runId: launchRunId, ...(parentRunId ? { parentRunId } : {}), ...(jobArmEnv ? { armEnv: jobArmEnv } : {}), hostId: resolveHostId(env), skillEnvs: Object.keys(skillEnvs), memoryLimit, imageCommit: options.imageCommit !== undefined ? options.imageCommit : options.kubectl ? null : podImageFreshness({ image }).imageCommit, ...(grok ? { grokCredential: 'grokAuth' in creds && creds.grokAuth ? 'subscription' as const : 'api_key' as const } : {}), ...(codexAccounts ? { codexAccounts } : {}), ...(hostRefresh ? { appCredential: true } : {}) }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
|
|
760
857
|
const a = kubectl(['apply', '-f', '-'], JSON.stringify(job));
|
|
761
858
|
if (a.status !== 0) { cleanupSecret(); return { exitCode: 1, output: a.stderr, error: { code: 'pod-apply', message: a.stderr.trim() } }; }
|
|
762
859
|
if (options.source?.kind === 'bundle') {
|
|
@@ -791,7 +888,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
791
888
|
try { appendRunLedgerEntry({ runId: parentRunId, event: 'pod-child-run', data: { childRunId: liveChildRunId, job: name, ...(goalFile ? { goalFile } : {}) } }, ledgerDir); }
|
|
792
889
|
catch (error) { debug.log('self-implement.pod', 'parent-ledger-unavailable', { job: name, reason: error instanceof Error ? error.message : String(error) }); }
|
|
793
890
|
}
|
|
794
|
-
writePodFragment({ spaceId: input.spaceId, context, namespace, job: name, inboxDir: POD_CONTROL_INBOX_DIR }, env);
|
|
891
|
+
writePodFragment({ spaceId: input.spaceId, context, namespace, job: name, inboxDir: POD_CONTROL_INBOX_DIR, ...(liveChildRunId === normalizeRunId(liveChildRunId) ? { runId: liveChildRunId } : {}), ...(parentRunId && normalizeRunId(parentRunId) === parentRunId ? { parentRunId } : {}) }, env);
|
|
795
892
|
recorded = true;
|
|
796
893
|
debug.log('self-implement.pod', 'job-applied', { job: name, namespace, ...(member ? { context: member.context } : {}), image: jobImage, spaceId: input.spaceId, passEnv, extraArgs: options.extraArgs ?? [], ...(options.armEnv?.ELANOUS_ARM_ID ? { armId: options.armEnv.ELANOUS_ARM_ID } : {}) });
|
|
797
894
|
let state: 'complete' | 'failed' | 'aborted' = 'failed';
|
|
@@ -829,6 +926,22 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
829
926
|
debug.log('self-implement.pod', 'container-terminated', { job: name, container: 'child', reason: containerReason, exitCode: containerExitCode, jobReason: failedReason || null, memoryLimit });
|
|
830
927
|
break;
|
|
831
928
|
}
|
|
929
|
+
if (ghTokenExpiresAt !== null && ghTokenExpiresAt - now() <= 10 * 60_000) {
|
|
930
|
+
try {
|
|
931
|
+
const fresh = await issueVerifiedAppCredential();
|
|
932
|
+
if (!fresh) throw new Error('mint-or-verification-failed');
|
|
933
|
+
// No bearer in argv, stdout, stderr, or the run ledger. kubectl receives it only over stdin.
|
|
934
|
+
const installed = kubectl(['-n', namespace, 'exec', '-i', `job/${name}`, '-c', 'child', '--', 'sh', '-c', APP_GH_EXEC, 'sh', 'gh', 'auth', 'login', '--with-token'], `${fresh.token}\n`);
|
|
935
|
+
if (installed.status !== 0) {
|
|
936
|
+
const fallback = kubectl(['-n', namespace, 'exec', '-i', `job/${name}`, '-c', 'child', '--', 'sh', '-c', APP_GH_EXEC, 'sh', 'bun', '-e', APP_GH_AUTH_SCRIPT], `${fresh.token}\n`);
|
|
937
|
+
if (fallback.status !== 0) throw new Error('exec-failed');
|
|
938
|
+
}
|
|
939
|
+
ghTokenExpiresAt = fresh.expiresAt;
|
|
940
|
+
debug.log('self-implement.pod', 'gh-token-refreshed', { job: name, expiresAt: new Date(fresh.expiresAt).toISOString() });
|
|
941
|
+
} catch {
|
|
942
|
+
debug.log('self-implement.pod', 'gh-token-refresh-failed', { job: name, reason: 'mint-verification-or-exec-failed' });
|
|
943
|
+
}
|
|
944
|
+
}
|
|
832
945
|
try { follower?.poll(); } catch (e) { debug.log('self-implement.pod', 'ledger-live-unavailable', { job: name, reason: e instanceof Error ? e.message : String(e) }); }
|
|
833
946
|
await sleep(options.pollMs ?? 15_000);
|
|
834
947
|
}
|
|
@@ -919,7 +1032,13 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
919
1032
|
try { const { appendGoalExecutionRecord } = await import('../../self-implement/orchestrator.js'); appendGoalExecutionRecord(goalFile, record); }
|
|
920
1033
|
catch (error) { debug.log('self-implement.pod', 'goal-record-unavailable', { job: name, childRunId: liveChildRunId, reason: error instanceof Error ? error.message : String(error) }); }
|
|
921
1034
|
}
|
|
922
|
-
|
|
1035
|
+
const childFailure = state === 'failed' && containerReason !== 'OOMKilled' && failedReason !== 'DeadlineExceeded'
|
|
1036
|
+
? lastPodChildFailure(logs) : null;
|
|
1037
|
+
if (childFailure) debug.log('self-implement.pod', 'child-error', { job: name, stage: childFailure.stage, error: childFailure.error }, { compact: { stringMax: 500 } });
|
|
1038
|
+
debug.log('self-implement.pod', 'job-finished', { job: name, ...(member ? { context: member.context } : {}), state, containerReason, stage: disposition?.stage ?? null, prUrl: disposition?.prUrl ?? null, childRunId: liveChildRunId, ledgerCompleteness,
|
|
1039
|
+
...(state === 'failed' && containerReason !== 'OOMKilled' && failedReason !== 'DeadlineExceeded'
|
|
1040
|
+
? { ...(childFailure ? { childStage: childFailure.stage } : {}), childError: childFailure?.error ?? 'no-result-line' } : {}) },
|
|
1041
|
+
state === 'failed' && containerReason !== 'OOMKilled' && failedReason !== 'DeadlineExceeded' ? { compact: { stringMax: 500 } } : undefined);
|
|
923
1042
|
const tail = podRunResultLine(logs, salvage);
|
|
924
1043
|
if (state === 'aborted') return { exitCode: null, output: tail, error: { code: 'aborted', message: 'aborted — Job deleted' }, ...(disposition ? { disposition } : {}) };
|
|
925
1044
|
const deadlineExceeded = state === 'failed' && failedReason === 'DeadlineExceeded';
|
|
@@ -944,7 +1063,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
944
1063
|
: state === 'failed'
|
|
945
1064
|
? containerReason === 'OOMKilled'
|
|
946
1065
|
? { error: { code: 'pod-oom-killed', message: `Job ${name} failed (OOMKilled/${containerExitCode ?? 'unknown'}, memoryLimit=${memoryLimit})\n${formatLastMemSample(samples.at(-1))}` } }
|
|
947
|
-
: { error: { code: 'pod-job-failed', message: `Job ${name} failed${failedReason || containerReason ? ` (${[failedReason, containerReason ? `container=${containerReason}/${containerExitCode ?? 'unknown'}` : ''].filter(Boolean).join(', ')})` : ''}` } }
|
|
1066
|
+
: { error: { code: 'pod-job-failed', message: `Job ${name} failed${failedReason || containerReason ? ` (${[failedReason, containerReason ? `container=${containerReason}/${containerExitCode ?? 'unknown'}` : ''].filter(Boolean).join(', ')})` : ''}${childFailure ? ` — childStage=${childFailure.stage} · childError=${childFailure.error}` : ' — childError=no-result-line'}` } }
|
|
948
1067
|
: {}),
|
|
949
1068
|
...(finishedDisposition ? { disposition: finishedDisposition } : {}),
|
|
950
1069
|
};
|
|
@@ -1006,6 +1125,23 @@ export function recordPodSalvage(logs: string, job: string, log: (category: stri
|
|
|
1006
1125
|
return rows.map((row) => row.branch);
|
|
1007
1126
|
}
|
|
1008
1127
|
|
|
1128
|
+
function lastPodChildFailure(logs: string): { stage: string; error: string } | null {
|
|
1129
|
+
for (const line of logs.split('\n').reverse()) {
|
|
1130
|
+
if (!line.trimStart().startsWith('{')) continue;
|
|
1131
|
+
let result: unknown;
|
|
1132
|
+
try { result = JSON.parse(line); } catch { continue; }
|
|
1133
|
+
if (!result || typeof result !== 'object' || Array.isArray(result)) continue;
|
|
1134
|
+
const row = result as Record<string, unknown>;
|
|
1135
|
+
if (typeof row.stage !== 'string' || typeof row.ok !== 'boolean') continue;
|
|
1136
|
+
if (row.ok !== false || typeof row.error !== 'string') return null;
|
|
1137
|
+
const lines = row.error.replace(/\/home\/[^/\s]+\//g, '~/').split(/\r\n|\n|\r/);
|
|
1138
|
+
const first = lines[0]!.slice(0, 240);
|
|
1139
|
+
const last = lines.at(-1)!.slice(0, 240);
|
|
1140
|
+
return { stage: row.stage, error: first === last ? first : `${first}\n${last}` };
|
|
1141
|
+
}
|
|
1142
|
+
return null;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1009
1145
|
/** 사람용 런 결과 한 줄 — 수확 브랜치가 있으면 «수확할 브랜치: …» 를 붙인다. */
|
|
1010
1146
|
export function podRunResultLine(logs: string, branches: readonly string[]): string {
|
|
1011
1147
|
const result = logs.split('\n').filter((line) => !line.startsWith('ELANOUS_MEM ')).join('\n').slice(-4000);
|
|
@@ -1183,6 +1319,14 @@ export function podImageFreshness(deps: {
|
|
|
1183
1319
|
: { imageCommit, headCommit, fresh: false, reason: `이미지 ${imageCommit.slice(0, 12)} ≠ HEAD ${headCommit.slice(0, 12)}` };
|
|
1184
1320
|
}
|
|
1185
1321
|
|
|
1322
|
+
function configHostMirror(): string | undefined {
|
|
1323
|
+
try {
|
|
1324
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1325
|
+
const { getUserConfig } = require('../../user-config.js') as typeof import('../../user-config.js');
|
|
1326
|
+
return getUserConfig().pod?.hostMirror;
|
|
1327
|
+
} catch { return undefined; }
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1186
1330
|
function configGroundingUrl(): string | undefined {
|
|
1187
1331
|
try {
|
|
1188
1332
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|