elanous 0.2.6 → 0.2.8
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/.env.example +10 -7
- package/README.md +4 -0
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3995-bee512a7063036ce.js → 2375-cb6debea56cce4fb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +25 -0
- package/apps/pwa/out/_next/static/chunks/{8d0d9faf-97887ff99713b84e.js → 2ea96958-e8c7e112de50b067.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{5329.8b7e4bca684b6eec.js → 3403.4bc97b4066945a94.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +1 -0
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7633.0654fe6ea259c1ee.js → 4323.c5dfb6f503304775.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4836-3084c58c1725a40b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2984-977cfa339fcd65a5.js → 5080-1613752891fcadb4.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{763-7d0e1913b5074e98.js → 5791-bb82743fa6299562.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4240-7153881b0b377306.js → 6062-73e68343a9814f6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{91ff6dda-3b33d42d20657429.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8894-7317998f6cb97d8c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-9aa47b0bc6d570ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f4d2ecbbae6088d6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-2053e49930c82748.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-d4b6df905f185055.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a70ba27c33003dd0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-186fc6952f027acc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-18166f885d831d3e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1c9d6cfe68b121c2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-cbbe03c0da7b081a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c108ff1fdb665c97.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-147a328753d983e2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-799395a9df7d95aa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-6a8158b71d1fe9d7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-9567560270857531.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-e7ed3d010153d818.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-c0c0d19d51342d3e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2e002ad8faa43291.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-b672e02e4bec8deb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-1ba4a5d857276334.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-9db2226f38c542fb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-70ee65f5f7a151dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-0bb8e43203efb23b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-092e1bcf27d751cc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-eb52580c4b75b905.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-7464c1987dd76e6d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-e573437b0a1c728f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-294300625e49e4d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-085847b088675978.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-9fe9d38c36999eb4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-d8ab4d1489223459.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-6141f67f531d8b6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-295a02b1026b8255.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-1eb9a0e1e054209d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-82efa177a30f3741.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d0be51cec4f62606.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-2e367075ffdbc062.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-647ed2b66c1d49f1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ba058c56e38f2792.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-37b8bf78a3c6eec5.js → webpack-e5bea36f183b25e6.js} +1 -1
- package/apps/pwa/out/_next/static/css/5ef72a618a8994f3.css +1 -0
- package/apps/pwa/out/_next/static/lywc8m_HDzH9MKSDQhyEj/_buildManifest.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +22 -22
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +22 -22
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +22 -22
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +21 -21
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +21 -21
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +21 -21
- package/apps/pwa/out/consult/index.html +1 -0
- package/apps/pwa/out/consult/index.txt +32 -0
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +22 -22
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +22 -22
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +22 -22
- package/apps/pwa/out/exec/index.html +1 -0
- package/apps/pwa/out/exec/index.txt +32 -0
- package/apps/pwa/out/field/index.html +1 -0
- package/apps/pwa/out/field/index.txt +32 -0
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +21 -21
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +22 -22
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +22 -22
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +21 -21
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +21 -21
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +22 -22
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +22 -22
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +22 -22
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +21 -21
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +22 -22
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +22 -22
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +22 -22
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +22 -22
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +22 -22
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +21 -21
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +22 -22
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +22 -22
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +22 -22
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +22 -22
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +22 -22
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +22 -22
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +22 -22
- package/bin/elanous.cjs +107 -0
- package/graphs/field/field-feed.yaml +24 -0
- package/graphs/field/recipes.yaml +12 -0
- package/graphs/landing/landing-heal.yaml +3 -1
- package/graphs/landing/recipes.yaml +3 -0
- package/graphs/release/recipes.yaml +10 -0
- package/graphs/release/release-loop.yaml +10 -3
- package/graphs/test-diet/recipes.yaml +9 -0
- package/graphs/test-diet/test-diet.yaml +22 -0
- package/package.json +6 -5
- package/persona-presets/_index.yaml +10 -0
- package/persona-presets/_schema.json +146 -0
- package/persona-presets/dana.yaml +26 -0
- package/persona-presets/haru.yaml +27 -0
- package/persona-presets/kai.yaml +27 -0
- package/persona-presets/leo.yaml +27 -0
- package/persona-presets/mira.yaml +30 -0
- package/persona-presets/nari.yaml +27 -0
- package/persona-presets/sora.yaml +27 -0
- package/persona-presets/yuna.yaml +27 -0
- package/plugins/card-followup/README.md +13 -4
- package/plugins/card-followup/graphs/card-followup.yaml +3 -1
- package/plugins/card-followup/graphs/recipes.yaml +5 -1
- package/plugins/card-followup/graphs/run-step.ts +122 -18
- package/plugins/card-followup/graphs/sales-strategy.ts +173 -0
- package/plugins/card-followup/plugin.json +1 -1
- package/plugins/doc-draft/README.md +13 -0
- package/plugins/doc-draft/examples/brief-sample.json +8 -0
- package/plugins/doc-draft/graphs/doc-draft.yaml +14 -0
- package/plugins/doc-draft/graphs/recipes.yaml +9 -0
- package/plugins/doc-draft/graphs/run-step.ts +113 -0
- package/plugins/doc-draft/plugin.json +24 -0
- package/plugins/geo-check/README.md +3 -1
- package/plugins/geo-check/graphs/run-step.ts +58 -13
- package/plugins/geo-check/plugin.json +1 -1
- package/plugins/job-coach/.codex-plugin/plugin.json +1 -1
- package/plugins/job-coach/README.md +1 -1
- package/plugins/job-coach/graphs/run-step.ts +2 -2
- package/plugins/job-coach/plugin.json +1 -1
- package/plugins/job-coach/plugin.ts +1 -1
- package/scripts/affinity/endcard.sh +3 -2
- package/scripts/affinity/reframe.sh +3 -1
- package/scripts/ci-public-export-leak-gate.ts +48 -0
- package/scripts/codex-quota-alert.ts +122 -258
- package/scripts/coord-tracks.json +4 -4
- package/scripts/field-feed/deliver-node.ts +57 -0
- package/scripts/field-feed/feed-node.ts +67 -0
- package/scripts/field-feed/lib.ts +164 -0
- package/scripts/field-feed/reel-node.ts +24 -0
- package/scripts/field-feed/start.ts +62 -0
- package/scripts/fixtures/vflow-sample.ndjson +6 -0
- package/scripts/install.sh +109 -21
- package/scripts/landing-heal/clean-checkout.ts +114 -0
- package/scripts/landing-heal/run-mission.ts +38 -3
- package/scripts/lib/pwa-scenarios/scenarios.ts +2 -1
- package/scripts/persona-presets/lint.ts +79 -0
- package/scripts/public-export.ts +8 -4
- package/scripts/release-loop/baseline-from-logs.ts +92 -0
- package/scripts/release-loop/checklist-gate-node.ts +2 -2
- package/scripts/release-loop/env-known-failures.ts +31 -0
- package/scripts/release-loop/gate-node.ts +108 -22
- package/scripts/release-loop/known-issues-node.ts +159 -0
- package/scripts/release-loop/npm-publish-node.ts +124 -0
- package/scripts/release-loop/ops-upgrade-node.ts +95 -0
- package/scripts/release-loop/publish-node.ts +23 -1
- package/scripts/release-loop/release-readiness.ts +58 -0
- package/scripts/release-loop/shard-plan.ts +17 -0
- package/scripts/release-loop/unattended-release.ts +93 -0
- package/scripts/storyboard/storyboard.ts +167 -0
- package/scripts/test-diet/lib.ts +88 -0
- package/scripts/test-diet/node.ts +102 -0
- package/scripts/ux/measure-first-text.ts +102 -0
- package/scripts/vflow-collect.ts +20 -7
- package/scripts/vflow-search.ts +73 -0
- package/scripts/video-free-line-test-walker.ts +36 -0
- package/scripts/video-pipeline.ts +2 -2
- package/skills/explainer-video/SKILL.md +12 -9
- package/skills/explainer-video/engine/build.mjs +1 -1
- package/skills/explainer-video/engine/reel.mjs +138 -31
- package/skills/explainer-video/engine/reel.sh +2 -1
- package/skills/explainer-video/examples/elanous-0.2.5/script.json +1 -1
- package/skills/grill-me/SKILL.md +1 -1
- package/skills/omni-digest/docs/diagrams/noncode-terminal-promotion-path-2026-08-13.mmd +1 -1
- package/src/a2a/server.ts +2 -0
- package/src/acp/auth-methods.ts +30 -0
- package/src/acp/elanous-aware-peers.ts +30 -0
- package/src/acp/server.ts +96 -20
- package/src/agent/monad-agent-turn.ts +25 -6
- package/src/agent/shared-app-tools.ts +5 -3
- package/src/agent-substrate/review-intent.ts +6 -2
- package/src/agent-substrate/shard-boundary-intent.ts +70 -0
- package/src/auth/github-app-token.ts +4 -1
- package/src/autopilot/track-agent-shadow.ts +151 -0
- package/src/autopilot/track-agent.ts +23 -3
- package/src/boot/acp-server-help.ts +16 -0
- package/src/boot/daemon-prompt-turn.ts +31 -10
- package/src/boot/daemon-tools/index.ts +7 -1
- package/src/budget/codex-credit-pace.ts +84 -0
- package/src/budget/codex-credit-plan.ts +154 -0
- package/src/budget/codex-reset-credit-state.ts +2 -0
- package/src/channel-import/detect.ts +191 -0
- package/src/channel-import/run.ts +111 -0
- package/src/chat/chat-cli.ts +4 -1
- package/src/chat/short-question.ts +40 -0
- package/src/cli/agent-cli.ts +24 -15
- package/src/cli/ask-bare.ts +42 -0
- package/src/cli/cli-user-error.ts +25 -0
- package/src/cli/codex-credit-plan-view.ts +25 -0
- package/src/cli/config-test-sync.ts +4 -0
- package/src/cli/decisions-cli.ts +13 -3
- package/src/cli/dependence-gauge.ts +237 -0
- package/src/cli/doctor-bundle.ts +128 -0
- package/src/cli/doctor-cli.ts +41 -1
- package/src/cli/doctor-local-llm.ts +95 -0
- package/src/cli/doctor-modes.ts +48 -0
- package/src/cli/import-cli.ts +60 -0
- package/src/cli/intake-cli.ts +12 -2
- package/src/cli/intake-source-cli.ts +58 -0
- package/src/cli/machine-cli.ts +23 -0
- package/src/cli/mcp-login.ts +13 -4
- package/src/cli/msg-cli.ts +137 -0
- package/src/cli/persona-cli.ts +116 -0
- package/src/cli/plugin-cli.ts +25 -0
- package/src/cli/pr-cli.ts +36 -0
- package/src/cli/provider-cli.ts +5 -0
- package/src/cli/release-cli.ts +50 -8
- package/src/cli/release-run-if-ready.ts +48 -0
- package/src/cli/setup-cli.ts +27 -6
- package/src/cli/setup-recommend.ts +8 -0
- package/src/cli/skills-cli.ts +173 -0
- package/src/cli/start-cli.ts +13 -4
- package/src/cli/usage-cli.ts +11 -1
- package/src/codex/setup.ts +37 -16
- package/src/dashboard/index.ts +18 -2
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -2
- package/src/dashboard/slash-runtime/help-from-registry.ts +4 -2
- package/src/decisions/decision-cards.ts +291 -0
- package/src/decisions/decision-ledger.ts +5 -2
- package/src/decisions/discord-decision-cards.ts +133 -0
- package/src/decisions/telegram-decision-cards.ts +100 -0
- package/src/directives/version-at.ts +77 -0
- package/src/discord.ts +73 -6
- package/src/domains/core-tools.ts +37 -14
- package/src/domains/outbound-alert.ts +94 -12
- package/src/domains/repeated-failure.ts +1 -1
- package/src/error-report/report.ts +200 -0
- package/src/exec-requests/planner.ts +146 -0
- package/src/exec-requests/runner.ts +378 -0
- package/src/exec-requests/store.ts +78 -0
- package/src/field/field-feed-auto.ts +48 -0
- package/src/field/field-media.ts +8 -3
- package/src/field/field-reel.ts +203 -0
- package/src/grok/credential.ts +7 -5
- package/src/harness/aside-run-status.ts +33 -0
- package/src/harness/browser-verify.ts +48 -17
- package/src/harness/harness-cli-command.ts +25 -3
- package/src/harness/image-load-state.ts +21 -0
- package/src/harness/process-garbage.ts +45 -0
- package/src/hooks/error-report.ts +210 -0
- package/src/hooks/receiver.ts +41 -1
- package/src/import/apply.ts +128 -0
- package/src/import/plan.ts +197 -0
- package/src/index.ts +168 -21
- package/src/input-core/text-entry.ts +13 -5
- package/src/instance/leader.ts +13 -1
- package/src/intake-plane/collect-github.ts +5 -3
- package/src/intake-plane/collect-telegram-saved.ts +15 -3
- package/src/intake-plane/discord-seat-work.ts +56 -0
- package/src/intake-plane/intake-sources.ts +202 -0
- package/src/intake-plane/items.ts +28 -12
- package/src/intake-plane/seat-answer.ts +96 -0
- package/src/intake-plane/seat-doc-route.ts +221 -0
- package/src/intake-plane/submit-intake-work.ts +9 -1
- package/src/intake-plane/telegram-seat-work.ts +72 -0
- package/src/llm/model-defaults.ts +24 -1
- package/src/llm.ts +5 -4
- package/src/machines/machine-ledger.ts +127 -0
- package/src/market/pack-internal-refs.ts +28 -0
- package/src/market/publish.ts +31 -4
- package/src/maturity/feature-maturity.ts +65 -0
- package/src/model-tier/turn-tier.ts +24 -0
- package/src/msg/msg-store.ts +149 -0
- package/src/native-tool-catalog.ts +2 -1
- package/src/nexus/api/chat-fast-path-config.ts +28 -0
- package/src/nexus/api/consult-requests.ts +128 -0
- package/src/nexus/api/discord-trigger-bot.ts +3 -0
- package/src/nexus/api/error-reports.ts +108 -0
- package/src/nexus/api/exec-requests.ts +51 -0
- package/src/nexus/api/field-uploads.ts +40 -3
- package/src/nexus/api/graph-approvals-feed.ts +140 -0
- package/src/nexus/api/graph-approvals.ts +71 -4
- package/src/nexus/api/http-server.ts +82 -8
- package/src/nexus/api/outbound-report.ts +7 -0
- package/src/nexus/api/running-runs-background.ts +68 -0
- package/src/nexus/api/running-runs-child.ts +6 -0
- package/src/nexus/api/seat-requests.ts +254 -0
- package/src/nexus/api/share-captures.ts +91 -0
- package/src/nexus/api/skill-problems.ts +56 -0
- package/src/nexus/api/terminals.ts +24 -8
- package/src/nexus/api/trace-run-scan.ts +37 -0
- package/src/nexus/api/trace.ts +19 -3
- package/src/nexus/index.ts +28 -1
- package/src/nexus/setup-status.ts +11 -7
- package/src/notify/llm-budget-alert.ts +16 -10
- package/src/oauth/browser-availability.ts +14 -0
- package/src/oauth/codex-account-rotation.ts +26 -4
- package/src/oauth/codex-account-store.ts +20 -2
- package/src/oauth/codex-browser-login.ts +173 -0
- package/src/oauth/codex-quota-policy.ts +16 -0
- package/src/oauth/codex.ts +4 -1
- package/src/oauth/fallback-chain.ts +5 -5
- package/src/oauth/store.ts +42 -5
- package/src/onboarding/continue-here.ts +34 -0
- package/src/onboarding/entry-hints.ts +8 -0
- package/src/onboarding/full-screen-io.ts +35 -3
- package/src/onboarding/help/skills.md +1 -1
- package/src/onboarding/non-interactive.ts +1 -0
- package/src/onboarding/subscription-hint.ts +27 -0
- package/src/onboarding/web-first.ts +90 -0
- package/src/onboarding.ts +287 -26
- package/src/persona/presets.ts +105 -0
- package/src/plugins/maker/node-install.ts +92 -0
- package/src/plugins/maker/node-maker.ts +139 -0
- package/src/plugins/maker/plugin-maker.ts +19 -7
- package/src/policy/llm-policy.ts +268 -0
- package/src/prompt-library/session-guidance.ts +7 -1
- package/src/prompt-library/universal-preamble.ts +2 -2
- package/src/relay/relay-cli.ts +20 -0
- package/src/relay/relay-server.ts +220 -0
- package/src/release-loop/checklist.ts +37 -13
- package/src/self-dev/decompose.ts +1 -0
- package/src/self-dev/dev-pipeline.ts +6 -3
- package/src/self-dev/orchestrate.ts +18 -11
- package/src/self-dev/run-record-mirror.ts +44 -0
- package/src/self-dev/run-store.ts +12 -3
- package/src/self-implement/harness-policy-gates.ts +4 -1
- package/src/self-implement/report-origin.ts +12 -1
- package/src/session/chat.ts +17 -5
- package/src/shell-env-bootstrap.ts +44 -7
- package/src/skills/connect-claude-plugin.ts +142 -0
- package/src/skills/connect.ts +70 -0
- package/src/skills/index.ts +29 -1
- package/src/skills/repair.ts +124 -0
- package/src/skills/runner.ts +11 -2
- package/src/skills/shared-agent-skill-roots.ts +25 -0
- package/src/skills/trigger-extract.ts +1 -1
- package/src/steward/failure-streak.ts +78 -0
- package/src/steward/steward-cards.ts +24 -0
- package/src/steward/triage-budget.ts +127 -0
- package/src/steward/triage-plan.ts +75 -0
- package/src/steward/triage.ts +244 -42
- package/src/task-cards/card-store.ts +1 -1
- package/src/task-orchestrator/surfaces/pod-command-job.ts +16 -8
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
- package/src/task-orchestrator/surfaces/pod-ledger-events.ts +68 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +78 -4
- package/src/telegram-commands.ts +13 -0
- package/src/telegram.ts +72 -2
- package/src/tool-runtime/elanous-skills-list-runtime.ts +3 -3
- package/src/tool-runtime/self-cognition-runtimes.ts +160 -2
- package/src/user-config.ts +189 -15
- package/src/version/packed-revision.json +1 -1
- package/src/video-pipeline/recipes/character.ts +42 -5
- package/src/video-pipeline/recipes/ffmpeg.ts +1 -1
- package/src/video-pipeline/recipes/free-line.ts +16 -3
- package/src/video-pipeline/recipes/remote.ts +7 -1
- package/src/video-pipeline/vflow-reference.ts +109 -0
- package/apps/pwa/out/_next/static/chunks/1169-696263ac7af8c4d4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1883.c91a2648af4842fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1947-9d64872eeb9cfdec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2808-dc06482994b40f70.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3165-95515ca5f299a120.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3190-e9282753e7ebde85.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4259-99614149577e77cb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4283-329eabeaf233e6cd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4328-cb5058b1acc2de5e.js +0 -25
- package/apps/pwa/out/_next/static/chunks/4801-ecee6ef62b349b18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5110-8058791dd25a2e14.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5330-c41e9670f594c38f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5423-e95fd125d1794349.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5478-b5eae07dc79ea619.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5787-96ee6642b9a661e5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6448-79d72368589f98bc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6522-9866c002b5531836.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6729-b4a558b7aa891e0c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6969-cb03f9410b7d2e03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7007-3b9861091bb8dd21.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7930-8dc7e20fc6fc19ca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8071-187edbc22a86741f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8198-d4101ad70cb9a579.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8250.4a2c5553a07cdf47.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8293-123b91e7b0330dc0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8296-d6d5d87dfa911d41.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8305-603963c33ee8eec3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8612-4a0066f20951b8fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9181-cf4e1b61c4783f92.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9262-26c2ecbd5a67e4a2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9328-45abdebce4fbecb7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9355-dbc67205adf8745f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9763.255b90dd69875689.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9933-66d31ec25c0231af.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-bd06fb55f4da0381.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-4e586142da455a25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-889ad5cddcf45b0d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-47ba401d56572c27.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-0b54f9c83458e038.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-4fcc296ef6c8ed90.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-fe9fda494a87f4e5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-f9642e25ce129590.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e1063e8ba5f3e2e7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-9d0df30027d8aa9d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-bd06fb55f4da0381.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-d97adf2c9455b8d0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-236b80d202110362.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-21a30f35f1a276ad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-b1a5be357c4fa25d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1705a60303f7f573.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-76b337c11ad0e237.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-7c0f85fbb56f64c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-65ca16b20617b44d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-e7f9b8fc29411c1d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-92ea6213d1aeec04.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a913a005aacb5476.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-2d78804a40dfdef6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-08a99148e780ac00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-deb674e7ed36f815.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-49f8de0534d192d7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-bd06fb55f4da0381.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-ba162a28d2176e18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-1ff1a37b550ab41a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-ccaa698f56c0fbdb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-53b944082de1d7db.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-9b70adf1552789bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-30d482aa63241fe8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-aecf786f54651d8a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6ab9b62c7b6e08f2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-08949735455e0c9a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-a1479005234a46c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3890e90bd232732.js +0 -1
- package/apps/pwa/out/_next/static/chunks/beb5cf10-9adcc10b83930090.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-d68d41c88e05c801.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-11c63971b7a9593b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-d09452353bd0d9cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-184d727cebeca767.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-21bbbbac99b720b1.js +0 -1
- package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +0 -1
- package/apps/pwa/out/_next/static/qMI0jnlZaBjjm4aIDYNGM/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{qMI0jnlZaBjjm4aIDYNGM → lywc8m_HDzH9MKSDQhyEj}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// 한 행사 폴더의 업로드 디바운스와 직렬 렌더. 프로세스 안의 폴더별 단일 실행기.
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { spawn, execFileSync } from 'node:child_process';
|
|
6
|
+
import { debug } from '../debug/log.js';
|
|
7
|
+
import { listFieldMedia } from './field-media.js';
|
|
8
|
+
import { startFieldFeed, type FieldFeedStart } from './field-feed-auto.js';
|
|
9
|
+
|
|
10
|
+
export interface FieldReelResult { ok: boolean; file?: string; seconds: number; error?: string; feed?: FieldFeedStart }
|
|
11
|
+
export type FieldReelRunner = (dir: string, opts: { title: string; sub: string }) => Promise<FieldReelResult>;
|
|
12
|
+
export interface FieldReelStatus {
|
|
13
|
+
state: 'waiting' | 'rendering' | 'done' | 'failed';
|
|
14
|
+
items: number;
|
|
15
|
+
updatedAt: string;
|
|
16
|
+
startedAt?: string;
|
|
17
|
+
finishedAt?: string;
|
|
18
|
+
seconds?: number;
|
|
19
|
+
file?: string;
|
|
20
|
+
error?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface FieldReelOptions {
|
|
23
|
+
quietMs?: number;
|
|
24
|
+
title?: string;
|
|
25
|
+
sub?: string;
|
|
26
|
+
runner?: FieldReelRunner;
|
|
27
|
+
onDone?: (result: FieldReelResult) => void | Promise<void>;
|
|
28
|
+
/** 같은 수신자가 앨범 항목마다 예약해도 한 렌더당 한 번만 통지한다. */
|
|
29
|
+
notificationKey?: string;
|
|
30
|
+
/** EV10e — 렌더 성공 뒤 인스타 피드 초안 런을 띄운다(기본 켬 · `false` 나 `ELANOUS_FIELD_FEED_AUTO=0` 이면 끔). */
|
|
31
|
+
autoFeed?: boolean;
|
|
32
|
+
/** 시험용 — 피드 시작을 바꿔 끼운다. */
|
|
33
|
+
startFeed?: (dir: string) => FieldFeedStart;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const REEL_FILE = 'reel-9x16.mp4';
|
|
37
|
+
interface Job {
|
|
38
|
+
timer?: ReturnType<typeof setTimeout>;
|
|
39
|
+
running: boolean;
|
|
40
|
+
dirty: boolean;
|
|
41
|
+
lastUploadAt: number;
|
|
42
|
+
opts: FieldReelOptions;
|
|
43
|
+
callbacks: Map<string, NonNullable<FieldReelOptions['onDone']>>;
|
|
44
|
+
}
|
|
45
|
+
const jobs = new Map<string, Job>();
|
|
46
|
+
|
|
47
|
+
export function fieldReelFile(dir: string): string { return join(dir, 'reel', REEL_FILE); }
|
|
48
|
+
|
|
49
|
+
/** 요청 경로용 싼 검사 — 파일이 있고 비어 있지 않다. 디코딩 검사는 렌더가 끝날 때 한 번(`isValidFieldReelFile`)만 한다. */
|
|
50
|
+
export function hasFieldReelFile(file: string): boolean {
|
|
51
|
+
try {
|
|
52
|
+
const stat = statSync(file);
|
|
53
|
+
return stat.isFile() && stat.size > 0;
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Publish only a reel with a decodable video frame, not merely valid-looking MP4 boxes.
|
|
60
|
+
* 렌더 완료 때 한 번만 부른다 — 첫 영상 프레임 하나만 읽는다(`-read_intervals %+#1`) · 긴 영상도 출력이 작다. */
|
|
61
|
+
export function isValidFieldReelFile(file: string): boolean {
|
|
62
|
+
try {
|
|
63
|
+
if (!hasFieldReelFile(file)) return false;
|
|
64
|
+
const probe = JSON.parse(execFileSync('ffprobe', [
|
|
65
|
+
'-v', 'error', '-select_streams', 'v:0', '-read_intervals', '%+#1', '-show_streams', '-show_frames',
|
|
66
|
+
'-show_entries', 'stream=codec_type:frame=media_type', '-of', 'json', file,
|
|
67
|
+
], { encoding: 'utf8', timeout: 30_000, maxBuffer: 2 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'] })) as {
|
|
68
|
+
streams?: Array<{ codec_type?: string }>;
|
|
69
|
+
frames?: Array<{ media_type?: string }>;
|
|
70
|
+
};
|
|
71
|
+
return probe.streams?.some((stream) => stream.codec_type === 'video') === true
|
|
72
|
+
&& probe.frames?.some((frame) => frame.media_type === 'video') === true;
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** 상태 파일은 `reel/` «밖»에 둔다 — `reel.sh` 가 시작할 때 `reel/` 을 지우므로 안에 두면 렌더 중에 사라진다. */
|
|
78
|
+
export function fieldReelStatusPath(dir: string): string { return join(dir, '.reel-status.json'); }
|
|
79
|
+
const statusPath = fieldReelStatusPath;
|
|
80
|
+
function writeStatus(dir: string, status: Omit<FieldReelStatus, 'updatedAt'>): void {
|
|
81
|
+
mkdirSync(dir, { recursive: true });
|
|
82
|
+
const tmp = join(dir, `.reel-status-${randomUUID()}`);
|
|
83
|
+
try {
|
|
84
|
+
writeFileSync(tmp, JSON.stringify({ ...status, updatedAt: new Date().toISOString() }));
|
|
85
|
+
renameSync(tmp, statusPath(dir));
|
|
86
|
+
} finally {
|
|
87
|
+
if (existsSync(tmp)) unlinkSync(tmp);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function readFieldReelStatus(dir: string): FieldReelStatus | null {
|
|
92
|
+
try { return JSON.parse(readFileSync(statusPath(dir), 'utf8')) as FieldReelStatus; }
|
|
93
|
+
catch (err) {
|
|
94
|
+
if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const defaultFieldReelRunner: FieldReelRunner = (dir, { title, sub }) => new Promise((finish) => {
|
|
100
|
+
const script = resolve(import.meta.dir, '../../skills/explainer-video/engine/reel.sh');
|
|
101
|
+
const started = Date.now();
|
|
102
|
+
const child = spawn('zsh', [script, dir, '--title', title, '--sub', sub], { stdio: 'ignore' });
|
|
103
|
+
let settled = false;
|
|
104
|
+
const done = (error?: string) => {
|
|
105
|
+
if (settled) return;
|
|
106
|
+
settled = true;
|
|
107
|
+
const file = fieldReelFile(dir);
|
|
108
|
+
const seconds = Math.round((Date.now() - started) / 1000);
|
|
109
|
+
// 디코딩 검사(ffprobe)는 `render()` 한 곳에서만 한다 — 여기선 존재만 본다(리뷰: 검사 두 번 금지).
|
|
110
|
+
finish(error || !hasFieldReelFile(file)
|
|
111
|
+
? { ok: false, seconds, error: error ?? `invalid or missing MP4 output: ${file}` }
|
|
112
|
+
: { ok: true, file, seconds });
|
|
113
|
+
};
|
|
114
|
+
child.once('error', (err) => done(err.message));
|
|
115
|
+
child.once('close', (code) => done(code === 0 ? undefined : `reel.sh exited ${code ?? 'unknown'}`));
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
function arm(dir: string, job: Job): void {
|
|
119
|
+
if (job.timer) clearTimeout(job.timer);
|
|
120
|
+
job.timer = setTimeout(() => {
|
|
121
|
+
job.timer = undefined;
|
|
122
|
+
if (!job.running) void render(dir, job);
|
|
123
|
+
}, Math.max(0, (job.opts.quietMs ?? 20_000) - (Date.now() - job.lastUploadAt)));
|
|
124
|
+
job.timer.unref?.();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function render(dir: string, job: Job): Promise<void> {
|
|
128
|
+
job.running = true;
|
|
129
|
+
job.dirty = false;
|
|
130
|
+
const items = listFieldMedia(dir).length;
|
|
131
|
+
const startedAt = new Date().toISOString();
|
|
132
|
+
const event = dir.split(/[\\/]/).at(-1)!;
|
|
133
|
+
// 현재 렌더의 수신자 스냅샷. 렌더 중 업로드의 수신자는 다음 렌더로 넘긴다.
|
|
134
|
+
const callbacks = job.callbacks;
|
|
135
|
+
const renderOpts = job.opts;
|
|
136
|
+
job.callbacks = new Map();
|
|
137
|
+
writeStatus(dir, { state: 'rendering', items, startedAt });
|
|
138
|
+
mkdirSync(join(dir, 'reel'), { recursive: true }); // 실행기 계약 — 산출 폴더는 있다(reel.sh 는 스스로 다시 만든다)
|
|
139
|
+
debug.log('field.reel', 'started', { event, items });
|
|
140
|
+
let result: FieldReelResult;
|
|
141
|
+
try {
|
|
142
|
+
const date = new Date();
|
|
143
|
+
const sub = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
|
144
|
+
let titleLines: string[] = [];
|
|
145
|
+
try { titleLines = readFileSync(join(dir, 'title.txt'), 'utf8').split(/\r?\n/); }
|
|
146
|
+
catch (err) { if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err; }
|
|
147
|
+
result = await (renderOpts.runner ?? defaultFieldReelRunner)(dir, {
|
|
148
|
+
title: titleLines[0]?.trim() || renderOpts.title || event,
|
|
149
|
+
sub: titleLines[1]?.trim() || renderOpts.sub || sub,
|
|
150
|
+
});
|
|
151
|
+
} catch (err) {
|
|
152
|
+
result = { ok: false, seconds: 0, error: err instanceof Error ? err.message : String(err) };
|
|
153
|
+
}
|
|
154
|
+
if (result.ok && (!result.file || resolve(result.file) !== fieldReelFile(dir) || !isValidFieldReelFile(result.file))) {
|
|
155
|
+
result = { ok: false, seconds: result.seconds, error: 'render output invalid or missing' };
|
|
156
|
+
}
|
|
157
|
+
const finishedAt = new Date().toISOString();
|
|
158
|
+
writeStatus(dir, { state: result.ok ? 'done' : 'failed', items, startedAt, finishedAt,
|
|
159
|
+
seconds: result.seconds, ...(result.ok ? { file: result.file } : { error: result.error ?? 'render failed' }) });
|
|
160
|
+
debug.log('field.reel', result.ok ? 'done' : 'failed', { event, items, seconds: result.seconds });
|
|
161
|
+
if (result.ok) {
|
|
162
|
+
// EV10b: 엔진이 timeline.json 에 남긴 비전 호출 수·성공·«현장 N» 강등·걸린 ms — codex 잔량과 겹치므로 관측한다(OP 10-01).
|
|
163
|
+
try {
|
|
164
|
+
const t = JSON.parse(readFileSync(join(dir, 'reel', 'timeline.json'), 'utf8')) as { vision?: Record<string, unknown>; music?: unknown };
|
|
165
|
+
if (t.vision) debug.log('field.reel', 'vision', { event, ...t.vision, music: t.music === true });
|
|
166
|
+
} catch { debug.log('field.reel', 'vision-unread', { event }); }
|
|
167
|
+
}
|
|
168
|
+
// 뒤이은 업로드로 다시 렌더할 예정이면(dirty) 피드는 그 마지막 렌더 뒤에 한 번만 띄운다.
|
|
169
|
+
if (result.ok && !job.dirty) result = { ...result, feed: (renderOpts.startFeed ?? ((d: string) => startFieldFeed(d, { enabled: renderOpts.autoFeed })))(dir) };
|
|
170
|
+
job.running = false;
|
|
171
|
+
if (job.dirty) {
|
|
172
|
+
writeStatus(dir, { state: 'waiting', items: listFieldMedia(dir).length });
|
|
173
|
+
arm(dir, job);
|
|
174
|
+
} else {
|
|
175
|
+
jobs.delete(dir);
|
|
176
|
+
}
|
|
177
|
+
for (const cb of callbacks.values()) {
|
|
178
|
+
void Promise.resolve().then(() => cb(result)).catch((err) => {
|
|
179
|
+
debug.log('field.reel', 'delivery-failed', { event, error: String(err) });
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** 저장 성공 직후 호출한다. quiet 기간의 연속 업로드를 합치고 렌더 중 변경은 뒤이어 다시 렌더한다. */
|
|
185
|
+
export function scheduleFieldReel(dir: string, opts: FieldReelOptions = {}): void {
|
|
186
|
+
dir = resolve(dir);
|
|
187
|
+
const event = dir.split(/[\\/]/).at(-1)!;
|
|
188
|
+
let job = jobs.get(dir);
|
|
189
|
+
if (!job) {
|
|
190
|
+
job = { running: false, dirty: false, lastUploadAt: Date.now(), opts, callbacks: new Map() };
|
|
191
|
+
jobs.set(dir, job);
|
|
192
|
+
}
|
|
193
|
+
job.opts = { ...opts, runner: opts.runner ?? job.opts.runner, quietMs: opts.quietMs ?? job.opts.quietMs };
|
|
194
|
+
job.lastUploadAt = Date.now();
|
|
195
|
+
if (opts.onDone) job.callbacks.set(opts.notificationKey ?? 'default', opts.onDone);
|
|
196
|
+
const items = listFieldMedia(dir).length;
|
|
197
|
+
if (job.running) job.dirty = true;
|
|
198
|
+
else {
|
|
199
|
+
writeStatus(dir, { state: 'waiting', items });
|
|
200
|
+
arm(dir, job);
|
|
201
|
+
}
|
|
202
|
+
debug.log('field.reel', 'scheduled', { event, items });
|
|
203
|
+
}
|
package/src/grok/credential.ts
CHANGED
|
@@ -35,11 +35,13 @@ export const GROK_SUBSCRIPTION_BASE_URL = 'https://cli-chat-proxy.grok.com/v1';
|
|
|
35
35
|
/** API 키 경로 베이스(토큰 과금). */
|
|
36
36
|
export const GROK_API_BASE_URL = 'https://api.x.ai/v1';
|
|
37
37
|
|
|
38
|
-
/** 프록시가 요구하는 최소 CLI 버전(실측 426 문면). 이보다 낮으면 거절된다.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
/** 프록시가 요구하는 최소 CLI 버전(실측 426 문면). 이보다 낮으면 거절된다.
|
|
39
|
+
* 2026-10-01 실측: «Your Grok CLI version (0.1.202) is outdated. Please update to version 1.0.13 or later». */
|
|
40
|
+
export const GROK_PROXY_MIN_CLI_VERSION = '1.0.13';
|
|
41
|
+
/** 설치본을 못 읽었을 때(grok CLI 없는 기계 · 파드) 신고할 값 — 하한이 아니라 실측된 최근 설치본.
|
|
42
|
+
* ⛔ 하한을 쓰면 프록시가 하한을 «올리는» 날 전부 426 이 된다(10-01 파드 자식이 0.1.202 로 전부 거절됨).
|
|
43
|
+
* 그래서 아래 `detectGrokCliVersion()` 이 1순위다. */
|
|
44
|
+
const GROK_PROXY_FALLBACK_CLI_VERSION = '1.0.41';
|
|
43
45
|
|
|
44
46
|
/** 설치된 grok CLI 의 버전. ⭐ 하드코딩 하한 대신 «실제 설치본»을 신고한다.
|
|
45
47
|
*
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { stripScreenAnsi } from './harness-screen.js';
|
|
2
|
+
|
|
3
|
+
export type AsideRunStatus = {
|
|
4
|
+
kind: 'ok' | 'not-running' | 'auth-timeout' | 'repl-error' | 'no-marker';
|
|
5
|
+
hint?: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function classifyAsideRun({ stdout, stderr, exitCode, timedOut = false }: {
|
|
9
|
+
stdout: string;
|
|
10
|
+
stderr: string;
|
|
11
|
+
exitCode: number | null;
|
|
12
|
+
/** We killed `aside` at our own timeout. */
|
|
13
|
+
timedOut?: boolean;
|
|
14
|
+
}): AsideRunStatus {
|
|
15
|
+
const diagnosticStdout = stripScreenAnsi(stdout).split(/\r?\n/).filter((line) => {
|
|
16
|
+
try { JSON.parse(line.trim()); return false; } catch { return true; }
|
|
17
|
+
}).join('\n');
|
|
18
|
+
const diagnostics = `${diagnosticStdout}\n${stripScreenAnsi(stderr)}`;
|
|
19
|
+
if (exitCode !== 0 && /Aside isn't running/i.test(diagnostics)) {
|
|
20
|
+
return { kind: 'not-running', hint: 'Aside 브라우저가 안 떠 있음 — Aside 브라우저를 켜고 다시 · 또는 `aside exec --host <host>`' };
|
|
21
|
+
}
|
|
22
|
+
if (exitCode !== 0 && /daemon auth challenge/i.test(diagnostics) && /timeout|timed out/i.test(diagnostics)) {
|
|
23
|
+
return { kind: 'auth-timeout', hint: 'Aside 인증 요청 시간 초과 — Aside 브라우저를 켜고 다시 · 또는 `aside exec --host <host>`' };
|
|
24
|
+
}
|
|
25
|
+
// Killed by our timeout with nothing printed: aside never answered — the browser daemon is not reachable.
|
|
26
|
+
if (timedOut && !stripScreenAnsi(stdout).trim()) {
|
|
27
|
+
return { kind: 'auth-timeout', hint: 'Aside 가 응답하지 않음 — Aside 브라우저가 떠 있는지 확인하고 다시 · 또는 `aside exec --host <host>`' };
|
|
28
|
+
}
|
|
29
|
+
const marker = stripScreenAnsi(stdout).trimEnd().match(/\[(ok|error)\s*\|[^\]]+\]\s*$/);
|
|
30
|
+
if (marker?.[1] === 'error') return { kind: 'repl-error' };
|
|
31
|
+
if (marker?.[1] === 'ok') return { kind: 'ok' };
|
|
32
|
+
return { kind: 'no-marker' };
|
|
33
|
+
}
|
|
@@ -16,9 +16,12 @@ import { createCdpClientFromEndpoint, CdpUnavailable, type CdpClient } from '../
|
|
|
16
16
|
import { captureWithTimeout, describeCaptureFailure } from './browser-capture-timeout.js';
|
|
17
17
|
import { debug } from '../debug/log.js';
|
|
18
18
|
import { stripScreenAnsi } from './harness-screen.js';
|
|
19
|
+
import { classifyAsideRun, type AsideRunStatus } from './aside-run-status.js';
|
|
20
|
+
import { IMAGE_LOAD_STATE_SOURCE } from './image-load-state.js';
|
|
19
21
|
|
|
20
22
|
const execFileAsync = promisify(execFile);
|
|
21
|
-
|
|
23
|
+
// Longer than aside's own ~10 s daemon auth-challenge timeout, so «Aside isn't running» reaches stderr before we kill it (10-01 live).
|
|
24
|
+
const DEFAULT_ASIDE_TIMEOUT_MS = 30_000;
|
|
22
25
|
// Observed load events and complete page content arrive within this window; timeout remains non-blocking.
|
|
23
26
|
const CDP_LOAD_WAIT_TIMEOUT_MS = 3_000;
|
|
24
27
|
|
|
@@ -74,6 +77,8 @@ export interface DeployVerifyResult {
|
|
|
74
77
|
title?: string;
|
|
75
78
|
bodyLength?: number;
|
|
76
79
|
screenshotBytes?: number;
|
|
80
|
+
/** 아직 화면 밖이라 요청되지 않은 lazy 그림 수(실패가 아님). */
|
|
81
|
+
pendingLazyImages?: number;
|
|
77
82
|
/** 렌더 문제(빈 페이지·에러 등). ok=false 면 최소 1건. */
|
|
78
83
|
findings: string[];
|
|
79
84
|
/** findings와 병행하는 등급 있는 판정. 정상·CDP 미가용 결과에는 생략해 기존 결과 모양을 보존한다. */
|
|
@@ -81,10 +86,10 @@ export interface DeployVerifyResult {
|
|
|
81
86
|
/** 측정하지 못한 선택적 신호. 빈 배열은 생략하므로 문제 없음과 구별된다. */
|
|
82
87
|
unmeasured?: DeployUnmeasuredCheck[];
|
|
83
88
|
/** 관측 백엔드 미가용으로 검증 스킵됨(배포는 막지 않음). */
|
|
84
|
-
skipped?: 'no-cdp' | 'no-aside';
|
|
89
|
+
skipped?: 'no-cdp' | 'no-aside' | 'aside-not-running';
|
|
85
90
|
}
|
|
86
91
|
|
|
87
|
-
export type AsideRunner = (command: string, args: string[], options: { timeout: number }) => Promise<{ stdout: string }>;
|
|
92
|
+
export type AsideRunner = (command: string, args: string[], options: { timeout: number }) => Promise<{ stdout: string; stderr?: string; exitCode?: number | null; timedOut?: boolean }>;
|
|
88
93
|
|
|
89
94
|
export interface DeployVerifyDeps {
|
|
90
95
|
/** 기본 CDP 또는 별도 브라우저 세션을 쓰는 aside. */
|
|
@@ -121,9 +126,18 @@ interface PageState {
|
|
|
121
126
|
title: string;
|
|
122
127
|
bodyLength: number;
|
|
123
128
|
unloadedImageCount: number;
|
|
129
|
+
pendingLazyImages: number;
|
|
124
130
|
consecutiveDuplicateText: string | null;
|
|
125
131
|
}
|
|
126
132
|
|
|
133
|
+
const IMAGE_COUNTS_EXPRESSION = `(${IMAGE_LOAD_STATE_SOURCE})(Array.from(document.images, (image) => {
|
|
134
|
+
const rect = image.getBoundingClientRect();
|
|
135
|
+
return {
|
|
136
|
+
complete: image.complete, naturalWidth: image.naturalWidth, loading: image.loading,
|
|
137
|
+
inViewport: rect.bottom > 0 && rect.right > 0 && rect.top < window.innerHeight && rect.left < window.innerWidth,
|
|
138
|
+
};
|
|
139
|
+
}))`;
|
|
140
|
+
|
|
127
141
|
const PAGE_STATE_EXPRESSION = `(() => {
|
|
128
142
|
const isVisible = (element) => {
|
|
129
143
|
for (let current = element; current; current = current.parentElement) {
|
|
@@ -141,10 +155,12 @@ const PAGE_STATE_EXPRESSION = `(() => {
|
|
|
141
155
|
}
|
|
142
156
|
}
|
|
143
157
|
const consecutiveDuplicateText = textUnits.find((text, index) => text === textUnits[index + 1]) ?? null;
|
|
158
|
+
const imageCounts = ${IMAGE_COUNTS_EXPRESSION};
|
|
144
159
|
return {
|
|
145
160
|
title: document.title,
|
|
146
161
|
bodyLength: document.body ? document.body.innerText.length : 0,
|
|
147
|
-
unloadedImageCount:
|
|
162
|
+
unloadedImageCount: imageCounts.broken,
|
|
163
|
+
pendingLazyImages: imageCounts.pendingLazy,
|
|
148
164
|
consecutiveDuplicateText,
|
|
149
165
|
};
|
|
150
166
|
})()`;
|
|
@@ -169,7 +185,7 @@ function buildAsideScript(url: string): string {
|
|
|
169
185
|
' const page = await openTab(url);',
|
|
170
186
|
' try {',
|
|
171
187
|
' const title = await page.title();',
|
|
172
|
-
|
|
188
|
+
` const state = await page.evaluate(() => { const imageCounts = ${IMAGE_COUNTS_EXPRESSION}; return { bodyLength: document.body?.innerText.length ?? 0, unloadedImageCount: imageCounts.broken, pendingLazyImages: imageCounts.pendingLazy }; });`,
|
|
173
189
|
' const screenshot = await page.screenshot({ fullPage: true });',
|
|
174
190
|
' console.log(JSON.stringify({ title, ...state, screenshotBytes: screenshot?.length ?? 0 }));',
|
|
175
191
|
' } finally { try { await closeTab(page); } catch {} }',
|
|
@@ -177,7 +193,7 @@ function buildAsideScript(url: string): string {
|
|
|
177
193
|
].join('\n');
|
|
178
194
|
}
|
|
179
195
|
|
|
180
|
-
function parseAsideObservation(stdout: string): { title: string; bodyLength: number; unloadedImageCount: number; screenshotBytes: number } {
|
|
196
|
+
function parseAsideObservation(stdout: string): { title: string; bodyLength: number; unloadedImageCount: number; pendingLazyImages: number; screenshotBytes: number } {
|
|
181
197
|
const normalizedStdout = stripScreenAnsi(stdout);
|
|
182
198
|
const verdict = normalizedStdout.trimEnd().match(/\[(ok|error)\s*\|[^\]]+\]\s*$/);
|
|
183
199
|
if (!verdict) throw new Error('aside observation output missing completion marker');
|
|
@@ -189,16 +205,17 @@ function parseAsideObservation(stdout: string): { title: string; bodyLength: num
|
|
|
189
205
|
const result: unknown = JSON.parse(line.trim());
|
|
190
206
|
parsedJson = true;
|
|
191
207
|
if (!result || typeof result !== 'object' || Array.isArray(result)) throw new Error('aside observation JSON must be an object');
|
|
192
|
-
const { title, bodyLength, unloadedImageCount, screenshotBytes } = result as Record<string, unknown>;
|
|
208
|
+
const { title, bodyLength, unloadedImageCount, pendingLazyImages = 0, screenshotBytes } = result as Record<string, unknown>;
|
|
193
209
|
if (
|
|
194
210
|
typeof title !== 'string'
|
|
195
211
|
|| typeof bodyLength !== 'number' || !Number.isFinite(bodyLength) || bodyLength < 0
|
|
196
212
|
|| typeof unloadedImageCount !== 'number' || !Number.isFinite(unloadedImageCount) || unloadedImageCount < 0
|
|
213
|
+
|| typeof pendingLazyImages !== 'number' || !Number.isFinite(pendingLazyImages) || pendingLazyImages < 0
|
|
197
214
|
|| typeof screenshotBytes !== 'number' || !Number.isFinite(screenshotBytes) || screenshotBytes < 0
|
|
198
215
|
) {
|
|
199
216
|
throw new Error('aside observation JSON has invalid required fields');
|
|
200
217
|
}
|
|
201
|
-
return { title, bodyLength, unloadedImageCount, screenshotBytes };
|
|
218
|
+
return { title, bodyLength, unloadedImageCount, pendingLazyImages, screenshotBytes };
|
|
202
219
|
} catch (error) {
|
|
203
220
|
if (parsedJson) throw error;
|
|
204
221
|
}
|
|
@@ -206,9 +223,9 @@ function parseAsideObservation(stdout: string): { title: string; bodyLength: num
|
|
|
206
223
|
throw new Error('aside observation output missing JSON');
|
|
207
224
|
}
|
|
208
225
|
|
|
209
|
-
function asideErrorResult(url: string, error: unknown): DeployVerifyResult {
|
|
226
|
+
function asideErrorResult(url: string, error: unknown, kind?: AsideRunStatus['kind']): DeployVerifyResult {
|
|
210
227
|
const message = error instanceof Error ? error.message : String(error);
|
|
211
|
-
const errorFinding = finding('aside-error', `aside 검증 오류: ${message.slice(0, 120)}`, 'confirmed');
|
|
228
|
+
const errorFinding = finding('aside-error', `aside 검증 오류: ${message.slice(0, 120)}${kind ? ` (${kind})` : ''}`, 'confirmed');
|
|
212
229
|
observe('verify-aside-failed', { url: url.slice(0, 80), error: message.slice(0, 120) });
|
|
213
230
|
return { ok: false, url, findings: [errorFinding.message], structuredFindings: [errorFinding], unmeasured: ['javascript-errors'] };
|
|
214
231
|
}
|
|
@@ -224,15 +241,21 @@ async function verifyAsidePage(url: string, deps: DeployVerifyDeps): Promise<Dep
|
|
|
224
241
|
const runAside = deps.runAside ?? (async (command, args, options) => {
|
|
225
242
|
try {
|
|
226
243
|
const result = await execFileAsync(command, args, { ...options, encoding: 'utf8' });
|
|
227
|
-
return { stdout: result.stdout };
|
|
244
|
+
return { stdout: result.stdout, stderr: result.stderr, exitCode: 0 };
|
|
228
245
|
} catch (error) {
|
|
229
246
|
if ((error as NodeJS.ErrnoException).code === 'ENOENT') throw error;
|
|
230
|
-
const stdout =
|
|
231
|
-
if (typeof stdout === 'string'
|
|
247
|
+
const { stdout, stderr, code, killed, signal } = error as { stdout?: unknown; stderr?: unknown; code?: unknown; killed?: unknown; signal?: unknown };
|
|
248
|
+
if (typeof stdout === 'string' || typeof stderr === 'string') {
|
|
249
|
+
return {
|
|
250
|
+
stdout: typeof stdout === 'string' ? stdout : '', stderr: typeof stderr === 'string' ? stderr : '',
|
|
251
|
+
exitCode: typeof code === 'number' ? code : null,
|
|
252
|
+
...(killed === true || typeof signal === 'string' ? { timedOut: true } : {}),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
232
255
|
throw error;
|
|
233
256
|
}
|
|
234
257
|
});
|
|
235
|
-
let observation:
|
|
258
|
+
let observation: Awaited<ReturnType<AsideRunner>>;
|
|
236
259
|
try {
|
|
237
260
|
observation = await runAside(deps.asideCommand ?? 'aside', ['repl', script], { timeout: deps.asideTimeoutMs ?? DEFAULT_ASIDE_TIMEOUT_MS });
|
|
238
261
|
} catch (error) {
|
|
@@ -242,13 +265,20 @@ async function verifyAsidePage(url: string, deps: DeployVerifyDeps): Promise<Dep
|
|
|
242
265
|
}
|
|
243
266
|
return asideErrorResult(url, error);
|
|
244
267
|
}
|
|
268
|
+
const status = classifyAsideRun({ stdout: observation.stdout, stderr: observation.stderr ?? '', exitCode: observation.exitCode ?? null, timedOut: observation.timedOut === true });
|
|
269
|
+
if (status.kind === 'not-running' || status.kind === 'auth-timeout') {
|
|
270
|
+
observe('verify-aside-skipped', { url, kind: status.kind });
|
|
271
|
+
return { ok: false, url, skipped: 'aside-not-running', findings: [status.hint ?? 'Aside 브라우저를 켜고 다시'], unmeasured: ['javascript-errors', 'screenshot'] };
|
|
272
|
+
}
|
|
273
|
+
if (status.kind === 'repl-error') return asideErrorResult(url, new Error('aside repl reported error'), status.kind);
|
|
274
|
+
if (status.kind === 'no-marker') return asideErrorResult(url, new Error('aside observation output missing completion marker'), status.kind);
|
|
245
275
|
const state = parseAsideObservation(observation.stdout);
|
|
246
276
|
const structuredFindings: DeployVerifyFinding[] = [];
|
|
247
277
|
if (state.bodyLength < minBody) structuredFindings.push(finding('empty-body', `페이지 본문이 비어있음(len=${state.bodyLength}·렌더 실패 의심)`, 'suspected'));
|
|
248
278
|
if (state.unloadedImageCount) structuredFindings.push(finding('unloaded-image', `로드되지 못한 그림 ${state.unloadedImageCount}개`, 'confirmed'));
|
|
249
279
|
if (!state.title.trim()) structuredFindings.push(finding('empty-title', '문서 제목이 비어있음', 'confirmed'));
|
|
250
280
|
const findings = structuredFindings.map(({ message }) => message);
|
|
251
|
-
const result: DeployVerifyResult = { ok: findings.length === 0, url, title: state.title, bodyLength: state.bodyLength, screenshotBytes: state.screenshotBytes, findings, unmeasured: ['javascript-errors'], ...(structuredFindings.length ? { structuredFindings } : {}) };
|
|
281
|
+
const result: DeployVerifyResult = { ok: findings.length === 0, url, title: state.title, bodyLength: state.bodyLength, screenshotBytes: state.screenshotBytes, findings, unmeasured: ['javascript-errors'], ...(state.pendingLazyImages ? { pendingLazyImages: state.pendingLazyImages } : {}), ...(structuredFindings.length ? { structuredFindings } : {}) };
|
|
252
282
|
observe('verified-aside', { url: url.slice(0, 80), ok: result.ok, bodyLength: state.bodyLength, bytes: state.screenshotBytes, findings: findings.length, unmeasured: result.unmeasured });
|
|
253
283
|
return result;
|
|
254
284
|
} catch (error) {
|
|
@@ -334,6 +364,7 @@ async function verifyCdpPage(url: string, deps: DeployVerifyDeps): Promise<Deplo
|
|
|
334
364
|
return {
|
|
335
365
|
ok, url, title, bodyLength, screenshotBytes: buf?.length ?? 0, findings,
|
|
336
366
|
...(structuredFindings.length ? { structuredFindings } : {}),
|
|
367
|
+
...(state?.pendingLazyImages ? { pendingLazyImages: state.pendingLazyImages } : {}),
|
|
337
368
|
...(unmeasured ? { unmeasured } : {}),
|
|
338
369
|
};
|
|
339
370
|
} catch (e) {
|
|
@@ -362,9 +393,9 @@ export function formatVerifyUrlReport(
|
|
|
362
393
|
const shotNote = shot === undefined ? '' : shot.written ? ` → ${shot.path}` : ` → ⛔ 못 씀(${shot.path})`;
|
|
363
394
|
const lines = [
|
|
364
395
|
`\n━━ 배포 검증: ${url} ━━`,
|
|
365
|
-
` ${result.ok ? '✅ 렌더 정상' : '⚠️ 문제 감지'}`,
|
|
396
|
+
` ${result.skipped === 'aside-not-running' ? '⚪ Aside 검증 건너뜀' : result.ok ? '✅ 렌더 정상' : '⚠️ 문제 감지'}`,
|
|
366
397
|
` title: ${result.title || '(없음)'}`,
|
|
367
|
-
` 본문 길이: ${result.bodyLength ?? '?'} · 스크린샷: ${result.screenshotBytes ?? 0} bytes${shotNote}`,
|
|
398
|
+
` 본문 길이: ${result.bodyLength ?? '?'} · 스크린샷: ${result.skipped === 'aside-not-running' ? '못 쟀다' : `${result.screenshotBytes ?? 0} bytes`}${shotNote}`,
|
|
368
399
|
];
|
|
369
400
|
for (const f of result.findings) lines.push(` - ${f}`);
|
|
370
401
|
// ⛔⭐ 「못 쟀다」는 초록도 빨강도 아닌 «제 칸»이다 — 「없다」로 접지 않는다.
|
|
@@ -21,6 +21,7 @@ import { getUserConfig } from '../user-config.js';
|
|
|
21
21
|
import { installHarnessCliSinkHook } from './harness-cli-sink.js';
|
|
22
22
|
import { resolveHarnessSubstrate, type ResolvedHarnessSubstrate } from './harness-substrate-default.js';
|
|
23
23
|
import { runHarnessPlanRfc } from './harness-plan-rfc.js';
|
|
24
|
+
import { classifyGarbage, isGarbageProcessTarget, type GarbageProcess } from './process-garbage.js';
|
|
24
25
|
import type { MissionSolveOutcome } from './mission-solve-loop.js';
|
|
25
26
|
import { formatAxis, formatAxisObservations, inspectAskMarkers, inspectUnpressedDecisionSignals } from '../../scripts/ask-marker-check.js';
|
|
26
27
|
|
|
@@ -75,6 +76,7 @@ export class HarnessCliInputError extends Error {
|
|
|
75
76
|
constructor(message: string) {
|
|
76
77
|
super(message);
|
|
77
78
|
this.name = 'HarnessCliInputError';
|
|
79
|
+
Object.defineProperty(this, Symbol.for('elanous.cli.HarnessCliInputError'), { value: true });
|
|
78
80
|
}
|
|
79
81
|
}
|
|
80
82
|
|
|
@@ -565,6 +567,7 @@ export interface HarnessProcessReport {
|
|
|
565
567
|
readonly resourceConsuming: readonly HarnessProcessObservationRow[];
|
|
566
568
|
readonly longRunningOnly: readonly HarnessProcessObservationRow[];
|
|
567
569
|
readonly parentUnknown: readonly HarnessProcessObservationRow[];
|
|
570
|
+
readonly garbage: readonly GarbageProcess[];
|
|
568
571
|
}
|
|
569
572
|
|
|
570
573
|
export interface HarnessProcessObservationDeps {
|
|
@@ -762,16 +765,22 @@ export function buildHarnessProcessReport(
|
|
|
762
765
|
resourceConsuming: [],
|
|
763
766
|
longRunningOnly: [],
|
|
764
767
|
parentUnknown: [],
|
|
768
|
+
garbage: [],
|
|
765
769
|
};
|
|
766
770
|
}
|
|
767
771
|
const livePids = new Set(listed.livePids ?? listed.records.map((record) => record.pid));
|
|
768
772
|
const universe = listed.pidUniverse ?? pidUniverse;
|
|
773
|
+
const garbage = classifyGarbage(listed.records.map((record) => ({
|
|
774
|
+
...record,
|
|
775
|
+
launchd: resolveHarnessProcessLaunchdEvidence(record.pid, launchd),
|
|
776
|
+
})), { nowMs }).garbage;
|
|
769
777
|
const resourceConsuming: HarnessProcessObservationRow[] = [];
|
|
770
778
|
const longRunningOnly: HarnessProcessObservationRow[] = [];
|
|
771
779
|
const parentUnknown: HarnessProcessObservationRow[] = [];
|
|
772
780
|
let unclassifiedCount = 0;
|
|
773
781
|
let parentPresentCount = 0;
|
|
774
782
|
for (const record of listed.records) {
|
|
783
|
+
if (!record.command.includes('elanous.mjs')) continue;
|
|
775
784
|
const classification = classifyHarnessProcess(record, thresholds);
|
|
776
785
|
if (classification === undefined) {
|
|
777
786
|
unclassifiedCount += 1;
|
|
@@ -811,6 +820,7 @@ export function buildHarnessProcessReport(
|
|
|
811
820
|
resourceConsuming,
|
|
812
821
|
longRunningOnly,
|
|
813
822
|
parentUnknown,
|
|
823
|
+
garbage,
|
|
814
824
|
};
|
|
815
825
|
}
|
|
816
826
|
return {
|
|
@@ -822,6 +832,7 @@ export function buildHarnessProcessReport(
|
|
|
822
832
|
resourceConsuming,
|
|
823
833
|
longRunningOnly,
|
|
824
834
|
parentUnknown,
|
|
835
|
+
garbage,
|
|
825
836
|
};
|
|
826
837
|
}
|
|
827
838
|
|
|
@@ -855,6 +866,13 @@ function renderHarnessProcessRow(row: HarnessProcessObservationRow): string[] {
|
|
|
855
866
|
];
|
|
856
867
|
}
|
|
857
868
|
|
|
869
|
+
function renderGarbageGroup(garbage: readonly GarbageProcess[]): string[] {
|
|
870
|
+
return [
|
|
871
|
+
`가비지 ${garbage.length}:`,
|
|
872
|
+
...garbage.map((row) => `- pid=${row.pid} ppid=${row.ppid} elapsed=${formatHarnessProcessElapsed(row.elapsedSeconds)} reason=${row.reason} command=${row.command.slice(0, 120)}`),
|
|
873
|
+
];
|
|
874
|
+
}
|
|
875
|
+
|
|
858
876
|
export function renderHarnessProcessReport(report: HarnessProcessReport): string[] {
|
|
859
877
|
const { resourceCpuPercent, longRunningElapsedSeconds } = report.thresholds;
|
|
860
878
|
const lines = [
|
|
@@ -868,6 +886,7 @@ export function renderHarnessProcessReport(report: HarnessProcessReport): string
|
|
|
868
886
|
const failure = report.observationFailure;
|
|
869
887
|
lines.push(`관찰 실패/확인 불가 (${failure?.stage ?? 'ps-exec'}: ${failure?.reason ?? 'unknown observation failure'})`);
|
|
870
888
|
lines.push('자원소비와 장기실행만은 확인하지 않았다 — 빈 관찰이 아니다');
|
|
889
|
+
lines.push('가비지 확인 불가 — ps 관찰 실패');
|
|
871
890
|
return lines;
|
|
872
891
|
}
|
|
873
892
|
if (report.observationStatus === 'incomplete') {
|
|
@@ -877,7 +896,7 @@ export function renderHarnessProcessReport(report: HarnessProcessReport): string
|
|
|
877
896
|
}
|
|
878
897
|
if (report.excludedCount > 0) {
|
|
879
898
|
lines.push(`모집단 제외 ${report.excludedCount}행`);
|
|
880
|
-
lines.push('제외 기준: command에 elanous.mjs
|
|
899
|
+
lines.push('제외 기준: command에 elanous.mjs가 없고 시험 데몬·러너 대상도 아닌 행');
|
|
881
900
|
}
|
|
882
901
|
lines.push(`분류 제외 ${report.unclassifiedCount}행`);
|
|
883
902
|
lines.push(`부모 생존 제외 ${report.parentPresentCount}행`);
|
|
@@ -885,11 +904,13 @@ export function renderHarnessProcessReport(report: HarnessProcessReport): string
|
|
|
885
904
|
if (report.parentUnknown.length > 0) lines.push(`부모 확인 불가 ${report.parentUnknown.length} — 자원소비/장기실행만에 넣지 않았다`);
|
|
886
905
|
const empty = report.resourceConsuming.length === 0 && report.longRunningOnly.length === 0 && report.parentUnknown.length === 0;
|
|
887
906
|
if (empty && report.observationStatus === 'ok') {
|
|
888
|
-
lines.push('관찰 대상 없음');
|
|
907
|
+
if (report.garbage.length === 0) lines.push('관찰 대상 없음');
|
|
908
|
+
lines.push(...renderGarbageGroup(report.garbage));
|
|
889
909
|
return lines;
|
|
890
910
|
}
|
|
891
911
|
if (empty && report.observationStatus === 'incomplete') {
|
|
892
912
|
lines.push('해석된 행 중 분류 대상이 없다 — 빈 관찰이 아니다');
|
|
913
|
+
lines.push(...renderGarbageGroup(report.garbage));
|
|
893
914
|
return lines;
|
|
894
915
|
}
|
|
895
916
|
if (report.resourceConsuming.length > 0) {
|
|
@@ -904,6 +925,7 @@ export function renderHarnessProcessReport(report: HarnessProcessReport): string
|
|
|
904
925
|
lines.push('부모 확인 불가:');
|
|
905
926
|
for (const row of report.parentUnknown) lines.push(...renderHarnessProcessRow(row));
|
|
906
927
|
}
|
|
928
|
+
lines.push(...renderGarbageGroup(report.garbage));
|
|
907
929
|
return lines;
|
|
908
930
|
}
|
|
909
931
|
|
|
@@ -944,7 +966,7 @@ function parseHarnessProcessPsLine(line: string): ParsedHarnessProcessPsLine {
|
|
|
944
966
|
if (!match) return { kind: 'malformed', line: trimmed, pid: parseHarnessProcessPsPid(line) };
|
|
945
967
|
const command = match[5]!.trim();
|
|
946
968
|
const pid = Number(match[1]);
|
|
947
|
-
if (!command
|
|
969
|
+
if (!isGarbageProcessTarget(command)) return { kind: 'excluded', pid };
|
|
948
970
|
const elapsedSeconds = parsePsEtime(match[4]!);
|
|
949
971
|
if (elapsedSeconds === undefined) return { kind: 'malformed', line: trimmed, pid };
|
|
950
972
|
return {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface ImageLoadState {
|
|
2
|
+
complete: boolean;
|
|
3
|
+
naturalWidth: number;
|
|
4
|
+
loading: string;
|
|
5
|
+
inViewport: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Shared by Node tests and the injected browser expression. */
|
|
9
|
+
export function classifyImages(images: ImageLoadState[]): { broken: number; pendingLazy: number } {
|
|
10
|
+
let broken = 0;
|
|
11
|
+
let pendingLazy = 0;
|
|
12
|
+
for (const image of images) {
|
|
13
|
+
if (image.complete && image.naturalWidth === 0) broken++;
|
|
14
|
+
else if (!image.complete && image.loading === 'lazy' && !image.inViewport) pendingLazy++;
|
|
15
|
+
else if (!image.complete) broken++;
|
|
16
|
+
}
|
|
17
|
+
return { broken, pendingLazy };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Executable function source for CDP and aside, without a module import in the page. */
|
|
21
|
+
export const IMAGE_LOAD_STATE_SOURCE = classifyImages.toString();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface GarbageProcessRow {
|
|
2
|
+
readonly pid: number;
|
|
3
|
+
readonly ppid: number;
|
|
4
|
+
readonly elapsedSeconds: number;
|
|
5
|
+
readonly command: string;
|
|
6
|
+
readonly launchd?: 'managed' | 'no-evidence' | 'unqueried';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface GarbageProcess extends Pick<GarbageProcessRow, 'pid' | 'ppid' | 'elapsedSeconds' | 'command'> {
|
|
10
|
+
readonly reason: 'orphan-test-daemon' | 'orphan-test-runner' | 'orphan-elanous';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const TEST_PATH = /elanous|repo\.worktrees|self-impl|\.elanous-test|gate-baseline/i;
|
|
14
|
+
const TEST_DAEMON = /(?:^|[\s/])daemon\.ts(?:\s|$)/;
|
|
15
|
+
const TEST_RUNNER = /\bbun\s+test\b|\bgate(?:[-\s]baseline|\b)/i;
|
|
16
|
+
const NEXUS_RUN = /\bnexus\s+run\b/;
|
|
17
|
+
const TEST_MARKER = /\.elanous-test|(?:^|\s)--test(?:\s|$)/;
|
|
18
|
+
|
|
19
|
+
/** A candidate for inspection only; this module never signals or removes a process. */
|
|
20
|
+
export function isGarbageProcessTarget(command: string): boolean {
|
|
21
|
+
return command.includes('elanous.mjs')
|
|
22
|
+
|| (TEST_DAEMON.test(command) && /elanous-nexus-|\.elanous-test/.test(command))
|
|
23
|
+
|| (TEST_RUNNER.test(command) && TEST_PATH.test(command));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function classifyGarbage(
|
|
27
|
+
rows: readonly GarbageProcessRow[],
|
|
28
|
+
options: { nowMs?: number; minOrphanSeconds?: number } = {},
|
|
29
|
+
): { garbage: GarbageProcess[] } {
|
|
30
|
+
const minOrphanSeconds = options.minOrphanSeconds ?? 3 * 60 * 60;
|
|
31
|
+
const garbage: GarbageProcess[] = [];
|
|
32
|
+
for (const row of rows) {
|
|
33
|
+
if (row.ppid !== 1 || row.elapsedSeconds < minOrphanSeconds || row.launchd === 'managed') continue;
|
|
34
|
+
if (!isGarbageProcessTarget(row.command)) continue;
|
|
35
|
+
if (NEXUS_RUN.test(row.command) && !TEST_MARKER.test(row.command)) continue;
|
|
36
|
+
const reason: GarbageProcess['reason'] = TEST_DAEMON.test(row.command)
|
|
37
|
+
&& /elanous-nexus-|\.elanous-test/.test(row.command)
|
|
38
|
+
? 'orphan-test-daemon'
|
|
39
|
+
: TEST_RUNNER.test(row.command) && TEST_PATH.test(row.command)
|
|
40
|
+
? 'orphan-test-runner'
|
|
41
|
+
: 'orphan-elanous';
|
|
42
|
+
garbage.push({ pid: row.pid, ppid: row.ppid, elapsedSeconds: row.elapsedSeconds, command: row.command, reason });
|
|
43
|
+
}
|
|
44
|
+
return { garbage };
|
|
45
|
+
}
|