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
|
@@ -60,7 +60,7 @@ import { pwaReachableSrcFiles } from './pwa-import-graph.js';
|
|
|
60
60
|
import { stageNonGitDir, stageFile, applyShadowToTarget, applyFileToTarget } from './shadow-stage.js';
|
|
61
61
|
import type { TargetKind } from './target-kind.js';
|
|
62
62
|
import { basename } from 'node:path';
|
|
63
|
-
import { makePrManager, type PrManager } from '../autopilot/pr-manager.js';
|
|
63
|
+
import { ghAutomationEnv, makePrManager, type PrManager } from '../autopilot/pr-manager.js';
|
|
64
64
|
import { defaultSpawnGh, lookupOpenDraftPrs } from '../cli/logs-abandoned-draft-prs.js';
|
|
65
65
|
import { loadRunLedger, runLedgerDir } from './run-ledger.js';
|
|
66
66
|
import { readdirSync } from 'node:fs';
|
|
@@ -2347,8 +2347,10 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
|
|
|
2347
2347
|
closePr: (prUrl, comment) => (o.prManager ?? makePrManager()).closePr(prUrl, comment),
|
|
2348
2348
|
|
|
2349
2349
|
postPrComment: async ({ number, body, cwd }) => {
|
|
2350
|
+
// Round comments go out as the GitHub App like the PR itself — a personal `gh` login made the owner a
|
|
2351
|
+
// participant and mailed them on merge (#22089).
|
|
2350
2352
|
await execFileAsync('gh', ['pr', 'comment', String(number), '--body', body], {
|
|
2351
|
-
cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, maxBuffer: 64 * 1024 * 1024,
|
|
2353
|
+
cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, maxBuffer: 64 * 1024 * 1024, env: ghAutomationEnv(process.env),
|
|
2352
2354
|
});
|
|
2353
2355
|
},
|
|
2354
2356
|
|
package/src/session/index.ts
CHANGED
|
@@ -434,12 +434,20 @@ function readIndex(root: string = sessionRoot()): SessionMeta[] {
|
|
|
434
434
|
}
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
-
|
|
437
|
+
// 여러 프로세스(`elanous ask` 동시 실행 등)가 같은 목록을 «읽고-고치고-통째로 쓰기» 한다.
|
|
438
|
+
// 쓰기 직전 잠금 안에서 디스크의 최신 목록을 다시 읽어 id 로 합치고, 임시 파일 이름은 프로세스마다 다르게 한다
|
|
439
|
+
// (10-01 MK 재현: 고정 `index.json.tmp` → 한쪽 ENOENT · 덮어쓰기로 남의 세션이 목록에서 사라짐).
|
|
440
|
+
function writeIndex(entries: SessionMeta[], root: string = sessionRoot(), removed: Iterable<string> = []): void {
|
|
438
441
|
ensureRoots(root);
|
|
439
442
|
const p = indexPath(root);
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
+
withDirLock(join(root, '.index.lock'), INDEX_LOCK_TIMEOUT_MS, () => {
|
|
444
|
+
const mine = new Set(entries.map((m) => m.id));
|
|
445
|
+
const gone = new Set(removed);
|
|
446
|
+
const peers = readIndex(root).filter((m) => !mine.has(m.id) && !gone.has(m.id));
|
|
447
|
+
const tmp = `${p}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
|
|
448
|
+
writeFileSync(tmp, JSON.stringify([...peers, ...entries], null, 2) + '\n', 'utf-8');
|
|
449
|
+
renameSync(tmp, p);
|
|
450
|
+
});
|
|
443
451
|
}
|
|
444
452
|
|
|
445
453
|
// ── Tier 1 Phase 3 — listener primitive ──────────────────────────────
|
|
@@ -661,7 +669,7 @@ export function rewriteSessionMessages(
|
|
|
661
669
|
const body = messages
|
|
662
670
|
.map(m => JSON.stringify(m.turn_id !== undefined ? m : { ...m, turn_id: mintTurnUri() }))
|
|
663
671
|
.join('\n');
|
|
664
|
-
const tmp = file
|
|
672
|
+
const tmp = `${file}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
|
|
665
673
|
writeFileSync(tmp, body ? body + '\n' : '', 'utf-8');
|
|
666
674
|
renameSync(tmp, file);
|
|
667
675
|
|
|
@@ -800,8 +808,13 @@ function residentSessionLockPath(root: string): string {
|
|
|
800
808
|
|
|
801
809
|
function withResidentSessionLock<T>(root: string, action: () => T): T {
|
|
802
810
|
ensureRoots(root);
|
|
803
|
-
|
|
804
|
-
|
|
811
|
+
return withDirLock(residentSessionLockPath(root), RESIDENT_SESSION_LOCK_TIMEOUT_MS, action);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
const INDEX_LOCK_TIMEOUT_MS = 10_000;
|
|
815
|
+
|
|
816
|
+
function withDirLock<T>(lock: string, timeoutMs: number, action: () => T): T {
|
|
817
|
+
const deadline = Date.now() + timeoutMs;
|
|
805
818
|
for (;;) {
|
|
806
819
|
try {
|
|
807
820
|
mkdirSync(lock);
|
|
@@ -814,7 +827,7 @@ function withResidentSessionLock<T>(root: string, action: () => T): T {
|
|
|
814
827
|
continue;
|
|
815
828
|
}
|
|
816
829
|
} catch { continue; }
|
|
817
|
-
if (Date.now() >= deadline) throw new Error(`timed out acquiring
|
|
830
|
+
if (Date.now() >= deadline) throw new Error(`timed out acquiring lock ${lock}`);
|
|
818
831
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
|
|
819
832
|
void error;
|
|
820
833
|
}
|
|
@@ -855,7 +868,7 @@ export function deleteSession(id: string, root: string = sessionRoot()): boolean
|
|
|
855
868
|
const before = idx.length;
|
|
856
869
|
const next = idx.filter(m => m.id !== id);
|
|
857
870
|
if (next.length === before) return false;
|
|
858
|
-
writeIndex(next, root);
|
|
871
|
+
writeIndex(next, root, [id]);
|
|
859
872
|
// Clear active marker if it pointed at the deleted session.
|
|
860
873
|
if (getActiveSessionId() === id) clearActiveSessionId();
|
|
861
874
|
return true;
|
|
@@ -876,7 +889,7 @@ export function deleteSessions(ids: string[], root: string = sessionRoot()): num
|
|
|
876
889
|
const idx = readIndex(root);
|
|
877
890
|
const next = idx.filter(m => !set.has(m.id));
|
|
878
891
|
const removed = idx.length - next.length;
|
|
879
|
-
if (removed > 0) writeIndex(next, root);
|
|
892
|
+
if (removed > 0) writeIndex(next, root, set);
|
|
880
893
|
const active = getActiveSessionId();
|
|
881
894
|
if (active && set.has(active)) clearActiveSessionId();
|
|
882
895
|
return removed;
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
// ★ 제1원칙: front door 결정 observe(harness.frontdoor) — objective digest·autoDrive·terminal.
|
|
14
14
|
// autoDrive 'safe'(기본): 저위험 자율·고위험(PR open)만 ux.confirm(막 채널 없으면 fail-closed).
|
|
15
15
|
|
|
16
|
+
import { createHash } from 'node:crypto';
|
|
16
17
|
import { tierModel } from '../../llm/model-defaults.js';
|
|
18
|
+
import { dispatchTask, type DispatchTaskDeps } from '../../execution-loop/dispatch-task.js';
|
|
17
19
|
import type { LLMToolSpec } from '../../llm.js';
|
|
18
20
|
import type { DaemonToolDispatchCtx } from '../../boot/daemon-tools/types.js';
|
|
19
21
|
import type { SelfImplementSeams } from '../../self-implement/orchestrator.js';
|
|
@@ -96,6 +98,9 @@ export function buildRunDevHarnessTool(): LLMToolSpec {
|
|
|
96
98
|
export interface DevHarnessDeps {
|
|
97
99
|
/** 하니스 구동(기본 runStagedHarnessOnSurface). */
|
|
98
100
|
runHarness?: (opts: RunHarnessOnSurfaceOptions) => Promise<HarnessResult>;
|
|
101
|
+
/** In-process 실행 직전의 관측 전용 디스패치 seam. */
|
|
102
|
+
dispatchTask?: typeof dispatchTask;
|
|
103
|
+
dispatchTaskDeps?: DispatchTaskDeps;
|
|
99
104
|
/** seam 팩토리(기본 defaultSeams). */
|
|
100
105
|
seamsFactory?: (o: DefaultSeamsOptions) => SelfImplementSeams;
|
|
101
106
|
/** 비대화형 HITL relay 팩토리 seam(기본 buildLlmHitlRelay). */
|
|
@@ -468,6 +473,43 @@ export async function dispatchRunDevHarness(
|
|
|
468
473
|
const runHarness = deps?.runHarness ?? runStagedHarnessOnSurface;
|
|
469
474
|
const knobs = resolveHarnessKnobs(rawArgs, ctx?.userText); // ★ B1 관통 — C1~C4 노브를 surface 파라미터에서 막으로
|
|
470
475
|
const harnessMention = harnessMentionState(ctx?.userText);
|
|
476
|
+
// Outer pipeline/Pod observations are not repeated; this decision never controls the harness.
|
|
477
|
+
const dispatchSkipped = rawArgs.plan_staged === true ? 'plan-staged'
|
|
478
|
+
: rawArgs.dispatch_recorded === true || process.env.ELANOUS_DISPATCH_RECORDED === '1' ? 'dispatch-recorded'
|
|
479
|
+
: process.env.ELANOUS_EXECUTION_LOOP_DISPATCH === 'off' && !deps?.dispatchTask && !deps?.dispatchTaskDeps ? 'disabled'
|
|
480
|
+
: undefined;
|
|
481
|
+
if (dispatchSkipped) {
|
|
482
|
+
try { debug.log('execution-loop.gate', 'dispatch-skipped', { reason: dispatchSkipped, runId }); } catch { /* fail-soft */ }
|
|
483
|
+
} else {
|
|
484
|
+
try {
|
|
485
|
+
const spec = { input: { text: objective }, executor: { kind: 'self' as const }, plan: true, runId };
|
|
486
|
+
const observer = new AbortController();
|
|
487
|
+
const timeoutMs = Math.min(5_000, Math.max(0, deps?.dispatchTaskDeps?.timeoutMs ?? 5_000));
|
|
488
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
489
|
+
try {
|
|
490
|
+
await Promise.race([
|
|
491
|
+
Promise.resolve().then(() => (deps?.dispatchTask ?? dispatchTask)({
|
|
492
|
+
goalId: `request-${createHash('sha256').update(objective).digest('hex').slice(0, 32)}`,
|
|
493
|
+
title: objective.split(/\r?\n/, 1)[0]?.trim() || 'Untitled request',
|
|
494
|
+
goalText: objective,
|
|
495
|
+
targetPaths: [],
|
|
496
|
+
spec,
|
|
497
|
+
runKey: runId,
|
|
498
|
+
}, { ...deps?.dispatchTaskDeps, signal: observer.signal })),
|
|
499
|
+
new Promise<never>((_, reject) => {
|
|
500
|
+
timer = setTimeout(() => {
|
|
501
|
+
observer.abort();
|
|
502
|
+
reject(new Error(`dispatch observation timed out after ${timeoutMs}ms`));
|
|
503
|
+
}, timeoutMs);
|
|
504
|
+
}),
|
|
505
|
+
]);
|
|
506
|
+
} finally {
|
|
507
|
+
if (timer) clearTimeout(timer);
|
|
508
|
+
}
|
|
509
|
+
} catch (error) {
|
|
510
|
+
try { debug.log('execution-loop.gate', 'dispatch-unavailable', { reason: String(error), runId }); } catch { /* fail-soft */ }
|
|
511
|
+
}
|
|
512
|
+
}
|
|
471
513
|
const result = await runHarness({
|
|
472
514
|
// ⛔⭐ `naturalLanguageDispatch` 를 «단정»하지 않는다 — 증거로 정한다.
|
|
473
515
|
// 이 함수는 «둘» 이 지난다: ⑴ 모델이 부른 도구(사용자 턴 문면이 ctx.userText 로 온다)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { CardStore, redactSecrets, type AppendSectionInput, type CreateCardInput, type TaskCard } from '../task-cards/card-store.js';
|
|
3
|
+
import type { ScheduledDecision, TriageIssue } from './triage.js';
|
|
4
|
+
|
|
5
|
+
export interface StewardCardStore {
|
|
6
|
+
createCard(input: CreateCardInput): TaskCard;
|
|
7
|
+
appendSection(id: string, input: AppendSectionInput): TaskCard;
|
|
8
|
+
close(): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface StewardCardDeps {
|
|
12
|
+
store?: StewardCardStore;
|
|
13
|
+
root?: string;
|
|
14
|
+
now?: () => Date;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Record observe-only intake and the latest scheduled judgment without launching any work. */
|
|
18
|
+
export function recordTriageOnCards(decisions: ScheduledDecision[], issues: TriageIssue[], deps: StewardCardDeps = {}): void {
|
|
19
|
+
const store = deps.store ?? new CardStore(deps.root);
|
|
20
|
+
const byIssue = new Map(issues.map(issue => [issue.identifier, issue]));
|
|
21
|
+
try {
|
|
22
|
+
for (const decision of decisions) {
|
|
23
|
+
const issue = byIssue.get(decision.issue);
|
|
24
|
+
if (!issue) throw new Error(`Missing issue ${decision.issue}`);
|
|
25
|
+
const card = store.createCard({ goalId: `linear:${issue.identifier}`, title: redactSecrets(issue.title) });
|
|
26
|
+
const intakeKey = `intake:${issue.identifier}`;
|
|
27
|
+
if (!card.sections.some(section => section.key === intakeKey)) {
|
|
28
|
+
const directiveSource = /^출처: (telegram|pwa|tui|cli)$/m.exec(issue.body)?.[1];
|
|
29
|
+
const directiveTime = /^시각: (\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z)$/m.exec(issue.body)?.[1];
|
|
30
|
+
store.appendSection(card.id, {
|
|
31
|
+
key: intakeKey, owner: 'steward',
|
|
32
|
+
content: JSON.stringify({ summary: redactSecrets(issue.title), source: directiveSource ?? `linear:${issue.identifier}`, at: directiveTime ?? (deps.now ?? (() => new Date()))().toISOString() }),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const { rung, dependsOn, priority, disposition, hitlReason, why, owner } = decision;
|
|
36
|
+
const judgment = { rung, dependsOn, priority, disposition: rung === 'hitl' || hitlReason ? 'hitl' : disposition, hitlReason: hitlReason ?? null, why: redactSecrets(why), ...(owner ? { owner } : {}) };
|
|
37
|
+
const key = `triage:${createHash('sha256').update(JSON.stringify(judgment)).digest('hex')}`;
|
|
38
|
+
if (!card.sections.some(section => section.key === key)) {
|
|
39
|
+
store.appendSection(card.id, { key, owner: 'steward', content: JSON.stringify(judgment) });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} finally {
|
|
43
|
+
if (!deps.store) store.close();
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/steward/triage.ts
CHANGED
|
@@ -4,30 +4,39 @@ import { fetchLinearIssues } from '../connectors/linear.js';
|
|
|
4
4
|
import { getSecretAsync } from '../nexus/config/secrets/index.js';
|
|
5
5
|
import { getUserConfig } from '../user-config.js';
|
|
6
6
|
import { emitDecision } from '../live/detail-switch.js';
|
|
7
|
+
import { debug, redactSecretText } from '../debug/log.js';
|
|
7
8
|
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
9
|
+
import { redactSecrets } from '../task-cards/card-store.js';
|
|
8
10
|
import { directiveHash } from './directive.js';
|
|
11
|
+
import { recordTriageOnCards, type StewardCardDeps } from './steward-cards.js';
|
|
9
12
|
|
|
10
13
|
export type Rung = 0 | 1 | 2 | 3 | 4 | 5 | 'hitl';
|
|
11
|
-
|
|
14
|
+
/** `other` = the judge chose human review for a reason outside the four named ones (kept as HITL, never downgraded). */
|
|
15
|
+
export type HitlReason = 'money' | 'public' | 'security' | 'irreversible' | 'other';
|
|
12
16
|
export interface TriageIssue { identifier: string; ref: string; title: string; body: string }
|
|
13
17
|
export interface TriageDecision {
|
|
14
18
|
issue: string; rung: Rung; dependsOn: string[]; priority: number; duplicateOf?: string;
|
|
15
19
|
hitlReason?: HitlReason; why: string; role?: string; cost?: number;
|
|
20
|
+
/** Owner track from `loops.steward.tracks` (absent when no table is configured or the judge picked none of its keys). */
|
|
21
|
+
owner?: string;
|
|
16
22
|
}
|
|
17
23
|
export interface ScheduledDecision extends TriageDecision { disposition: 'now' | 'wait' | 'hitl' }
|
|
18
24
|
export interface StewardSettings {
|
|
19
25
|
mode?: 'observe' | 'act'; linearTeam?: string;
|
|
20
26
|
roles?: Record<string, { maxConcurrent?: number }>;
|
|
21
27
|
budget?: number;
|
|
28
|
+
tracks?: Record<string, string>;
|
|
22
29
|
}
|
|
23
30
|
export interface StewardDeps {
|
|
24
31
|
fetch?: typeof fetch;
|
|
25
32
|
getSecret?: (id: string) => Promise<string | undefined>;
|
|
26
|
-
judge?: (issue: TriageIssue, issues: TriageIssue[]) => Promise<unknown>;
|
|
33
|
+
judge?: (issue: TriageIssue, issues: TriageIssue[], tracks?: Record<string, string>) => Promise<unknown>;
|
|
27
34
|
decide?: typeof emitDecision;
|
|
28
35
|
sendDigest?: (text: string) => Promise<void>;
|
|
29
36
|
now?: () => Date;
|
|
30
37
|
root?: string;
|
|
38
|
+
cardStore?: StewardCardDeps['store'];
|
|
39
|
+
warn?: (message: string) => void;
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
export function stewardSettings(): StewardSettings {
|
|
@@ -41,7 +50,7 @@ const unsafe: Array<[HitlReason, RegExp]> = [
|
|
|
41
50
|
['irreversible', /비가역|강제\s*푸시|force.push|영구\s*삭제|리셋권\s*소비|drop\s*table|초기화/i],
|
|
42
51
|
];
|
|
43
52
|
|
|
44
|
-
function parseJudgment(issue: TriageIssue, raw: unknown): TriageDecision {
|
|
53
|
+
function parseJudgment(issue: TriageIssue, raw: unknown, tracks?: Record<string, string>): TriageDecision {
|
|
45
54
|
const value = typeof raw === 'string' ? JSON.parse(raw) as unknown : raw;
|
|
46
55
|
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`Invalid triage judgment: ${issue.identifier}`);
|
|
47
56
|
const d = value as Record<string, unknown>;
|
|
@@ -51,8 +60,9 @@ function parseJudgment(issue: TriageIssue, raw: unknown): TriageDecision {
|
|
|
51
60
|
throw new Error(`Invalid triage judgment: ${issue.identifier}`);
|
|
52
61
|
}
|
|
53
62
|
const forced = unsafe.find(([, pattern]) => pattern.test(`${issue.title}\n${issue.body}`))?.[0];
|
|
54
|
-
const
|
|
55
|
-
|
|
63
|
+
const named = forced ?? (unsafe.some(([reason]) => reason === d.hitlReason) ? d.hitlReason as HitlReason : undefined);
|
|
64
|
+
// A HITL judgment without one of the four named reasons stays HITL (`other`) — one issue must not stop the stage.
|
|
65
|
+
const hitlReason = named ?? (d.rung === 'hitl' ? 'other' as const : undefined);
|
|
56
66
|
return {
|
|
57
67
|
issue: issue.identifier, rung: hitlReason ? 'hitl' : d.rung as Rung,
|
|
58
68
|
dependsOn: d.dependsOn as string[], priority: d.priority as number,
|
|
@@ -60,24 +70,40 @@ function parseJudgment(issue: TriageIssue, raw: unknown): TriageDecision {
|
|
|
60
70
|
...(hitlReason ? { hitlReason } : {}), why: (d.why as string).trim().replace(/\s+/g, ' '),
|
|
61
71
|
...(typeof d.role === 'string' ? { role: d.role } : {}),
|
|
62
72
|
...(typeof d.cost === 'number' && Number.isFinite(d.cost) && d.cost >= 0 ? { cost: d.cost } : {}),
|
|
73
|
+
...(tracks && typeof d.owner === 'string' && Object.hasOwn(tracks, d.owner) ? { owner: d.owner } : {}),
|
|
63
74
|
};
|
|
64
75
|
}
|
|
65
76
|
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
/** The role table as prompt lines — read from config so a reorg edits one place. */
|
|
78
|
+
export function trackTablePrompt(tracks?: Record<string, string>): string {
|
|
79
|
+
if (!tracks || !Object.keys(tracks).length) return '';
|
|
80
|
+
const keys = Object.keys(tracks);
|
|
81
|
+
return `\n담당 트랙(owner) — 아래 중 하나(${keys.map(k => JSON.stringify(k)).join('|')}) · 이슈가 어느 트랙 일인지:\n${keys.map(k => `- ${k}: ${tracks[k]}`).join('\n')}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function judgeStewardIssue(issue: TriageIssue, issues: TriageIssue[], tracks?: Record<string, string>): Promise<unknown> {
|
|
85
|
+
const ownerField = tracks && Object.keys(tracks).length ? `,"owner":${JSON.stringify(Object.keys(tracks)[0])}` : '';
|
|
86
|
+
const prompt = `스튜어드 트리아지. JSON 객체만 출력: {"rung":0|1|2|3|4|5|"hitl","dependsOn":[],"priority":1,"duplicateOf":null,"hitlReason":null,"why":"한 문장","role":"builder","cost":0${ownerField}}.\n0=이미됨/중복, 1=셸, 2=CLI, 3=조사, 4=하니스 골, 5=외부 에이전트. 돈·공개·보안·비가역은 hitl(hitlReason = "money"|"public"|"security"|"irreversible" · 그 밖의 이유로 사람 확인이 필요하면 "other"). 확인되지 않은 완료/중복은 추측하지 마라. 의존은 제공된 이슈 키만 써라.${trackTablePrompt(tracks)}\n이슈 목록: ${JSON.stringify(issues.map(i => ({ key: i.identifier, title: i.title })))}\n판정 대상: ${JSON.stringify(issue)}`;
|
|
68
87
|
const { streamLLM } = await import('../llm.js');
|
|
69
88
|
const { tierModel } = await import('../llm/model-defaults.js');
|
|
70
89
|
const result = await streamLLM([{ role: 'user', content: prompt }], () => {}, { model: tierModel('best') });
|
|
71
90
|
return JSON.parse(result.replace(/^```(?:json)?\s*|\s*```$/g, '').trim()) as unknown;
|
|
72
91
|
}
|
|
73
92
|
|
|
74
|
-
export async function triageIssues(issues: TriageIssue[], judge: NonNullable<StewardDeps['judge']> = judgeStewardIssue, decide: typeof emitDecision = emitDecision): Promise<TriageDecision[]> {
|
|
93
|
+
export async function triageIssues(issues: TriageIssue[], judge: NonNullable<StewardDeps['judge']> = judgeStewardIssue, decide: typeof emitDecision = emitDecision, tracks?: Record<string, string>): Promise<TriageDecision[]> {
|
|
75
94
|
const decisions: TriageDecision[] = [];
|
|
76
95
|
for (const issue of issues) {
|
|
77
|
-
|
|
96
|
+
let result: TriageDecision;
|
|
97
|
+
try {
|
|
98
|
+
result = parseJudgment(issue, await judge(issue, issues, tracks), tracks);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
// A malformed or failed judgment for one issue goes to a human; the other issues are still triaged.
|
|
101
|
+
try { debug.log('steward.stage', 'judgment-invalid', { issue: issue.identifier, reason: redactSecrets(String(error)) }); } catch { /* fail-soft */ }
|
|
102
|
+
result = { issue: issue.identifier, rung: 'hitl', hitlReason: 'other', dependsOn: [], priority: 0, why: 'triage judgment unavailable — human review' };
|
|
103
|
+
}
|
|
78
104
|
decisions.push(result);
|
|
79
105
|
decide({ kind: result.rung === 'hitl' ? 'ESCALATE' : 'ROUTE', what: issue.identifier, reason: result.why,
|
|
80
|
-
purpose: 'steward triage', target: String(result.rung), refs: { issue: issue.identifier } });
|
|
106
|
+
purpose: 'steward triage', target: result.owner ? `${result.rung} · ${result.owner}` : String(result.rung), refs: { issue: issue.identifier } });
|
|
81
107
|
}
|
|
82
108
|
return decisions;
|
|
83
109
|
}
|
|
@@ -99,7 +125,7 @@ export function scheduleTriage(decisions: TriageDecision[], settings: StewardSet
|
|
|
99
125
|
const slots = { ...running };
|
|
100
126
|
let remaining = settings.budget ?? Infinity;
|
|
101
127
|
return sorted.map(item => {
|
|
102
|
-
const role = item.role ?? String(item.rung);
|
|
128
|
+
const role = item.owner ?? item.role ?? String(item.rung);
|
|
103
129
|
const max = settings.roles?.[role]?.maxConcurrent ?? Infinity;
|
|
104
130
|
const blocked = item.dependsOn.some(dep => !completed.has(dep));
|
|
105
131
|
const status: ScheduledDecision['disposition'] = item.rung === 'hitl' || item.hitlReason ? 'hitl' :
|
|
@@ -146,7 +172,7 @@ export async function runStewardStage(stage: 'sync' | 'triage' | 'schedule' | 'r
|
|
|
146
172
|
} else if (stage === 'triage') {
|
|
147
173
|
const issues = JSON.parse(readFileSync(snapshot, 'utf8')) as TriageIssue[];
|
|
148
174
|
const judge = deps.judge ?? judgeStewardIssue;
|
|
149
|
-
writeFileSync(judgments, JSON.stringify(await triageIssues(issues, judge, deps.decide)));
|
|
175
|
+
writeFileSync(judgments, JSON.stringify(await triageIssues(issues, judge, deps.decide, settings.tracks)));
|
|
150
176
|
} else if (stage === 'schedule') {
|
|
151
177
|
const decisions = JSON.parse(readFileSync(judgments, 'utf8')) as TriageDecision[];
|
|
152
178
|
const pending = new Set(decisions.flatMap(decision => decision.dependsOn));
|
|
@@ -163,6 +189,13 @@ export async function runStewardStage(stage: 'sync' | 'triage' | 'schedule' | 'r
|
|
|
163
189
|
const state = readState(path);
|
|
164
190
|
const at = (deps.now ?? (() => new Date()))().toISOString();
|
|
165
191
|
const issues = JSON.parse(readFileSync(snapshot, 'utf8')) as TriageIssue[];
|
|
192
|
+
try {
|
|
193
|
+
recordTriageOnCards(rows, issues, { root, store: deps.cardStore, now: deps.now });
|
|
194
|
+
} catch (error) {
|
|
195
|
+
const message = `steward report: card write failed: ${redactSecrets(error instanceof Error ? error.message : String(error))}`;
|
|
196
|
+
debug.log('steward.cards', 'write-failed', { error: message });
|
|
197
|
+
(deps.warn ?? console.warn)(message);
|
|
198
|
+
}
|
|
166
199
|
for (const row of rows) {
|
|
167
200
|
const issue = issues.find(item => item.identifier === row.issue);
|
|
168
201
|
if (!issue) throw new Error(`Missing issue ${row.issue}`);
|
|
@@ -195,8 +228,11 @@ if (import.meta.main) {
|
|
|
195
228
|
console.error('steward: expected sync|triage|schedule|report');
|
|
196
229
|
process.exitCode = 2;
|
|
197
230
|
} else {
|
|
198
|
-
runStewardStage(stage as 'sync' | 'triage' | 'schedule' | 'report').catch(() => {
|
|
199
|
-
|
|
231
|
+
runStewardStage(stage as 'sync' | 'triage' | 'schedule' | 'report').catch((error: unknown) => {
|
|
232
|
+
// Say why (secrets masked) — 09-29 the first resident run failed as a bare «stage failed» on a missing Linear key.
|
|
233
|
+
const reason = redactSecretText(error instanceof Error ? error.message : String(error)).slice(0, 300);
|
|
234
|
+
console.error(`steward ${stage}: stage failed — ${reason}`);
|
|
235
|
+
debug.log('steward.stage', 'failed', { stage, reason });
|
|
200
236
|
process.exitCode = 1;
|
|
201
237
|
});
|
|
202
238
|
}
|
|
@@ -2,6 +2,8 @@ import { lstatSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
3
3
|
import { gunzipSync } from 'node:zlib';
|
|
4
4
|
import { debug } from '../../debug/log.js';
|
|
5
|
+
import { getDefaultLogStore, type LogStore } from '../../mss/logging/log-store.js';
|
|
6
|
+
import type { LogRecord } from '../../mss/logging/record.js';
|
|
5
7
|
|
|
6
8
|
const FILE_LIMIT = 5 * 1024 * 1024;
|
|
7
9
|
const TOTAL_LIMIT = 20 * 1024 * 1024;
|
|
@@ -79,14 +81,63 @@ export function parsePodArtifactChunks(logs: string): ParseResult {
|
|
|
79
81
|
return error.length ? { error, artifacts } : artifacts;
|
|
80
82
|
}
|
|
81
83
|
|
|
84
|
+
const POD_LOGS_PATH = 'pod-logs/logs.jsonl';
|
|
85
|
+
|
|
86
|
+
function childRunId(data: unknown): string | undefined {
|
|
87
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) return undefined;
|
|
88
|
+
const value = (data as Record<string, unknown>).runId;
|
|
89
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function decisionRecord(line: string): { rec: LogRecord; surface: string } | undefined {
|
|
93
|
+
let parsed: unknown;
|
|
94
|
+
try { parsed = JSON.parse(line); } catch { return undefined; }
|
|
95
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return undefined;
|
|
96
|
+
const row = parsed as Record<string, unknown>;
|
|
97
|
+
if (row.category !== 'harness.decision') return undefined;
|
|
98
|
+
if (typeof row.ts !== 'string' || typeof row.event !== 'string') return undefined;
|
|
99
|
+
const runId = childRunId(row.data);
|
|
100
|
+
if (!runId) return undefined;
|
|
101
|
+
const data = { ...(row.data as Record<string, unknown>), podJob: '', importedFrom: 'pod' as const };
|
|
102
|
+
return {
|
|
103
|
+
rec: {
|
|
104
|
+
ts: row.ts,
|
|
105
|
+
category: 'harness.decision',
|
|
106
|
+
event: row.event,
|
|
107
|
+
data,
|
|
108
|
+
...(typeof row.level === 'string' ? { level: row.level as LogRecord['level'] } : {}),
|
|
109
|
+
...(typeof row.trace_id === 'string' ? { trace_id: row.trace_id } : {}),
|
|
110
|
+
},
|
|
111
|
+
surface: typeof row.surface === 'string' && row.surface ? row.surface : 'pod',
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Import only harness.decision rows from a returned pod log into the launching host store. */
|
|
116
|
+
export function importPodDecisionLogs(bytes: Buffer, job: string, store: LogStore | null = getDefaultLogStore()): { imported: number; skipped: number } {
|
|
117
|
+
if (!store) return { imported: 0, skipped: 0 };
|
|
118
|
+
const text = bytes.toString('utf8');
|
|
119
|
+
const rows: Array<{ rec: LogRecord; surface: string }> = [];
|
|
120
|
+
let skipped = 0;
|
|
121
|
+
for (const line of text.split(/\r?\n/)) {
|
|
122
|
+
if (!line) continue;
|
|
123
|
+
const row = decisionRecord(line);
|
|
124
|
+
if (!row) { skipped += 1; continue; }
|
|
125
|
+
row.rec.data = { ...(row.rec.data as Record<string, unknown>), podJob: job };
|
|
126
|
+
rows.push(row);
|
|
127
|
+
}
|
|
128
|
+
if (rows.length > 0) store.insertBatch(rows);
|
|
129
|
+
return { imported: rows.length, skipped };
|
|
130
|
+
}
|
|
131
|
+
|
|
82
132
|
/** Reject symlink parents as well as lexical traversal; never replace a host file. */
|
|
83
|
-
export function collectPodArtifacts(logs: string, { dir, job, log = (c, e, d) => debug.log(c, e, d) }: {
|
|
133
|
+
export function collectPodArtifacts(logs: string, { dir, job, log = (c, e, d) => debug.log(c, e, d), logStore = getDefaultLogStore() }: {
|
|
84
134
|
dir: string;
|
|
85
135
|
job: string;
|
|
86
136
|
log?: (category: string, event: string, data: Record<string, unknown>) => void;
|
|
137
|
+
logStore?: LogStore | null;
|
|
87
138
|
}): void {
|
|
88
139
|
const parsed = parsePodArtifactChunks(logs);
|
|
89
|
-
const podLogsPath =
|
|
140
|
+
const podLogsPath = POD_LOGS_PATH;
|
|
90
141
|
let podLogsReturned = false;
|
|
91
142
|
let podLogsMissingReason = logs.split(/\r?\n/).find((line) => line.startsWith(`ELANOUS_POD_ARTIFACT_SKIPPED ${podLogsPath} `))
|
|
92
143
|
? 'artifact skipped (size limit or encoding failure)'
|
|
@@ -123,7 +174,14 @@ export function collectPodArtifacts(logs: string, { dir, job, log = (c, e, d) =>
|
|
|
123
174
|
log('self-implement.pod', 'artifact-collected', { job, path, bytes: bytes.length });
|
|
124
175
|
if (path === podLogsPath) {
|
|
125
176
|
podLogsReturned = true;
|
|
126
|
-
|
|
177
|
+
const lines = bytes.length ? bytes.toString('utf8').split('\n').length - (bytes.at(-1) === 10 ? 1 : 0) : 0;
|
|
178
|
+
log('self-implement.pod', 'pod-logs-returned', { job, path: destination, lines });
|
|
179
|
+
try {
|
|
180
|
+
const imported = importPodDecisionLogs(bytes, job, logStore);
|
|
181
|
+
log('self-implement.pod', 'pod-decisions-imported', { job, imported: imported.imported, skipped: imported.skipped });
|
|
182
|
+
} catch (error) {
|
|
183
|
+
log('self-implement.pod', 'pod-logs-import-failed', { job, reason: error instanceof Error ? error.message : String(error) });
|
|
184
|
+
}
|
|
127
185
|
}
|
|
128
186
|
} catch (e) {
|
|
129
187
|
const reason = (e as NodeJS.ErrnoException).code === 'EEXIST' ? 'exists' : e instanceof Error ? e.message : String(e);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const POD_BUN_CACHE_HOST_PATH = 'POD_BUN_CACHE_HOST_PATH';
|
|
2
|
+
|
|
3
|
+
/** A host-backed Bun install cache; only opt into it when the mounted directory is writable. */
|
|
4
|
+
export function podBunCacheVolume(hostPath: string) {
|
|
5
|
+
return {
|
|
6
|
+
volume: { name: 'bun-cache', hostPath: { path: hostPath, type: 'DirectoryOrCreate' } },
|
|
7
|
+
volumeMount: { name: 'bun-cache', mountPath: '/bun-cache', readOnly: false },
|
|
8
|
+
shellPrefix: 'if [ -d /bun-cache ] && [ -w /bun-cache ]; then export BUN_INSTALL_CACHE_DIR=/bun-cache; fi;',
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** First Bun install timing in a combined shard log (including a no-op install). */
|
|
13
|
+
export function parseInstallSeconds(log: string): number | null {
|
|
14
|
+
const match = /(?:\d+ packages? installed|Checked \d+ installs across \d+ packages(?: \(no changes\))?)\s*\[(\d+(?:\.\d+)?)(ms|s)\]/.exec(log);
|
|
15
|
+
if (!match) return null;
|
|
16
|
+
const duration = Number(match[1]);
|
|
17
|
+
return match[2] === 'ms' ? duration / 1000 : duration;
|
|
18
|
+
}
|
|
@@ -6,12 +6,15 @@
|
|
|
6
6
|
// 명령 인자는 셸 문자열로 이어붙이지 않고 `"$@"` 로 따로 인용한다.
|
|
7
7
|
|
|
8
8
|
import { spawnSync } from 'node:child_process';
|
|
9
|
+
import { isAbsolute } from 'node:path';
|
|
10
|
+
import { getUserConfig } from '../../user-config.js';
|
|
9
11
|
import { debug } from '../../debug/log.js';
|
|
10
12
|
import { effectiveInstanceRoot } from '../../instance/resolve.js';
|
|
11
13
|
import { collectPodArtifacts } from './pod-artifact-return.js';
|
|
14
|
+
import { podBunCacheVolume } from './pod-bun-cache.js';
|
|
12
15
|
import { type PodPoolMember, PodPoolScheduler, resolvePodPoolSpec, parsePodPool, checkPodPool, syncPoolImages, type PoolKubectl } from './pod-pool.js';
|
|
13
16
|
import { readSkillEnvFiles } from './pod-skills.js';
|
|
14
|
-
import { podSourceScript } from './pod-source-receive.js';
|
|
17
|
+
import { podSourceScript, type PodSource } from './pod-source-receive.js';
|
|
15
18
|
import {
|
|
16
19
|
POD_CHILD_REQUESTS,
|
|
17
20
|
POD_JOB_DEADLINE_SECONDS,
|
|
@@ -73,6 +76,11 @@ export interface PodCommandJobInput {
|
|
|
73
76
|
llm?: 'grok';
|
|
74
77
|
/** 저장소를 clone 한다 — 비공개 저장소라 GitHub 토큰이 Secret 으로 간다(명시 opt-in). 기본은 clone 없이 `~/work` 에서 이미지의 `elanous` 로 돈다. */
|
|
75
78
|
clone?: boolean;
|
|
79
|
+
hostMirror?: string;
|
|
80
|
+
bunCache?: string;
|
|
81
|
+
/** 컨테이너 메모리 한도(기본 16Gi) — 게이트가 파일 하나 격리 Job 에 올린다. */
|
|
82
|
+
memoryLimit?: string;
|
|
83
|
+
source?: PodSource;
|
|
76
84
|
deadlineSeconds: number;
|
|
77
85
|
runId?: string;
|
|
78
86
|
}
|
|
@@ -88,6 +96,7 @@ export function podCommandJobManifest(o: PodCommandJobInput): Record<string, unk
|
|
|
88
96
|
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(name)) throw new Error(`pod command skill name rejected: ${name}`);
|
|
89
97
|
}
|
|
90
98
|
const grok = o.llm === 'grok';
|
|
99
|
+
const bunCache = o.bunCache ? podBunCacheVolume(o.bunCache) : undefined;
|
|
91
100
|
const quotedArgs = o.command.map(bashSingleQuote).join(' ');
|
|
92
101
|
const credLines = [
|
|
93
102
|
...(grok ? ['mkdir -p ~/.grok && install -m 600 /creds/grok-auth.json ~/.grok/auth.json'] : []),
|
|
@@ -100,8 +109,9 @@ export function podCommandJobManifest(o: PodCommandJobInput): Record<string, unk
|
|
|
100
109
|
...credLines,
|
|
101
110
|
'curl -s -m 3 -o /dev/null http://host.orb.internal:31415/health && { echo "[pod] ISOLATION FAIL"; exit 3; }',
|
|
102
111
|
...(o.clone
|
|
103
|
-
? ['export GH_TOKEN="$(cat /creds/gh-token)" && gh auth setup-git', podSourceScript({ kind: 'default' }, o.repoUrl)]
|
|
112
|
+
? ['export GH_TOKEN="$(cat /creds/gh-token)" && gh auth setup-git', podSourceScript(o.source ?? { kind: 'default' }, o.repoUrl)]
|
|
104
113
|
: ['mkdir -p ~/work && cd ~/work']),
|
|
114
|
+
...(bunCache ? [bunCache.shellPrefix] : []),
|
|
105
115
|
`set -- ${quotedArgs}`,
|
|
106
116
|
'"$@"',
|
|
107
117
|
'rc=$?',
|
|
@@ -125,16 +135,24 @@ export function podCommandJobManifest(o: PodCommandJobInput): Record<string, unk
|
|
|
125
135
|
initContainers: [{ name: 'isolation-gate', image: o.image, imagePullPolicy: o.imagePullPolicy ?? 'Never', command: ['bash', '-c'], args: [GATE] }],
|
|
126
136
|
containers: [{
|
|
127
137
|
name: 'child', image: o.image, imagePullPolicy: o.imagePullPolicy ?? 'Never',
|
|
128
|
-
resources: { requests: { ...POD_CHILD_REQUESTS }, limits: { memory: '16Gi', cpu: '4' } },
|
|
138
|
+
resources: { requests: { ...POD_CHILD_REQUESTS }, limits: { memory: o.memoryLimit ?? '16Gi', cpu: '4' } },
|
|
129
139
|
command: ['bash', '-c'], args: [script],
|
|
130
140
|
env: [
|
|
131
141
|
...(o.runId ? [{ name: 'ELANOUS_RUN_ID', value: o.runId }] : []),
|
|
132
142
|
{ name: 'ELANOUS_SUBSTRATE', value: 'pod' },
|
|
133
143
|
{ name: 'ELANOUS_POD_NAME', valueFrom: { fieldRef: { fieldPath: 'metadata.name' } } },
|
|
134
144
|
],
|
|
135
|
-
...(secretKeys ? { volumeMounts: [
|
|
145
|
+
...(secretKeys || o.hostMirror || bunCache ? { volumeMounts: [
|
|
146
|
+
...(secretKeys ? [{ name: 'creds', mountPath: '/creds', readOnly: true }] : []),
|
|
147
|
+
...(o.hostMirror ? [{ name: 'host-mirror', mountPath: '/host-mirror', readOnly: true }] : []),
|
|
148
|
+
...(bunCache ? [bunCache.volumeMount] : []),
|
|
149
|
+
] } : {}),
|
|
136
150
|
}],
|
|
137
|
-
...(secretKeys
|
|
151
|
+
...(secretKeys || o.hostMirror || bunCache ? { volumes: [
|
|
152
|
+
...(secretKeys ? [{ name: 'creds', secret: { secretName: `${o.name}-creds`, defaultMode: 0o400 } }] : []),
|
|
153
|
+
...(o.hostMirror ? [{ name: 'host-mirror', hostPath: { path: o.hostMirror, type: 'Directory' } }] : []),
|
|
154
|
+
...(bunCache ? [bunCache.volume] : []),
|
|
155
|
+
] } : {}),
|
|
138
156
|
},
|
|
139
157
|
},
|
|
140
158
|
},
|
|
@@ -188,6 +206,11 @@ export interface RunPodCommandOptions {
|
|
|
188
206
|
skills?: readonly string[];
|
|
189
207
|
llm?: 'grok';
|
|
190
208
|
clone?: boolean;
|
|
209
|
+
hostMirror?: string;
|
|
210
|
+
bunCache?: string;
|
|
211
|
+
/** 컨테이너 메모리 한도(기본 16Gi). */
|
|
212
|
+
memoryLimit?: string;
|
|
213
|
+
source?: PodSource;
|
|
191
214
|
ghToken?: () => string;
|
|
192
215
|
deadlineSeconds?: number;
|
|
193
216
|
namespace?: string;
|
|
@@ -207,6 +230,7 @@ export interface RunPodCommandOptions {
|
|
|
207
230
|
sleep?: (ms: number) => Promise<void>;
|
|
208
231
|
log?: (category: string, event: string, data: Record<string, unknown>) => void;
|
|
209
232
|
env?: NodeJS.ProcessEnv;
|
|
233
|
+
configHostMirror?: () => string | undefined;
|
|
210
234
|
}
|
|
211
235
|
|
|
212
236
|
function jobFinished(types: string): 'complete' | 'failed' | null {
|
|
@@ -243,6 +267,11 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
243
267
|
const deadlineSeconds = options.deadlineSeconds ?? POD_COMMAND_DEADLINE_SECONDS;
|
|
244
268
|
const name = options.name ?? podJobName(`cmd-${Date.now().toString(36)}`);
|
|
245
269
|
const env = options.env ?? process.env;
|
|
270
|
+
const hostMirror = (options.hostMirror ?? env.ELANOUS_POD_HOST_MIRROR
|
|
271
|
+
?? (options.configHostMirror ?? (() => getUserConfig().pod?.hostMirror))())?.trim() || undefined;
|
|
272
|
+
if (hostMirror && !isAbsolute(hostMirror)) throw new Error('pod.hostMirror must be an absolute directory path');
|
|
273
|
+
const bunCache = options.bunCache?.trim() || undefined;
|
|
274
|
+
if (bunCache && !isAbsolute(bunCache)) throw new Error('pod.bunCache must be an absolute directory path');
|
|
246
275
|
|
|
247
276
|
let member: PodPoolMember | null = null;
|
|
248
277
|
let pool = options.poolScheduler;
|
|
@@ -307,7 +336,8 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
307
336
|
const manifest = podCommandJobManifest({
|
|
308
337
|
name, namespace, image: member?.imageRef ?? image,
|
|
309
338
|
...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}),
|
|
310
|
-
repoUrl, command, skills, ...(llm ? { llm } : {}), ...(options.clone ? { clone: true } : {}),
|
|
339
|
+
repoUrl, command, skills, ...(llm ? { llm } : {}), ...(options.clone ? { clone: true } : {}),
|
|
340
|
+
...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), ...(bunCache ? { bunCache } : {}), ...(options.memoryLimit ? { memoryLimit: options.memoryLimit } : {}), deadlineSeconds,
|
|
311
341
|
...(options.runId ? { runId: options.runId } : {}),
|
|
312
342
|
});
|
|
313
343
|
kubectl(['-n', namespace, 'delete', 'job', name, '--ignore-not-found']);
|