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
package/src/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { CLI_HARNESS_DOGFOOD_ENTRANCE, CLI_HARNESS_ORCHESTRATE_ENTRANCE, describ
|
|
|
26
26
|
import { collectCommandEntrances, renderCommandEntrances } from './self-dev/entrance-inventory.js';
|
|
27
27
|
import { isGoalAuthorFileName } from './self-implement/goal-document.js';
|
|
28
28
|
import { DEV_PIPELINE_SINK_SURFACE } from './self-implement/self-cli-sink-surface.js';
|
|
29
|
+
import { registerPrivateLedgerCommands } from './cli/private-ledger-commands.js';
|
|
29
30
|
import { applyTestStateDirFlagFromArgv } from './cli/test-state-dir-flag.js';
|
|
30
31
|
import { applyTestFlagFromArgv, observeTestFlagOwnership, uncoveredTestFlagPaths, staleTestFlagPaths } from './cli/test-flag.js';
|
|
31
32
|
import { registerPtyTakeoverCommands } from './cli/pty-takeover-cli.js';
|
|
@@ -115,7 +116,7 @@ import { DEFAULT_REVIEW_BACKEND } from './agent-substrate/acp-reviewer.js';
|
|
|
115
116
|
import type { ReviewResult } from './agent-substrate/pr-reviewer.js';
|
|
116
117
|
import type { ReviewVerdict } from './agent-mission/review-loop.js';
|
|
117
118
|
import type { SyncMode } from './types.js';
|
|
118
|
-
import { runOnboarding, runOnboardingStep, runOnboardingNonInteractive, needsOnboarding, type OnboardingStepId } from './onboarding.js';
|
|
119
|
+
import { runOnboarding, runOnboardingStep, runOnboardingNonInteractive, needsOnboarding, handleOnboardingRefusal, type OnboardingStepId } from './onboarding.js';
|
|
119
120
|
import {
|
|
120
121
|
getUserConfig, reloadUserConfig, userConfigPath, saveUserConfig,
|
|
121
122
|
backupUserConfig, backupConfigPath, addRotationEntry, type RotationEntry,
|
|
@@ -2088,13 +2089,14 @@ harnessCmd
|
|
|
2088
2089
|
});
|
|
2089
2090
|
harnessCmd
|
|
2090
2091
|
.command('stop <runId>')
|
|
2091
|
-
.description('런 하나를 멈춘다 —
|
|
2092
|
+
.description('런 하나를 멈춘다 — pid.json 시작 시각 대조 ⊕ 런 발사 프로세스 표 조회 ⊕ 라벨 elanous.run=<runId> Pod Job')
|
|
2092
2093
|
.option('--context <ctx...>', 'kube 문맥(여러 번 · 기본 = 알려진 문맥 전부)')
|
|
2093
2094
|
.option('--json', 'JSON 출력')
|
|
2094
|
-
.
|
|
2095
|
+
.option('--dry-run', '프로세스 후보만 보고 신호·Pod Job 삭제는 하지 않는다')
|
|
2096
|
+
.action(async (runId: string, opts: { context?: string[]; json?: boolean; dryRun?: boolean }) => {
|
|
2095
2097
|
const { stopHarnessRun, defaultHarnessStopDeps, formatHarnessStop } = await import('./harness/harness-stop.js');
|
|
2096
2098
|
try {
|
|
2097
|
-
const result = await stopHarnessRun(runId, defaultHarnessStopDeps(), opts.context);
|
|
2099
|
+
const result = await stopHarnessRun(runId, defaultHarnessStopDeps(), opts.context, opts.dryRun === true);
|
|
2098
2100
|
process.stdout.write(`${opts.json ? JSON.stringify(result) : formatHarnessStop(result)}\n`);
|
|
2099
2101
|
} catch (error) {
|
|
2100
2102
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
@@ -4238,8 +4240,9 @@ const selfOrchestrateCmd = selfCmd
|
|
|
4238
4240
|
if (!opts.json) ui.info(`[pod] 이미지 다시 굽기 — ${image.reason} (~1분)`);
|
|
4239
4241
|
const { spawnSync } = await import('node:child_process');
|
|
4240
4242
|
const top = spawnSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8' }).stdout.trim() || process.cwd();
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
+
// bun 1.4 applies the 1 MiB default maxBuffer: a docker build log past it kills the child (ENOBUFS · status null).
|
|
4244
|
+
const b = spawnSync('bash', [`${top}/docker/harness/build.sh`], { encoding: 'utf8', timeout: 900_000, maxBuffer: 64 * 1024 * 1024 });
|
|
4245
|
+
if (b.status !== 0) { ui.error(`--substrate pod: 이미지 굽기 실패 rc=${b.status}${b.error ? ` (${(b.error as NodeJS.ErrnoException).code ?? b.error.message})` : ''}: ${(b.stdout + b.stderr).slice(-400)}`); process.exit(2); }
|
|
4243
4246
|
image = podImageFreshness();
|
|
4244
4247
|
}
|
|
4245
4248
|
}
|
|
@@ -4410,7 +4413,7 @@ const selfOrchestrateCmd = selfCmd
|
|
|
4410
4413
|
...results.map((r) => {
|
|
4411
4414
|
const icon = r.status === 'done' ? '✅' : r.status === 'cancelled' ? '⛔' : '❌';
|
|
4412
4415
|
const disp = r.merged ? ` → merged ${r.prUrl}` : r.prUrl ? ` → PR ${r.prUrl}` : r.stage ? ` [${r.stage}]` : '';
|
|
4413
|
-
return ` ${icon} ${r.status} · ${r.feature.slice(0, 56)}${disp}${r.error ? ` — ${r.error.code}` : ''}`;
|
|
4416
|
+
return ` ${icon} ${r.status} · ${r.feature.slice(0, 56)}${disp}${r.error ? ` — ${r.error.code}${r.error.code === 'pod-job-failed' ? `: ${r.error.message}` : ''}` : ''}`;
|
|
4414
4417
|
}),
|
|
4415
4418
|
].join('\n'));
|
|
4416
4419
|
await reportRun(results);
|
|
@@ -4608,7 +4611,7 @@ selfCmd
|
|
|
4608
4611
|
.action(async (space: string | undefined, opts: { stop?: boolean; memo?: string; run?: string; includeStale?: boolean; readWait?: string }) => {
|
|
4609
4612
|
const { classifyRunScreenMissing, queryRunScreenKey, listRunLedgers } = await import('./self-implement/run-ledger.js');
|
|
4610
4613
|
const { getPtyManifest, listPtyManifestRows } = await import('./pty-shell/pty-manifest.js');
|
|
4611
|
-
const { readPodFragment, podFragmentFinished, dispatchPodSelfSend } = await import('./harness/self-send-target.js');
|
|
4614
|
+
const { readPodFragment, podFragmentFinished, listPodFragmentsForRun, dispatchPodSelfSend } = await import('./harness/self-send-target.js');
|
|
4612
4615
|
const { listHarnessScreens, readHarnessHeartbeat } = await import('./harness/harness-screen.js');
|
|
4613
4616
|
const decision = decideSelfSend({ space, opts, now: Date.now() }, {
|
|
4614
4617
|
resolveRunScreen: (runId) => {
|
|
@@ -4616,6 +4619,7 @@ selfCmd
|
|
|
4616
4619
|
return { ...resolved, missingStatus: resolved.screenKey ? undefined : classifyRunScreenMissing(resolved.lastEvent) };
|
|
4617
4620
|
},
|
|
4618
4621
|
resolveTarget: (target) => resolveSelfSendTarget(target, { getPtyManifest, listPtyManifestRows }),
|
|
4622
|
+
podFragmentsForRun: (runId) => listPodFragmentsForRun(runId),
|
|
4619
4623
|
isPodFragment: (spaceId) => Boolean(readPodFragment(spaceId) || podFragmentFinished(spaceId)),
|
|
4620
4624
|
screens: () => listHarnessScreens().map((screen): SelfSendCandidate => {
|
|
4621
4625
|
const heartbeat = readHarnessHeartbeat(screen.spaceId);
|
|
@@ -5426,6 +5430,7 @@ program.command('ad [input...]')
|
|
|
5426
5430
|
});
|
|
5427
5431
|
|
|
5428
5432
|
registerDocsCommands(program);
|
|
5433
|
+
registerPrivateLedgerCommands(program);
|
|
5429
5434
|
registerFleetCommands(program);
|
|
5430
5435
|
|
|
5431
5436
|
// ── ops (운영 관측 — 지금 뭐 도나·이상 없나·상태 전이) ──
|
|
@@ -5592,6 +5597,7 @@ agentCmd
|
|
|
5592
5597
|
.option('--backend <id>', `외부 에이전트 backend (${agentBackendHelpList}). 각 CLI auto-approve 모드로 PTY 구동. 미등록 지정 시 명시 에러(조용한 codex 폴백 없음·애그노스틱)`)
|
|
5593
5598
|
.option('--chain <backends>', '한 미션의 backend 사슬 (예: codex,claude,elanous)')
|
|
5594
5599
|
.option('--plugin <name@market>', '에이전트 화면 안에서 플러그인을 설치하고 그 스킬로 미션 실행 (codex/claude)')
|
|
5600
|
+
.addOption(new Option('--resources <mode>', '미션 전 자원 판단 사다리 (on|off, 기본 on)').choices(['on', 'off']).default('on'))
|
|
5595
5601
|
.option('--headless', 'Claude 구독 CLI를 비대화형 stream-json 모드로 실행 (claude backend 전용)')
|
|
5596
5602
|
.action(async (textParts: string[], opts: Record<string, any>) => {
|
|
5597
5603
|
// 구독 모드 보장 — 브레인(streamLLM)·codex 둘 다 ChatGPT 구독으로 (API 과금 회피).
|
|
@@ -7815,6 +7821,16 @@ sessionCmd
|
|
|
7815
7821
|
console.log(res.formatted ?? '(no context)');
|
|
7816
7822
|
});
|
|
7817
7823
|
|
|
7824
|
+
async function runOnboardingForCli(): Promise<boolean> {
|
|
7825
|
+
try {
|
|
7826
|
+
await runOnboarding();
|
|
7827
|
+
return true;
|
|
7828
|
+
} catch (error) {
|
|
7829
|
+
if (handleOnboardingRefusal(error, 'other')) return false;
|
|
7830
|
+
throw error;
|
|
7831
|
+
}
|
|
7832
|
+
}
|
|
7833
|
+
|
|
7818
7834
|
function announceChatToolsCompatibility(): void {
|
|
7819
7835
|
process.stderr.write('`chat --tools` is a compatibility entrypoint; use `elanous agent` for tool-loop calls.\n');
|
|
7820
7836
|
debug.log('chat.tools-compatibility', 'invoked', { toolLoopEnabled: true });
|
|
@@ -7841,7 +7857,7 @@ program
|
|
|
7841
7857
|
const cfg = getUserConfig();
|
|
7842
7858
|
if (needsOnboarding(cfg)) {
|
|
7843
7859
|
ui.info('No config yet — launching setup wizard first.');
|
|
7844
|
-
await
|
|
7860
|
+
if (!await runOnboardingForCli()) return;
|
|
7845
7861
|
}
|
|
7846
7862
|
const refreshed = reloadUserConfig();
|
|
7847
7863
|
// ★ 관측갭 수리(2026-07-21·제1원칙·트랙A) — self-implement 자식 goal-loop(`chat --goal-loop`)은 데몬과
|
|
@@ -7885,7 +7901,7 @@ program
|
|
|
7885
7901
|
.option('--new', 'Force a new session at boot instead of resuming the active one')
|
|
7886
7902
|
.option('--session <id>', 'Resume an explicit session (id or unique prefix)')
|
|
7887
7903
|
.option('--scenario <path>', 'Run a YAML multi-turn scenario before handing back to interactive (or exit)')
|
|
7888
|
-
.option('--replay <session-id>', 'Re-execute the user prompts from a previous session in a fresh REPL run
|
|
7904
|
+
.option('--replay <session-id>', 'Re-execute the user prompts from a previous session in a fresh REPL run. User prompts are extracted in order and fed through the same dispatcher as --scenario; assistant/tool messages and attachments are dropped. Mutually exclusive with --scenario.')
|
|
7889
7905
|
.option('--exit-after-scenario', 'Exit after the scenario / replay completes (default true when stdin is not a TTY)')
|
|
7890
7906
|
.option('--no-exit-after-scenario', 'Stay in the interactive prompt after the scenario / replay completes (TTY default)')
|
|
7891
7907
|
.option('--json', 'Emit one JSON line per turn (sessionId/provider/model/reply/...) instead of streaming text')
|
|
@@ -7908,7 +7924,7 @@ program
|
|
|
7908
7924
|
const cfg = getUserConfig();
|
|
7909
7925
|
if (needsOnboarding(cfg)) {
|
|
7910
7926
|
ui.info('No config yet — launching setup wizard first.');
|
|
7911
|
-
await
|
|
7927
|
+
if (!await runOnboardingForCli()) return;
|
|
7912
7928
|
}
|
|
7913
7929
|
const refreshed = reloadUserConfig();
|
|
7914
7930
|
const { runRepl } = await import('./repl/index.js');
|
|
@@ -8471,7 +8487,7 @@ program
|
|
|
8471
8487
|
'ACP client for the running elanous daemon. Three modes: handshake-only (default), one-shot (--message), interactive REPL (--interactive). Local (unix socket) or remote (--host / --url) over Tailscale.',
|
|
8472
8488
|
)
|
|
8473
8489
|
.option('--socket <path>', 'Override the unix socket path (default: ~/.elanous/elanous.sock)')
|
|
8474
|
-
.option('--host <hostport>', 'Remote daemon host:port (e.g.
|
|
8490
|
+
.option('--host <hostport>', 'Remote daemon host:port (e.g. laptop.tailnet:31415). Coerced to ws://<hostport>/v1/acp.')
|
|
8475
8491
|
.option('--url <wsurl>', 'Remote daemon WS URL (e.g. ws://host:31415/v1/acp). Overrides --host.')
|
|
8476
8492
|
.option('-r, --remote [name]', 'Bookmark name (`-r` alone = default). Fills host/token-file; explicit --url/--host/--token/--token-file win.')
|
|
8477
8493
|
.option('--token <token>', 'Bearer token for remote auth. Overrides --token-file and ELANOUS_TOKEN.')
|
|
@@ -8945,6 +8961,7 @@ const nexusRunCmd = nexusCmd
|
|
|
8945
8961
|
.option('--watch', 'Static-mode: fs.watch loop inside daemon — source edits auto-rebuild apps/pwa. Default on for `nexus run` (no args), off for `--test`.')
|
|
8946
8962
|
.option('--no-watch', 'Disable the static-mode fs.watch loop (canonical entry default-on).')
|
|
8947
8963
|
.option('--no-mcp', 'Skip the entire MCP-client boot wire (every `mcp.servers[]` in user-config). Use when a misbehaving server (e.g. xcrun mcpbridge tools/list hang) drags every startup through its 8s timeout.')
|
|
8964
|
+
.option('--no-auth', 'Explicitly disable Nexus HTTP and ACP websocket bearer authentication (unsafe).')
|
|
8948
8965
|
.option('--dispatch', '§5-③ — enable the autonomous idle-continuation scheduler for this run (drives an active auto-mode goal on idle). Same as user-config `dispatch.enabled`. Default off — ignites self-firing turns, so opt-in.')
|
|
8949
8966
|
.option('--force', 'Take the lock even if another nexus instance is recorded as running.')
|
|
8950
8967
|
.option('--status', 'Alias for `elanous nexus status` (or test-mode status when combined with --test).')
|
|
@@ -8980,6 +8997,7 @@ const nexusRunCmd = nexusCmd
|
|
|
8980
8997
|
autoRestart?: boolean;
|
|
8981
8998
|
watch?: boolean;
|
|
8982
8999
|
mcp?: boolean;
|
|
9000
|
+
auth?: boolean;
|
|
8983
9001
|
fresh?: boolean;
|
|
8984
9002
|
force?: boolean;
|
|
8985
9003
|
dispatch?: boolean;
|
|
@@ -9129,6 +9147,7 @@ const nexusRunCmd = nexusCmd
|
|
|
9129
9147
|
preflightStaleServe: true,
|
|
9130
9148
|
verifyListen: true,
|
|
9131
9149
|
...(opts.mcp === false ? { mcpEnabled: false } : {}),
|
|
9150
|
+
...(opts.auth === false ? { noAuth: true } : {}),
|
|
9132
9151
|
...(opts.https ? { https: true } : {}),
|
|
9133
9152
|
...(resolvedPort !== undefined ? { httpPort: resolvedPort } : {}),
|
|
9134
9153
|
...(opts.force ? { force: true } : {}),
|
|
@@ -9156,6 +9175,7 @@ const nexusRunCmd = nexusCmd
|
|
|
9156
9175
|
// it — service-manager production posture stays watch-off.
|
|
9157
9176
|
...((bgChild ? opts.watch === true : watchOn) ? { pwaWatch: true } : {}),
|
|
9158
9177
|
...(opts.mcp === false ? { mcpEnabled: false } : {}),
|
|
9178
|
+
...(opts.auth === false ? { noAuth: true } : {}),
|
|
9159
9179
|
...(opts.force ? { force: true } : {}),
|
|
9160
9180
|
...(opts.dispatch ? { dispatch: true } : {}),
|
|
9161
9181
|
...(opts.toolCwd ? { toolCwd: opts.toolCwd } : {}),
|
|
@@ -9187,6 +9207,7 @@ const nexusRunCmd = nexusCmd
|
|
|
9187
9207
|
...(opts.rebuild ? { rebuild: true } : {}),
|
|
9188
9208
|
...(watchOnTty ? { watch: true } : {}),
|
|
9189
9209
|
...(opts.mcp === false ? { mcpEnabled: false } : {}),
|
|
9210
|
+
...(opts.auth === false ? { noAuth: true } : {}),
|
|
9190
9211
|
});
|
|
9191
9212
|
process.exit(result.exitCode);
|
|
9192
9213
|
});
|
|
@@ -9892,6 +9913,22 @@ nexusCmd
|
|
|
9892
9913
|
}
|
|
9893
9914
|
});
|
|
9894
9915
|
|
|
9916
|
+
// 휴대폰 앱 온보딩 — 링크 하나(⊕ QR)로 이 Mac 의 넥서스에 붙인다(대표 2026-09-30 · 아이폰 첫 화면 단순화).
|
|
9917
|
+
program
|
|
9918
|
+
.command('phone')
|
|
9919
|
+
.description('Connect the phone app to this Mac')
|
|
9920
|
+
.command('link')
|
|
9921
|
+
.description('Print a one-tap connect link (and a QR code) for the Elanous phone app')
|
|
9922
|
+
.option('--simulator', 'Use the loopback address (the simulator on this Mac) instead of the tailnet address — `--local` is taken by the global remote flag')
|
|
9923
|
+
.option('--android [serial]', 'Hand the link straight to a USB-connected Android phone (adb reverse + am start · the token is never printed)')
|
|
9924
|
+
.option('--temp', 'Use a short-lived token instead of the owner token')
|
|
9925
|
+
.option('--ttl <duration>', 'Lifetime for --temp (max 24h)', '24h')
|
|
9926
|
+
.option('--no-qr', 'Do not print a QR code')
|
|
9927
|
+
.action(async (opts: { simulator?: boolean; android?: boolean | string; temp?: boolean; ttl: string; qr: boolean }) => {
|
|
9928
|
+
const { runPhoneLink } = await import('./cli/phone-link.js');
|
|
9929
|
+
process.exitCode = await runPhoneLink({ local: opts.simulator, android: opts.android, temp: opts.temp, ttl: opts.ttl, qr: opts.qr });
|
|
9930
|
+
});
|
|
9931
|
+
|
|
9895
9932
|
// ── ACP — Agent Client Protocol (claude-code, codex, ...) ──
|
|
9896
9933
|
const tokenCmd = program
|
|
9897
9934
|
.command('token')
|
|
@@ -10560,7 +10597,7 @@ async function main(): Promise<void> {
|
|
|
10560
10597
|
// gates on config completeness — no entry-mode branch.)
|
|
10561
10598
|
const cfg = getUserConfig();
|
|
10562
10599
|
if (needsOnboarding(cfg)) {
|
|
10563
|
-
await
|
|
10600
|
+
if (!await runOnboardingForCli()) return;
|
|
10564
10601
|
}
|
|
10565
10602
|
|
|
10566
10603
|
// Active-provider banner so users know WHICH model/auth is about to
|
package/src/instance/resolve.ts
CHANGED
|
@@ -32,6 +32,12 @@ export function prodInstanceRoot(): string {
|
|
|
32
32
|
return normRoot(join(homedir(), '.elanous'));
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/** Release records are machine-wide except in an explicitly selected config universe. */
|
|
36
|
+
export function releaseLedgerRoot(): string {
|
|
37
|
+
const { getElanousConfigDirOverride } = require('../elanous-config-dir.js') as typeof import('../elanous-config-dir.js');
|
|
38
|
+
return getElanousConfigDirOverride() ? effectiveInstanceRoot() : prodInstanceRoot();
|
|
39
|
+
}
|
|
40
|
+
|
|
35
41
|
export function resolveInstance(deps: ResolveDeps = {}): InstanceResolution {
|
|
36
42
|
const prodRoot = normRoot(deps.prodRoot ?? prodInstanceRoot());
|
|
37
43
|
if (deps.explicitFlagRoot?.trim()) {
|
|
@@ -27,6 +27,11 @@ export function readSavedCursor(root: string): number {
|
|
|
27
27
|
try { return Number(JSON.parse(readFileSync(cursorFile(root), 'utf8')).lastId) || 0; } catch { return 0; }
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/** When the cursor last moved (= a run last found a new saved message). Undefined before the first message. */
|
|
31
|
+
export function readSavedCursorAt(root: string): string | undefined {
|
|
32
|
+
try { const at = JSON.parse(readFileSync(cursorFile(root), 'utf8')).at; return typeof at === 'string' ? at : undefined; } catch { return undefined; }
|
|
33
|
+
}
|
|
34
|
+
|
|
30
35
|
function writeSavedCursor(root: string, lastId: number): void {
|
|
31
36
|
mkdirSync(intakeLedgerDir(root), { recursive: true });
|
|
32
37
|
writeFileSync(cursorFile(root), JSON.stringify({ lastId, at: new Date().toISOString() }) + '\n');
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
import { existsSync, readFileSync } from 'node:fs';
|
|
4
4
|
import { basename, join, relative } from 'node:path';
|
|
5
5
|
import { loadIntakeLedger, type IntakeItem } from './items.js';
|
|
6
|
+
import { readSavedCursorAt } from './collect-telegram-saved.js';
|
|
6
7
|
import { intakeOutboxDir, kstDay } from './route.js';
|
|
7
8
|
|
|
8
9
|
export interface DigestEntry { id: string; sources: string[]; url?: string; note?: string; noteName?: string; oneLiner?: string; axis: string }
|
|
9
|
-
export interface IntakeDigest { day: string; absorbed: DigestEntry[]; goals: { fact: string; url?: string }[]; review?: { fact: string; note?: string }[]; grounding: number; release: number; manual: number }
|
|
10
|
+
export interface IntakeDigest { day: string; absorbed: DigestEntry[]; goals: { fact: string; url?: string }[]; review?: { fact: string; note?: string }[]; grounding: number; release: number; manual: number; savedSilence?: { days: number; lastNewAt: string } }
|
|
10
11
|
|
|
11
12
|
const AXIS_LABEL: Record<string, string> = {
|
|
12
13
|
video_automation: '영상 자동화', agent_basics: '에이전트 기본', agent_applied: '에이전트 응용', unrelated: '그 밖',
|
|
@@ -36,7 +37,10 @@ function readJsonl(file: string): Record<string, unknown>[] {
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
/** `day`(KST YYYY-MM-DD)에 흡수·갈래가 끝난 항목과 그날의 산출 큐를 모은다. */
|
|
39
|
-
|
|
40
|
+
/** Saved Messages silent for more than two days → the digest says so (a quiet 0 must not look like «nothing to do»). */
|
|
41
|
+
const SAVED_SILENCE_MS = 48 * 3600_000;
|
|
42
|
+
|
|
43
|
+
export function buildIntakeDigest(root: string, day: string, readFile: (p: string) => string | undefined = (p) => (existsSync(p) ? readFileSync(p, 'utf8') : undefined), now: Date = new Date()): IntakeDigest {
|
|
40
44
|
const items = [...loadIntakeLedger(root).items.values()]
|
|
41
45
|
.filter((i) => (i.status === 'absorbed' || i.status === 'routed') && kstDay(i.lastSeenAt) === day && i.outputs.some((o) => o.kind === 'note'));
|
|
42
46
|
const absorbed = items.map((i: IntakeItem): DigestEntry => {
|
|
@@ -53,16 +57,20 @@ export function buildIntakeDigest(root: string, day: string, readFile: (p: strin
|
|
|
53
57
|
const goals = readJsonl(join(out, 'goals', `${day}.jsonl`)).map((g) => ({ fact: String(g.fact ?? ''), ...(g.url ? { url: String(g.url) } : {}) }));
|
|
54
58
|
const review = readJsonl(join(out, 'review', `${day}.jsonl`)).map((r) => ({ fact: String(r.fact ?? ''), ...(r.note ? { note: String(r.note) } : {}) }));
|
|
55
59
|
const grounding = readJsonl(join(out, 'grounding.jsonl')).filter((g) => typeof g.at === 'string' && kstDay(g.at) === day).length;
|
|
60
|
+
const lastNewAt = readSavedCursorAt(root);
|
|
61
|
+
const silentMs = lastNewAt ? now.getTime() - Date.parse(lastNewAt) : NaN;
|
|
56
62
|
return {
|
|
57
63
|
day, absorbed, goals, review, grounding,
|
|
58
64
|
release: readJsonl(join(out, 'release', `${day}.jsonl`)).length,
|
|
59
65
|
manual: readJsonl(join(out, 'manual', `${day}.jsonl`)).length,
|
|
66
|
+
...(lastNewAt && silentMs > SAVED_SILENCE_MS ? { savedSilence: { days: Math.floor(silentMs / 86_400_000), lastNewAt } } : {}),
|
|
60
67
|
};
|
|
61
68
|
}
|
|
62
69
|
|
|
63
70
|
/** 노트 절 — 축별 묶음 · 노트 링크 · 한 줄 요약 · 골 후보. */
|
|
64
71
|
export function renderDigestMarkdown(d: IntakeDigest): string {
|
|
65
72
|
const L: string[] = [`## 📰 오늘의 흡수 요약 (${d.absorbed.length})`, ''];
|
|
73
|
+
if (d.savedSilence) L.push(`> ⚠️ 텔레그램 «저장된 메시지»에서 새 글을 ${d.savedSilence.days}일째 못 받았다(마지막 새 글 수집 ${kstDay(d.savedSilence.lastNewAt)}) — 저장했는데 안 들어왔다면 저장한 곳(본인 «저장된 메시지»인지)을 확인한다.`, '');
|
|
66
74
|
if (!d.absorbed.length) { L.push('오늘 흡수한 것이 없다.', ''); return L.join('\n'); }
|
|
67
75
|
const byAxis = new Map<string, DigestEntry[]>();
|
|
68
76
|
for (const e of d.absorbed) (byAxis.get(e.axis) ?? byAxis.set(e.axis, []).get(e.axis)!).push(e);
|
|
@@ -89,6 +97,7 @@ export function renderDigestMarkdown(d: IntakeDigest): string {
|
|
|
89
97
|
/** 텔레그램 짧은 판 — 수 · 한 줄 요약 다섯 · 골 후보 · 노트 자리. */
|
|
90
98
|
export function renderDigestTelegram(d: IntakeDigest, opts: { vaultRoot?: string; notePath?: string } = {}): string {
|
|
91
99
|
const L = [`📰 *${d.day} 흡수 ${d.absorbed.length}편*`];
|
|
100
|
+
if (d.savedSilence) L.push(`⚠️ 저장된 메시지 새 글 ${d.savedSilence.days}일째 0 — 저장한 곳 확인`);
|
|
92
101
|
for (const e of d.absorbed.slice(0, 5)) L.push(`• ${e.oneLiner ?? e.noteName ?? e.url ?? e.id}`);
|
|
93
102
|
if (d.absorbed.length > 5) L.push(`… 외 ${d.absorbed.length - 5}편`);
|
|
94
103
|
if (d.goals.length) L.push('', `🔴 엘라누스에 없는 것 ${d.goals.length}건 — 골 후보`);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// 상태 = `<state>/live/detail.json` `{ scope: 'all' | '<runId>', until: <epoch ms>, by?, since }`.
|
|
8
8
|
// - 넥서스(`/v1/live/detail`)가 쓰고, 하니스·런(다른 프로세스)은 파일을 읽는다 — 프로세스 경계를 넘는 가장 단순한 관.
|
|
9
9
|
// - `until` 이 지나면 꺼진 것이다(자동 꺼짐 · 파일을 지우는 사람이 없어도 된다).
|
|
10
|
-
// ⛔ 읽기 실패·깨진 파일은 «꺼짐» —
|
|
10
|
+
// ⛔ 읽기 실패·깨진 파일은 기본 «꺼짐» — 단 유효한 미래 ELANOUS_LIVE_DETAIL_UNTIL 은 전체 범위를 켠다.
|
|
11
11
|
|
|
12
12
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
13
13
|
import { dirname, join } from 'node:path';
|
|
@@ -107,10 +107,15 @@ export function selectLiveDetail(opts: DetailReadOptions = {}): LiveDetailSelect
|
|
|
107
107
|
return { ...selection, state: parsed && parsed.until > now ? parsed : null };
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
/** 이 런에 상세 관측이 켜져 있나 —
|
|
110
|
+
/** 이 런에 상세 관측이 켜져 있나 — 파일은 5초 캐시, env 만료는 매번 확인한다. */
|
|
111
111
|
export function isLiveDetailOn(runId?: string, opts: DetailReadOptions = {}): boolean {
|
|
112
112
|
const now = opts.now ?? Date.now();
|
|
113
|
-
|
|
113
|
+
const state = selectLiveDetail({ ...opts, now }).state;
|
|
114
|
+
if (state) return matches(state, runId, now);
|
|
115
|
+
const rawUntil = process.env.ELANOUS_LIVE_DETAIL_UNTIL;
|
|
116
|
+
if (!rawUntil || !/^\d+$/.test(rawUntil)) return false;
|
|
117
|
+
const until = Number(rawUntil);
|
|
118
|
+
return Number.isSafeInteger(until) && until > now;
|
|
114
119
|
}
|
|
115
120
|
|
|
116
121
|
function matches(state: LiveDetailState | null, runId: string | undefined, now: number): boolean {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { decideProviderForConfig, type ProviderDecision } from '../llm.js';
|
|
2
|
+
import { getUserConfig, type UserConfig } from '../user-config.js';
|
|
3
|
+
|
|
4
|
+
export interface UsableLlm {
|
|
5
|
+
usable: boolean;
|
|
6
|
+
provider?: string;
|
|
7
|
+
via: 'login' | 'key' | 'local-server' | 'none';
|
|
8
|
+
why: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface UsableLlmDeps {
|
|
12
|
+
config?: UserConfig;
|
|
13
|
+
/** The same selector used to construct a runtime LLM; injectable for isolated tests. */
|
|
14
|
+
decide?: (config: UserConfig) => ProviderDecision;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Evaluate the selected route, not the union of discovered credentials or servers. */
|
|
18
|
+
export function resolveUsableLlm(deps: UsableLlmDeps = {}): UsableLlm {
|
|
19
|
+
const decision = (deps.decide ?? decideProviderForConfig)(deps.config ?? getUserConfig());
|
|
20
|
+
const provider = decision.provider.replace(/^auto:/, '');
|
|
21
|
+
// Never echo an untrusted configured provider or a model/URL/credential into output.
|
|
22
|
+
const known = ['openai-codex', 'openai', 'anthropic', 'grok', 'gemini', 'openrouter', 'local'];
|
|
23
|
+
if (provider === 'auto' || decision.auth === 'none') {
|
|
24
|
+
// An explicitly selected provider stays named so callers can offer that provider's fix,
|
|
25
|
+
// not the Codex login (the auto route's default remedy).
|
|
26
|
+
const explicit = !decision.provider.startsWith('auto') && known.includes(provider);
|
|
27
|
+
return { usable: false, ...(explicit ? { provider } : {}), via: 'none', why: 'no usable LLM route selected' };
|
|
28
|
+
}
|
|
29
|
+
if (!known.includes(provider)) return { usable: false, via: 'none', why: 'no usable LLM route selected' };
|
|
30
|
+
const via = decision.auth === 'oauth' ? 'login' : decision.auth === 'apikey' ? 'key' : 'local-server';
|
|
31
|
+
return { usable: true, provider, via, why: `LLM via ${via} (${provider})` };
|
|
32
|
+
}
|
package/src/loops/registry.ts
CHANGED
|
@@ -48,9 +48,13 @@ function graphFiles(dir: string): string[] {
|
|
|
48
48
|
return entries.flatMap(e => e.isDirectory() ? graphFiles(join(dir, e.name)) : e.isFile() && /\.ya?ml$/.test(e.name) ? [join(dir, e.name)] : []).sort();
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/** A wrapper cron that runs the graph itself declares its loop with a trailing `# elanous-loop=<graph_id>` comment. */
|
|
52
|
+
const LOOP_MARKER = /#\s*elanous-loop=([A-Za-z0-9][\w.-]*)\s*$/;
|
|
53
|
+
|
|
51
54
|
/** Match only the actual graph CLI invocation, not a filename mentioned in a shell argument. */
|
|
52
|
-
export function graphJobMatches(command: string | null, file: string, root: string): boolean {
|
|
55
|
+
export function graphJobMatches(command: string | null, file: string, root: string, graphId?: string): boolean {
|
|
53
56
|
if (!command) return false;
|
|
57
|
+
if (graphId && command.match(LOOP_MARKER)?.[1] === graphId) return true;
|
|
54
58
|
const invocation = /(?:^|&&\s*|;\s*)(?:(?:\S*\/)?(?:bun|node)\s+(?:\S*\/)?bin\/elanous\.mjs|(?:\S*\/)?elanous)\s+(?:--test(?:=\S+|\s+\S+)\s+)?graph\s+run\s+(['"]?)([^\s'";&|]+)\1(?=\s|$|[;&|])/g;
|
|
55
59
|
for (const match of command.matchAll(invocation)) {
|
|
56
60
|
const arg = match[2]!;
|
|
@@ -100,8 +104,17 @@ function schedules(opts: LoopRegistryOptions): ScheduleRow[] {
|
|
|
100
104
|
} finally { db.close(); }
|
|
101
105
|
}
|
|
102
106
|
|
|
107
|
+
/** Where loop graphs live and where their cron line `cd`s — the same rule as the other elanous crons:
|
|
108
|
+
* the installed package when there is one, else this checkout. Never the caller's cwd: a loop started
|
|
109
|
+
* from a disposable worktree would schedule a cron into a directory that is about to disappear (🅣 09-29). */
|
|
110
|
+
export function defaultLoopRoot(): string {
|
|
111
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
112
|
+
const { installedCronRoot, repoRoot } = require('../domains/schedule-registry.js') as typeof import('../domains/schedule-registry.js');
|
|
113
|
+
return installedCronRoot() ?? repoRoot();
|
|
114
|
+
}
|
|
115
|
+
|
|
103
116
|
export function listLoops(opts: LoopRegistryOptions = {}): LoopEntry[] {
|
|
104
|
-
const root = resolve(opts.root ??
|
|
117
|
+
const root = resolve(opts.root ?? defaultLoopRoot());
|
|
105
118
|
const stateRoot = opts.stateRoot ?? effectiveInstanceRoot();
|
|
106
119
|
const rows = schedules(opts);
|
|
107
120
|
const loops: LoopEntry[] = [];
|
|
@@ -117,7 +130,7 @@ export function listLoops(opts: LoopRegistryOptions = {}): LoopEntry[] {
|
|
|
117
130
|
const trigger = header?.trigger && typeof header.trigger === 'object' && !Array.isArray(header.trigger) ? header.trigger as Record<string, unknown> : null;
|
|
118
131
|
const declaredCron = typeof trigger?.cron === 'string' ? trigger.cron : null;
|
|
119
132
|
const events = Array.isArray(trigger?.events) ? trigger.events.filter((e): e is string => typeof e === 'string') : [];
|
|
120
|
-
const matched = rows.filter(row => graphJobMatches(row.command, file, root) && row.source === 'crontab' && row.run_via === 'crontab' && row.disabled_reason !== 'vanished');
|
|
133
|
+
const matched = rows.filter(row => graphJobMatches(row.command, file, root, doc.graph_id as string) && row.source === 'crontab' && row.run_via === 'crontab' && row.disabled_reason !== 'vanished');
|
|
121
134
|
if (!declaredCron && !events.length && !matched.length) continue;
|
|
122
135
|
if (!/^[a-zA-Z0-9][\w.-]*$/.test(doc.graph_id)) throw new Error(`unsafe loop graph_id: ${doc.graph_id}`);
|
|
123
136
|
if (loops.some(loop => loop.id === doc.graph_id)) throw new Error(`duplicate loop graph_id: ${doc.graph_id}`);
|
|
@@ -142,6 +155,15 @@ export function loopStatus(id: string, opts: LoopRegistryOptions = {}): LoopEntr
|
|
|
142
155
|
return { ...loop, recentRuns: recent };
|
|
143
156
|
}
|
|
144
157
|
|
|
158
|
+
/** The crontab line a loop runs — same shape as the other elanous crons: cron starts in $HOME with a thin PATH,
|
|
159
|
+
* so a bare `elanous graph run <relative file>` fails silently every tick (09-29 steward). */
|
|
160
|
+
export function loopCronCommand(id: string, file: string, root: string, bun = process.execPath): string {
|
|
161
|
+
// Quote only when needed: the other crons (and `graphJobMatches`) use bare absolute paths.
|
|
162
|
+
const q = (value: string) => /^[\w./@+-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
|
|
163
|
+
const log = `/tmp/elanous-loop-${id.replace(/[^\w.-]/g, '_')}.log`;
|
|
164
|
+
return `cd ${q(root)} && ${q(bun)} bin/elanous.mjs graph run ${q(file)} >> ${log} 2>&1`;
|
|
165
|
+
}
|
|
166
|
+
|
|
145
167
|
export async function setLoopEnabled(id: string, enabled: boolean, yes = false, opts: LoopRegistryOptions = {}): Promise<unknown> {
|
|
146
168
|
const loop = loopStatus(id, opts);
|
|
147
169
|
const action = enabled ? 'start' : 'stop';
|
|
@@ -149,7 +171,7 @@ export async function setLoopEnabled(id: string, enabled: boolean, yes = false,
|
|
|
149
171
|
const pending = loop.jobs.filter(job => job.enabled !== enabled);
|
|
150
172
|
if (!pending.length && loop.jobs.length) return { action, id, changed: false, enabled: loop.enabled };
|
|
151
173
|
const changes = loop.jobs.length ? pending.map(job => ({ action: enabled ? 'enable' : 'disable', id: job.id })) :
|
|
152
|
-
[{ action: 'create', cron: loop.trigger.cron, command:
|
|
174
|
+
[{ action: 'create', cron: loop.trigger.cron, command: loopCronCommand(id, loop.file, resolve(opts.root ?? defaultLoopRoot())) }];
|
|
153
175
|
if (!yes) return { action, id, dryRun: true, changes, note: 'Apply with --yes (crontab backup).' };
|
|
154
176
|
const dispatch = opts.scheduleAction ?? (async (kind: 'create' | 'enable' | 'disable', args: Record<string, unknown>) => {
|
|
155
177
|
const { dispatchScheduleManage } = await import('../domains/schedule-manage-tool.js');
|
|
@@ -170,5 +192,5 @@ export async function setLoopEnabled(id: string, enabled: boolean, yes = false,
|
|
|
170
192
|
export async function runLoop(id: string, dryRun = false, opts: LoopRegistryOptions = {}): Promise<GraphRunState> {
|
|
171
193
|
const loop = loopStatus(id, opts);
|
|
172
194
|
debug.log('loops.registry', 'run', { id, dryRun });
|
|
173
|
-
return runGraph(join(resolve(opts.root ??
|
|
195
|
+
return runGraph(join(resolve(opts.root ?? defaultLoopRoot()), loop.file), { dryRun, deps: { root: opts.stateRoot ?? effectiveInstanceRoot() } });
|
|
174
196
|
}
|
package/src/market/publish.ts
CHANGED
|
@@ -7,6 +7,41 @@ import { debug } from '../debug/log.js';
|
|
|
7
7
|
import { packDirDeterministic } from './tgz.js';
|
|
8
8
|
import { signIndex, verifyIndex, type MarketplaceIndex } from './signed-index.js';
|
|
9
9
|
|
|
10
|
+
type IndexConnector = MarketplaceIndex['plugins'][number]['ai.elanous']['connectors'][number];
|
|
11
|
+
|
|
12
|
+
function normalizeConnectors(connectors: unknown): { ok: true; value: IndexConnector[] } | { ok: false } {
|
|
13
|
+
if (!Array.isArray(connectors)) return { ok: false };
|
|
14
|
+
const value: IndexConnector[] = [];
|
|
15
|
+
const record = (item: unknown): item is Record<string, unknown> =>
|
|
16
|
+
item !== null && typeof item === 'object' && !Array.isArray(item);
|
|
17
|
+
for (const connector of connectors) {
|
|
18
|
+
if (!record(connector) || typeof connector.id !== 'string' || !connector.id) return { ok: false };
|
|
19
|
+
if (Array.isArray(connector.userConfig)) {
|
|
20
|
+
if (typeof connector.kind !== 'string' || !connector.userConfig.every((field: unknown) =>
|
|
21
|
+
record(field) && typeof field.key === 'string' && field.key.trim().length > 0 && typeof field.label === 'string' &&
|
|
22
|
+
typeof field.secret === 'boolean' && (field.env === undefined || typeof field.env === 'string'))) {
|
|
23
|
+
return { ok: false };
|
|
24
|
+
}
|
|
25
|
+
value.push(connector as IndexConnector);
|
|
26
|
+
} else if (Array.isArray(connector.fields)) {
|
|
27
|
+
if ((connector.kind !== undefined && typeof connector.kind !== 'string') || !connector.fields.every((field: unknown) =>
|
|
28
|
+
record(field) && typeof field.name === 'string' && field.name.trim().length > 0 && typeof field.secret === 'boolean' &&
|
|
29
|
+
(field.label === undefined || typeof field.label === 'string') &&
|
|
30
|
+
(field.env === undefined || typeof field.env === 'string'))) {
|
|
31
|
+
return { ok: false };
|
|
32
|
+
}
|
|
33
|
+
value.push({ id: connector.id, kind: (connector.kind ?? 'credentials') as string,
|
|
34
|
+
userConfig: connector.fields.map((field: Record<string, unknown>) => ({
|
|
35
|
+
key: field.name as string, label: (field.label ?? field.name) as string, secret: field.secret === true,
|
|
36
|
+
...(field.env ? { env: field.env as string } : {}),
|
|
37
|
+
})) });
|
|
38
|
+
} else {
|
|
39
|
+
return { ok: false };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { ok: true, value };
|
|
43
|
+
}
|
|
44
|
+
|
|
10
45
|
function unpackLocal(archive: Uint8Array, destination: string): void {
|
|
11
46
|
const tar = gunzipSync(archive);
|
|
12
47
|
const field = (part: Buffer) => part.toString('utf8').replace(/\0.*$/, '');
|
|
@@ -113,13 +148,15 @@ export function publishMarket(input: {
|
|
|
113
148
|
skip('invalid-plugin.json'); continue;
|
|
114
149
|
}
|
|
115
150
|
const { category, bundle, ...metadata } = ai as MarketplaceIndex['plugins'][number]['ai.elanous'] & { category?: string; bundle?: unknown };
|
|
116
|
-
if (!Array.isArray(metadata.capabilities) ||
|
|
151
|
+
if (!Array.isArray(metadata.capabilities) ||
|
|
117
152
|
(category !== undefined && typeof category !== 'string')) {
|
|
118
153
|
skip('invalid-plugin.json'); continue;
|
|
119
154
|
}
|
|
120
155
|
if (metadata.pricing !== undefined && metadata.pricing?.model !== 'free') {
|
|
121
156
|
skip('paid-not-allowed-in-M0'); continue;
|
|
122
157
|
}
|
|
158
|
+
const normalized = normalizeConnectors(metadata.connectors);
|
|
159
|
+
if (!normalized.ok) { skip('invalid-connectors'); continue; }
|
|
123
160
|
const bundleItem = (item: unknown): item is string | { from: string; as: string } => typeof item === 'string' ||
|
|
124
161
|
(!!item && typeof item === 'object' && !Array.isArray(item) && typeof (item as { from?: unknown }).from === 'string' &&
|
|
125
162
|
typeof (item as { as?: unknown }).as === 'string');
|
|
@@ -208,7 +245,26 @@ export function publishMarket(input: {
|
|
|
208
245
|
if (message.startsWith('bundle-conflict')) { skip('bundle-conflict'); continue; }
|
|
209
246
|
throw error;
|
|
210
247
|
}
|
|
211
|
-
|
|
248
|
+
let sha256 = createHash('sha256').update(archive).digest('hex');
|
|
249
|
+
const recorded = recordedHashes.get(JSON.stringify([manifest.name, manifest.version]));
|
|
250
|
+
if (recorded && recorded !== sha256) {
|
|
251
|
+
const existingPath = join(input.outDir, manifest.name, manifest.version, `${recorded}.tgz`);
|
|
252
|
+
let sameContent = false;
|
|
253
|
+
if (existsSync(existingPath)) {
|
|
254
|
+
try {
|
|
255
|
+
const existing = readFileSync(existingPath);
|
|
256
|
+
sameContent = createHash('sha256').update(existing).digest('hex') === recorded &&
|
|
257
|
+
gunzipSync(existing).equals(gunzipSync(archive));
|
|
258
|
+
if (sameContent) {
|
|
259
|
+
archive = existing;
|
|
260
|
+
sha256 = recorded;
|
|
261
|
+
}
|
|
262
|
+
} catch { /* An unreadable archive cannot prove immutability. */ }
|
|
263
|
+
}
|
|
264
|
+
if (!sameContent) throw new Error(`version-immutable: ${manifest.name}@${manifest.version}`);
|
|
265
|
+
debug.log('market.publish', 'reused-artifact', { name: manifest.name, version: manifest.version,
|
|
266
|
+
reason: 'same-content-different-gzip' });
|
|
267
|
+
}
|
|
212
268
|
remember(manifest.name, manifest.version, sha256);
|
|
213
269
|
const key = `${manifest.name}/${manifest.version}/${sha256}.tgz`;
|
|
214
270
|
const source: MarketplaceIndex['plugins'][number]['source'] = input.source
|
|
@@ -218,7 +274,7 @@ export function publishMarket(input: {
|
|
|
218
274
|
policy: { installation: 'AVAILABLE', authentication: 'ON_USE' },
|
|
219
275
|
category: category ?? 'Productivity', version: manifest.version,
|
|
220
276
|
description: manifest.description, artifact: { sha256, bytes: archive.length, key },
|
|
221
|
-
'ai.elanous': { ...metadata, pricing: metadata.pricing ?? { model: 'free' } },
|
|
277
|
+
'ai.elanous': { ...metadata, connectors: normalized.value, pricing: metadata.pricing ?? { model: 'free' } },
|
|
222
278
|
});
|
|
223
279
|
archives.push({ key, data: archive, name: dir });
|
|
224
280
|
published.push({ name: manifest.name, version: manifest.version, sha256, bundled });
|
|
@@ -1142,8 +1142,8 @@ export const nativeToolCatalog: NativeToolCatalogEntry[] = [
|
|
|
1142
1142
|
kind: 'web',
|
|
1143
1143
|
aliases: ['WebSearch', 'web_search'],
|
|
1144
1144
|
displayName: 'WebSearch',
|
|
1145
|
-
description: 'Search the public web via a configured provider (Grok live-search / Firecrawl / MCP / custom).',
|
|
1146
|
-
promptSummary: '`WebSearch` (provider-backed web search — Grok live-search by default; pair with WebFetch for full bodies)',
|
|
1145
|
+
description: 'Search the public web via a configured provider (Grok live-search / Firecrawl / MCP / custom); when no key is available, fall back to keyless DuckDuckGo.',
|
|
1146
|
+
promptSummary: '`WebSearch` (provider-backed web search — Grok live-search by default; when no key is available, fall back to keyless DuckDuckGo; pair with WebFetch for full bodies)',
|
|
1147
1147
|
host: ['skill'],
|
|
1148
1148
|
safety: ['network', 'read-only'],
|
|
1149
1149
|
supportsParallel: true,
|