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
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// codex·elanous 자격 파일은 이 Job 에 없다. 격리 관문(호스트 넥서스에 닿으면 종료)은 기존 Job 과 같다.
|
|
6
6
|
// 명령 인자는 셸 문자열로 이어붙이지 않고 `"$@"` 로 따로 인용한다.
|
|
7
7
|
|
|
8
|
+
import { randomBytes } from 'node:crypto';
|
|
8
9
|
import { spawnSync } from 'node:child_process';
|
|
9
10
|
import { isAbsolute } from 'node:path';
|
|
10
11
|
import { getUserConfig } from '../../user-config.js';
|
|
@@ -66,6 +67,8 @@ if [ -d "$HOME/outbox" ]; then
|
|
|
66
67
|
fi`;
|
|
67
68
|
|
|
68
69
|
export interface PodCommandJobInput {
|
|
70
|
+
/** POD1 — this launch's own label; cleanup selects by it, never by name alone. */
|
|
71
|
+
launch?: string;
|
|
69
72
|
name: string;
|
|
70
73
|
namespace: string;
|
|
71
74
|
image: string;
|
|
@@ -122,13 +125,13 @@ export function podCommandJobManifest(o: PodCommandJobInput): Record<string, unk
|
|
|
122
125
|
return {
|
|
123
126
|
apiVersion: 'batch/v1',
|
|
124
127
|
kind: 'Job',
|
|
125
|
-
metadata: { name: o.name, namespace: o.namespace, labels: { 'elanous.substrate': 'pod', 'elanous.job': o.name, 'elanous.kind': 'command' } },
|
|
128
|
+
metadata: { name: o.name, namespace: o.namespace, labels: { 'elanous.substrate': 'pod', 'elanous.job': o.name, 'elanous.kind': 'command', ...(o.launch ? { 'elanous.launch': o.launch } : {}) } },
|
|
126
129
|
spec: {
|
|
127
130
|
backoffLimit: 0,
|
|
128
131
|
ttlSecondsAfterFinished: 7200,
|
|
129
132
|
activeDeadlineSeconds: o.deadlineSeconds,
|
|
130
133
|
template: {
|
|
131
|
-
metadata: { labels: { 'elanous.job': o.name, 'elanous.kind': 'command' } },
|
|
134
|
+
metadata: { labels: { 'elanous.job': o.name, 'elanous.kind': 'command', ...(o.launch ? { 'elanous.launch': o.launch } : {}) } },
|
|
132
135
|
spec: {
|
|
133
136
|
restartPolicy: 'Never',
|
|
134
137
|
securityContext: { runAsUser: 1000, fsGroup: 1000 },
|
|
@@ -166,6 +169,7 @@ export function commandJobSecret(o: {
|
|
|
166
169
|
skillEnvText: Readonly<Record<string, string>>;
|
|
167
170
|
grokAuth?: string;
|
|
168
171
|
ghToken?: string;
|
|
172
|
+
launch?: string;
|
|
169
173
|
}): Record<string, unknown> | null {
|
|
170
174
|
const stringData: Record<string, string> = {};
|
|
171
175
|
if (o.grokAuth !== undefined) stringData['grok-auth.json'] = o.grokAuth;
|
|
@@ -177,7 +181,7 @@ export function commandJobSecret(o: {
|
|
|
177
181
|
if (Object.keys(stringData).length === 0) return null;
|
|
178
182
|
return {
|
|
179
183
|
apiVersion: 'v1', kind: 'Secret', type: 'Opaque',
|
|
180
|
-
metadata: { name: `${o.name}-creds`, namespace: o.namespace, labels: { 'elanous.job': o.name } },
|
|
184
|
+
metadata: { name: `${o.name}-creds`, namespace: o.namespace, labels: { 'elanous.job': o.name, ...(o.launch ? { 'elanous.launch': o.launch } : {}) } },
|
|
181
185
|
stringData,
|
|
182
186
|
};
|
|
183
187
|
}
|
|
@@ -265,7 +269,11 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
265
269
|
const image = options.image ?? 'elanous-harness:local';
|
|
266
270
|
const repoUrl = options.repoUrl ?? 'https://github.com/ElanvitalAI/elanous';
|
|
267
271
|
const deadlineSeconds = options.deadlineSeconds ?? POD_COMMAND_DEADLINE_SECONDS;
|
|
268
|
-
|
|
272
|
+
// POD1 (10-01): two launches in the same moment got the same `si-cmd-<time>` and the failing one's cleanup
|
|
273
|
+
// deleted the other's credentials. The name now has a random tail, and cleanup selects this launch's label.
|
|
274
|
+
const launch = randomBytes(6).toString('hex');
|
|
275
|
+
const name = options.name ?? podJobName(`cmd-${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`);
|
|
276
|
+
const own = `elanous.job=${name},elanous.launch=${launch}`;
|
|
269
277
|
const env = options.env ?? process.env;
|
|
270
278
|
const hostMirror = (options.hostMirror ?? env.ELANOUS_POD_HOST_MIRROR
|
|
271
279
|
?? (options.configHostMirror ?? (() => getUserConfig().pod?.hostMirror))())?.trim() || undefined;
|
|
@@ -293,7 +301,7 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
293
301
|
const currentContext = member || options.kubectl ? null : baseKubectl(['config', 'current-context']);
|
|
294
302
|
const context = member?.context ?? (currentContext?.status === 0 ? currentContext.stdout.trim() : '');
|
|
295
303
|
const kubectl: Kubectl = (args, stdin) => baseKubectl(context ? ['--context', context, ...args] : [...args], stdin);
|
|
296
|
-
const cleanupSecret = () => { kubectl(['-n', namespace, 'delete', 'secret',
|
|
304
|
+
const cleanupSecret = () => { kubectl(['-n', namespace, 'delete', 'secret', '-l', own, '--ignore-not-found']); };
|
|
297
305
|
const artifactsDir = `${options.artifactsRoot ?? `${effectiveInstanceRoot()}/pod-artifacts`}/${name}`;
|
|
298
306
|
try {
|
|
299
307
|
if (!context && !options.kubectl) throw new Error('pod command: kubectl context 를 확인할 수 없다');
|
|
@@ -328,19 +336,19 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
328
336
|
if (synced?.imageRef) member = { ...member, imageRef: synced.imageRef };
|
|
329
337
|
}
|
|
330
338
|
const ghToken = options.clone ? (options.ghToken ?? defaultGhToken)() : undefined;
|
|
331
|
-
const secret = commandJobSecret({ name, namespace, skills, skillEnvText, ...(grokAuth !== undefined ? { grokAuth } : {}), ...(ghToken !== undefined ? { ghToken } : {}) });
|
|
339
|
+
const secret = commandJobSecret({ name, namespace, launch, skills, skillEnvText, ...(grokAuth !== undefined ? { grokAuth } : {}), ...(ghToken !== undefined ? { ghToken } : {}) });
|
|
332
340
|
if (secret) {
|
|
333
341
|
const applied = kubectl(['apply', '-f', '-'], JSON.stringify(secret));
|
|
334
342
|
if (applied.status !== 0) throw new Error(applied.stderr.trim() || 'pod command: Secret 적용 실패');
|
|
335
343
|
}
|
|
336
344
|
const manifest = podCommandJobManifest({
|
|
337
|
-
name, namespace, image: member?.imageRef ?? image,
|
|
345
|
+
name, namespace, launch, image: member?.imageRef ?? image,
|
|
338
346
|
...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}),
|
|
339
347
|
repoUrl, command, skills, ...(llm ? { llm } : {}), ...(options.clone ? { clone: true } : {}),
|
|
340
348
|
...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), ...(bunCache ? { bunCache } : {}), ...(options.memoryLimit ? { memoryLimit: options.memoryLimit } : {}), deadlineSeconds,
|
|
341
349
|
...(options.runId ? { runId: options.runId } : {}),
|
|
342
350
|
});
|
|
343
|
-
kubectl(['-n', namespace, 'delete', 'job',
|
|
351
|
+
kubectl(['-n', namespace, 'delete', 'job', '-l', own, '--ignore-not-found']);
|
|
344
352
|
const applied = kubectl(['apply', '-f', '-'], JSON.stringify(manifest));
|
|
345
353
|
if (applied.status !== 0) {
|
|
346
354
|
cleanupSecret();
|
|
@@ -2,6 +2,7 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } fr
|
|
|
2
2
|
import { gunzipSync } from 'node:zlib';
|
|
3
3
|
import { debug } from '../../debug/log.js';
|
|
4
4
|
import { runLedgerDir, runLedgerPath } from '../../self-implement/run-ledger.js';
|
|
5
|
+
import { ledgerLineToLogEvent } from './pod-ledger-events.js';
|
|
5
6
|
|
|
6
7
|
type Ledger = { runId: string; jsonl: string };
|
|
7
8
|
type Incomplete = { runId: string; reason: string };
|
|
@@ -124,7 +125,7 @@ export function createPodLedgerFollower(opts: {
|
|
|
124
125
|
let owned = false;
|
|
125
126
|
let disabled = false;
|
|
126
127
|
let lastProgressAt = now();
|
|
127
|
-
let
|
|
128
|
+
let visibleProgressEvent: string | null = null;
|
|
128
129
|
let lastActivityMeasuredAt = lastProgressAt;
|
|
129
130
|
let lastActivityLogAt = -Infinity;
|
|
130
131
|
let nextStallMinute = stallMinutes;
|
|
@@ -132,11 +133,11 @@ export function createPodLedgerFollower(opts: {
|
|
|
132
133
|
function checkStall(ledgerRead: boolean, activityMeasured: boolean) {
|
|
133
134
|
const idleMinutes = Math.max(0, (now() - lastProgressAt) / 60_000);
|
|
134
135
|
if (idleMinutes >= nextStallMinute) {
|
|
135
|
-
log('self-implement.pod', 'stalled', { runId: opts.runId, lastProgressEvent, idleMinutes });
|
|
136
|
+
log('self-implement.pod', 'stalled', { runId: opts.runId, lastProgressEvent: visibleProgressEvent, idleMinutes });
|
|
136
137
|
const observation = ledgerRead
|
|
137
138
|
? (activityMeasured ? '원장·작업 트리 모두 조용함' : '원장 조용함')
|
|
138
139
|
: '원장 못 잼';
|
|
139
|
-
onStall(`[pod] 진행 없음 ${Math.floor(idleMinutes)}분 — ${observation} · 마지막 진행 ${
|
|
140
|
+
onStall(`[pod] 진행 없음 ${Math.floor(idleMinutes)}분 — ${observation} · 마지막 진행 ${visibleProgressEvent ?? '없음'}${activityMeasured ? '' : ' (작업 트리 못 잼)'}`);
|
|
140
141
|
stalled = true;
|
|
141
142
|
nextStallMinute = (Math.floor(idleMinutes / stallMinutes) + 1) * stallMinutes;
|
|
142
143
|
}
|
|
@@ -169,7 +170,7 @@ export function createPodLedgerFollower(opts: {
|
|
|
169
170
|
if (stalled) log('self-implement.pod', 'stall-cleared', { runId: opts.runId, idleMinutes });
|
|
170
171
|
stalled = false;
|
|
171
172
|
lastProgressAt = now();
|
|
172
|
-
|
|
173
|
+
visibleProgressEvent = 'worktree-activity';
|
|
173
174
|
nextStallMinute = stallMinutes;
|
|
174
175
|
if (now() - lastActivityLogAt >= 5 * 60_000) {
|
|
175
176
|
log('self-implement.pod', 'worktree-activity', { runId: opts.runId, files });
|
|
@@ -190,6 +191,11 @@ export function createPodLedgerFollower(opts: {
|
|
|
190
191
|
log('self-implement.pod', 'ledger-live-appended', { runId: opts.runId, lines: complete.split('\n').filter(Boolean).length, bytes: Buffer.byteLength(complete), offset });
|
|
191
192
|
for (const line of complete.split('\n')) {
|
|
192
193
|
if (!line) continue;
|
|
194
|
+
let logEvent: ReturnType<typeof ledgerLineToLogEvent>;
|
|
195
|
+
try {
|
|
196
|
+
logEvent = ledgerLineToLogEvent(line, opts.runId);
|
|
197
|
+
if (logEvent) log(logEvent.category, logEvent.event, logEvent.data);
|
|
198
|
+
} catch { /* A failed host log write must not interrupt ledger following or stall accounting. */ }
|
|
193
199
|
let entry: unknown;
|
|
194
200
|
try { entry = JSON.parse(line); } catch { continue; }
|
|
195
201
|
const event = (entry && typeof entry === 'object' && 'event' in entry) ? entry.event : undefined;
|
|
@@ -198,7 +204,7 @@ export function createPodLedgerFollower(opts: {
|
|
|
198
204
|
if (stalled) log('self-implement.pod', 'stall-cleared', { runId: opts.runId, idleMinutes });
|
|
199
205
|
stalled = false;
|
|
200
206
|
lastProgressAt = now();
|
|
201
|
-
|
|
207
|
+
visibleProgressEvent = logEvent?.event ?? '[redacted]';
|
|
202
208
|
nextStallMinute = stallMinutes;
|
|
203
209
|
}
|
|
204
210
|
checkStall(true, activityMeasured);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const SECRET_FIELD = /token|secret|password|authorization|key/i;
|
|
2
|
+
const SAFE_FIELD = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
3
|
+
const SECRET_VALUE = /(?:\b(?:bearer|basic)\s+\S+|\b(?:token|secret|password|authorization|api[-_]?key|access[-_]?key|private[-_]?key)\b\s*(?:is|[=:])\s*["']?\S+|\b(?:sk-[A-Za-z0-9_-]{8,}|gh[pousr]_[A-Za-z0-9_]{8,}))\b/i;
|
|
4
|
+
const MAX_FIELDS = 32;
|
|
5
|
+
// Review R2: only lifecycle events the host needs to see, and no free text. Event names must start with a known
|
|
6
|
+
// lifecycle prefix; string fields survive only when they are short code-like values in a fixed set of keys.
|
|
7
|
+
const EVENT_ALLOWED = /^(?:gate|gated|review|reviewed|repair|repaired|rework|merge|merged|pr|land|landed|stage|round|supervisor|child|plan|planned|decompose|decomposed|shard|stall|harvest|decision|run|pipeline|progress|human|start|started|finish|finished|done|implement|implemented|commit|test|typecheck)(?:-(?:started|finished|round|opened|closed|failed|passed|blocked|done|entry|node|outcome|delivery|decision|stop|merged|ready|requested|resolved|result|fix|must-fix|verdict|skipped|origin|cleared))*$/;
|
|
8
|
+
const CODE_FIELDS = new Set(['stage', 'outcome', 'verdict', 'status', 'kind', 'phase', 'role', 'result']);
|
|
9
|
+
const CODE_VALUE = /^[a-z0-9_.:-]{1,40}$/i;
|
|
10
|
+
|
|
11
|
+
function secretValues(root: Record<string, unknown>): string[] {
|
|
12
|
+
const secrets: string[] = [];
|
|
13
|
+
const pending: unknown[] = [root];
|
|
14
|
+
while (pending.length) {
|
|
15
|
+
const current = pending.pop();
|
|
16
|
+
if (!current || typeof current !== 'object') continue;
|
|
17
|
+
if (Array.isArray(current)) {
|
|
18
|
+
pending.push(...current);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
for (const [name, value] of Object.entries(current)) {
|
|
22
|
+
if (SECRET_FIELD.test(name) && typeof value === 'string' && value.length >= 3) secrets.push(value);
|
|
23
|
+
if (value && typeof value === 'object') pending.push(value);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return secrets;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Project bounded ledger metadata, excluding named secrets and recognizable credential values. */
|
|
30
|
+
export function ledgerLineToLogEvent(line: string, runId: string): {
|
|
31
|
+
category: string;
|
|
32
|
+
event: string;
|
|
33
|
+
data: Record<string, unknown>;
|
|
34
|
+
} | undefined {
|
|
35
|
+
let entry: unknown;
|
|
36
|
+
try { entry = JSON.parse(line); } catch { return undefined; }
|
|
37
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry) || !('event' in entry) || typeof entry.event !== 'string') return undefined;
|
|
38
|
+
// The real run-ledger writer wraps observer fields in `data`; older/synthetic lines can be flat.
|
|
39
|
+
const details = 'data' in entry && entry.data && typeof entry.data === 'object' && !Array.isArray(entry.data)
|
|
40
|
+
? entry.data as Record<string, unknown> : undefined;
|
|
41
|
+
const sources = [entry, details].filter((source): source is Record<string, unknown> => source !== undefined);
|
|
42
|
+
const knownSecrets = secretValues(entry);
|
|
43
|
+
const containsCredential = (value: string) => SECRET_VALUE.test(value) || knownSecrets.some((secret) => value.includes(secret));
|
|
44
|
+
if (containsCredential(entry.event) || !EVENT_ALLOWED.test(entry.event)) return undefined;
|
|
45
|
+
|
|
46
|
+
const data: Record<string, unknown> = { runId };
|
|
47
|
+
let fields = 0;
|
|
48
|
+
for (const source of sources) {
|
|
49
|
+
for (const [name, value] of Object.entries(source)) {
|
|
50
|
+
if (name === 'event' || name === 'runId' || name === 'data' || !SAFE_FIELD.test(name) || SECRET_FIELD.test(name)) continue;
|
|
51
|
+
if (fields >= MAX_FIELDS) break;
|
|
52
|
+
const countName = `${name}Count`;
|
|
53
|
+
const outputName = Array.isArray(value) || value && typeof value === 'object' ? countName : name;
|
|
54
|
+
if (!SAFE_FIELD.test(outputName) || SECRET_FIELD.test(outputName) || Object.hasOwn(data, outputName)) continue;
|
|
55
|
+
if (typeof value === 'string') {
|
|
56
|
+
// Free text (reasons, messages, patches) never leaves the pod ledger; only short code values in known keys.
|
|
57
|
+
if (!CODE_FIELDS.has(name) || !CODE_VALUE.test(value) || containsCredential(value)) continue;
|
|
58
|
+
data[name] = value;
|
|
59
|
+
} else if (typeof value === 'number' && Number.isFinite(value) || typeof value === 'boolean' || value === null) {
|
|
60
|
+
data[name] = value;
|
|
61
|
+
} else if (Array.isArray(value) || value && typeof value === 'object') {
|
|
62
|
+
data[countName] = Array.isArray(value) ? value.length : Object.keys(value).length;
|
|
63
|
+
} else continue;
|
|
64
|
+
fields += 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { category: 'self-implement.pod.ledger', event: entry.event, data };
|
|
68
|
+
}
|
|
@@ -50,7 +50,7 @@ import { findGitDir } from '../../git-fs/locate.js';
|
|
|
50
50
|
import { debug } from '../../debug/log.js';
|
|
51
51
|
import { selectLiveDetail } from '../../live/detail-switch.js';
|
|
52
52
|
import { authStorePath } from '../../oauth/store.js';
|
|
53
|
-
import { grokAuthFilePath, resolveGrokCredential } from '../../grok/credential.js';
|
|
53
|
+
import { grokAuthFilePath, isGrokSubscriptionExpiring, refreshGrokSubscriptionToken, resolveGrokCredential } from '../../grok/credential.js';
|
|
54
54
|
import { defaultGrokModel } from '../../grok/models.js';
|
|
55
55
|
import { resolveHostId } from '../../platform/host-id.js';
|
|
56
56
|
import { envLiteral } from '../../platform/env-literal.js';
|
|
@@ -171,12 +171,26 @@ export function hostCredentials(account: string, storePath: string = authStorePa
|
|
|
171
171
|
return { elanousAuth, codexAuth, ghToken: ghToken() };
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
/**
|
|
174
|
+
/** A Pod gets a refresh-less access copy, so the copy must outlive the Job: same rule as the Codex path (3 h). */
|
|
175
|
+
export const POD_GROK_MIN_VALIDITY_MS = 3 * 3600_000;
|
|
176
|
+
|
|
177
|
+
/** resolveGrokCredential 과 동일한 출처를 읽되 Pod 로는 refresh 없는 access 사본만 보낸다.
|
|
178
|
+
* POD4 (10-01): the copy used to ship whatever the host had — an AUTH1 shard copied a token that had expired
|
|
179
|
+
* 4 minutes earlier and died at `grok-credential-preflight status=expired`. Refresh on the host first. */
|
|
175
180
|
export function hostGrokCredentials(opts: {
|
|
176
181
|
home?: string; env?: NodeJS.ProcessEnv; apiKeyOptIn?: boolean; ghToken?: () => string;
|
|
182
|
+
/** Test seams — the host refresh (read-only `grok models`) and the expiry probe. */
|
|
183
|
+
refresh?: () => void; isExpiring?: () => boolean;
|
|
177
184
|
} = {}): { grokAuth?: string; grokApiKey?: string; ghToken: string } {
|
|
178
|
-
|
|
185
|
+
let credential = resolveGrokCredential({ home: opts.home, env: opts.env });
|
|
179
186
|
if (credential?.kind === 'subscription') {
|
|
187
|
+
const expiring = opts.isExpiring ?? (() => isGrokSubscriptionExpiring({ home: opts.home, bufferMs: POD_GROK_MIN_VALIDITY_MS }));
|
|
188
|
+
if (expiring()) {
|
|
189
|
+
try { (opts.refresh ?? (() => { refreshGrokSubscriptionToken({ home: opts.home }); }))(); } catch { /* judged below */ }
|
|
190
|
+
if (expiring()) throw new Error('grok: 구독 access 토큰이 3시간 안에 만료 — 호스트 갱신(grok models)도 못 늘렸다 · 호스트에서 grok login 후 다시');
|
|
191
|
+
credential = resolveGrokCredential({ home: opts.home, env: opts.env });
|
|
192
|
+
if (credential?.kind !== 'subscription') throw new Error('grok: 갱신 뒤 구독 자격을 다시 읽지 못했다');
|
|
193
|
+
}
|
|
180
194
|
const scopes = JSON.parse(readFileSync(grokAuthFilePath(opts.home), 'utf8')) as Record<string, unknown>;
|
|
181
195
|
const redacted: Record<string, { key: string; expires_at?: string; user_id?: string }> = {};
|
|
182
196
|
for (const [name, value] of Object.entries(scopes)) {
|
|
@@ -219,6 +233,64 @@ function githubRepositoryFromUrl(repoUrl: string): string | null {
|
|
|
219
233
|
|
|
220
234
|
/** Job 스크립트의 salvage 단계 — 자식 rc 가 0 이 아닐 때만, 변경·미푸시 커밋이 있는 격리 워크트리를 `salvage/<job>/<worktree>` 로 push.
|
|
221
235
|
* main·self-impl/ 로는 절대 안 민다. 실패해도 제어 흐름을 바꾸지 않는다(호출자는 이 뒤에 `exit $rc`). */
|
|
236
|
+
/**
|
|
237
|
+
* App installation tokens live one hour and only the host process renews them (at 10 minutes left).
|
|
238
|
+
* If that process dies (host reboot), nothing renews the Pod's token. hosts.yml unchanged for this long
|
|
239
|
+
* means the host is gone. Push a snapshot while the token still works.
|
|
240
|
+
*/
|
|
241
|
+
export const POD_GH_STALE_SECONDS = 53 * 60;
|
|
242
|
+
|
|
243
|
+
/** Snapshot every worktree to salvage/<job>/<wt>-early without touching the child's index, worktree or HEAD. */
|
|
244
|
+
export function podEarlySalvageScript(): string {
|
|
245
|
+
return [
|
|
246
|
+
'job_name="${ELANOUS_POD_NAME:-unknown-job}"',
|
|
247
|
+
'while IFS= read -r wt; do',
|
|
248
|
+
' [ -n "$wt" ] || continue',
|
|
249
|
+
' case "$wt" in /*) ;; *) continue ;; esac',
|
|
250
|
+
' wt_name=$(basename -- "$wt")',
|
|
251
|
+
" case \"$wt_name\" in ''|.*|*/*|*'..'*) continue ;; esac",
|
|
252
|
+
' branch="salvage/${job_name}/${wt_name}-early"',
|
|
253
|
+
' (',
|
|
254
|
+
' set +e',
|
|
255
|
+
' cd -- "$wt" || exit 0',
|
|
256
|
+
' head=$(git rev-parse --verify -q HEAD) || { printf \'ELANOUS_POD_SALVAGE_NONE early-no-head\\n\'; exit 0; }',
|
|
257
|
+
' idx=$(mktemp) || exit 0',
|
|
258
|
+
' cp -- "$(git rev-parse --git-path index)" "$idx" 2>/dev/null || :',
|
|
259
|
+
' GIT_INDEX_FILE="$idx" git add -A -- . >/dev/null 2>&1',
|
|
260
|
+
' tree=$(GIT_INDEX_FILE="$idx" git write-tree 2>/dev/null)',
|
|
261
|
+
' rm -f -- "$idx"',
|
|
262
|
+
' [ -n "$tree" ] || { printf \'ELANOUS_POD_SALVAGE_NONE early-write-tree-failed\\n\'; exit 0; }',
|
|
263
|
+
' base=$(git rev-parse --verify -q origin/HEAD || git rev-parse --verify -q origin/main || true)',
|
|
264
|
+
' if [ "$tree" = "$(git rev-parse "${head}^{tree}")" ]; then',
|
|
265
|
+
' if [ -z "$base" ] || [ "$(git rev-list --count "${base}..${head}" 2>/dev/null || echo 1)" = 0 ]; then printf \'ELANOUS_POD_SALVAGE_NONE clean\\n\'; exit 0; fi',
|
|
266
|
+
' commit=$head',
|
|
267
|
+
' else',
|
|
268
|
+
' commit=$(git commit-tree "$tree" -p "$head" -m "salvage(early): ${job_name} host token refresh stopped") || { printf \'ELANOUS_POD_SALVAGE_NONE commit-failed\\n\'; exit 0; }',
|
|
269
|
+
' fi',
|
|
270
|
+
' git push --force origin "${commit}:refs/heads/${branch}" || { printf \'ELANOUS_POD_SALVAGE_NONE push-failed\\n\'; exit 0; }',
|
|
271
|
+
" printf 'ELANOUS_POD_SALVAGE %s %s\\n' \"$branch\" \"$commit\"",
|
|
272
|
+
' )',
|
|
273
|
+
"done < <(git worktree list --porcelain 2>/dev/null | awk '/^worktree / { sub(/^worktree /, \"\"); print }')",
|
|
274
|
+
].join('\n');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Background watchdog: fires the early snapshot once when hosts.yml has not been rewritten for staleSeconds. */
|
|
278
|
+
export function podGithubWatchdogScript(staleSeconds: number, intervalSeconds = 30): string {
|
|
279
|
+
return [
|
|
280
|
+
'(',
|
|
281
|
+
` while sleep ${intervalSeconds}; do`,
|
|
282
|
+
' f="$GH_CONFIG_DIR/hosts.yml"',
|
|
283
|
+
' m=$(stat -c %Y "$f" 2>/dev/null || stat -f %m "$f" 2>/dev/null) || continue',
|
|
284
|
+
' age=$(( $(date +%s) - m ))',
|
|
285
|
+
` [ "$age" -ge ${staleSeconds} ] || continue`,
|
|
286
|
+
" printf 'ELANOUS_POD_GH_STALE %s\\n' \"$age\"",
|
|
287
|
+
` ${podEarlySalvageScript().split('\n').join('\n ')}`,
|
|
288
|
+
' break',
|
|
289
|
+
' done',
|
|
290
|
+
') & gh_watch_pid=$!',
|
|
291
|
+
].join('\n');
|
|
292
|
+
}
|
|
293
|
+
|
|
222
294
|
export function podSalvageScript(): string {
|
|
223
295
|
return [
|
|
224
296
|
'if [ "${rc:-0}" -ne 0 ]; then',
|
|
@@ -368,7 +440,7 @@ export function podMemoryLimitFor(feature: string, env: NodeJS.ProcessEnv | Read
|
|
|
368
440
|
return { limit: a !== null && b !== null && a > b ? base : high, tier, source };
|
|
369
441
|
}
|
|
370
442
|
|
|
371
|
-
export function podJobManifest(o: { name: string; namespace: string; image: string; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; hostMirror?: string; args: readonly string[]; passEnv: readonly string[]; deadlineSeconds: number; runId?: string; parentRunId?: string; armEnv?: Readonly<Record<string, string>>; hostId?: string; imageCommit?: string | null; skillEnvs?: readonly string[]; memoryLimit?: string; goalDoc?: string; grokCredential?: 'subscription' | 'api_key'; codexAccounts?: readonly string[]; appCredential?: boolean }): Record<string, unknown> {
|
|
443
|
+
export function podJobManifest(o: { name: string; namespace: string; image: string; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; hostMirror?: string; args: readonly string[]; passEnv: readonly string[]; deadlineSeconds: number; runId?: string; parentRunId?: string; armEnv?: Readonly<Record<string, string>>; hostId?: string; imageCommit?: string | null; skillEnvs?: readonly string[]; memoryLimit?: string; goalDoc?: string; grokCredential?: 'subscription' | 'api_key'; codexAccounts?: readonly string[]; appCredential?: boolean; /** Test seam — default POD_GH_STALE_SECONDS. */ githubStaleSeconds?: number }): Record<string, unknown> {
|
|
372
444
|
const quoted = o.args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(' ');
|
|
373
445
|
const goalPath = o.goalDoc ? `'${(o.goalDoc.startsWith('-') ? `./${o.goalDoc}` : o.goalDoc).replace(/'/g, `'\\''`)}'` : undefined;
|
|
374
446
|
const askBaseIndex = o.goalDoc ? o.args.indexOf('--base') : -1;
|
|
@@ -394,6 +466,7 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
|
|
|
394
466
|
] : []),
|
|
395
467
|
// Only fixed command shapes leave the Pod; arbitrary argv (including shell -c text) is never emitted.
|
|
396
468
|
`(while :; do { mem=$(if [ -r /sys/fs/cgroup/memory.current ]; then cat /sys/fs/cgroup/memory.current; elif [ -r /sys/fs/cgroup/memory/memory.usage_in_bytes ]; then cat /sys/fs/cgroup/memory/memory.usage_in_bytes; else printf -- -; fi); top=$(ps -eo rss=,comm=,args= --sort=-rss | head -5 | awk 'function encode(s) { gsub(/%/, "%25", s); gsub(/:/, "%3A", s); gsub(/ /, "%20", s); gsub(/\\t/, "%09", s); return s } { rss=$1; name=$2; sub(/^[[:space:]]*[0-9]+[[:space:]]+[^[:space:]]+[[:space:]]*/, ""); n=split($0, a, /[[:space:]]+/); cmd="<redacted>"; if (name=="sleep" && a[2]=="30") cmd="sleep 30" (n>2 ? " <redacted>" : ""); else if (name=="bun") { cmd="bun <redacted>"; if (a[2]=="test") cmd="bun test <redacted>"; else if (a[2]=="run") cmd="bun run <redacted>"; else if ((a[2]=="x" || a[2]=="exec") && a[3]=="tsc") cmd="bun x tsc <redacted>" } else if (name=="tsc") cmd="tsc <redacted>"; else if (name=="elanous") { cmd="elanous <redacted>"; if (a[2]=="self") cmd="elanous self <redacted>"; else if (a[2]=="harness") cmd="elanous harness <redacted>" } else if (name=="node") cmd="node <redacted>"; if (name=="sleep" && cmd=="<redacted>") cmd="sleep <redacted>"; else if (cmd=="<redacted>" && name!="bash" && name!="sh") name="other"; printf " %s:%s:%s", rss, encode(name), encode(substr(cmd,1,120)) }'); printf "ELANOUS_MEM %s %s%s\\n" "$(date +%s)" "$mem" "$top"; } || true; sleep 15 || break; done) & mem_sampler_pid=$!`,
|
|
469
|
+
...(o.appCredential ? [podGithubWatchdogScript(o.githubStaleSeconds ?? POD_GH_STALE_SECONDS)] : []),
|
|
397
470
|
// 마지막 줄 JSON 이 «맨 끝»이어야 한다(parseSelfImplementJson) — rollup 은 그 앞에.
|
|
398
471
|
goalPath
|
|
399
472
|
? `elanous harness ask ${goalPath} --json${quotedAsk} > /tmp/si.out 2>&1; rc=$?; bun -e 'const fs=require("fs");const p="/tmp/si.out";const s=fs.readFileSync(p,"utf8");const line=s.trimEnd().split("\\n").at(-1);try{const o=JSON.parse(line);if(o.kind==="self"&&o.result&&typeof o.result==="object")fs.appendFileSync(p,"\\n"+JSON.stringify({...o.result,...(typeof o.ok==="boolean"?{ok:o.ok}:{})})+"\\n")}catch{}'`
|
|
@@ -454,6 +527,7 @@ for ledger in "\${ELANOUS_STATE_DIR:-$HOME/.elanous}"/run-ledger/*.jsonl; do
|
|
|
454
527
|
done
|
|
455
528
|
if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
|
|
456
529
|
'kill "$mem_sampler_pid" 2>/dev/null || true',
|
|
530
|
+
...(o.appCredential ? ['kill "$gh_watch_pid" 2>/dev/null || true'] : []),
|
|
457
531
|
'wait "$mem_sampler_pid" 2>/dev/null || true',
|
|
458
532
|
'tail -n 1 /tmp/si.out',
|
|
459
533
|
podSalvageScript(),
|
package/src/telegram-commands.ts
CHANGED
|
@@ -762,6 +762,19 @@ export function defaultTelegramCommands(): TgSlashCommand[] {
|
|
|
762
762
|
});
|
|
763
763
|
},
|
|
764
764
|
},
|
|
765
|
+
{
|
|
766
|
+
name: 'decisions',
|
|
767
|
+
description: '열린 결정 목록 — 카드를 다시 보내 여기서 바로 정할 수 있습니다',
|
|
768
|
+
handler: async (_args, ctx) => {
|
|
769
|
+
const { telegramDecisionService } = await import('./decisions/telegram-decision-cards.js');
|
|
770
|
+
const service = telegramDecisionService();
|
|
771
|
+
if (!service) return '결정 카드가 이 봇에 연결되어 있지 않습니다.';
|
|
772
|
+
if (!service.isOwner(String(ctx.userId))) return '결정은 소유자만 볼 수 있습니다.';
|
|
773
|
+
// A group would show every member the cards (and route memos from them) — private chat only (TC review #22660).
|
|
774
|
+
if (ctx.chatId !== ctx.userId) return '결정은 개인 대화에서만 볼 수 있습니다.';
|
|
775
|
+
return service.listOpen(String(ctx.chatId));
|
|
776
|
+
},
|
|
777
|
+
},
|
|
765
778
|
{
|
|
766
779
|
name: 'work',
|
|
767
780
|
description: '글을 알맞은 곳(흡수·태스크·하니스)에 넣고 결과를 여기로 답합니다 — /work [absorb|tasks|graph] <글>',
|
package/src/telegram.ts
CHANGED
|
@@ -66,9 +66,11 @@ import { observeDevRequestRouteFailSoft } from './skills/dev-request-router.js';
|
|
|
66
66
|
import { runUrlRoute } from './skills/url-route-exec.js';
|
|
67
67
|
import { recordRoutedLinkAbsorbed } from './intake-plane/link-ledger.js';
|
|
68
68
|
import { classifyFrontInput } from './intake-plane/front-classifier.js';
|
|
69
|
+
import { handleTelegramSeatWork, type TelegramSeatWorkDeps } from './intake-plane/telegram-seat-work.js';
|
|
69
70
|
import { effectiveInstanceRoot } from './instance/resolve.js';
|
|
70
71
|
import { getElanousConfigDir } from './elanous-config-dir.js';
|
|
71
72
|
import { defaultFieldEvent, FieldUploadError, parseFieldCaption, resolveFieldMime, saveFieldMedia } from './field/field-media.js';
|
|
73
|
+
import { scheduleFieldReel, type FieldReelOptions } from './field/field-reel.js';
|
|
72
74
|
|
|
73
75
|
export type TgAttachmentKind = 'photo' | 'voice' | 'audio' | 'document';
|
|
74
76
|
|
|
@@ -206,10 +208,14 @@ export interface TelegramBotOpts {
|
|
|
206
208
|
* independently of the LLM chat path. Best-effort: errors are
|
|
207
209
|
* swallowed by the caller — never blocks chat reply. */
|
|
208
210
|
onTriggerTap?: (event: TgTriggerEvent) => void | Promise<void>;
|
|
211
|
+
/** Test seam for addressed seat intake; defaults to the real intake door. */
|
|
212
|
+
seatWorkDeps?: TelegramSeatWorkDeps;
|
|
209
213
|
/** `#현장` 기본 행사 — 없으면 `slashContext.userConfig.telegram.fieldDefaultEvent` · 그것도 없으면 `field-<로컬 날짜>`. */
|
|
210
214
|
fieldDefaultEvent?: string;
|
|
211
215
|
/** 시험 seam — 현장 폴더 뿌리(기본 `getElanousConfigDir()`). */
|
|
212
216
|
fieldRootDir?: () => string;
|
|
217
|
+
/** 시험에서는 실제 렌더 대신 가짜 실행기를 주입한다. */
|
|
218
|
+
fieldReel?: FieldReelOptions;
|
|
213
219
|
}
|
|
214
220
|
|
|
215
221
|
/** Surface-unification v2 (FU-2) — Telegram trigger event passed to
|
|
@@ -326,6 +332,8 @@ export class TelegramBot {
|
|
|
326
332
|
private readonly globalWindow: number[] = [];
|
|
327
333
|
private readonly fieldDefaultEventOpt?: string;
|
|
328
334
|
private readonly fieldRootDir: () => string;
|
|
335
|
+
private readonly fieldReel?: FieldReelOptions;
|
|
336
|
+
private readonly seatWorkDeps: TelegramSeatWorkDeps;
|
|
329
337
|
/** 앨범 id → `#현장` 행사 · 답장 디바운스. 태그된 앨범만 들어온다. */
|
|
330
338
|
private readonly fieldGroups = new Map<string, {
|
|
331
339
|
event: string; at: number; chatId: number; threadId?: number; replyTo: number; count: number;
|
|
@@ -359,6 +367,8 @@ export class TelegramBot {
|
|
|
359
367
|
this.voiceAdapter = opts.voiceAdapter;
|
|
360
368
|
this.fieldDefaultEventOpt = opts.fieldDefaultEvent;
|
|
361
369
|
this.fieldRootDir = opts.fieldRootDir ?? getElanousConfigDir;
|
|
370
|
+
this.fieldReel = opts.fieldReel;
|
|
371
|
+
this.seatWorkDeps = opts.seatWorkDeps ?? {};
|
|
362
372
|
if (opts.onTriggerTap) {
|
|
363
373
|
// Assign through `as unknown` to bypass `readonly` + private —
|
|
364
374
|
// the tap is set once at construction and never reassigned.
|
|
@@ -796,6 +806,7 @@ export class TelegramBot {
|
|
|
796
806
|
replyTo?: number;
|
|
797
807
|
threadId?: number;
|
|
798
808
|
caption?: string;
|
|
809
|
+
mimeType?: string;
|
|
799
810
|
} = {}): Promise<{ messageId: number } | undefined> {
|
|
800
811
|
// Per-chat gap mirror — keep telegram happy on rapid document spills.
|
|
801
812
|
const last = this.chatLastSentAt.get(chatId) ?? 0;
|
|
@@ -816,7 +827,7 @@ export class TelegramBot {
|
|
|
816
827
|
new Uint8Array(blobBytes).set(bytes);
|
|
817
828
|
form.set(
|
|
818
829
|
'document',
|
|
819
|
-
new Blob([blobBytes], { type: 'text/plain' }),
|
|
830
|
+
new Blob([blobBytes], { type: opts.mimeType ?? 'text/plain' }),
|
|
820
831
|
filename,
|
|
821
832
|
);
|
|
822
833
|
|
|
@@ -908,6 +919,24 @@ export class TelegramBot {
|
|
|
908
919
|
return undefined;
|
|
909
920
|
}
|
|
910
921
|
|
|
922
|
+
/** DEC-TG — replace a message's text AND its inline keyboard (empty rows remove the buttons). */
|
|
923
|
+
async editMessageWithKeyboard(
|
|
924
|
+
chatId: number,
|
|
925
|
+
messageId: number,
|
|
926
|
+
text: string,
|
|
927
|
+
buttons: Array<Array<{ text: string; data: string }>>,
|
|
928
|
+
): Promise<void> {
|
|
929
|
+
await this.throttledCall<unknown>(chatId, 'editMessageText', {
|
|
930
|
+
chat_id: chatId,
|
|
931
|
+
message_id: messageId,
|
|
932
|
+
text: text.length > this.maxChars ? `${text.slice(0, this.maxChars - 2)} …` : text,
|
|
933
|
+
reply_markup: { inline_keyboard: buttons.map((row) => row.map((b) => ({ text: b.text, callback_data: b.data.slice(0, 64) }))) },
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/** True while the long-poll loop runs — only a polling bot should push decision cards (nexus may hold a send-only bot). */
|
|
938
|
+
isPolling(): boolean { return this.running; }
|
|
939
|
+
|
|
911
940
|
/** T2-P6 — acknowledge a callback_query so Telegram clears the
|
|
912
941
|
* spinner next to the button the user tapped. Pass a `text` to
|
|
913
942
|
* flash a tiny toast on the user's device (≤200 chars). Returns
|
|
@@ -1145,6 +1174,8 @@ export class TelegramBot {
|
|
|
1145
1174
|
if (u.callback_query) {
|
|
1146
1175
|
const cq = u.callback_query;
|
|
1147
1176
|
if (!this.isOwnerAllowed(cq.from.id)) {
|
|
1177
|
+
// DEC-TG — a decision-card tap from someone else is the rehearsal's «refused» case; name it there too.
|
|
1178
|
+
if ((cq.data ?? '').startsWith('dec:')) debug.log('decisions.telegram', 'tap-refused', { platform: 'telegram', gate: 'owner-allowlist' }, { level: 'warn' });
|
|
1148
1179
|
debug.log('telegram.owner-gate', 'refused', {
|
|
1149
1180
|
kind: 'callback', reason: this.allowedUsers.size === 0 ? 'empty-allowlist' : 'not-allowed', userId: cq.from.id,
|
|
1150
1181
|
});
|
|
@@ -1402,7 +1433,26 @@ export class TelegramBot {
|
|
|
1402
1433
|
}
|
|
1403
1434
|
}
|
|
1404
1435
|
// 글(자막)은 캡션이 달린 항목에만 있다 — 앨범이면 첫 장에만 붙는다(MK 2026-09-30).
|
|
1405
|
-
|
|
1436
|
+
const saved = saveFieldMedia(inputs, { rootDir: this.fieldRootDir(), event, device: 'telegram', ...(tag?.text ? { caption: tag.text } : {}) });
|
|
1437
|
+
count = saved.count;
|
|
1438
|
+
scheduleFieldReel(saved.dir, {
|
|
1439
|
+
...this.fieldReel,
|
|
1440
|
+
notificationKey: `telegram:${this.botId}:${ctx.chatId}:${ctx.threadId ?? ''}:${ctx.mediaGroupId ?? ctx.messageId}`,
|
|
1441
|
+
onDone: async (result) => {
|
|
1442
|
+
const reply = { replyTo: ctx.messageId, threadId: ctx.threadId };
|
|
1443
|
+
if (result.ok && result.file) {
|
|
1444
|
+
const sent = await this.sendDocument(ctx.chatId, readFileSync(result.file), 'reel-9x16.mp4', {
|
|
1445
|
+
...reply, mimeType: 'video/mp4', caption: `현장 영상 · ${event} · ${result.seconds}초`,
|
|
1446
|
+
});
|
|
1447
|
+
if (!sent) throw new Error('sendDocument failed');
|
|
1448
|
+
if (result.feed?.started) {
|
|
1449
|
+
await this.sendMessage(ctx.chatId, `인스타 피드 초안을 만드는 중 · ${event} — 1분쯤 뒤 앱 «게시 대기»에서 고치고 «최종 게시»를 누르면 됩니다(누르기 전엔 아무 데도 안 올라갑니다).`, reply);
|
|
1450
|
+
}
|
|
1451
|
+
} else {
|
|
1452
|
+
await this.sendMessage(ctx.chatId, `현장 영상 실패 · ${event} · ${result.error ?? 'render failed'}`, reply);
|
|
1453
|
+
}
|
|
1454
|
+
},
|
|
1455
|
+
});
|
|
1406
1456
|
debug.log('field.upload', 'saved', { surface: 'telegram', files: inputs.length, count, album: !!ctx.mediaGroupId });
|
|
1407
1457
|
} catch (err) {
|
|
1408
1458
|
failure = err instanceof FieldUploadError ? err.code : 'download-failed';
|
|
@@ -1440,6 +1490,15 @@ export class TelegramBot {
|
|
|
1440
1490
|
}
|
|
1441
1491
|
// 현장 업로드 — `#현장` 캡션(또는 태그된 앨범의 나머지)의 사진·영상은 LLM 대신 현장 폴더로.
|
|
1442
1492
|
if (await this.tryHandleFieldUpload(ctx)) return;
|
|
1493
|
+
if (ctx.text.startsWith('@')) {
|
|
1494
|
+
const reply = await handleTelegramSeatWork(ctx.text, {
|
|
1495
|
+
chatId: ctx.chatId, messageId: ctx.messageId, threadId: ctx.threadId, botId: ctx.botId,
|
|
1496
|
+
}, this.seatWorkDeps);
|
|
1497
|
+
if (reply !== null) {
|
|
1498
|
+
await this.sendMessage(ctx.chatId, reply, { replyTo: ctx.messageId, threadId: ctx.threadId });
|
|
1499
|
+
return;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1443
1502
|
// 영상은 `#현장` 저장 전용으로만 파싱한다 — 그 밖의 영상은 종전처럼 무시한다.
|
|
1444
1503
|
if (ctx.video && ctx.attachments.length === 0) return;
|
|
1445
1504
|
// Cascade-zyu U2 — capture utterance intent at update ingress.
|
|
@@ -2804,6 +2863,17 @@ export function botFromConfig(opts: BotFromConfigOpts): TelegramBot {
|
|
|
2804
2863
|
// poll loop requests `callback_query` from the first getUpdates —
|
|
2805
2864
|
// in-turn approval taps then arrive without a mid-turn delay.
|
|
2806
2865
|
botRef.ensureSurfaceHitl();
|
|
2866
|
+
// DEC-TG(대표 10-02) — decision cards on the MAIN bot only (other channel pollers carry other tokens). The ticker
|
|
2867
|
+
// itself sends only while this bot polls, so a send-only nexus bot never duplicates the standalone poller's cards.
|
|
2868
|
+
if (opts.userConfig.raw?.decisions === undefined || (opts.userConfig.raw.decisions as { cards?: unknown }).cards !== false) {
|
|
2869
|
+
const mainToken = resolveChannelBotToken('telegram', opts.userConfig)?.token;
|
|
2870
|
+
if (mainToken && botToken === mainToken) {
|
|
2871
|
+
const bot = botRef;
|
|
2872
|
+
void import('./decisions/telegram-decision-cards.js')
|
|
2873
|
+
.then(({ attachTelegramDecisionCards }) => { attachTelegramDecisionCards(bot, opts.userConfig); })
|
|
2874
|
+
.catch((err: unknown) => log(`[telegram] decision cards not attached: ${err instanceof Error ? err.message : String(err)}`));
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2807
2877
|
return botRef;
|
|
2808
2878
|
}
|
|
2809
2879
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// `elanous_skills_list` MCP tool. Mirrors the daemon-side ACP method
|
|
3
3
|
// `elanous/skills/list` (src/acp/server.ts:2241) but session-agnostic so
|
|
4
4
|
// the codex app-server can call it through the elanous-tools MCP server
|
|
5
|
-
// without first opening an ACP session. Enumerates
|
|
5
|
+
// without first opening an ACP session. Enumerates configured skill roots
|
|
6
6
|
// and surfaces the first non-heading line of each SKILL.md as the
|
|
7
7
|
// description.
|
|
8
8
|
//
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { join } from 'node:path';
|
|
15
15
|
import { readdir, readFile } from 'node:fs/promises';
|
|
16
16
|
import { defaultSkillDirs } from '../user-config.js';
|
|
17
|
+
import { SKILLS_LIST_DESCRIPTION } from '../onboarding/entry-hints.js';
|
|
17
18
|
import type { LLMToolSpec } from '../llm.js';
|
|
18
19
|
import type { ToolRuntime } from './types.js';
|
|
19
20
|
|
|
@@ -41,8 +42,7 @@ export interface ElanousSkillsListResult extends Record<string, unknown> {
|
|
|
41
42
|
export function buildElanousSkillsListTool(): LLMToolSpec {
|
|
42
43
|
return {
|
|
43
44
|
name: 'elanous_skills_list',
|
|
44
|
-
description:
|
|
45
|
-
'List installed elanous skills (sub-directories of ~/.elanous/skills with optional SKILL.md). Codex app-server callback via the elanous-tools MCP server. Read-only.',
|
|
45
|
+
description: SKILLS_LIST_DESCRIPTION,
|
|
46
46
|
parameters: {
|
|
47
47
|
type: 'object',
|
|
48
48
|
properties: {
|