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
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { debug } from '../debug/log.js';
|
|
2
|
+
import type { CmdResult, CmdRunner } from './pr-manager.js';
|
|
3
|
+
|
|
4
|
+
type Pull = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
function flag(args: readonly string[], name: string): string | undefined {
|
|
7
|
+
const index = args.indexOf(name);
|
|
8
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function hasOnlyFlags(args: readonly string[], start: number, valued: readonly string[], bare: readonly string[] = []): boolean {
|
|
12
|
+
for (let i = start; i < args.length; i++) {
|
|
13
|
+
if (bare.includes(args[i]!)) continue;
|
|
14
|
+
if (!valued.includes(args[i]!) || args[++i] === undefined) return false;
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function repoFromOrigin(run: CmdRunner, opts?: { cwd?: string }): { owner: string; repo: string } | null {
|
|
20
|
+
const remote = run('git', ['remote', 'get-url', 'origin'], opts);
|
|
21
|
+
if (!remote.ok) return null;
|
|
22
|
+
const value = remote.out.trim();
|
|
23
|
+
let path: string;
|
|
24
|
+
try {
|
|
25
|
+
const url = new URL(value);
|
|
26
|
+
if (!['https:', 'ssh:'].includes(url.protocol) || url.hostname.toLowerCase() !== 'github.com') return null;
|
|
27
|
+
path = url.pathname;
|
|
28
|
+
} catch {
|
|
29
|
+
const scp = /^(?:[^@/]+@)?github\.com:([^\s]+)$/.exec(value);
|
|
30
|
+
if (!scp) return null;
|
|
31
|
+
path = scp[1]!;
|
|
32
|
+
}
|
|
33
|
+
const match = /^\/?([\w.-]+)\/([\w.-]+?)(?:\.git)?\/?$/.exec(path);
|
|
34
|
+
return match ? { owner: match[1]!, repo: match[2]! } : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function prNumber(value: string, owner: string, repo: string): string | null {
|
|
38
|
+
if (/^[1-9]\d*$/.test(value)) return value;
|
|
39
|
+
const match = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/([1-9]\d*)\/?$/.exec(value);
|
|
40
|
+
return match && match[1] === owner && match[2] === repo ? match[3]! : null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readPull(result: CmdResult): Pull | null {
|
|
44
|
+
if (!result.ok) return null;
|
|
45
|
+
try {
|
|
46
|
+
const value: unknown = JSON.parse(result.out);
|
|
47
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value as Pull : null;
|
|
48
|
+
} catch { return null; }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function pullField(pull: Pull, field: string): unknown {
|
|
52
|
+
switch (field) {
|
|
53
|
+
case 'state': return pull.merged_at ? 'MERGED' : pull.state === 'open' ? 'OPEN' : pull.state === 'closed' && pull.merged_at === null ? 'CLOSED' : undefined;
|
|
54
|
+
case 'isDraft': return pull.draft;
|
|
55
|
+
case 'baseRefName': return (pull.base as Pull | undefined)?.ref;
|
|
56
|
+
case 'headRefName': return (pull.head as Pull | undefined)?.ref;
|
|
57
|
+
case 'headRepository': {
|
|
58
|
+
const nameWithOwner = ((pull.head as Pull | undefined)?.repo as Pull | undefined)?.full_name;
|
|
59
|
+
return typeof nameWithOwner === 'string' ? { nameWithOwner } : undefined;
|
|
60
|
+
}
|
|
61
|
+
case 'mergeable': return pull.mergeable === true ? 'MERGEABLE' : pull.mergeable === false ? 'CONFLICTING' : pull.mergeable === null ? 'UNKNOWN' : undefined;
|
|
62
|
+
case 'mergeStateStatus': {
|
|
63
|
+
const state = pull.mergeable_state;
|
|
64
|
+
return typeof state === 'string' && ['behind', 'blocked', 'clean', 'dirty', 'draft', 'has_hooks', 'unknown', 'unstable'].includes(state)
|
|
65
|
+
? state.toUpperCase() : undefined;
|
|
66
|
+
}
|
|
67
|
+
default: return undefined;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function restCall(run: CmdRunner, args: string[], opts?: { cwd?: string }): CmdResult {
|
|
72
|
+
return run('gh', ['api', ...args], opts);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Retry exactly once via REST after the secondary GraphQL limit; unsupported shapes fail closed. */
|
|
76
|
+
export function withGhRestFallback(run: CmdRunner): CmdRunner {
|
|
77
|
+
return (cmd, args, opts) => {
|
|
78
|
+
const original = run(cmd, args, opts);
|
|
79
|
+
if (cmd !== 'gh' || args[0] !== 'pr' || original.ok ||
|
|
80
|
+
!/GraphQL/i.test(`${original.out}\n${original.err ?? ''}`) ||
|
|
81
|
+
!/API rate limit/i.test(`${original.out}\n${original.err ?? ''}`)) return original;
|
|
82
|
+
const sub = args[1];
|
|
83
|
+
if (!['list', 'view', 'create', 'edit', 'merge', 'comment'].includes(sub ?? '')) return original;
|
|
84
|
+
if (sub === 'list' && !args.includes('--json')) return original;
|
|
85
|
+
const location = repoFromOrigin(run, opts);
|
|
86
|
+
if (!location) return original;
|
|
87
|
+
const { owner, repo } = location;
|
|
88
|
+
const path = `repos/${owner}/${repo}`;
|
|
89
|
+
let result: CmdResult | null = null;
|
|
90
|
+
|
|
91
|
+
if (sub === 'list' && args[2] === '--head' && args[3] && args[4] === '--state' && args[5] === 'open' &&
|
|
92
|
+
args[6] === '--json' && args[7] === 'url' &&
|
|
93
|
+
(args.length === 8 || (args.length === 10 && args[8] === '--jq' && args[9] === '.[0].url // ""'))) {
|
|
94
|
+
const listed = restCall(run, [`${path}/pulls?head=${encodeURIComponent(`${owner}:${args[3]}`)}&state=open`], opts);
|
|
95
|
+
if (listed.ok) {
|
|
96
|
+
try {
|
|
97
|
+
const items: unknown = JSON.parse(listed.out);
|
|
98
|
+
if (Array.isArray(items) && items.every((item) => item && typeof item.html_url === 'string')) {
|
|
99
|
+
const urls = items.map((item) => ({ url: item.html_url as string }));
|
|
100
|
+
result = { ok: true, out: args[8] === '--jq' ? (urls[0]?.url ?? '') : JSON.stringify(urls) };
|
|
101
|
+
}
|
|
102
|
+
} catch { /* malformed REST response */ }
|
|
103
|
+
} else result = listed;
|
|
104
|
+
} else if (sub === 'view' && args[2] && args[3] === '--json' && args[4] &&
|
|
105
|
+
hasOnlyFlags(args, 5, ['--jq', '-q'])) {
|
|
106
|
+
const number = prNumber(args[2], owner, repo);
|
|
107
|
+
const fields = args[4].split(',');
|
|
108
|
+
const jq = flag(args, '--jq') ?? flag(args, '-q');
|
|
109
|
+
if (number && fields.every((field) => ['state', 'isDraft', 'mergeable', 'baseRefName', 'mergeStateStatus', 'headRefName', 'headRepository'].includes(field)) &&
|
|
110
|
+
(!jq || (fields.length === 1 && jq === `.${fields[0]}`))) {
|
|
111
|
+
const response = restCall(run, [`${path}/pulls/${number}`], opts);
|
|
112
|
+
const pull = readPull(response);
|
|
113
|
+
if (pull) {
|
|
114
|
+
const mapped = Object.fromEntries(fields.map((field) => [field, pullField(pull, field)]));
|
|
115
|
+
if (Object.values(mapped).every((value) => value !== undefined)) {
|
|
116
|
+
const selected = mapped[fields[0]!];
|
|
117
|
+
result = { ok: true, out: jq
|
|
118
|
+
? typeof selected === 'string' ? selected : JSON.stringify(selected)
|
|
119
|
+
: JSON.stringify(mapped) };
|
|
120
|
+
}
|
|
121
|
+
} else if (!response.ok) result = response;
|
|
122
|
+
}
|
|
123
|
+
} else if (sub === 'create' && hasOnlyFlags(args, 2, ['--head', '--title', '--body', '--base'], ['--draft']) &&
|
|
124
|
+
flag(args, '--head') && flag(args, '--title') !== undefined && flag(args, '--body') !== undefined) {
|
|
125
|
+
let base = flag(args, '--base');
|
|
126
|
+
if (!base) {
|
|
127
|
+
const repository = restCall(run, [path], opts);
|
|
128
|
+
if (!repository.ok) result = repository;
|
|
129
|
+
else {
|
|
130
|
+
const defaultBranch = readPull(repository)?.default_branch;
|
|
131
|
+
if (typeof defaultBranch === 'string' && defaultBranch) base = defaultBranch;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (base) {
|
|
135
|
+
const parameters = ['-f', `head=${flag(args, '--head')}`, '-f', `title=${flag(args, '--title')}`, '-f', `body=${flag(args, '--body')}`, '-f', `base=${base}`];
|
|
136
|
+
if (args.includes('--draft')) parameters.push('-F', 'draft=true');
|
|
137
|
+
const response = restCall(run, ['--method', 'POST', `${path}/pulls`, ...parameters], opts);
|
|
138
|
+
const pull = readPull(response);
|
|
139
|
+
if (pull && typeof pull.html_url === 'string') result = { ok: true, out: pull.html_url };
|
|
140
|
+
else if (!response.ok) result = response;
|
|
141
|
+
}
|
|
142
|
+
} else if (sub === 'edit' && args[2] && hasOnlyFlags(args, 3, ['--title', '--body', '--base']) &&
|
|
143
|
+
flag(args, '--title') !== undefined && flag(args, '--body') !== undefined) {
|
|
144
|
+
const number = prNumber(args[2], owner, repo);
|
|
145
|
+
if (number) {
|
|
146
|
+
const parameters = ['-f', `title=${flag(args, '--title')}`, '-f', `body=${flag(args, '--body')}`];
|
|
147
|
+
if (flag(args, '--base')) parameters.push('-f', `base=${flag(args, '--base')}`);
|
|
148
|
+
const response = restCall(run, ['--method', 'PATCH', `${path}/pulls/${number}`, ...parameters], opts);
|
|
149
|
+
if (readPull(response)) result = { ok: true, out: '' };
|
|
150
|
+
else if (!response.ok) result = response;
|
|
151
|
+
}
|
|
152
|
+
} else if (sub === 'merge' && args[2] && args[3] === '--squash' && args.length === 4) {
|
|
153
|
+
const number = prNumber(args[2], owner, repo);
|
|
154
|
+
if (number) {
|
|
155
|
+
const head = restCall(run, [`${path}/pulls/${number}`], opts);
|
|
156
|
+
const sha = (readPull(head)?.head as Pull | undefined)?.sha;
|
|
157
|
+
if (typeof sha === 'string' && /^[a-f0-9]{40}$/.test(sha)) {
|
|
158
|
+
const response = restCall(run, ['--method', 'PUT', `${path}/pulls/${number}/merge`, '-f', 'merge_method=squash', '-f', `sha=${sha}`], opts);
|
|
159
|
+
const body = readPull(response);
|
|
160
|
+
if (body?.merged === true) result = { ok: true, out: '' };
|
|
161
|
+
else if (!response.ok) result = response;
|
|
162
|
+
} else if (!head.ok) result = head;
|
|
163
|
+
}
|
|
164
|
+
} else if (sub === 'comment' && args[2] && args[3] === '--body' && args[4] !== undefined && args.length === 5) {
|
|
165
|
+
const number = prNumber(args[2], owner, repo);
|
|
166
|
+
if (number) {
|
|
167
|
+
const response = restCall(run, ['--method', 'POST', `${path}/issues/${number}/comments`, '-f', `body=${args[4]}`], opts);
|
|
168
|
+
if (readPull(response)) result = { ok: true, out: '' };
|
|
169
|
+
else if (!response.ok) result = response;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (!result) return original;
|
|
173
|
+
debug.log('autopilot.pr', 'rest-fallback', { sub, ok: result.ok });
|
|
174
|
+
process.stderr.write(`[gh] REST 폴백: pr ${sub}\n`);
|
|
175
|
+
return result;
|
|
176
|
+
};
|
|
177
|
+
}
|
|
@@ -9,11 +9,14 @@
|
|
|
9
9
|
// 구현을 교체(누적 아님). 기존 PR 없으면 새로 생성.
|
|
10
10
|
|
|
11
11
|
import { spawnSync } from 'node:child_process';
|
|
12
|
+
import { githubAutomationToken } from '../auth/github-app-token.js';
|
|
12
13
|
import { debug } from '../debug/log.js';
|
|
14
|
+
import { POD_GITHUB_CREDENTIAL_TOKEN_ENV, POD_GITHUB_CREDENTIAL_URL_ENV } from '../nexus/api/pod-credential-api.js';
|
|
13
15
|
import { runGitWithRetry } from '../git-fs/retry.js';
|
|
14
16
|
import { DEFAULT_BRANCH_WORKTREE_BASE } from '../git-fs/worktree.js';
|
|
15
17
|
import { isElanousRuntimeArtifactPath } from '../self-implement/gate-scope.js';
|
|
16
18
|
import { isTransientExecutionFailure } from '../self-dev/execution-transient.js';
|
|
19
|
+
import { withGhRestFallback } from './gh-rest-fallback.js';
|
|
17
20
|
|
|
18
21
|
/** ★ stderr 캡처(대표 2026-07-21·관측 갭) — 종전엔 stdout 만 담아 git/gh 실패 사유가 소실됐다
|
|
19
22
|
* (705308: makePr 실패가 bare null 로 뭉뚱그려져 triage 가 "인증·네트워크"로 환각 오라우팅).
|
|
@@ -22,12 +25,55 @@ export interface CmdResult { ok: boolean; out: string; err?: string }
|
|
|
22
25
|
/** 명령 실행 추상화(주입형·테스트 스텁) — git/gh 를 실제로 돌리지 않고 로직 검증. */
|
|
23
26
|
export type CmdRunner = (cmd: string, args: readonly string[], opts?: { cwd?: string }) => CmdResult;
|
|
24
27
|
|
|
28
|
+
const GH_TOKEN_REFRESH_WINDOW_MS = 10 * 60_000;
|
|
29
|
+
let cachedRelayToken: { url: string; bearer: string; original: string | undefined; token: string; expiresAt: number } | undefined;
|
|
30
|
+
|
|
31
|
+
/** The runner is synchronous; ask the host relay synchronously before starting gh. */
|
|
32
|
+
function relayGhToken(env: NodeJS.ProcessEnv): string | null {
|
|
33
|
+
const url = env[POD_GITHUB_CREDENTIAL_URL_ENV];
|
|
34
|
+
const bearer = env[POD_GITHUB_CREDENTIAL_TOKEN_ENV];
|
|
35
|
+
if (!url || !bearer) return null;
|
|
36
|
+
const now = Date.now();
|
|
37
|
+
const original = env.GH_TOKEN;
|
|
38
|
+
if (cachedRelayToken?.url === url && cachedRelayToken.bearer === bearer && cachedRelayToken.original === original
|
|
39
|
+
&& now + GH_TOKEN_REFRESH_WINDOW_MS < cachedRelayToken.expiresAt) return cachedRelayToken.token;
|
|
40
|
+
try {
|
|
41
|
+
const response = spawnSync('curl', [
|
|
42
|
+
'--silent', '--show-error', '--fail', '--noproxy', '*', '--max-time', '10',
|
|
43
|
+
'--request', 'POST', '--header', '@-', url,
|
|
44
|
+
], { input: `Authorization: Bearer ${bearer}\n`, encoding: 'utf8', timeout: 12_000, maxBuffer: 64 * 1024, env });
|
|
45
|
+
if (response.error || response.status !== 0) throw new Error('request-failed');
|
|
46
|
+
const body: unknown = JSON.parse(response.stdout);
|
|
47
|
+
const token = body && typeof body === 'object' ? (body as { token?: unknown }).token : undefined;
|
|
48
|
+
const expiry = body && typeof body === 'object' ? (body as { expires_at?: unknown }).expires_at : undefined;
|
|
49
|
+
const expiresAt = typeof expiry === 'string' ? Date.parse(expiry) : NaN;
|
|
50
|
+
if (typeof token !== 'string' || !token || !Number.isFinite(expiresAt) || expiresAt <= now + GH_TOKEN_REFRESH_WINDOW_MS) throw new Error('invalid-response');
|
|
51
|
+
cachedRelayToken = { url, bearer, original, token, expiresAt };
|
|
52
|
+
return token;
|
|
53
|
+
} catch {
|
|
54
|
+
debug.log('pr-manager', 'gh-token', { reason: 'refresh-failed' }, { level: 'warn' });
|
|
55
|
+
return cachedRelayToken?.url === url && cachedRelayToken.bearer === bearer && cachedRelayToken.original === original
|
|
56
|
+
&& now < cachedRelayToken.expiresAt ? cachedRelayToken.token : null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Environment for a `gh` call made by automation: the Pod relay token, else the GitHub App token when the
|
|
61
|
+
* caller set no GH_TOKEN — so PRs, merges and PR comments all act as the App, not the person logged into `gh`. */
|
|
62
|
+
export function ghAutomationEnv(env: NodeJS.ProcessEnv = process.env, appToken: () => string | null = githubAutomationToken): NodeJS.ProcessEnv {
|
|
63
|
+
const token = relayGhToken(env) ?? (!Object.hasOwn(env, 'GH_TOKEN') ? appToken() : null);
|
|
64
|
+
return token ? { ...env, GH_TOKEN: token } : env;
|
|
65
|
+
}
|
|
66
|
+
|
|
25
67
|
/** 기본 러너 — spawnSync(dep-free). 실패/예외는 ok=false. stdout·stderr 모두 캡처. */
|
|
26
68
|
export const defaultCmdRunner: CmdRunner = (cmd, args, opts) => {
|
|
27
69
|
try {
|
|
28
|
-
//
|
|
29
|
-
const
|
|
30
|
-
|
|
70
|
+
// Relay only applies to gh; without it, retain caller GH_TOKEN and the existing App fallback.
|
|
71
|
+
const env = cmd === 'gh' ? ghAutomationEnv(process.env) : process.env;
|
|
72
|
+
// bun ≥1.4 applies Node's 1MB default maxBuffer and silently truncates (`gh pr diff`, file lists) — raise it and
|
|
73
|
+
// never read an overflow as success.
|
|
74
|
+
const r = spawnSync(cmd, [...args], { encoding: 'utf-8', timeout: 120_000, maxBuffer: 256 * 1024 * 1024, env, ...(opts?.cwd ? { cwd: opts.cwd } : {}) });
|
|
75
|
+
const overflow = (r.error as NodeJS.ErrnoException | undefined)?.code === 'ENOBUFS';
|
|
76
|
+
return { ok: r.status === 0 && !overflow, out: (r.stdout ?? '').trim(), err: [(r.stderr ?? '').trim(), overflow ? 'output exceeded 256MiB (ENOBUFS)' : ''].filter(Boolean).join('\n') };
|
|
31
77
|
} catch (e) { return { ok: false, out: '', err: e instanceof Error ? e.message : String(e) }; }
|
|
32
78
|
};
|
|
33
79
|
|
|
@@ -259,7 +305,8 @@ function isTransientPushFailure(detail: string): boolean {
|
|
|
259
305
|
}
|
|
260
306
|
|
|
261
307
|
/** ★ PR 매니저 팩토리(대표 2026-07-12) — CmdRunner 주입(테스트 스텁). 실 배선은 기본 러너. */
|
|
262
|
-
export function makePrManager(
|
|
308
|
+
export function makePrManager(runner: CmdRunner = defaultCmdRunner): PrManager {
|
|
309
|
+
const run = withGhRestFallback(runner);
|
|
263
310
|
const findPrForBranchOutcome = (branch: string, cwd?: string): FindPrForBranchOutcome => {
|
|
264
311
|
// ⛔ 표식은 base 를 「생략했다」는 뜻이지 브랜치 이름이 아니다. 그대로 `gh --head` 로 넘기면
|
|
265
312
|
// gh 가 `no pull requests found for branch "elanous:default-branch"` 를 stderr 로 뱉는다.
|
package/src/boot/acp-server.ts
CHANGED
|
@@ -24,14 +24,14 @@
|
|
|
24
24
|
// no dashboard, no tui-client — verified by the expanded guard
|
|
25
25
|
// test.
|
|
26
26
|
|
|
27
|
-
import {
|
|
27
|
+
import { mkdirSync } from 'node:fs';
|
|
28
28
|
import { getElanousConfigDir } from '../elanous-config-dir.js';
|
|
29
29
|
import { dirname, join as joinPath } from 'node:path';
|
|
30
|
+
import { ensureAuthToken } from '../auth/acp-token.js';
|
|
30
31
|
|
|
31
32
|
import {
|
|
32
33
|
listenUnixSocket,
|
|
33
34
|
listenWebSocket,
|
|
34
|
-
generateAuthToken,
|
|
35
35
|
createAuthVerifier,
|
|
36
36
|
} from '../acp/transport/index.js';
|
|
37
37
|
import { runAcpServer, type AcpServerOptions } from '../acp/server.js';
|
|
@@ -110,20 +110,6 @@ export function parseAcpBootArgs(argv: readonly string[]): AcpBootOptions {
|
|
|
110
110
|
return opts;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
/** Load or mint a persistent auth token for websocket boot. Stored
|
|
114
|
-
* at `~/.elanous/acp-token` with 0600 perms so the socket-holding
|
|
115
|
-
* user can bind-mount it into a parent client config. */
|
|
116
|
-
function ensureAuthToken(): { token: string; path: string } {
|
|
117
|
-
const tokenPath = joinPath(getElanousConfigDir(), 'acp-token');
|
|
118
|
-
mkdirSync(dirname(tokenPath), { recursive: true });
|
|
119
|
-
if (existsSync(tokenPath)) {
|
|
120
|
-
return { token: readFileSync(tokenPath, 'utf-8').trim(), path: tokenPath };
|
|
121
|
-
}
|
|
122
|
-
const token = generateAuthToken();
|
|
123
|
-
writeFileSync(tokenPath, token, { mode: 0o600 });
|
|
124
|
-
return { token, path: tokenPath };
|
|
125
|
-
}
|
|
126
|
-
|
|
127
113
|
/** Resolved daemon-runtime status surfaced in the startup banner.
|
|
128
114
|
* Matches the shape returned by `createDaemonRuntime()` so callers
|
|
129
115
|
* can spread it directly. Lets every transport (stdio / unix-socket
|
|
@@ -39,7 +39,8 @@ export function buildWebSearchTool(): LLMToolSpec {
|
|
|
39
39
|
name: 'WebSearch',
|
|
40
40
|
description:
|
|
41
41
|
'Search the public web via the daemon\'s configured provider (Grok / ' +
|
|
42
|
-
'Firecrawl / etc.).
|
|
42
|
+
'Firecrawl / etc.). When no key is available, falls back to keyless DuckDuckGo. ' +
|
|
43
|
+
'Returns a ranked list of URLs + titles + snippets. ' +
|
|
43
44
|
'Pair with a follow-up Read or external WebFetch when the model needs ' +
|
|
44
45
|
'the full article body.',
|
|
45
46
|
parameters: {
|
package/src/boot/ws-bridge.ts
CHANGED
|
@@ -26,6 +26,7 @@ import type {
|
|
|
26
26
|
PwaVoiceSession,
|
|
27
27
|
} from '../voice/channel-adapters/pwa-voice-adapter.js';
|
|
28
28
|
import { PWA_VOICE_FRAME_KIND } from '../voice/channel-adapters/pwa-voice-adapter.js';
|
|
29
|
+
import { isSameOriginRequest } from './check-same-origin.js';
|
|
29
30
|
import {
|
|
30
31
|
buildDevProxyWsUrl,
|
|
31
32
|
closeDevProxyUpstream,
|
|
@@ -47,6 +48,8 @@ export type WsLike = {
|
|
|
47
48
|
|
|
48
49
|
export type BunServerLike = {
|
|
49
50
|
upgrade: (req: Request, opts?: { data?: unknown; headers?: HeadersInit }) => boolean;
|
|
51
|
+
/** Bun's transport peer — used only for the same-origin exemption (HTTP 과 같은 규칙). */
|
|
52
|
+
requestIP?: (req: Request) => { address: string } | null;
|
|
50
53
|
};
|
|
51
54
|
|
|
52
55
|
export type WsHandlers = {
|
|
@@ -246,6 +249,14 @@ export function createWsBridge(opts: WsBridgeOpts): WsBridge {
|
|
|
246
249
|
});
|
|
247
250
|
}
|
|
248
251
|
authed = true;
|
|
252
|
+
} else if (isSameOriginRequest(req, server.requestIP?.(req)?.address)) {
|
|
253
|
+
// ⭐ HTTP 의 same-origin 면제(`meta-api.ts` decideAuth)와 «같은» 규칙 — 루프백 직결 ⊕ 프록시 표지 없음 ⊕
|
|
254
|
+
// Sec-Fetch-Site/Origin 이 같은 출처. 이 데몬이 내준 PWA 페이지가 토큰 없이 연 소켓이다.
|
|
255
|
+
// 🩸 2026-09-30 0.2.5 회귀: #21937 이 토큰을 «늘» 만들게 되면서 WS 검증기가 늘 켜졌고, 토큰을
|
|
256
|
+
// 저장하지 않은 PWA(새 설치 · 격리 넥서스)의 ACP 가 첫 `initialize` 에서 1008 auth_failed 로 닫혔다
|
|
257
|
+
// — HTTP 는 이 면제로 살아 있어 «목록은 뜨는데 채팅·터미널만 죽는» 모양이 됐다.
|
|
258
|
+
authed = true;
|
|
259
|
+
trace('acp.ws.same-origin', { path: url.pathname });
|
|
249
260
|
}
|
|
250
261
|
}
|
|
251
262
|
const acpState: AcpPerSocketState = {
|
package/src/chat/index.ts
CHANGED
|
@@ -107,9 +107,27 @@ import {
|
|
|
107
107
|
import { resolveTextInputControlAction } from './input-control-key.js';
|
|
108
108
|
import { resolveTextInputEditAction } from './input-edit-key.js';
|
|
109
109
|
import { resolveTextInputTextAction } from './input-text-key.js';
|
|
110
|
+
import { classifyLeftoverInput, summarizeSubmitText } from './submit-trace.js';
|
|
110
111
|
|
|
111
112
|
import { perf } from '../perf-counters.js';
|
|
112
113
|
|
|
114
|
+
let lastSubmitAt: number | null = null;
|
|
115
|
+
|
|
116
|
+
function traceAfterSubmitKey(key: Key): void {
|
|
117
|
+
if (lastSubmitAt === null) return;
|
|
118
|
+
const withinMs = Date.now() - lastSubmitAt;
|
|
119
|
+
if (withinMs > 1000) {
|
|
120
|
+
lastSubmitAt = null;
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (key.ctrl || key.alt) return;
|
|
124
|
+
const input = key.name === 'paste' ? key.paste ?? '' : key.name;
|
|
125
|
+
if (!input || (key.name !== 'paste' && [...input].length !== 1)) return;
|
|
126
|
+
lastSubmitAt = null;
|
|
127
|
+
const kind = classifyLeftoverInput(input, withinMs);
|
|
128
|
+
if (kind) debug.log('chat.input.leftover', 'after-submit', { withinMs, kind });
|
|
129
|
+
}
|
|
130
|
+
|
|
113
131
|
/** Row where the input prompt starts — set by the textInput paint
|
|
114
132
|
* loop so writeTerminal knows which rows to invalidate in the
|
|
115
133
|
* frame cache. Falls back to "invalidate everything" when unknown
|
|
@@ -419,14 +437,14 @@ export const SLASH_COMMANDS: SlashCommand[] = [
|
|
|
419
437
|
// B4 — craft rulebook verdict, same resolution as `elanous repo design-check`
|
|
420
438
|
// and the PWA `/design-check` panel. Listed (not baselined as hidden) on
|
|
421
439
|
// purpose: a surface nobody can discover is not a surface.
|
|
422
|
-
{ name: 'design', aliases: ['design-check'], description: '
|
|
440
|
+
{ name: 'design', aliases: ['design-check'], description: 'Design rulebooks this repository declares · /design [--declared] · /design pick [number|id] to choose a design direction', subcommands: ['--declared', 'pick'] },
|
|
423
441
|
{ name: 'provider', aliases: ['p'], description: 'LLM providers — /provider (list) · next (cycle, also Alt+M / pill click) · use <name> · pick (visual picker) · reset', subcommands: ['next', 'use', 'pick', 'picker', 'menu', 'reset', 'list'] },
|
|
424
442
|
{ name: 'reasoning', aliases: ['r', 'think'], description: 'Reasoning level (codex effort + summary, anthropic extended-thinking budget) — /reasoning [off|low|medium|high|xhigh] (cycle when no arg · xhigh 는 모델 상한이 xhigh 이상일 때만 wire 에 실린다, 아니면 high 로 깎인다)', subcommands: ['off', 'low', 'medium', 'high', 'xhigh'] },
|
|
425
443
|
{ name: 'model', aliases: ['m'], description: 'Switch active model — /model <codex|terra|sol|luna|opus|sonnet|grok> (list + current when no arg). OpenAI 는 Codex(Responses API)만. effort 는 /reasoning.', subcommands: ['codex', 'terra', 'sol', 'luna', 'opus', 'sonnet', 'grok'] },
|
|
426
444
|
{ name: 'local', aliases: ['ll'], description: 'Local OpenAI-compatible LLM — ping / models / test / use', subcommands: ['ping', 'models', 'test', 'use', 'status'] },
|
|
427
445
|
{ name: 'session', aliases: ['sess'], description: 'Session resume — list / load / sync / new (handoff from mobile)', subcommands: ['list', 'load', 'sync', 'new'] },
|
|
428
|
-
{ name: 'resume', aliases: [], description: '
|
|
429
|
-
{ name: 'fork', aliases: [], description: '
|
|
446
|
+
{ name: 'resume', aliases: [], description: 'Pick a past session, or load one by id prefix · /resume <id-prefix>' },
|
|
447
|
+
{ name: 'fork', aliases: [], description: 'Copy this session into a new one' },
|
|
430
448
|
{ name: 'rewind', aliases: [], description: 'Rewind — 과거 user 턴 픽커로 되감기 (원본 보존 · 새 세션 분기 · /rewind <n> 숏컷)' },
|
|
431
449
|
// TUI 부활 C-a (2026-07-12): autopilot 미션 TUI 표면 — CLI 동등 단일 창구 소비.
|
|
432
450
|
{ name: 'mission', aliases: [], description: 'Autopilot 미션 — list(+헬스) · trace <id> 계보 · arm <id> 승인 (구체화/종료는 CLI)', subcommands: ['list', 'trace', 'arm'] },
|
|
@@ -468,7 +486,7 @@ export const SLASH_COMMANDS: SlashCommand[] = [
|
|
|
468
486
|
{ name: 'acp', aliases: [], description: 'ACP chat — stream claude-code / codex / gemini replies into the chat pane (not a VW spawn). /acp codex points at the canonical codex app-server path; /acp cas is a synonym.', subcommands: ['claude', 'codex', 'gemini', 'cas', 'cancel', 'status', 'drop'] },
|
|
469
487
|
{ name: 'conv', aliases: [], description: 'Conversation widget/popup host — /conv list · /conv open <session-id> · /conv layout <cascade|tile|stack> · /conv focus <next|prev>.', subcommands: ['list', 'ls', 'open', 'layout', 'focus'] },
|
|
470
488
|
{ name: 'handoff', aliases: [], description: 'H5 P3 cross-agent context handoff: /handoff <from_session_id> <to_brand> [--channels r,p,m] [--prompt "prefix"]. Takes source session snapshot (filtered by channels if observer present) and launches target via adapter registry. Brands: codex · claude · claude-code · gemini · elanous.' },
|
|
471
|
-
{ name: 'remaining', aliases: [], description: '
|
|
489
|
+
{ name: 'remaining', aliases: [], description: 'What each account has left' },
|
|
472
490
|
{ name: 'agent-room', aliases: [], description: 'H6 P4 VW agent-room — /agent-room <N> <brands...> · /agent-room list · /agent-room close <id> · N ∈ {2,3,4} · brands: codex/claude/gemini/elanous/auto/lll:<m>', subcommands: ['list', 'close', 'preset', 'help'] },
|
|
473
491
|
{ name: 'showroom', aliases: ['sr'], description: 'Showroom v2 multi-LLM lane composer — /showroom (default 2-pane) · /showroom <lane1> <lane2> [<lane3> [<lane4>]] · lane = role:provider[:transport] · roles plan/build/exec/review/reflect · pair with /lane and /relay for cross-lane handoffs.', subcommands: ['help'] },
|
|
474
492
|
{ name: 'reply', aliases: [], description: 'H6 P5 AgentReply — /reply <target-session-id> <message...> · send message to a live session, capture the response · flags: --from · --channels r,m · --idle-ms · --timeout-ms', subcommands: ['help'] },
|
|
@@ -701,6 +719,8 @@ export async function textInput(opts: {
|
|
|
701
719
|
* typing. The host is expected to mutate its own state; the input
|
|
702
720
|
* redraws the bar + picker on top afterward. */
|
|
703
721
|
onMouse?: (mouse: { row: number; col: number; type: 'click' | 'double-click' | 'right-click' | 'scroll-up' | 'scroll-down' | 'drag' | 'release'; shift?: boolean }) => void | boolean | Promise<void | boolean>;
|
|
722
|
+
/** Scroll the host's chat log from the input without editing its buffer. */
|
|
723
|
+
onScrollKey?: (name: 'pageup' | 'pagedown') => boolean | void;
|
|
704
724
|
/** MX11b — key interceptor called BEFORE textInput's own key
|
|
705
725
|
* handling. Host returns 'consumed' to short-circuit the event
|
|
706
726
|
* (no further input processing), 'passthrough' to let the default
|
|
@@ -1331,13 +1351,15 @@ export async function textInput(opts: {
|
|
|
1331
1351
|
writeTerminal(endTextInputCursor(opts.cursorSink));
|
|
1332
1352
|
return result;
|
|
1333
1353
|
};
|
|
1334
|
-
const submitBufferResult = (text: string): InputResult =>
|
|
1335
|
-
|
|
1354
|
+
const submitBufferResult = (text: string): InputResult => {
|
|
1355
|
+
debug.log('chat.input.submit', 'summary', summarizeSubmitText(text.trim()));
|
|
1356
|
+
lastSubmitAt = Date.now();
|
|
1357
|
+
return finalizeInput({
|
|
1336
1358
|
text: text.trim(),
|
|
1337
1359
|
submitted: true,
|
|
1338
1360
|
...(pendingExternalSubmitSource ? { externalSubmitSource: pendingExternalSubmitSource } : {}),
|
|
1339
|
-
})
|
|
1340
|
-
|
|
1361
|
+
});
|
|
1362
|
+
};
|
|
1341
1363
|
const currentBufferText = (): string => lines.join('\n').trim();
|
|
1342
1364
|
|
|
1343
1365
|
// Expose drawAll so the host can ping us to re-paint after an
|
|
@@ -1401,6 +1423,7 @@ export async function textInput(opts: {
|
|
|
1401
1423
|
}
|
|
1402
1424
|
|
|
1403
1425
|
const key = await (opts.readKey ?? (() => readKey('input')))();
|
|
1426
|
+
traceAfterSubmitKey(key);
|
|
1404
1427
|
|
|
1405
1428
|
// Raw-key trace — diagnoses "modifier bit dropped before global-action
|
|
1406
1429
|
// matcher saw the chord" cases (e.g. tablet terminal apps that
|
|
@@ -1627,6 +1650,11 @@ export async function textInput(opts: {
|
|
|
1627
1650
|
}
|
|
1628
1651
|
}
|
|
1629
1652
|
|
|
1653
|
+
if (!key.ctrl && (key.name === 'pageup' || key.name === 'pagedown') && opts.onScrollKey) {
|
|
1654
|
+
if (opts.onScrollKey(key.name) !== false) drawAll();
|
|
1655
|
+
continue;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1630
1658
|
const controlAction = resolveTextInputControlAction(key, {
|
|
1631
1659
|
currentLine: lines[lineIdx] ?? '',
|
|
1632
1660
|
cursor: colIdx,
|
|
@@ -1897,6 +1925,7 @@ export function attachStreamingKeys(
|
|
|
1897
1925
|
};
|
|
1898
1926
|
function deliver(keys: readonly Key[]): void {
|
|
1899
1927
|
for (const key of keys) {
|
|
1928
|
+
traceAfterSubmitKey(key);
|
|
1900
1929
|
traceKey(key, 'stream');
|
|
1901
1930
|
try {
|
|
1902
1931
|
const r = handler(key);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function nextChatScrollOffset(
|
|
2
|
+
current: number,
|
|
3
|
+
key: 'pageup' | 'pagedown',
|
|
4
|
+
pageSize: number,
|
|
5
|
+
maxOffset: number,
|
|
6
|
+
): number {
|
|
7
|
+
if (maxOffset <= 0) return -1;
|
|
8
|
+
const offset = current < 0 ? maxOffset : Math.max(0, Math.min(current, maxOffset));
|
|
9
|
+
if (key === 'pageup') return Math.max(0, offset - pageSize);
|
|
10
|
+
const next = offset + pageSize;
|
|
11
|
+
return next >= maxOffset ? -1 : next;
|
|
12
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const LONE_JAMO = /[\u3131-\u318e\u1100-\u11ff]/u;
|
|
2
|
+
const HANGUL_SYLLABLE = /[\uac00-\ud7a3]/u;
|
|
3
|
+
|
|
4
|
+
export function summarizeSubmitText(text: string): {
|
|
5
|
+
chars: number;
|
|
6
|
+
lines: number;
|
|
7
|
+
lastIsLoneJamo: boolean;
|
|
8
|
+
loneJamoCount: number;
|
|
9
|
+
hasHangul: boolean;
|
|
10
|
+
} {
|
|
11
|
+
const characters = [...text];
|
|
12
|
+
const loneJamoCount = characters.filter(char => LONE_JAMO.test(char)).length;
|
|
13
|
+
return {
|
|
14
|
+
chars: characters.length,
|
|
15
|
+
lines: text.split('\n').length,
|
|
16
|
+
lastIsLoneJamo: LONE_JAMO.test(characters.at(-1) ?? ''),
|
|
17
|
+
loneJamoCount,
|
|
18
|
+
hasHangul: loneJamoCount > 0 || HANGUL_SYLLABLE.test(text),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function classifyLeftoverInput(
|
|
23
|
+
input: string,
|
|
24
|
+
msSinceSubmit: number,
|
|
25
|
+
): 'syllable' | 'jamo' | null {
|
|
26
|
+
if (msSinceSubmit < 0 || msSinceSubmit > 1000 || [...input].length !== 1) return null;
|
|
27
|
+
if (HANGUL_SYLLABLE.test(input)) return 'syllable';
|
|
28
|
+
if (LONE_JAMO.test(input)) return 'jamo';
|
|
29
|
+
return null;
|
|
30
|
+
}
|
package/src/cli/agent-cli.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
import { getUserConfig, reloadUserConfig } from '../user-config.js';
|
|
4
|
-
import { needsOnboarding, runOnboarding } from '../onboarding.js';
|
|
4
|
+
import { handleOnboardingRefusal, needsOnboarding, runOnboarding } from '../onboarding.js';
|
|
5
5
|
import { runTurn, ensureCliSession, sessionBudget } from '../session/chat.js';
|
|
6
6
|
import { NoLlmProviderAvailableError, noProviderAvailableMessage } from '../llm.js';
|
|
7
7
|
import { resolveSessionId, getActiveSessionId, setActiveSessionId } from '../session/index.js';
|
|
@@ -93,7 +93,12 @@ export function registerAgentCommands(program: Command): void {
|
|
|
93
93
|
const cfg = getUserConfig();
|
|
94
94
|
if (needsOnboarding(cfg)) {
|
|
95
95
|
ui.info('No config yet — launching setup wizard first.');
|
|
96
|
-
|
|
96
|
+
try {
|
|
97
|
+
await runOnboarding();
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (handleOnboardingRefusal(error, 'agent')) return;
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
97
102
|
}
|
|
98
103
|
const refreshed = reloadUserConfig();
|
|
99
104
|
await runChatTurnCli({
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import { DecisionLedger, importDecisionMarkdown, type DecisionCategory, type DecisionLedgerOptions, type DecisionOption, type DecisionTrack, type DecisionEntry } from '../decisions/decision-ledger.js';
|
|
3
|
+
|
|
4
|
+
const repeat = (value: string, values: string[]) => [...values, value];
|
|
5
|
+
function date(raw?: string): string | undefined {
|
|
6
|
+
if (!raw) return undefined;
|
|
7
|
+
const days = /^(\d+)d$/.exec(raw);
|
|
8
|
+
const parsed = days ? new Date(Date.now() - Number(days[1]) * 86400000) : new Date(raw);
|
|
9
|
+
if (!Number.isFinite(parsed.getTime())) throw new Error(`invalid since: ${raw}`);
|
|
10
|
+
return parsed.toISOString();
|
|
11
|
+
}
|
|
12
|
+
const kst = (at: string) => new Intl.DateTimeFormat('sv-SE', { timeZone: 'Asia/Seoul', dateStyle: 'short', timeStyle: 'medium' }).format(new Date(at)) + ' KST';
|
|
13
|
+
const version = (e: DecisionEntry['version']) => e ? `released=${e.released ?? '-'} · dev=${e.dev ?? '-'}${e.codename ? ` «${e.codename}»` : ''}` : '판 미상';
|
|
14
|
+
function agent(o?: string): string {
|
|
15
|
+
const value = o?.trim() || process.env.AI_AGENT?.trim();
|
|
16
|
+
if (!value) throw new Error('agent unknown: provide --agent or AI_AGENT');
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
function parseOption(raw: string): DecisionOption {
|
|
20
|
+
const m = /^([a-z])=(.+?):(.+)$/.exec(raw);
|
|
21
|
+
if (!m) throw new Error(`invalid --option: ${raw} (expected a=label:consequence)`);
|
|
22
|
+
return { key: m[1]!, label: m[2]!, consequence: m[3]! };
|
|
23
|
+
}
|
|
24
|
+
export function formatDecisionRow(e: DecisionEntry): string {
|
|
25
|
+
const recommendation = 'skipped' in e.recommendation ? '권고 생략' : `${e.recommendation.option} (${e.recommendation.why})`;
|
|
26
|
+
return `${e.id} · ${e.category} · ${e.title} · ${recommendation} · ${e.raisedAt ? kst(e.raisedAt) : '올린 시각 미상'} · ${version(e.version)}`;
|
|
27
|
+
}
|
|
28
|
+
export function formatDecisionDetail(e: DecisionEntry): string {
|
|
29
|
+
return [formatDecisionRow(e), `상태: ${e.status}`, `S: ${e.scqa.s}`, `C: ${e.scqa.c}`, `Q: ${e.scqa.q ?? '(비움)'}`, `A: ${e.scqa.a ?? '(비움)'}`,
|
|
30
|
+
'선택지 | 결과', ...(e.options.length ? e.options.map(o => `${o.key}: ${o.label} | ${o.consequence}`) : ['원문에 선택지 없음 · 확인 필요']),
|
|
31
|
+
'skipped' in e.recommendation ? `권고 생략: ${e.recommendation.reason}` : `권고: ${e.recommendation.option} — ${e.recommendation.why}`,
|
|
32
|
+
`올림: ${e.raisedAt ? kst(e.raisedAt) : '시각 미상'} · ${e.raisedBy.agent} · ${version(e.version)}`,
|
|
33
|
+
...(e.importedAt ? [`가져옴: ${kst(e.importedAt)}`] : []),
|
|
34
|
+
...(e.status === 'decided' ? [`결정: ${e.decidedAt ? kst(e.decidedAt) : '시각 미상'} · ${e.decidedBy?.kind === 'auto' ? `AUTO ${e.decidedBy.agent} (${e.decidedBy.delegation})` : e.decidedBy?.kind === 'human' ? 'human' : '주체 미상'} · ${e.choice ?? '선택 키 미상'} · ${e.versionAtDecision ? version(e.versionAtDecision) : '결정 당시 판 미상'}`,
|
|
35
|
+
...(e.note ? [`메모: ${e.note}`] : [])] : []),
|
|
36
|
+
...(e.withdrawnAt ? [`철회: ${kst(e.withdrawnAt)} · ${e.withdrawReason}`] : []),
|
|
37
|
+
'이력:', ...e.history.map(h => ` ${h.type} · ${h.at ? kst(h.at) : '시각 미상'} · ${h.by}${h.choice ? ` · ${h.choice}` : ''}${h.reason ? ` · ${h.reason}` : ''} · ${h.version ? version(h.version) : '판 미상'}`),
|
|
38
|
+
...(e.refs?.length ? [`참조: ${e.refs.join(' · ')}`] : [])].join('\n');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function registerDecisionsCommands(program: Command, config: DecisionLedgerOptions = {}, out: Pick<Console, 'log'> = console): void {
|
|
42
|
+
const root = program.command('decisions').description('대표 결정 원장 · 로컬 전용');
|
|
43
|
+
const emit = (value: unknown, json?: boolean, text?: string) => out.log(json ? JSON.stringify(value) : text ?? JSON.stringify(value));
|
|
44
|
+
const fail = (action: () => void) => { try { action(); } catch (e) { throw new Error(`decisions: ${e instanceof Error ? e.message : String(e)}`); } };
|
|
45
|
+
root.command('raise').description('SCQA·선택지와 권고로 결정 항목을 올린다')
|
|
46
|
+
.requiredOption('--title <text>').requiredOption('--category <category>')
|
|
47
|
+
.requiredOption('--s <text>').requiredOption('--c <text>').option('--q <text>').option('--a <text>')
|
|
48
|
+
.requiredOption('--option <key=label:consequence>', '선택지 (두 번 이상)', repeat, [] as string[])
|
|
49
|
+
.option('--recommend <key>').option('--why <text>').option('--skip-recommend <reason>')
|
|
50
|
+
.option('--track <track>').option('--agent <agent>').option('--session <id>')
|
|
51
|
+
.option('--ref <url>', '참조 (반복)', repeat, [] as string[]).option('--json')
|
|
52
|
+
.action((o: { title: string; category: DecisionCategory; s: string; c: string; q?: string; a?: string; option: string[]; recommend?: string; why?: string; skipRecommend?: string; track?: DecisionTrack; agent?: string; session?: string; ref: string[]; json?: boolean }) => fail(() => {
|
|
53
|
+
if (o.skipRecommend !== undefined && (o.recommend !== undefined || o.why !== undefined)) throw new Error('choose recommendation or skip, not both');
|
|
54
|
+
if (o.skipRecommend === undefined && (!o.recommend || !o.why)) throw new Error('--recommend and --why required, or --skip-recommend <reason>');
|
|
55
|
+
const options: DecisionOption[] = o.option.map(parseOption);
|
|
56
|
+
const entry = new DecisionLedger(config).raise({ title: o.title, category: o.category, scqa: { s: o.s, c: o.c, ...(o.q ? { q: o.q } : {}), ...(o.a ? { a: o.a } : {}) }, options,
|
|
57
|
+
recommendation: o.skipRecommend !== undefined ? { skipped: true, reason: o.skipRecommend } : { option: o.recommend!, why: o.why! },
|
|
58
|
+
raisedBy: { agent: agent(o.agent), ...(o.track ? { track: o.track } : {}), ...(o.session ? { session: o.session } : {}) }, ...(o.ref.length ? { refs: o.ref } : {}) });
|
|
59
|
+
emit(entry, o.json, `올림: ${formatDecisionRow(entry)}`);
|
|
60
|
+
}));
|
|
61
|
+
root.command('list').description('결정 목록 (기본 열린 것)')
|
|
62
|
+
.option('--status <open|decided|all>', '기본 open', 'open').option('--since <date>').option('--version <version>')
|
|
63
|
+
.option('--decided-by <human|auto>').option('--category <category>').option('--json')
|
|
64
|
+
.action((o: { status: 'open' | 'decided' | 'all'; since?: string; version?: string; decidedBy?: 'human' | 'auto'; category?: DecisionCategory; json?: boolean }) => fail(() => {
|
|
65
|
+
const rows = new DecisionLedger(config).list({ status: o.status, since: date(o.since), version: o.version, decidedBy: o.decidedBy, category: o.category });
|
|
66
|
+
emit(rows, o.json, rows.length ? rows.map(formatDecisionRow).join('\n') : '결정 0건');
|
|
67
|
+
}));
|
|
68
|
+
root.command('show <id>').description('SCQA · 선택지 · 권고 · 이력 · 판').option('--json')
|
|
69
|
+
.action((id: string, o: { json?: boolean }) => fail(() => { const entry = new DecisionLedger(config).show(id); emit(entry, o.json, formatDecisionDetail(entry)); }));
|
|
70
|
+
root.command('decide <id> <option>').description('사람 또는 AUTO 위임 결정 기록')
|
|
71
|
+
.option('--note <text>').option('--auto').option('--delegation <reason>').option('--track <track>').option('--agent <agent>').option('--json')
|
|
72
|
+
.action((id: string, choice: string, o: { note?: string; auto?: boolean; delegation?: string; track?: DecisionTrack; agent?: string; json?: boolean }) => fail(() => {
|
|
73
|
+
if (o.auto && !o.delegation?.trim()) throw new Error('--auto requires --delegation');
|
|
74
|
+
if (!o.auto && (o.delegation || o.track || o.agent)) throw new Error('delegation/track/agent require --auto');
|
|
75
|
+
const by = o.auto ? { kind: 'auto' as const, agent: agent(o.agent), delegation: o.delegation!, ...(o.track ? { track: o.track } : {}) } : { kind: 'human' as const };
|
|
76
|
+
const entry = new DecisionLedger(config).decide(id, choice, by, o.note);
|
|
77
|
+
emit(entry, o.json, `결정: ${formatDecisionDetail(entry)}`);
|
|
78
|
+
}));
|
|
79
|
+
root.command('add-options <id>').description('선택지가 미기재된 과거 항목에 확인된 선택지를 추가')
|
|
80
|
+
.requiredOption('--option <key=label:consequence>', '선택지 (두 번 이상)', repeat, [] as string[])
|
|
81
|
+
.option('--agent <agent>').option('--json')
|
|
82
|
+
.action((id: string, o: { option: string[]; agent?: string; json?: boolean }) => fail(() => {
|
|
83
|
+
const entry = new DecisionLedger(config).addOptions(id, o.option.map(parseOption), agent(o.agent));
|
|
84
|
+
emit(entry, o.json, formatDecisionDetail(entry));
|
|
85
|
+
}));
|
|
86
|
+
root.command('withdraw <id>').description('결정 철회도 원장에 남긴다').requiredOption('--reason <reason>').option('--json')
|
|
87
|
+
.action((id: string, o: { reason: string; json?: boolean }) => fail(() => { const entry = new DecisionLedger(config).withdraw(id, o.reason); emit(entry, o.json, `철회: ${formatDecisionDetail(entry)}`); }));
|
|
88
|
+
root.command('import-markdown <file>').description('기존 대표 결정 문서 씨앗을 가져온다 (재실행 안전)').option('--json')
|
|
89
|
+
.action((file: string, o: { json?: boolean }) => fail(() => {
|
|
90
|
+
const result = importDecisionMarkdown(new DecisionLedger(config), file);
|
|
91
|
+
emit(result, o.json, `가져옴 ${result.imported.length} · 기존 ${result.existing.length} · 불완전 ${result.incomplete.length} · 못 읽음 ${result.unread.length}${result.incomplete.length ? `\n${result.incomplete.join('\n')}` : ''}${result.unread.length ? `\n${result.unread.join('\n')}` : ''}`);
|
|
92
|
+
if (result.unread.length) process.exitCode = 1;
|
|
93
|
+
}));
|
|
94
|
+
}
|