elanous 0.2.4 → 0.2.5
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 +3 -3
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/06ef2eba-d1b99ac6d2ea663c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1413-7179a49332db5a80.js +1 -0
- package/apps/pwa/out/_next/static/chunks/150-25c277137a9638ab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1640-bc99ec23bbe29d12.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2415.ed6428ff334c7ce2.js → 1751.50b96ea9c37b49b6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2300-de059d0ab2a16630.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2684-05b0a81e75298c16.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3026-2ed7e317d9b02102.js +25 -0
- package/apps/pwa/out/_next/static/chunks/3611-77496ada38cd6358.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3717-68957c4dc9ceec90.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3929-46ba9af6f9d895cc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4015-62c5a3744698a8da.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4217-3c4317a7905f9eeb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4240-3230f230fae2d7d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4271-e53e075ebd48593b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4377.3b125fef79576dfd.js +5 -0
- package/apps/pwa/out/_next/static/chunks/4440-18bb160d95819f4e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4853-35fdbe6bdbcdc0fc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2ea96958-e8c7e112de50b067.js → 4daa2798-1363d725c79f3593.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5007-6a3b30e23ff68a6e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5244-12b424c42070f5ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5791-bb82743fa6299562.js → 5348-bbf838e119661eee.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5391-8793f88b03b30161.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5611-df00af3e4e4a61bb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5890-dca2321773be829a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6123-37c9dc57ddaa7ab0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6588-b7975304a302ab91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6604-c03961962b6273b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7109-cf8c87e968e83a65.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7178-30f77877525a2efb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7262-871563fc9663730b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7294-f0eeebb92aa93459.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7429-881af44deed6958f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7527.943753b6ed829f09.js +1 -0
- package/apps/pwa/out/_next/static/chunks/764-a01e858525a190c0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7868-e35d5f7985ecd3f1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8320-51484af7b01fdfa5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 8617.465ca22a319c95b3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8918-482b30ce2a8ebf4c.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/9131-52f5fad51cceca04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9238.2cd5f47f4bf7a2c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 9704-3221c102623c8d73.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-70c404c7bb6d7f0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-3ffd80166b1050aa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f89e9a303515a033.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-4f403cd060442c95.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-9fa879ebf5959096.js → page-0ac5d053f0444d21.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-4bcc63e272ae8205.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-880734bd07d62882.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-4472ddf1e2458e69.js → page-72bd384898d88955.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-1018bbc9586ecae5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-de86f8c38076dd7b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-70c404c7bb6d7f0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-cd6447196d8bafe6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-e733630c47fa102b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-d1c1a8357b161875.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-60c514128e81d787.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8b637b7e325e13c1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-112f7e477bda77fd.js → page-bccce0babceae30f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-63301d11dbe73836.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-a8cf0dc472060db2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-02fba54881ccfa49.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-d5e8e90f0038f8de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-b1ef6824f494466a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-d589bb0f233b1009.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c9bbaebef4cda059.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-3639ce7916b92fed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7450ae7478955653.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-70c404c7bb6d7f0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-b41ca4abfd618452.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-b333dd79ebee3fa9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-8be1227492793655.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-6e03e9371b552e0d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-911411e9fca18d03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-792170e649b37088.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c40463466937bc91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-509c798b80384536.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-1cac2aa21d77c417.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-9626799d9b4a2cd1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-84cd419494e16b0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → f6f3850d-7f324c6566002113.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/framework-361487d2da603917.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-607f5beb94cb2e07.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-d0222d219e29dddf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-a26980a31126decc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-edb2b37decf23e4d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-e5bea36f183b25e6.js → webpack-2bf59c720fe9542c.js} +1 -1
- package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +1 -0
- package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → ktMqfrBbf8BMGac0jqLrb}/_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 +8 -1
- package/graphs/release/release-loop.yaml +6 -1
- package/graphs/video/README.md +1 -0
- package/graphs/video/explainer-line.yaml +58 -0
- package/package.json +3 -1
- package/plugins/job-coach/.codex-plugin/plugin.json +18 -1
- package/plugins/job-coach/README.md +12 -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/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/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/gate-node.ts +332 -44
- package/scripts/release-loop/publish-node.ts +7 -3
- package/scripts/release-loop/release-docs.ts +20 -13
- 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 +50 -0
- package/skills/explainer-video/SKILL.md +99 -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/vo.ts +37 -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/pr-manager.ts +51 -4
- 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/plugin-cli.ts +109 -11
- package/src/cli/pr-lineage.ts +2 -1
- package/src/cli/pwa-start.ts +3 -0
- package/src/cli/release-cli.ts +94 -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 +8 -2
- 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/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 +56 -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/graph-approvals.ts +125 -0
- package/src/nexus/api/http-server.ts +60 -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/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/prompt-library/universal-preamble.ts +8 -2
- package/src/release-loop/checklist.ts +191 -0
- package/src/release-loop/release-note.ts +6 -3
- 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/seams.ts +4 -2
- 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-command-job.ts +27 -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/tui-client/acp-transport-ws-client.ts +72 -5
- package/src/tui.ts +3 -2
- package/src/user-config.ts +28 -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/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/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/3403.4bc97b4066945a94.js +0 -5
- 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/8142-011e1f474efb8b33.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/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/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/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 → ktMqfrBbf8BMGac0jqLrb}/_ssgManifest.js +0 -0
|
@@ -3,25 +3,106 @@
|
|
|
3
3
|
// 🩸 2026-09-27: 멈추려면 사람이 PID 둘(harness ask 부모 · self orchestrate 자식)과 Job 이름을 손으로 찾아야 했고,
|
|
4
4
|
// 그 사이 떠 있던 조각이 main 에 병합됐다(#21048). 부모를 죽이면 자식이 PPID 1 고아로 2h36m 더 돌았다(🅞 보고).
|
|
5
5
|
// B 조각(#21171)이 오케스트레이터에 `self-dev-runs/<runId>/pid.json` ⊕ SIGTERM 정리(새 잡 멈춤 · 자기 Job 삭제 · cancelled)를 넣었다.
|
|
6
|
-
// 이
|
|
6
|
+
// 이 파일은 그 기록과 런 원장·프로세스 표를 읽고, 시작 시각이 확인된 후보에만 신호를 보내며, `elanous.run=<runId>` 라벨 Job 을 지운다.
|
|
7
7
|
//
|
|
8
8
|
// ⛔ 불변식: 기록된 시작 시각과 실제 프로세스 시작 시각이 다르면 신호를 보내지 않는다(PID 재사용 방지).
|
|
9
9
|
// ⛔ 불변식: `elanous.run=<runId>` 라벨이 붙은 Job 만 지운다.
|
|
10
10
|
|
|
11
|
-
import { readFileSync } from 'node:fs';
|
|
12
|
-
import { join } from 'node:path';
|
|
11
|
+
import { readFileSync, readlinkSync } from 'node:fs';
|
|
12
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
13
13
|
import { spawnSync } from 'node:child_process';
|
|
14
14
|
import { debug } from '../debug/log.js';
|
|
15
15
|
import { selfDevRunsDir } from '../self-dev/run-store.js';
|
|
16
|
+
import { loadRunLedger, runLedgerDir } from '../self-implement/run-ledger.js';
|
|
16
17
|
|
|
17
18
|
const RUN_ID = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
18
19
|
/** `pid.json` 의 `startedAt` 은 `Date.now() − uptime` 근삿값이다 — 이만큼의 차이는 같은 프로세스로 본다. */
|
|
19
20
|
export const START_TOLERANCE_MS = 5_000;
|
|
20
21
|
|
|
21
22
|
export interface PidRecord { pid: number; startedAt: number; argv0?: string }
|
|
23
|
+
export interface StopProcessCandidate { pid: number; startedAt: number; via: 'pid.json' | 'argv-runId' | 'argv-goal' }
|
|
24
|
+
export type StopProcessScan = { status: 'ok'; candidates: StopProcessCandidate[] } | { status: 'failed'; reason: string };
|
|
25
|
+
|
|
26
|
+
/** The start event, not later bookkeeping, owns the goal document path. */
|
|
27
|
+
export function stopGoalPathFromLedger(runId: string, dir = runLedgerDir()): string | null {
|
|
28
|
+
try {
|
|
29
|
+
const start = loadRunLedger(runId, dir)?.find((entry) => entry.event === 'start');
|
|
30
|
+
const goal = start?.data.goalFile;
|
|
31
|
+
const root = start?.data.targetRoot;
|
|
32
|
+
if (typeof goal !== 'string' || !goal.trim()) return null;
|
|
33
|
+
return isAbsolute(goal) ? resolve(goal) : typeof root === 'string' && isAbsolute(root) ? resolve(root, goal) : goal;
|
|
34
|
+
} catch { return null; }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Parse ps rows without treating a partial or failed measurement as an empty table. Relative goal arguments require the candidate's own cwd, never the stop caller's cwd. */
|
|
38
|
+
export function discoverStopProcesses(runId: string, goalPath: string | null, table: string, processCwd: (pid: number) => string | null = () => null): StopProcessScan {
|
|
39
|
+
const candidates: StopProcessCandidate[] = [];
|
|
40
|
+
for (const line of table.split(/\r?\n/)) {
|
|
41
|
+
if (!line.trim() || /^\s*PID\s/.test(line)) continue;
|
|
42
|
+
const match = /^\s*(\d+)\s+((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s+\w+\s+\d+\s+\d\d:\d\d:\d\d\s+\d{4})\s+(.+)$/.exec(line);
|
|
43
|
+
if (!match) return { status: 'failed', reason: 'unparseable process table row' };
|
|
44
|
+
const pid = Number(match[1]);
|
|
45
|
+
const startedAt = Date.parse(match[2]!);
|
|
46
|
+
if (!Number.isSafeInteger(pid) || !Number.isFinite(startedAt)) return { status: 'failed', reason: 'invalid process table row' };
|
|
47
|
+
if (pid <= 1) continue;
|
|
48
|
+
const args = match[3]!.match(/"[^"]*"|'[^']*'|\S+/g)?.map((part) => part.replace(/^(['"])(.*)\1$/, '$2')) ?? [];
|
|
49
|
+
const entry = args.findIndex((arg) => /(?:^|\/)elanous(?:\.mjs)?$/.test(arg));
|
|
50
|
+
const executable = args[0] ?? '';
|
|
51
|
+
const runtime = /(?:^|\/)(?:bun|node)$/.test(executable);
|
|
52
|
+
if (entry < 0 || (entry !== 0 && (!runtime || entry !== 1))) continue;
|
|
53
|
+
const command = args.slice(entry + 1);
|
|
54
|
+
const launcher = command[0] === 'harness' && ['ask', 'say'].includes(command[1] ?? '')
|
|
55
|
+
|| command[0] === 'dev' && command.some((arg) => arg === '--ask' || arg === '--say' || arg.startsWith('--ask=') || arg.startsWith('--say='))
|
|
56
|
+
|| command[0] === 'self' && command[1] === 'orchestrate';
|
|
57
|
+
if (!launcher) continue;
|
|
58
|
+
const namedRunIds = command.flatMap((arg, i) => arg === '--run-id' || arg === '--runId'
|
|
59
|
+
? [command[i + 1] ?? '']
|
|
60
|
+
: arg.startsWith('--run-id=') || arg.startsWith('--runId=') ? [arg.slice(arg.indexOf('=') + 1)] : []);
|
|
61
|
+
const runMatch = namedRunIds.includes(runId) || command.includes(runId);
|
|
62
|
+
// An explicitly different run takes precedence over a shared goal document.
|
|
63
|
+
if (namedRunIds.some((id) => id !== runId)) continue;
|
|
64
|
+
// A goal path is a positional or option value, not text embedded in a prompt.
|
|
65
|
+
const goalArgs = command.flatMap((arg, i) => {
|
|
66
|
+
if (arg === '--ask' || arg === '--say' || arg === '--goal-file' || arg === '--goal') return [command[i + 1] ?? ''];
|
|
67
|
+
if (arg.startsWith('--ask=') || arg.startsWith('--say=') || arg.startsWith('--goal-file=') || arg.startsWith('--goal=')) return [arg.slice(arg.indexOf('=') + 1)];
|
|
68
|
+
if (i > 0 && (command[i - 1] === '--ask' || command[i - 1] === '--say' || command[i - 1] === '--goal-file' || command[i - 1] === '--goal')) return [];
|
|
69
|
+
if (command[0] === 'harness' && i === 2 && (command[1] === 'ask' || command[1] === 'say')) return [arg];
|
|
70
|
+
if (command[0] === 'self' && command[1] === 'orchestrate' && i === 2) return [arg];
|
|
71
|
+
return [];
|
|
72
|
+
});
|
|
73
|
+
const goalMatch = goalPath !== null && goalArgs.some((arg) => {
|
|
74
|
+
if (isAbsolute(arg)) return isAbsolute(goalPath) && resolve(arg) === resolve(goalPath);
|
|
75
|
+
if (!isAbsolute(goalPath)) return false;
|
|
76
|
+
let cwd: string | null;
|
|
77
|
+
try { cwd = processCwd(pid); } catch { return false; }
|
|
78
|
+
return cwd !== null && isAbsolute(cwd) && resolve(cwd, arg) === resolve(goalPath);
|
|
79
|
+
});
|
|
80
|
+
if (runMatch || goalMatch) candidates.push({ pid, startedAt, via: runMatch ? 'argv-runId' : 'argv-goal' });
|
|
81
|
+
}
|
|
82
|
+
return { status: 'ok', candidates };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function stopProcessCwd(pid: number): string | null {
|
|
86
|
+
try {
|
|
87
|
+
if (process.platform === 'linux') return readlinkSync(`/proc/${pid}/cwd`);
|
|
88
|
+
const r = spawnSync('lsof', ['-a', '-p', String(pid), '-d', 'cwd', '-Fn'], { encoding: 'utf8', timeout: 5_000 });
|
|
89
|
+
if (r.error || r.status !== 0 || typeof r.stdout !== 'string') return null;
|
|
90
|
+
return r.stdout.split(/\r?\n/).find((line) => line.startsWith('n/'))?.slice(1) ?? null;
|
|
91
|
+
} catch { return null; }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function scanStopProcessTable(runId: string, goalPath: string | null): StopProcessScan {
|
|
95
|
+
let r: ReturnType<typeof spawnSync>;
|
|
96
|
+
try { r = spawnSync('ps', ['-eo', 'pid=,lstart=,args='], { encoding: 'utf8', timeout: 5_000, maxBuffer: 16 * 1024 * 1024 }); }
|
|
97
|
+
catch (error) { return { status: 'failed', reason: String(error) }; }
|
|
98
|
+
if (r.error || r.status !== 0 || typeof r.stdout !== 'string') return { status: 'failed', reason: String(r.error ?? r.stderr ?? `ps status ${r.status}`) };
|
|
99
|
+
return discoverStopProcesses(runId, goalPath, r.stdout, stopProcessCwd);
|
|
100
|
+
}
|
|
22
101
|
|
|
23
102
|
export interface HarnessStopDeps {
|
|
24
103
|
readPidRecord: (runId: string) => PidRecord | null;
|
|
104
|
+
readGoalPath: (runId: string) => string | null;
|
|
105
|
+
scanProcesses: (runId: string, goalPath: string | null) => StopProcessScan;
|
|
25
106
|
/** 살아 있는 프로세스의 시작 시각(ms) · 없으면 null. */
|
|
26
107
|
processStartMs: (pid: number) => number | null;
|
|
27
108
|
kill: (pid: number, signal: NodeJS.Signals) => void;
|
|
@@ -34,8 +115,12 @@ export interface HarnessStopDeps {
|
|
|
34
115
|
|
|
35
116
|
export interface HarnessStopResult {
|
|
36
117
|
runId: string;
|
|
37
|
-
process: 'stopped' | 'killed' | 'absent' | 'owner-mismatch';
|
|
118
|
+
process: 'stopped' | 'killed' | 'absent' | 'owner-mismatch' | 'unmeasured' | 'dry-run';
|
|
38
119
|
pid?: number;
|
|
120
|
+
candidates: StopProcessCandidate[];
|
|
121
|
+
scanned: boolean;
|
|
122
|
+
pidRecordFound: boolean;
|
|
123
|
+
dryRun: boolean;
|
|
39
124
|
jobsDeleted: number;
|
|
40
125
|
contexts: string[];
|
|
41
126
|
}
|
|
@@ -69,6 +154,8 @@ export function defaultHarnessStopDeps(overrides: Partial<HarnessStopDeps> = {})
|
|
|
69
154
|
};
|
|
70
155
|
return {
|
|
71
156
|
readPidRecord: (runId) => readPidRecordFrom(selfDevRunsDir(), runId),
|
|
157
|
+
readGoalPath: stopGoalPathFromLedger,
|
|
158
|
+
scanProcesses: scanStopProcessTable,
|
|
72
159
|
processStartMs: psProcessStartMs,
|
|
73
160
|
kill: (pid, signal) => { process.kill(pid, signal); },
|
|
74
161
|
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
@@ -83,49 +170,91 @@ export function defaultHarnessStopDeps(overrides: Partial<HarnessStopDeps> = {})
|
|
|
83
170
|
};
|
|
84
171
|
}
|
|
85
172
|
|
|
86
|
-
export async function stopHarnessRun(runId: string, deps: HarnessStopDeps, contexts?: readonly string[]): Promise<HarnessStopResult> {
|
|
173
|
+
export async function stopHarnessRun(runId: string, deps: HarnessStopDeps, contexts?: readonly string[], dryRun = false): Promise<HarnessStopResult> {
|
|
87
174
|
if (!RUN_ID.test(runId)) throw new Error(`invalid run id: ${runId}`);
|
|
88
175
|
// ── ⓐ 프로세스 ────────────────────────────────────────────
|
|
89
176
|
let processState: HarnessStopResult['process'] = 'absent';
|
|
90
177
|
const record = deps.readPidRecord(runId);
|
|
178
|
+
let scan: StopProcessScan;
|
|
179
|
+
let goalPath: string | null = null;
|
|
180
|
+
try { goalPath = deps.readGoalPath(runId); }
|
|
181
|
+
catch { /* 원장 판독 실패 — runId 단독 대조 */ }
|
|
182
|
+
try { scan = deps.scanProcesses(runId, goalPath); }
|
|
183
|
+
catch (error) { scan = { status: 'failed', reason: String(error) }; }
|
|
184
|
+
const candidates: StopProcessCandidate[] = [];
|
|
185
|
+
const seen = new Set<number>();
|
|
186
|
+
const add = (candidate: StopProcessCandidate) => {
|
|
187
|
+
if (!seen.has(candidate.pid)) { seen.add(candidate.pid); candidates.push(candidate); }
|
|
188
|
+
};
|
|
189
|
+
let recordMismatch = false;
|
|
91
190
|
if (record) {
|
|
92
|
-
const
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
else {
|
|
96
|
-
|
|
191
|
+
const recordedStart = deps.processStartMs(record.pid);
|
|
192
|
+
if (recordedStart !== null && Math.abs(recordedStart - record.startedAt) <= START_TOLERANCE_MS) {
|
|
193
|
+
add({ pid: record.pid, startedAt: recordedStart, via: 'pid.json' });
|
|
194
|
+
} else if (recordedStart !== null) { processState = 'owner-mismatch'; recordMismatch = true; }
|
|
195
|
+
}
|
|
196
|
+
if (scan.status === 'ok') {
|
|
197
|
+
for (const candidate of scan.candidates) {
|
|
198
|
+
if (candidate.pid !== record?.pid || !recordMismatch) add(candidate);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
debug.log('harness.stop', 'process-candidates', { runId, found: candidates.map(({ via, pid }) => ({ via, pid })), scanned: scan.status === 'ok' });
|
|
202
|
+
if (dryRun) processState = 'dry-run';
|
|
203
|
+
else {
|
|
204
|
+
const verified: StopProcessCandidate[] = [];
|
|
205
|
+
for (const candidate of candidates) {
|
|
206
|
+
if (candidate.via === 'pid.json') { verified.push(candidate); continue; }
|
|
207
|
+
const actual = deps.processStartMs(candidate.pid);
|
|
208
|
+
if (actual === null) continue;
|
|
209
|
+
if (Math.abs(actual - candidate.startedAt) > START_TOLERANCE_MS) { processState = 'owner-mismatch'; continue; }
|
|
210
|
+
verified.push(candidate);
|
|
211
|
+
}
|
|
212
|
+
for (const candidate of verified) {
|
|
213
|
+
try { deps.kill(candidate.pid, 'SIGTERM'); } catch { /* 방금 끝났다 */ }
|
|
214
|
+
}
|
|
215
|
+
for (const candidate of verified) {
|
|
97
216
|
processState = 'stopped';
|
|
98
217
|
const step = 250;
|
|
99
218
|
for (let waited = 0; waited < deps.graceMs; waited += step) {
|
|
100
219
|
await deps.sleep(step);
|
|
101
|
-
if (deps.processStartMs(
|
|
220
|
+
if (deps.processStartMs(candidate.pid) === null) break;
|
|
102
221
|
}
|
|
103
|
-
const still = deps.processStartMs(
|
|
104
|
-
|
|
105
|
-
|
|
222
|
+
const still = deps.processStartMs(candidate.pid);
|
|
223
|
+
const expectedStart = candidate.via === 'pid.json' ? record!.startedAt : candidate.startedAt;
|
|
224
|
+
if (still !== null && Math.abs(still - expectedStart) <= START_TOLERANCE_MS) {
|
|
225
|
+
try { deps.kill(candidate.pid, 'SIGKILL'); processState = 'killed'; } catch { /* 끝났다 */ }
|
|
106
226
|
}
|
|
107
227
|
}
|
|
228
|
+
if (processState === 'absent' && (scan.status === 'failed' || candidates.length > 0)) processState = 'unmeasured';
|
|
108
229
|
}
|
|
109
230
|
// ── ⓑ Pod Job (라벨로만) ───────────────────────────────────
|
|
110
|
-
const targets = contexts && contexts.length > 0 ? [...contexts] : deps.kubeContexts();
|
|
231
|
+
const targets = dryRun ? [] : contexts && contexts.length > 0 ? [...contexts] : deps.kubeContexts();
|
|
111
232
|
let jobsDeleted = 0;
|
|
112
233
|
for (const context of targets) {
|
|
113
234
|
const r = deps.kubectl(['--context', context, '-n', deps.namespace, 'delete', 'job', '-l', `elanous.run=${runId}`, '--wait=false']);
|
|
114
235
|
if (r.status === 0) jobsDeleted += r.stdout.split('\n').filter((line) => line.startsWith('job.batch/')).length;
|
|
115
236
|
else debug.log('harness.stop', 'job-delete-failed', { runId, context, error: r.stderr.slice(0, 200) }, { level: 'warn' });
|
|
116
237
|
}
|
|
117
|
-
const result: HarnessStopResult = { runId, process: processState, ...(record ? { pid: record.pid } : {}), jobsDeleted, contexts: targets };
|
|
238
|
+
const result: HarnessStopResult = { runId, process: processState, ...(record ? { pid: record.pid } : {}), candidates, scanned: scan.status === 'ok', pidRecordFound: record !== null, dryRun, jobsDeleted, contexts: targets };
|
|
118
239
|
debug.log('harness.stop', 'stopped', { runId, process: processState, jobsDeleted, contexts: targets.length });
|
|
119
240
|
return result;
|
|
120
241
|
}
|
|
121
242
|
|
|
122
243
|
export function formatHarnessStop(r: HarnessStopResult): string {
|
|
123
244
|
const label: Record<HarnessStopResult['process'], string> = {
|
|
124
|
-
stopped: '멈춤(SIGTERM)', killed: '멈춤(SIGKILL — 제한 시간 안에 안 끝났다)',
|
|
245
|
+
stopped: '멈춤(SIGTERM)', killed: '멈춤(SIGKILL — 제한 시간 안에 안 끝났다)',
|
|
246
|
+
absent: `못 찾음(pid.json ${r.pidRecordFound ? '확인' : '없음'} · 프로세스 표에서 runId·골 경로 0)`,
|
|
247
|
+
unmeasured: '못 잼(프로세스 표 또는 후보 시작 시각 확인 실패 — 없음이 아님)',
|
|
248
|
+
'dry-run': 'dry-run(신호·Pod Job 삭제 없음)',
|
|
249
|
+
'owner-mismatch': '소유 불일치(건드리지 않음 — PID 가 다른 프로세스로 재사용됐다)',
|
|
125
250
|
};
|
|
126
251
|
return [
|
|
127
252
|
`harness stop ${r.runId}`,
|
|
128
253
|
` 프로세스: ${label[r.process]}${r.pid ? ` · pid ${r.pid}` : ''}`,
|
|
129
|
-
|
|
254
|
+
...(!r.scanned && r.process !== 'unmeasured' ? [' 프로세스 표: 못 잼(기록 밖 발사 프로세스는 확인하지 못함)'] : []),
|
|
255
|
+
...r.candidates.map(({ pid, startedAt, via }) => ` 후보: pid ${pid} · 시작 ${new Date(startedAt).toISOString()} · ${via}`),
|
|
256
|
+
r.dryRun
|
|
257
|
+
? ` Pod Job: dry-run(조회·삭제하지 않음 · 라벨 elanous.run=${r.runId})`
|
|
258
|
+
: ` Pod Job: ${r.jobsDeleted}개 지움 (문맥 ${r.contexts.length}개 · 라벨 elanous.run=${r.runId})`,
|
|
130
259
|
].join('\n');
|
|
131
260
|
}
|
|
@@ -16,7 +16,7 @@ export interface SelfSendCandidateDisplay {
|
|
|
16
16
|
readonly hiddenStaleCount: number;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const SELF_SEND_GOAL_ATTEMPT_SUFFIX = /^(.*)-[0-9a-f]{8}
|
|
19
|
+
const SELF_SEND_GOAL_ATTEMPT_SUFFIX = /^(.*)-[0-9a-f]{8}(?:-r[a-z0-9]{6})?$/;
|
|
20
20
|
|
|
21
21
|
function selfSendGoalPrefix(spaceId: string): string | undefined {
|
|
22
22
|
return SELF_SEND_GOAL_ATTEMPT_SUFFIX.exec(spaceId)?.[1];
|
|
@@ -104,6 +104,7 @@ export interface SelfSendDecisionInput {
|
|
|
104
104
|
|
|
105
105
|
export interface SelfSendDecisionDeps {
|
|
106
106
|
resolveRunScreen(runId: string): SelfSendRunScreen;
|
|
107
|
+
podFragmentsForRun(runId: string): readonly { spaceId: string }[];
|
|
107
108
|
resolveTarget(space: string): SelfSendTargetResolution;
|
|
108
109
|
isPodFragment(spaceId: string): boolean;
|
|
109
110
|
screens(): readonly SelfSendCandidate[];
|
|
@@ -122,26 +123,34 @@ export function decideSelfSend(input: SelfSendDecisionInput, deps: SelfSendDecis
|
|
|
122
123
|
if (space !== undefined && opts.run !== undefined) return refuse('--run 과 space 는 함께 사용할 수 없습니다.\n', 2);
|
|
123
124
|
if (opts.run !== undefined && opts.run.trim() === '') return refuse('--run 에 빈 runId 를 줄 수 없습니다.\n', 2);
|
|
124
125
|
let requestedSpace = space;
|
|
126
|
+
let podRunSpace: string | undefined;
|
|
125
127
|
if (opts.run !== undefined) {
|
|
126
128
|
const resolved = deps.resolveRunScreen(opts.run);
|
|
127
|
-
if (resolved.logStoreStatus !== 'read') {
|
|
129
|
+
if (resolved.logStoreStatus !== 'read' && resolved.screenKey) {
|
|
128
130
|
return refuse(`run 화면 해석 불가: 로그 스토어 ${resolved.logStoreStatus} (${resolved.logStorePath})\n`, 1);
|
|
129
131
|
}
|
|
130
132
|
if (!resolved.screenKey) {
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
133
|
+
const fragments = deps.podFragmentsForRun(opts.run);
|
|
134
|
+
if (fragments.length === 1) requestedSpace = podRunSpace = fragments[0]!.spaceId;
|
|
135
|
+
else {
|
|
136
|
+
if (fragments.length > 1) return refuse(`run 의 Pod 조각이 여러 개입니다: ${opts.run}. space 이름으로 다시 보내세요:\n${fragments.map((fragment) => ` ${fragment.spaceId}`).join('\n')}\n`, 2);
|
|
137
|
+
if (resolved.logStoreStatus !== 'read') {
|
|
138
|
+
return refuse(`run 화면 해석 불가: 로그 스토어 ${resolved.logStoreStatus} (${resolved.logStorePath}) · Pod 조각 기록도 0\n`, 1);
|
|
139
|
+
}
|
|
140
|
+
const last = resolved.lastEvent;
|
|
141
|
+
const lastDetail = last
|
|
142
|
+
? ` 마지막 이벤트: ${last.category}/${last.event} (${Number.isFinite(Date.parse(last.timestamp)) ? `${Math.max(0, Math.floor((now - Date.parse(last.timestamp)) / 60_000))}분 전` : '시각 알 수 없음'})`
|
|
143
|
+
: ' 마지막 이벤트: 없음';
|
|
144
|
+
const status = resolved.missingStatus;
|
|
145
|
+
const guidance = status === 'awaiting-start'
|
|
146
|
+
? '아직 화면을 띄우기 전입니다. 되묻기에 답하거나 저작이 끝날 때까지 기다리세요.'
|
|
147
|
+
: status === 'pipeline-failed' ? '파이프라인이 오류로 멈췄습니다. 해당 error를 읽어 원인을 수리하세요.'
|
|
148
|
+
: status === 'cleaned' ? '하니스가 정리되어 화면이 없습니다. 필요하면 새 런을 시작하세요.'
|
|
149
|
+
: status === 'not-found' ? '이 runId의 이벤트가 없습니다. runId와 인스턴스 우주를 확인하세요.'
|
|
150
|
+
: '화면을 아직 분류할 수 없습니다. 마지막 이벤트를 조사하세요.';
|
|
151
|
+
return refuse(`run 화면 해석 불가: ${guidance}${lastDetail} (${opts.run}) · Pod 조각 기록도 0\n`, 1);
|
|
152
|
+
}
|
|
153
|
+
} else requestedSpace = normalizeSpaceId(resolved.screenKey);
|
|
145
154
|
}
|
|
146
155
|
const hasMemo = opts.memo !== undefined;
|
|
147
156
|
if (opts.stop && hasMemo) return refuse('self send에서는 --stop 과 --memo를 함께 사용할 수 없습니다.\n', 2);
|
|
@@ -158,7 +167,7 @@ export function decideSelfSend(input: SelfSendDecisionInput, deps: SelfSendDecis
|
|
|
158
167
|
}
|
|
159
168
|
requestedSpace = resolution.spaceId;
|
|
160
169
|
}
|
|
161
|
-
if (requestedSpace !== undefined && deps.isPodFragment(requestedSpace)) {
|
|
170
|
+
if (requestedSpace !== undefined && (podRunSpace !== undefined || deps.isPodFragment(requestedSpace))) {
|
|
162
171
|
return { kind: 'send', spaceId: requestedSpace, channel: 'pod', warnings: [] };
|
|
163
172
|
}
|
|
164
173
|
const screens = deps.screens();
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { dirname } from 'node:path';
|
|
4
|
+
import { normalizeRunId } from './harness-space.js';
|
|
4
5
|
import { controlInboxPath, type ControlMemoPayload } from './control-inbox.js';
|
|
5
6
|
import { sendPodControl } from './pod-control-send.js';
|
|
6
7
|
import type { Kubectl } from '../task-orchestrator/surfaces/self-implement-pod.js';
|
|
7
8
|
|
|
8
9
|
export interface PodFragmentRecord {
|
|
9
10
|
spaceId: string;
|
|
11
|
+
runId?: string;
|
|
12
|
+
parentRunId?: string;
|
|
10
13
|
context: string;
|
|
11
14
|
namespace: string;
|
|
12
15
|
job: string;
|
|
@@ -29,6 +32,24 @@ export function podFragmentFinished(spaceId: string, env?: NodeJS.ProcessEnv): b
|
|
|
29
32
|
return existsSync(`${podRecordPath(spaceId, env)}.finished`);
|
|
30
33
|
}
|
|
31
34
|
|
|
35
|
+
export function listPodFragmentsForRun(runId: string, env?: NodeJS.ProcessEnv): PodFragmentRecord[] {
|
|
36
|
+
if (!runId || runId !== normalizeRunId(runId)) return [];
|
|
37
|
+
const dir = dirname(podRecordPath('fragment', env));
|
|
38
|
+
let names: string[];
|
|
39
|
+
try { names = readdirSync(dir); } catch { return []; }
|
|
40
|
+
const suffix = '.inbox.pod.json';
|
|
41
|
+
const matches: PodFragmentRecord[] = [];
|
|
42
|
+
for (const name of names) {
|
|
43
|
+
if (!name.endsWith(suffix)) continue;
|
|
44
|
+
const spaceId = name.slice(0, -suffix.length);
|
|
45
|
+
if (podFragmentFinished(spaceId, env)) continue;
|
|
46
|
+
const record = readPodFragment(spaceId, env);
|
|
47
|
+
if (record && ((record.runId === runId && normalizeRunId(record.runId) === runId)
|
|
48
|
+
|| (record.parentRunId === runId && normalizeRunId(record.parentRunId) === runId))) matches.push(record);
|
|
49
|
+
}
|
|
50
|
+
return matches;
|
|
51
|
+
}
|
|
52
|
+
|
|
32
53
|
export function writePodFragment(record: PodFragmentRecord, env?: NodeJS.ProcessEnv): void {
|
|
33
54
|
const path = podRecordPath(record.spaceId, env);
|
|
34
55
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -22,11 +22,12 @@ export function slugifyFeature(feature: string): string {
|
|
|
22
22
|
/** 하니스가 만드는 `self-impl/<슬러그>-<8자리 16진수>` 브랜치 이름.
|
|
23
23
|
* goalId 가 있으면 읽는 자(`branchGoalId`, `/(?:^|-)goalid-([^/-]+)-/`)와 호환되는
|
|
24
24
|
* `goalid-<id>-` 구간을 슬러그 앞에 싣는다. 없으면 기존과 바이트 단위로 같다. */
|
|
25
|
-
export function plannedSelfImplBranch(feature: string, goalId?: string): string {
|
|
25
|
+
export function plannedSelfImplBranch(feature: string, goalId?: string, runId?: string): string {
|
|
26
26
|
const slug = slugifyFeature(feature);
|
|
27
27
|
const id = goalId?.trim() ?? '';
|
|
28
|
-
|
|
28
|
+
const runSuffix = runId === undefined ? '' : `-r${runId.replace(/^run-/i, '').replace(/[^a-z0-9]/gi, '').slice(0, 6).toLowerCase()}`;
|
|
29
|
+
if (!id) return `${WORKTREE_BRANCH_PREFIX}${slug}${runSuffix}`;
|
|
29
30
|
const readable = slug.replace(/-[0-9a-f]{8}$/, '');
|
|
30
31
|
const digest = createHash('sha256').update(id).digest('hex').slice(0, 8);
|
|
31
|
-
return `${WORKTREE_BRANCH_PREFIX}goalid-${id}-${readable}-${digest}`;
|
|
32
|
+
return `${WORKTREE_BRANCH_PREFIX}goalid-${id}-${readable}-${digest}${runSuffix}`;
|
|
32
33
|
}
|
package/src/index.ts
CHANGED
|
@@ -115,7 +115,7 @@ import { DEFAULT_REVIEW_BACKEND } from './agent-substrate/acp-reviewer.js';
|
|
|
115
115
|
import type { ReviewResult } from './agent-substrate/pr-reviewer.js';
|
|
116
116
|
import type { ReviewVerdict } from './agent-mission/review-loop.js';
|
|
117
117
|
import type { SyncMode } from './types.js';
|
|
118
|
-
import { runOnboarding, runOnboardingStep, runOnboardingNonInteractive, needsOnboarding, type OnboardingStepId } from './onboarding.js';
|
|
118
|
+
import { runOnboarding, runOnboardingStep, runOnboardingNonInteractive, needsOnboarding, handleOnboardingRefusal, type OnboardingStepId } from './onboarding.js';
|
|
119
119
|
import {
|
|
120
120
|
getUserConfig, reloadUserConfig, userConfigPath, saveUserConfig,
|
|
121
121
|
backupUserConfig, backupConfigPath, addRotationEntry, type RotationEntry,
|
|
@@ -2088,13 +2088,14 @@ harnessCmd
|
|
|
2088
2088
|
});
|
|
2089
2089
|
harnessCmd
|
|
2090
2090
|
.command('stop <runId>')
|
|
2091
|
-
.description('런 하나를 멈춘다 —
|
|
2091
|
+
.description('런 하나를 멈춘다 — pid.json 시작 시각 대조 ⊕ 런 발사 프로세스 표 조회 ⊕ 라벨 elanous.run=<runId> Pod Job')
|
|
2092
2092
|
.option('--context <ctx...>', 'kube 문맥(여러 번 · 기본 = 알려진 문맥 전부)')
|
|
2093
2093
|
.option('--json', 'JSON 출력')
|
|
2094
|
-
.
|
|
2094
|
+
.option('--dry-run', '프로세스 후보만 보고 신호·Pod Job 삭제는 하지 않는다')
|
|
2095
|
+
.action(async (runId: string, opts: { context?: string[]; json?: boolean; dryRun?: boolean }) => {
|
|
2095
2096
|
const { stopHarnessRun, defaultHarnessStopDeps, formatHarnessStop } = await import('./harness/harness-stop.js');
|
|
2096
2097
|
try {
|
|
2097
|
-
const result = await stopHarnessRun(runId, defaultHarnessStopDeps(), opts.context);
|
|
2098
|
+
const result = await stopHarnessRun(runId, defaultHarnessStopDeps(), opts.context, opts.dryRun === true);
|
|
2098
2099
|
process.stdout.write(`${opts.json ? JSON.stringify(result) : formatHarnessStop(result)}\n`);
|
|
2099
2100
|
} catch (error) {
|
|
2100
2101
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
@@ -4238,8 +4239,9 @@ const selfOrchestrateCmd = selfCmd
|
|
|
4238
4239
|
if (!opts.json) ui.info(`[pod] 이미지 다시 굽기 — ${image.reason} (~1분)`);
|
|
4239
4240
|
const { spawnSync } = await import('node:child_process');
|
|
4240
4241
|
const top = spawnSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8' }).stdout.trim() || process.cwd();
|
|
4241
|
-
|
|
4242
|
-
|
|
4242
|
+
// bun 1.4 applies the 1 MiB default maxBuffer: a docker build log past it kills the child (ENOBUFS · status null).
|
|
4243
|
+
const b = spawnSync('bash', [`${top}/docker/harness/build.sh`], { encoding: 'utf8', timeout: 900_000, maxBuffer: 64 * 1024 * 1024 });
|
|
4244
|
+
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
4245
|
image = podImageFreshness();
|
|
4244
4246
|
}
|
|
4245
4247
|
}
|
|
@@ -4410,7 +4412,7 @@ const selfOrchestrateCmd = selfCmd
|
|
|
4410
4412
|
...results.map((r) => {
|
|
4411
4413
|
const icon = r.status === 'done' ? '✅' : r.status === 'cancelled' ? '⛔' : '❌';
|
|
4412
4414
|
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}` : ''}`;
|
|
4415
|
+
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
4416
|
}),
|
|
4415
4417
|
].join('\n'));
|
|
4416
4418
|
await reportRun(results);
|
|
@@ -4608,7 +4610,7 @@ selfCmd
|
|
|
4608
4610
|
.action(async (space: string | undefined, opts: { stop?: boolean; memo?: string; run?: string; includeStale?: boolean; readWait?: string }) => {
|
|
4609
4611
|
const { classifyRunScreenMissing, queryRunScreenKey, listRunLedgers } = await import('./self-implement/run-ledger.js');
|
|
4610
4612
|
const { getPtyManifest, listPtyManifestRows } = await import('./pty-shell/pty-manifest.js');
|
|
4611
|
-
const { readPodFragment, podFragmentFinished, dispatchPodSelfSend } = await import('./harness/self-send-target.js');
|
|
4613
|
+
const { readPodFragment, podFragmentFinished, listPodFragmentsForRun, dispatchPodSelfSend } = await import('./harness/self-send-target.js');
|
|
4612
4614
|
const { listHarnessScreens, readHarnessHeartbeat } = await import('./harness/harness-screen.js');
|
|
4613
4615
|
const decision = decideSelfSend({ space, opts, now: Date.now() }, {
|
|
4614
4616
|
resolveRunScreen: (runId) => {
|
|
@@ -4616,6 +4618,7 @@ selfCmd
|
|
|
4616
4618
|
return { ...resolved, missingStatus: resolved.screenKey ? undefined : classifyRunScreenMissing(resolved.lastEvent) };
|
|
4617
4619
|
},
|
|
4618
4620
|
resolveTarget: (target) => resolveSelfSendTarget(target, { getPtyManifest, listPtyManifestRows }),
|
|
4621
|
+
podFragmentsForRun: (runId) => listPodFragmentsForRun(runId),
|
|
4619
4622
|
isPodFragment: (spaceId) => Boolean(readPodFragment(spaceId) || podFragmentFinished(spaceId)),
|
|
4620
4623
|
screens: () => listHarnessScreens().map((screen): SelfSendCandidate => {
|
|
4621
4624
|
const heartbeat = readHarnessHeartbeat(screen.spaceId);
|
|
@@ -5426,6 +5429,30 @@ program.command('ad [input...]')
|
|
|
5426
5429
|
});
|
|
5427
5430
|
|
|
5428
5431
|
registerDocsCommands(program);
|
|
5432
|
+
// Private local decision ledger follows the same JS-only dispatch path as directives.
|
|
5433
|
+
if (existsSync(resolve(import.meta.dir, 'cli/decisions-cli.ts')) || existsSync(resolve(import.meta.dir, 'cli/decisions-cli.js'))) {
|
|
5434
|
+
program.command('decisions').description('대표 결정 로컬 원장').allowUnknownOption().allowExcessArguments(true)
|
|
5435
|
+
.action(async (_opts: unknown, command: Command) => {
|
|
5436
|
+
try { await (await import('./domains/standalone-log-sink.js')).registerStandaloneLogSink('decisions'); }
|
|
5437
|
+
catch { /* Logging must not prevent recording a decision. */ }
|
|
5438
|
+
const privateDecisionsModule: string = './cli/decisions-cli.js';
|
|
5439
|
+
const { registerDecisionsCommands } = await import(privateDecisionsModule) as { registerDecisionsCommands: (program: Command) => void };
|
|
5440
|
+
const privateProgram = new Command();
|
|
5441
|
+
registerDecisionsCommands(privateProgram);
|
|
5442
|
+
await privateProgram.parseAsync(['decisions', ...command.args], { from: 'user' });
|
|
5443
|
+
});
|
|
5444
|
+
}
|
|
5445
|
+
// Private command is absent from the public export; JS-only distributions still register it.
|
|
5446
|
+
if (existsSync(resolve(import.meta.dir, 'cli/directives-cli.ts')) || existsSync(resolve(import.meta.dir, 'cli/directives-cli.js'))) {
|
|
5447
|
+
program.command('directives').description('대표 지시 로컬 색인·검색').allowUnknownOption().allowExcessArguments(true)
|
|
5448
|
+
.action(async (_opts: unknown, command: Command) => {
|
|
5449
|
+
const privateDirectivesModule: string = './cli/directives-cli.js';
|
|
5450
|
+
const { registerDirectivesCommands } = await import(privateDirectivesModule) as { registerDirectivesCommands: (program: Command) => void };
|
|
5451
|
+
const privateProgram = new Command();
|
|
5452
|
+
registerDirectivesCommands(privateProgram);
|
|
5453
|
+
await privateProgram.parseAsync(['directives', ...command.args], { from: 'user' });
|
|
5454
|
+
});
|
|
5455
|
+
}
|
|
5429
5456
|
registerFleetCommands(program);
|
|
5430
5457
|
|
|
5431
5458
|
// ── ops (운영 관측 — 지금 뭐 도나·이상 없나·상태 전이) ──
|
|
@@ -5592,6 +5619,7 @@ agentCmd
|
|
|
5592
5619
|
.option('--backend <id>', `외부 에이전트 backend (${agentBackendHelpList}). 각 CLI auto-approve 모드로 PTY 구동. 미등록 지정 시 명시 에러(조용한 codex 폴백 없음·애그노스틱)`)
|
|
5593
5620
|
.option('--chain <backends>', '한 미션의 backend 사슬 (예: codex,claude,elanous)')
|
|
5594
5621
|
.option('--plugin <name@market>', '에이전트 화면 안에서 플러그인을 설치하고 그 스킬로 미션 실행 (codex/claude)')
|
|
5622
|
+
.addOption(new Option('--resources <mode>', '미션 전 자원 판단 사다리 (on|off, 기본 on)').choices(['on', 'off']).default('on'))
|
|
5595
5623
|
.option('--headless', 'Claude 구독 CLI를 비대화형 stream-json 모드로 실행 (claude backend 전용)')
|
|
5596
5624
|
.action(async (textParts: string[], opts: Record<string, any>) => {
|
|
5597
5625
|
// 구독 모드 보장 — 브레인(streamLLM)·codex 둘 다 ChatGPT 구독으로 (API 과금 회피).
|
|
@@ -7815,6 +7843,16 @@ sessionCmd
|
|
|
7815
7843
|
console.log(res.formatted ?? '(no context)');
|
|
7816
7844
|
});
|
|
7817
7845
|
|
|
7846
|
+
async function runOnboardingForCli(): Promise<boolean> {
|
|
7847
|
+
try {
|
|
7848
|
+
await runOnboarding();
|
|
7849
|
+
return true;
|
|
7850
|
+
} catch (error) {
|
|
7851
|
+
if (handleOnboardingRefusal(error, 'other')) return false;
|
|
7852
|
+
throw error;
|
|
7853
|
+
}
|
|
7854
|
+
}
|
|
7855
|
+
|
|
7818
7856
|
function announceChatToolsCompatibility(): void {
|
|
7819
7857
|
process.stderr.write('`chat --tools` is a compatibility entrypoint; use `elanous agent` for tool-loop calls.\n');
|
|
7820
7858
|
debug.log('chat.tools-compatibility', 'invoked', { toolLoopEnabled: true });
|
|
@@ -7841,7 +7879,7 @@ program
|
|
|
7841
7879
|
const cfg = getUserConfig();
|
|
7842
7880
|
if (needsOnboarding(cfg)) {
|
|
7843
7881
|
ui.info('No config yet — launching setup wizard first.');
|
|
7844
|
-
await
|
|
7882
|
+
if (!await runOnboardingForCli()) return;
|
|
7845
7883
|
}
|
|
7846
7884
|
const refreshed = reloadUserConfig();
|
|
7847
7885
|
// ★ 관측갭 수리(2026-07-21·제1원칙·트랙A) — self-implement 자식 goal-loop(`chat --goal-loop`)은 데몬과
|
|
@@ -7885,7 +7923,7 @@ program
|
|
|
7885
7923
|
.option('--new', 'Force a new session at boot instead of resuming the active one')
|
|
7886
7924
|
.option('--session <id>', 'Resume an explicit session (id or unique prefix)')
|
|
7887
7925
|
.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
|
|
7926
|
+
.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
7927
|
.option('--exit-after-scenario', 'Exit after the scenario / replay completes (default true when stdin is not a TTY)')
|
|
7890
7928
|
.option('--no-exit-after-scenario', 'Stay in the interactive prompt after the scenario / replay completes (TTY default)')
|
|
7891
7929
|
.option('--json', 'Emit one JSON line per turn (sessionId/provider/model/reply/...) instead of streaming text')
|
|
@@ -7908,7 +7946,7 @@ program
|
|
|
7908
7946
|
const cfg = getUserConfig();
|
|
7909
7947
|
if (needsOnboarding(cfg)) {
|
|
7910
7948
|
ui.info('No config yet — launching setup wizard first.');
|
|
7911
|
-
await
|
|
7949
|
+
if (!await runOnboardingForCli()) return;
|
|
7912
7950
|
}
|
|
7913
7951
|
const refreshed = reloadUserConfig();
|
|
7914
7952
|
const { runRepl } = await import('./repl/index.js');
|
|
@@ -8471,7 +8509,7 @@ program
|
|
|
8471
8509
|
'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
8510
|
)
|
|
8473
8511
|
.option('--socket <path>', 'Override the unix socket path (default: ~/.elanous/elanous.sock)')
|
|
8474
|
-
.option('--host <hostport>', 'Remote daemon host:port (e.g.
|
|
8512
|
+
.option('--host <hostport>', 'Remote daemon host:port (e.g. laptop.tailnet:31415). Coerced to ws://<hostport>/v1/acp.')
|
|
8475
8513
|
.option('--url <wsurl>', 'Remote daemon WS URL (e.g. ws://host:31415/v1/acp). Overrides --host.')
|
|
8476
8514
|
.option('-r, --remote [name]', 'Bookmark name (`-r` alone = default). Fills host/token-file; explicit --url/--host/--token/--token-file win.')
|
|
8477
8515
|
.option('--token <token>', 'Bearer token for remote auth. Overrides --token-file and ELANOUS_TOKEN.')
|
|
@@ -8945,6 +8983,7 @@ const nexusRunCmd = nexusCmd
|
|
|
8945
8983
|
.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
8984
|
.option('--no-watch', 'Disable the static-mode fs.watch loop (canonical entry default-on).')
|
|
8947
8985
|
.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.')
|
|
8986
|
+
.option('--no-auth', 'Explicitly disable Nexus HTTP and ACP websocket bearer authentication (unsafe).')
|
|
8948
8987
|
.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
8988
|
.option('--force', 'Take the lock even if another nexus instance is recorded as running.')
|
|
8950
8989
|
.option('--status', 'Alias for `elanous nexus status` (or test-mode status when combined with --test).')
|
|
@@ -8980,6 +9019,7 @@ const nexusRunCmd = nexusCmd
|
|
|
8980
9019
|
autoRestart?: boolean;
|
|
8981
9020
|
watch?: boolean;
|
|
8982
9021
|
mcp?: boolean;
|
|
9022
|
+
auth?: boolean;
|
|
8983
9023
|
fresh?: boolean;
|
|
8984
9024
|
force?: boolean;
|
|
8985
9025
|
dispatch?: boolean;
|
|
@@ -9129,6 +9169,7 @@ const nexusRunCmd = nexusCmd
|
|
|
9129
9169
|
preflightStaleServe: true,
|
|
9130
9170
|
verifyListen: true,
|
|
9131
9171
|
...(opts.mcp === false ? { mcpEnabled: false } : {}),
|
|
9172
|
+
...(opts.auth === false ? { noAuth: true } : {}),
|
|
9132
9173
|
...(opts.https ? { https: true } : {}),
|
|
9133
9174
|
...(resolvedPort !== undefined ? { httpPort: resolvedPort } : {}),
|
|
9134
9175
|
...(opts.force ? { force: true } : {}),
|
|
@@ -9156,6 +9197,7 @@ const nexusRunCmd = nexusCmd
|
|
|
9156
9197
|
// it — service-manager production posture stays watch-off.
|
|
9157
9198
|
...((bgChild ? opts.watch === true : watchOn) ? { pwaWatch: true } : {}),
|
|
9158
9199
|
...(opts.mcp === false ? { mcpEnabled: false } : {}),
|
|
9200
|
+
...(opts.auth === false ? { noAuth: true } : {}),
|
|
9159
9201
|
...(opts.force ? { force: true } : {}),
|
|
9160
9202
|
...(opts.dispatch ? { dispatch: true } : {}),
|
|
9161
9203
|
...(opts.toolCwd ? { toolCwd: opts.toolCwd } : {}),
|
|
@@ -9187,6 +9229,7 @@ const nexusRunCmd = nexusCmd
|
|
|
9187
9229
|
...(opts.rebuild ? { rebuild: true } : {}),
|
|
9188
9230
|
...(watchOnTty ? { watch: true } : {}),
|
|
9189
9231
|
...(opts.mcp === false ? { mcpEnabled: false } : {}),
|
|
9232
|
+
...(opts.auth === false ? { noAuth: true } : {}),
|
|
9190
9233
|
});
|
|
9191
9234
|
process.exit(result.exitCode);
|
|
9192
9235
|
});
|
|
@@ -10560,7 +10603,7 @@ async function main(): Promise<void> {
|
|
|
10560
10603
|
// gates on config completeness — no entry-mode branch.)
|
|
10561
10604
|
const cfg = getUserConfig();
|
|
10562
10605
|
if (needsOnboarding(cfg)) {
|
|
10563
|
-
await
|
|
10606
|
+
if (!await runOnboardingForCli()) return;
|
|
10564
10607
|
}
|
|
10565
10608
|
|
|
10566
10609
|
// 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');
|