elanous 0.2.13 → 0.2.14
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/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-6e7e0377c1db0bdc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9656-c1540088f5859c80.js → 1844-9ec4b1d12f6e80eb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{390.c373e31b3b9d0c41.js → 1948.15f356b15a625597.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
- package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4685-c3c1c625b71accd1.js → 4614-21403aac730d4586.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{8039-df92e08fede5de8f.js → 4795-f372490b178583fc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-2df489622a52a33a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1452.93700483b9d4fc61.js → 6110.1297d4093fdfbb0d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +2 -0
- package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +1 -0
- package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1829-b453a1b22d3999bb.js → 7327-2d69fb321ba73389.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +25 -0
- package/apps/pwa/out/_next/static/chunks/{8421-1c4acfb1395a0edf.js → 8949-ecb4128a0858b911.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9103-7a3ae7f60af3b99f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3869-3cd46526879f454c.js → 9247-29d889b809288e65.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{79f8be9e-957d68913b590b08.js → a9f2b54d-7865aba0f4d2ee49.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-7b2b4f378354869a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-f14987d5850a31e0.js → page-1abfe3aec3be6bdc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-1512d91c7491ed62.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-a627db94ec6904a0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-e6b84ab07d3567d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-fed69f9759d248d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-15374909d8f1aebd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-012a742f81218b01.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-99effd9d893a43dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-938de57ae0315e46.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-160089123a40dd91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-629e1ede482ec6eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-66c27608f651b866.js → page-2ce5471926761fdd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-27e99b4e54a849d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-32cde713be47cddb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e7e10cd3386222e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-e64caff084f7ce21.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-db07eaffba0df7e3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-78b40a344ed22f24.js → page-e1e47d8d940aba3d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-86f91034bd5cabe5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-ce4d8a1215ce53a3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-58043db78ba519ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-357750dfb91bf960.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-63d14a4d8fe1e1c1.js → page-274703bc41b71333.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-3b7e821dc492bdfd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-f8da5dc6f3752276.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2f810b18d89ecba6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5cd7e3a9-404fd6b8d0ecd44c.js → f6231284-45726550aba08fca.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{f48170cc-d05d28bf87a43bf4.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +1 -0
- package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +1 -0
- package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +23 -23
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +23 -23
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +23 -23
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +22 -22
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +22 -22
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +22 -22
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +23 -23
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +23 -23
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +23 -23
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +23 -23
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +23 -23
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +22 -22
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +22 -22
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +23 -23
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +23 -23
- package/apps/pwa/out/loops/index.html +1 -0
- package/apps/pwa/out/loops/index.txt +30 -0
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +22 -22
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +22 -22
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +23 -23
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +23 -23
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +22 -22
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +22 -22
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +22 -22
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +23 -23
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +23 -23
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +22 -22
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +23 -23
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +23 -23
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +23 -23
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +23 -23
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +23 -23
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +22 -22
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +23 -23
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +23 -23
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +23 -23
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +23 -23
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +23 -23
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +23 -23
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +23 -23
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +23 -23
- package/graphs/orchestrator/orchestrator.yaml +59 -0
- package/graphs/orchestrator/recipes.yaml +18 -0
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +4 -1
- package/graphs/rhythm/daily.yaml +38 -0
- package/graphs/rhythm/recipes.yaml +18 -0
- package/graphs/rhythm/weekly.yaml +39 -0
- package/graphs/video/recipes.yaml +3 -0
- package/graphs/video/youtube-summary.yaml +17 -0
- package/package.json +1 -1
- package/scripts/audit-test-state-writes.ts +72 -17
- package/scripts/brand/check.ts +186 -0
- package/scripts/coord-post.sh +11 -1
- package/scripts/docs-cli-check.ts +23 -6
- package/scripts/expose-rubric-check.ts +118 -0
- package/scripts/expose-rubric-suggest.ts +163 -0
- package/scripts/heal/run-role.ts +14 -0
- package/scripts/hq/standby-verify.ts +11 -7
- package/scripts/lessons/handbook-page.ts +85 -0
- package/scripts/release-loop/gate-node.ts +9 -0
- package/scripts/release-loop/publish-node.ts +14 -1
- package/scripts/release-loop/unattended-release.ts +23 -1
- package/scripts/release-loop/vault-note-node.ts +196 -0
- package/scripts/release-story/draft.ts +172 -0
- package/scripts/rhythm/daily.ts +383 -0
- package/scripts/rhythm/weekly.ts +294 -0
- package/scripts/skills/public-check.ts +169 -0
- package/scripts/test-deterministic.ts +4 -8
- package/scripts/test-diet/effectiveness.ts +195 -0
- package/scripts/test-diet/lib.ts +16 -8
- package/scripts/test-diet/node.ts +77 -9
- package/scripts/ux/demo-rehearsal.ts +2 -2
- package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
- package/scripts/video/youtube-summary.ts +133 -0
- package/skills/diagram-master/SKILL.md +42 -49
- package/skills/diagram-master/references/pyproject.toml +4 -1
- package/skills/diagram-master/references/routing-guide.md +4 -2
- package/skills/grill-me/SKILL.md +1 -0
- package/skills/youtube-master/SKILL.md +13 -13
- package/skills/youtube-master/package-lock.json +0 -545
- package/skills/youtube-master/package.json +2 -5
- package/skills/youtube-master/scripts/main.ts +2 -2
- package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
- package/skills/youtube-master/src/util.ts +30 -7
- package/src/agent-mission/driver.ts +3 -3
- package/src/autopilot/build/llm-conflict-merge.ts +45 -8
- package/src/autopilot/build/merge-intent.ts +53 -0
- package/src/chat/index.ts +2 -0
- package/src/claims/claims-ledger.ts +232 -0
- package/src/claims/claims-render.ts +157 -0
- package/src/cli/claims-cli.ts +94 -0
- package/src/cli/decisions-cli.ts +35 -3
- package/src/cli/docs-cli.ts +2 -0
- package/src/cli/flow-cli.ts +11 -0
- package/src/cli/freeze-cli.ts +45 -0
- package/src/cli/lesson-cli.ts +108 -0
- package/src/cli/logs-cli.ts +104 -2
- package/src/cli/logs-model-input.ts +131 -0
- package/src/cli/pr-cli.ts +29 -1
- package/src/cli/private-ledger-commands.ts +54 -4
- package/src/cli/pty-drive-verdict.ts +11 -2
- package/src/cli/release-cli.ts +144 -21
- package/src/cli/rfc-status.ts +91 -0
- package/src/connectors/linear.ts +5 -3
- package/src/context-bus/condense.ts +15 -20
- package/src/context-bus/context-now-public.ts +86 -0
- package/src/context-bus/context-now-surfaces.ts +55 -2
- package/src/context-bus/context-now.ts +4 -3
- package/src/context-bus/long-term-memory.ts +92 -11
- package/src/dashboard/index.ts +75 -22
- package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
- package/src/dashboard/slash-runtime/loops-table.ts +77 -0
- package/src/decisions/decision-ledger.ts +40 -1
- package/src/discord/sprint21-runtime.ts +16 -0
- package/src/discord-self-message.ts +8 -0
- package/src/discord-slash-wire.ts +31 -2
- package/src/domains/outbound-alert.ts +1 -1
- package/src/elanous-config-dir.ts +29 -4
- package/src/execution-loop/launch-gate.ts +29 -14
- package/src/flow/cards.ts +239 -0
- package/src/graph-runner/graph-tick.ts +4 -0
- package/src/graph-runner/runner.ts +30 -5
- package/src/harness/harness-cli-command.ts +152 -27
- package/src/harness/harness-incidents.ts +43 -12
- package/src/harness/harness-pod-dispatch.ts +2 -0
- package/src/harness/harness-queue-child.ts +3 -3
- package/src/harness/harness-queue.ts +216 -28
- package/src/harness/helper-scan.ts +139 -0
- package/src/harness/model-input-observation.ts +37 -0
- package/src/hq/hq.ts +1 -1
- package/src/hq/promote.ts +301 -0
- package/src/index.ts +83 -21
- package/src/intake-plane/linear-wish.ts +39 -11
- package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
- package/src/intake-plane/wish-card.ts +53 -2
- package/src/intake-plane/wish-folder.ts +5 -2
- package/src/intake-plane/wish-reply.ts +72 -0
- package/src/lessons/lesson-import.ts +106 -0
- package/src/lessons/lesson-ledger.ts +206 -0
- package/src/loops/activity.ts +203 -0
- package/src/loops/budget.ts +32 -0
- package/src/loops/checker.ts +5 -3
- package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
- package/src/loops/loop-cli.ts +36 -4
- package/src/loops/neighbors.ts +40 -0
- package/src/loops/observe.ts +10 -0
- package/src/loops/orchestrator/author-depth.ts +164 -0
- package/src/loops/orchestrator/author-input-check.ts +77 -0
- package/src/loops/orchestrator/author-ledger.ts +230 -0
- package/src/loops/orchestrator/finish-rate.ts +175 -0
- package/src/loops/orchestrator/overlap.ts +51 -12
- package/src/loops/orchestrator/resync-shadow.ts +227 -0
- package/src/loops/orchestrator/tick.ts +302 -0
- package/src/loops/orchestrator/traffic.ts +18 -3
- package/src/loops/registry.ts +454 -9
- package/src/loops/status-rows.ts +118 -0
- package/src/loops/verdict.ts +17 -5
- package/src/maturity/bot-slash-catalog.ts +66 -0
- package/src/maturity/feature-maturity.ts +7 -4
- package/src/nexus/api/dashboard.ts +6 -1
- package/src/nexus/api/http-server.ts +18 -2
- package/src/nexus/api/merge-approvals-gate.ts +1 -1
- package/src/nexus/api/schedules-read.ts +5 -1
- package/src/nexus/api/task-cards-api.ts +44 -5
- package/src/nexus/api/workflows.ts +2 -1
- package/src/release-loop/auto-start.ts +48 -0
- package/src/release-loop/checklist.ts +13 -4
- package/src/release-loop/feature-store.ts +28 -16
- package/src/release-loop/landing-freeze.ts +121 -0
- package/src/release-loop/placement.ts +242 -0
- package/src/release-loop/release-schedule.ts +3 -1
- package/src/seat-dispatch/ceo-commands.ts +2 -2
- package/src/seat-dispatch/seat-ask.ts +16 -3
- package/src/seat-dispatch/seat-questions.ts +58 -0
- package/src/seat-loop/seat-loop.ts +406 -31
- package/src/self-dev/dev-pipeline.ts +6 -2
- package/src/self-dev/draft-sweep.ts +76 -0
- package/src/self-dev/run-store.ts +73 -1
- package/src/self-dev/run-supervisor.ts +8 -0
- package/src/self-dev/self-orchestrate-runtime.ts +5 -1
- package/src/self-implement/budget-gate.ts +29 -0
- package/src/self-implement/expose-gate.ts +47 -0
- package/src/self-implement/frozen-merges.ts +294 -0
- package/src/self-implement/gate-cli.ts +10 -4
- package/src/self-implement/gate-scope.ts +46 -9
- package/src/self-implement/goal-archive.ts +222 -0
- package/src/self-implement/goal-lookup.ts +147 -0
- package/src/self-implement/heal-intake.ts +79 -0
- package/src/self-implement/host-regate.ts +25 -2
- package/src/self-implement/orchestrator.ts +114 -10
- package/src/self-implement/revert-guard.ts +62 -0
- package/src/self-implement/run-ledger.ts +1 -1
- package/src/self-implement/seams.ts +28 -8
- package/src/session/index.ts +2 -0
- package/src/task-cards/card-cli.ts +10 -2
- package/src/task-cards/card-store.ts +10 -6
- package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +80 -16
- package/src/telegram-commands.ts +27 -3
- package/src/telegram-loops-command.ts +44 -0
- package/src/user-config.ts +120 -16
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/storage.ts +31 -13
- package/apps/pwa/out/_next/static/D51l8jUBplJrIpWvENsDK/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1394-931c9827067b2276.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3992-fe5c06d6b31f8b25.js +0 -25
- package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
- package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8605-9da4678f8a310d18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e49b9b76c565a0b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-4c1dbb21861beab2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-f6bc66b919b2ea7d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-60603c1e056c9b25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-a0ea8f5a2341ee6b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-31012264da8a341f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-50309aeb0bdd554a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-0696ee3fac9fe56c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-6ddd2109b7fc3a80.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-7a2bf830038a2264.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1f327f18694fabca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e5cef2fe6e4028df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cf596128d2b420ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-f672536f14f0969f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-29a709616df9b050.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-fee842d7c2151129.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-a011dd5b0bff29c5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-d92e32d51ab5157e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-5f21aed6932ef375.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-1e1a53946804debf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-a1833cd43d832601.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-2a511d320f762e06.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-1c3e03f88dac1e21.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c4d5289c2ea97a89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b91f8952deaeaf27.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-71b43a2588489987.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
- package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
- /package/apps/pwa/out/_next/static/{D51l8jUBplJrIpWvENsDK → x6mjI6vqOkVVu0jFuDkz1}/_ssgManifest.js +0 -0
|
@@ -9,6 +9,32 @@ type Incomplete = { runId: string; reason: string };
|
|
|
9
9
|
type ParseResult = Ledger[] | { error: Incomplete[]; ledgers: Ledger[] };
|
|
10
10
|
|
|
11
11
|
const NON_PROGRESS_EVENTS = new Set(['progress-delivery-outcome']);
|
|
12
|
+
const REEMIT_EXCLUDED_PREFIX = 'progress-delivery';
|
|
13
|
+
type Emit = (category: string, event: string, data: Record<string, unknown>) => void;
|
|
14
|
+
|
|
15
|
+
/** Reemit only complete ledger records; offsets name the first byte of each line in the Pod ledger. */
|
|
16
|
+
function reemitLedgerLines(jsonl: string, runId: string, startOffset: number, emit: Emit): number {
|
|
17
|
+
let offset = startOffset;
|
|
18
|
+
let lines = 0;
|
|
19
|
+
for (const raw of jsonl.split('\n').slice(0, -1)) {
|
|
20
|
+
const podLedgerOffset = offset;
|
|
21
|
+
offset += Buffer.byteLength(raw) + 1;
|
|
22
|
+
try {
|
|
23
|
+
const entry: unknown = JSON.parse(raw);
|
|
24
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) continue;
|
|
25
|
+
const { category, event, data } = entry as Record<string, unknown>;
|
|
26
|
+
if (typeof event !== 'string' || !event || event.startsWith(REEMIT_EXCLUDED_PREFIX)
|
|
27
|
+
|| !data || typeof data !== 'object' || Array.isArray(data)) continue;
|
|
28
|
+
emit(typeof category === 'string' && category ? category : 'self-implement', event,
|
|
29
|
+
{ ...data, runId, origin: 'pod', podLedgerOffset });
|
|
30
|
+
lines += 1;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
try { debug.log('self-implement.pod', 'reemit-failed', { runId, reason: error instanceof Error ? error.message : String(error) }); }
|
|
33
|
+
catch { /* Host log failure must not interrupt the ledger append or collection. */ }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return lines;
|
|
37
|
+
}
|
|
12
38
|
|
|
13
39
|
/** Reassemble only complete, unambiguous transfers; retain good runs when another run is incomplete. */
|
|
14
40
|
export function parsePodLedgerChunks(logs: string): ParseResult {
|
|
@@ -68,9 +94,10 @@ export function parsePodLedgerChunks(logs: string): ParseResult {
|
|
|
68
94
|
* except a ledger that THIS run's live follower created (`replace`): that one is a partial copy and the final is complete. */
|
|
69
95
|
export function collectPodLedgers(
|
|
70
96
|
logs: string,
|
|
71
|
-
{ dir = runLedgerDir(), log = (c, e, d) => debug.log(c, e, d), replace = new Set<string>() }: {
|
|
97
|
+
{ dir = runLedgerDir(), log = (c, e, d) => debug.log(c, e, d), emit = (c, e, d) => debug.log(c, e, d), replace = new Set<string>() }: {
|
|
72
98
|
dir?: string;
|
|
73
99
|
log?: (category: string, event: string, data: Record<string, unknown>) => void;
|
|
100
|
+
emit?: Emit;
|
|
74
101
|
replace?: ReadonlySet<string>;
|
|
75
102
|
} = {},
|
|
76
103
|
): void {
|
|
@@ -82,7 +109,11 @@ export function collectPodLedgers(
|
|
|
82
109
|
for (const { runId, jsonl } of ledgers) {
|
|
83
110
|
try {
|
|
84
111
|
mkdirSync(dir, { recursive: true });
|
|
85
|
-
|
|
112
|
+
const path = runLedgerPath(runId, dir);
|
|
113
|
+
const previousBytes = replace.has(runId) && existsSync(path) ? readFileSync(path).length : 0;
|
|
114
|
+
writeFileSync(path, jsonl, { flag: replace.has(runId) ? 'w' : 'wx' });
|
|
115
|
+
const newLines = Buffer.from(jsonl).subarray(previousBytes).toString('utf8');
|
|
116
|
+
reemitLedgerLines(newLines, runId, previousBytes, emit);
|
|
86
117
|
log('self-implement.pod', 'ledger-collected', { runId, lines: jsonl.split('\n').filter(Boolean).length, bytes: Buffer.byteLength(jsonl) });
|
|
87
118
|
} catch (e) {
|
|
88
119
|
if ((e as NodeJS.ErrnoException).code === 'EEXIST') {
|
|
@@ -109,12 +140,14 @@ export function createPodLedgerFollower(opts: {
|
|
|
109
140
|
exec: (script: string) => { status: number | null; stdout: string; stderr: string };
|
|
110
141
|
dir?: string;
|
|
111
142
|
log?: (category: string, event: string, data: Record<string, unknown>) => void;
|
|
143
|
+
emit?: Emit;
|
|
112
144
|
stallMinutes?: number;
|
|
113
145
|
now?: () => number;
|
|
114
146
|
onStall?: (message: string) => void;
|
|
115
147
|
}): { poll(): void; readonly owned: boolean } {
|
|
116
148
|
const dir = opts.dir ?? runLedgerDir();
|
|
117
149
|
const log = opts.log ?? ((c, e, d) => debug.log(c, e, d));
|
|
150
|
+
const emit: Emit = opts.emit ?? ((c, e, d) => debug.log(c, e, d));
|
|
118
151
|
const now = opts.now ?? Date.now;
|
|
119
152
|
const stallMinutes = opts.stallMinutes ?? 30;
|
|
120
153
|
if (!Number.isFinite(stallMinutes) || stallMinutes <= 0) throw new RangeError('stallMinutes must be positive');
|
|
@@ -188,17 +221,24 @@ export function createPodLedgerFollower(opts: {
|
|
|
188
221
|
mkdirSync(dir, { recursive: true });
|
|
189
222
|
appendFileSync(path, complete);
|
|
190
223
|
owned = true;
|
|
224
|
+
const reemitted = reemitLedgerLines(complete, opts.runId, offset - Buffer.byteLength(text), emit);
|
|
225
|
+
if (reemitted) {
|
|
226
|
+
try { debug.log('self-implement.pod', 'reemitted', { runId: opts.runId, lines: reemitted }); }
|
|
227
|
+
catch { /* The observation cannot interrupt following. */ }
|
|
228
|
+
}
|
|
191
229
|
log('self-implement.pod', 'ledger-live-appended', { runId: opts.runId, lines: complete.split('\n').filter(Boolean).length, bytes: Buffer.byteLength(complete), offset });
|
|
192
230
|
for (const line of complete.split('\n')) {
|
|
193
231
|
if (!line) continue;
|
|
194
232
|
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. */ }
|
|
199
233
|
let entry: unknown;
|
|
200
234
|
try { entry = JSON.parse(line); } catch { continue; }
|
|
201
235
|
const event = (entry && typeof entry === 'object' && 'event' in entry) ? entry.event : undefined;
|
|
236
|
+
try {
|
|
237
|
+
if (typeof event === 'string' && !event.startsWith(REEMIT_EXCLUDED_PREFIX)) {
|
|
238
|
+
logEvent = ledgerLineToLogEvent(line, opts.runId);
|
|
239
|
+
if (logEvent && opts.log) log(logEvent.category, logEvent.event, logEvent.data);
|
|
240
|
+
}
|
|
241
|
+
} catch { /* A failed host log write must not interrupt ledger following or stall accounting. */ }
|
|
202
242
|
if (typeof event !== 'string' || !event || NON_PROGRESS_EVENTS.has(event)) continue;
|
|
203
243
|
const idleMinutes = Math.max(0, (now() - lastProgressAt) / 60_000);
|
|
204
244
|
if (stalled) log('self-implement.pod', 'stall-cleared', { runId: opts.runId, idleMinutes });
|
|
@@ -40,7 +40,7 @@ export const POD_JOB_DEADLINE_SECONDS = 10_800;
|
|
|
40
40
|
* 예약만 실사용에 맞춘다. */
|
|
41
41
|
export const POD_CHILD_REQUESTS = { cpu: '1', memory: '4Gi' } as const;
|
|
42
42
|
import type { PodPoolMember, PodPoolScheduler } from './pod-pool.js';
|
|
43
|
-
import { measurePoolLease, recommendConcurrency, POD_HOST_LEASE_ANNOTATION } from './pod-lease.js';
|
|
43
|
+
import { measurePoolLease, recommendConcurrency, POD_HOST_LEASE_ANNOTATION, LEASE_KUBECTL_MAX_BUFFER } from './pod-lease.js';
|
|
44
44
|
import { ACTUAL_SUBSTRATE_ENV, RUN_CONTRACT_ENV, carryRunContract, completionFloorFor } from '../../self-implement/graph-run-contract.js';
|
|
45
45
|
import { spawnSync } from 'node:child_process';
|
|
46
46
|
import { createHash } from 'node:crypto';
|
|
@@ -58,7 +58,7 @@ import { envLiteral } from '../../platform/env-literal.js';
|
|
|
58
58
|
import { LLM_TIER_MAP_BY_PROVIDER, lookupLlmTierSpec, type LlmTierProvider } from '../../model-tier/llm-tier-map.js';
|
|
59
59
|
import { parseSelfImplementJson, type SelfImplementJobDone, type SelfImplementJobSpawn } from './self-implement.js';
|
|
60
60
|
import { codexQuotaPolicyFromConfig } from '../../oauth/codex-account-store.js';
|
|
61
|
-
import { goalTypeOf } from '../../../scripts/measure-pod-memory-by-goal.js';
|
|
61
|
+
import { goalTypeOf, type GoalType } from '../../../scripts/measure-pod-memory-by-goal.js';
|
|
62
62
|
import { readPodMemoryAdvice, type PodMemoryAdvice } from '../../cli/pod-memory-advice.js';
|
|
63
63
|
import { getUserConfig } from '../../user-config.js';
|
|
64
64
|
|
|
@@ -145,8 +145,11 @@ export function defaultKubectl(args: readonly string[], input?: string): { statu
|
|
|
145
145
|
// 셸 프록시가 kubectl 의 로컬 API 요청을 가로챈다(2026-09-25 실측) — 자식 env 에서 뺀다.
|
|
146
146
|
const env = { ...process.env };
|
|
147
147
|
for (const k of ['HTTPS_PROXY', 'https_proxy', 'HTTP_PROXY', 'http_proxy', 'ALL_PROXY', 'all_proxy']) delete env[k];
|
|
148
|
-
const r = spawnSync('kubectl', [...args], { encoding: 'utf8', input, env, timeout: 120_000 });
|
|
149
|
-
|
|
148
|
+
const r = spawnSync('kubectl', [...args], { encoding: 'utf8', input, env, timeout: 120_000, maxBuffer: LEASE_KUBECTL_MAX_BUFFER });
|
|
149
|
+
const error = (r.error as NodeJS.ErrnoException | undefined)?.code === 'ENOBUFS'
|
|
150
|
+
? `kubectl 출력 너무 큼 (ENOBUFS; maxBuffer=${LEASE_KUBECTL_MAX_BUFFER} bytes)`
|
|
151
|
+
: r.error ? String(r.error) : '';
|
|
152
|
+
return { status: r.status, stdout: r.stdout ?? '', stderr: (r.stderr ?? '') + error };
|
|
150
153
|
}
|
|
151
154
|
|
|
152
155
|
/** Job·Secret 이름 — 런마다 다르게(병렬) · k8s 이름 규칙(소문자·숫자·하이픈 · 63자 이하). */
|
|
@@ -220,6 +223,46 @@ export function hostGrokCredentials(opts: {
|
|
|
220
223
|
throw new Error('grok: 구독 자격 없음 · API 키 opt-in 꺼짐 또는 키 없음');
|
|
221
224
|
}
|
|
222
225
|
|
|
226
|
+
export type PodGrokSkipReason = 'missing' | 'expiring' | 'refresh-failed';
|
|
227
|
+
|
|
228
|
+
/** GROK-OPTIONAL (10-05) — may this launch plan put grok in the Pod's chain at all?
|
|
229
|
+
* The Pod copy must outlive the Job (POD_GROK_MIN_VALIDITY_MS), so an expiring subscription that the host cannot
|
|
230
|
+
* refresh is «not available» here: the plan drops grok and goes on with codex instead of failing every launch. */
|
|
231
|
+
export function podGrokSubscriptionUsable(opts: {
|
|
232
|
+
home?: string; env?: NodeJS.ProcessEnv;
|
|
233
|
+
refresh?: () => void; isExpiring?: () => boolean; expiresAt?: () => string | null;
|
|
234
|
+
log?: (category: string, event: string, data: Record<string, unknown>) => void;
|
|
235
|
+
} = {}): { usable: true } | { usable: false; reason: PodGrokSkipReason; expiresAt: string | null } {
|
|
236
|
+
const log = opts.log ?? ((category, event, data) => debug.log(category, event, data));
|
|
237
|
+
const expiresAt = () => {
|
|
238
|
+
if (opts.expiresAt) return opts.expiresAt();
|
|
239
|
+
try {
|
|
240
|
+
const scopes = JSON.parse(readFileSync(grokAuthFilePath(opts.home), 'utf8')) as Record<string, { expires_at?: unknown }>;
|
|
241
|
+
const all = Object.values(scopes).flatMap((s) => (s && typeof s.expires_at === 'string' ? [s.expires_at] : []));
|
|
242
|
+
return all.sort().at(-1) ?? null;
|
|
243
|
+
} catch { return null; }
|
|
244
|
+
};
|
|
245
|
+
const skip = (reason: PodGrokSkipReason) => {
|
|
246
|
+
const at = expiresAt();
|
|
247
|
+
try { log('pod.grok-credentials', 'skipped', { reason, expiresAt: at }); } catch { /* observation is fail-soft */ }
|
|
248
|
+
return { usable: false as const, reason, expiresAt: at };
|
|
249
|
+
};
|
|
250
|
+
const credential = resolveGrokCredential({ home: opts.home, env: opts.env });
|
|
251
|
+
if (credential?.kind !== 'subscription') return skip('missing');
|
|
252
|
+
const expiring = opts.isExpiring ?? (() => isGrokSubscriptionExpiring({ home: opts.home, bufferMs: POD_GROK_MIN_VALIDITY_MS }));
|
|
253
|
+
if (!expiring()) return { usable: true };
|
|
254
|
+
let refreshed = true;
|
|
255
|
+
try { (opts.refresh ?? (() => { refreshGrokSubscriptionToken({ home: opts.home }); }))(); } catch { refreshed = false; }
|
|
256
|
+
if (!expiring()) return { usable: true };
|
|
257
|
+
return skip(refreshed ? 'expiring' : 'refresh-failed');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** The one launch line for a skipped grok copy — the reader sees why grok left this run's chain. */
|
|
261
|
+
export function podGrokSkippedLine(skip: { reason: PodGrokSkipReason; expiresAt: string | null }): string {
|
|
262
|
+
const why = skip.reason === 'missing' ? '구독 자격 없음' : skip.reason === 'expiring' ? '만료 임박' : '만료 임박 · 호스트 갱신 실패';
|
|
263
|
+
return `⚠️ grok 자격 없음(${why}${skip.expiresAt ? ` · 만료 ${skip.expiresAt}` : ''}) — 이 런의 폴백 체인에서 grok 을 뺐다`;
|
|
264
|
+
}
|
|
265
|
+
|
|
223
266
|
/** 키 캐시 관례: `~/.cache/<env 이름 소문자>`(예: OPENROUTER_API_KEY → ~/.cache/openrouter_api_key · src/config.ts 와 같다). */
|
|
224
267
|
function defaultReadKeyCache(name: string): string | undefined {
|
|
225
268
|
try { const v = readFileSync(join(homedir(), '.cache', name.toLowerCase()), 'utf8').trim(); return v || undefined; } catch { return undefined; }
|
|
@@ -416,6 +459,20 @@ export const POD_MEMORY_DEFAULT = '16Gi';
|
|
|
416
459
|
export const POD_MEMORY_HIGH_DEFAULT = '32Gi';
|
|
417
460
|
/** POD7 — `lite`(`ELANOUS_POD_MEMORY_LITE` 또는 2Gi): 명시할 때만(인자·문면 줄) · 자동 판정은 없다(성공 런 피크 기록 0 — 설계 §②). OOM 이면 POD9 가 high 로 한 번 다시 띄운다. */
|
|
418
461
|
export const POD_MEMORY_LITE_DEFAULT = '2Gi';
|
|
462
|
+
|
|
463
|
+
/** Reserve from measured per-run peaks, never from the advisory limit tier. Sparse or missing measurements keep the old reservation. */
|
|
464
|
+
export function podMemoryRequestFor(kind: GoalType | null, advice?: PodMemoryAdvice): string {
|
|
465
|
+
const evidence = advice?.byGoalType.find((entry) => entry.goalType === kind)?.evidence;
|
|
466
|
+
const peak = evidence?.peakMiB.p95;
|
|
467
|
+
const reason = !kind ? 'unknown-kind' : !evidence ? 'no-advice'
|
|
468
|
+
: evidence.measured < 3 || evidence.runs < 3 || evidence.measured * 2 < evidence.runs ? 'insufficient-sample'
|
|
469
|
+
: peak === null || peak === undefined || !Number.isFinite(peak) || peak < 0 ? 'no-peak' : null;
|
|
470
|
+
if (reason) {
|
|
471
|
+
debug.log('pod.memory', 'request-default', { kind, reason });
|
|
472
|
+
return POD_CHILD_REQUESTS.memory;
|
|
473
|
+
}
|
|
474
|
+
return `${Math.max(1, Math.ceil(peak! * 1.25 / 1024))}Gi`;
|
|
475
|
+
}
|
|
419
476
|
export const POD_MEMORY_TIERS = ['lite', 'standard', 'high'] as const;
|
|
420
477
|
export type PodMemoryTier = (typeof POD_MEMORY_TIERS)[number];
|
|
421
478
|
export type PodMemorySource = 'option' | 'goal-line' | 'pwa-auto' | 'default' | 'parent-goal-line' | 'parent-pwa-auto' | 'advise';
|
|
@@ -485,7 +542,7 @@ export function podMemoryLimitFor(feature: string, env: NodeJS.ProcessEnv | Read
|
|
|
485
542
|
return { limit: a !== null && b !== null && a > b ? base : high, tier, source };
|
|
486
543
|
}
|
|
487
544
|
|
|
488
|
-
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; /** Which goal execution and attempt this Job is — a resumed host verifies it before following the Job (POD9). */ execution?: { key: string; attempt: number }; hostLeaseAdmitted?: boolean }): Record<string, unknown> {
|
|
545
|
+
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; memoryRequest?: string; goalDoc?: string; grokCredential?: 'subscription' | 'api_key'; codexAccounts?: readonly string[]; appCredential?: boolean; /** Test seam — default POD_GH_STALE_SECONDS. */ githubStaleSeconds?: number; /** Which goal execution and attempt this Job is — a resumed host verifies it before following the Job (POD9). */ execution?: { key: string; attempt: number }; hostLeaseAdmitted?: boolean }): Record<string, unknown> {
|
|
489
546
|
const quoted = o.args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(' ');
|
|
490
547
|
const goalPath = o.goalDoc ? `'${(o.goalDoc.startsWith('-') ? `./${o.goalDoc}` : o.goalDoc).replace(/'/g, `'\\''`)}'` : undefined;
|
|
491
548
|
const askBaseIndex = o.goalDoc ? o.args.indexOf('--base') : -1;
|
|
@@ -598,8 +655,8 @@ if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
|
|
|
598
655
|
name: 'child', image: o.image, imagePullPolicy: o.imagePullPolicy ?? 'Never',
|
|
599
656
|
// 📏 09-25: 6Gi 는 빠듯했다 — 자식이 6,127Mi 에 붙어 OOMKilled(137) → 12Gi.
|
|
600
657
|
// 📏 09-27: 12Gi 에서도 OOMKilled 셋(구현 노드 도중) — 같은 골을 16Gi 로 다시 쏘니 끝까지 갔다(#20930) → 기본 16Gi.
|
|
601
|
-
//
|
|
602
|
-
resources: { requests: { ...POD_CHILD_REQUESTS }, limits: { memory: o.memoryLimit ?? POD_MEMORY_DEFAULT, cpu: '4' } },
|
|
658
|
+
// 요청은 실측 피크별로 고르고 상한은 독립적으로 유지한다. ELANOUS_POD_MEMORY 로 상한 조정.
|
|
659
|
+
resources: { requests: { ...POD_CHILD_REQUESTS, memory: o.memoryRequest ?? POD_CHILD_REQUESTS.memory }, limits: { memory: o.memoryLimit ?? POD_MEMORY_DEFAULT, cpu: '4' } },
|
|
603
660
|
command: ['bash', '-c'], args: [script],
|
|
604
661
|
env: [
|
|
605
662
|
...(o.runId ? [{ name: 'ELANOUS_RUN_ID', value: o.runId }] : []),
|
|
@@ -674,11 +731,11 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
674
731
|
let advice: PodMemoryAdvice | undefined;
|
|
675
732
|
const explicitMemory = Boolean(parsePodMemoryTier(env.ELANOUS_POD_MEMORY_TIER) || GOAL_LINE.test(input.feature) || GOAL_LINE.test(parentGoal ?? ''));
|
|
676
733
|
const useAdvice = options.adviseDefaults ?? getUserConfig().pod?.memory?.adviseDefaults === true;
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
734
|
+
try { advice = (options.memoryAdvice ?? readPodMemoryAdvice)(); }
|
|
735
|
+
catch (error) { debug.log('self-implement.pod', 'memory-advice-unavailable', { spaceId: input.spaceId, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
|
|
736
|
+
const { limit: selectedMemoryLimit, tier: memoryTier, source: memorySource } = podMemoryLimitFor(input.feature, env, parentGoal, useAdvice && !explicitMemory ? advice : undefined);
|
|
737
|
+
const requestKind = broadPodTestWarning(input.feature) ? 'test' : goalTypeOf(input.feature)
|
|
738
|
+
?? (parentGoal ? (broadPodTestWarning(parentGoal) ? 'test' : goalTypeOf(parentGoal)) : null);
|
|
682
739
|
for (const [where, text] of [['feature', input.feature], ['parent-goal', parentGoal]] as const) {
|
|
683
740
|
if (text && text.split(/\r?\n/u).some((line) => /Pod 메모리/u.test(line) && !GOAL_LINE.test(line))) {
|
|
684
741
|
const reason = '한 줄 단독 `Pod 메모리: high|standard` 형식이 아님';
|
|
@@ -694,7 +751,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
694
751
|
let oomRetried = false;
|
|
695
752
|
let retryFromChildRunId: string | undefined;
|
|
696
753
|
let retryFromMemoryLimit: string | undefined;
|
|
697
|
-
debug.log('self-implement.pod', 'memory-limit', { spaceId: input.spaceId, memoryLimit, tier: memoryTier, source: memorySource,
|
|
754
|
+
debug.log('self-implement.pod', 'memory-limit', { spaceId: input.spaceId, goalType: requestKind, memoryLimit, tier: memoryTier, source: memorySource,
|
|
698
755
|
...(memorySource.startsWith('parent-') ? { inheritedFrom } : {}), ...(parentGoalUnreadable ? { parentGoal: 'unreadable' } : {}) });
|
|
699
756
|
const childRunId = mintRunId();
|
|
700
757
|
const executionKey = createHash('sha256').update(JSON.stringify({ spaceId: input.spaceId, feature: input.feature, base: input.base, autoMerge: input.autoMerge, autoReview: input.autoReview, openPr: input.openPr, draft: input.draft })).digest('hex');
|
|
@@ -1074,17 +1131,23 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1074
1131
|
const detailUntil = detail && (detail.scope === 'all' || detail.scope === parentRunId || detail.scope === launchRunId) && detail.until > Date.now()
|
|
1075
1132
|
? String(detail.until) : undefined;
|
|
1076
1133
|
const armEnv = Object.fromEntries(Object.entries(options.armEnv ?? {}).filter(([key]) => key !== 'ELANOUS_LIVE_DETAIL_UNTIL' && key !== POD_GITHUB_CREDENTIAL_TOKEN_ENV && key !== POD_GITHUB_CREDENTIAL_URL_ENV && (!hostRefresh || !['GH_TOKEN', 'GITHUB_TOKEN', 'GH_CONFIG_DIR'].includes(key))));
|
|
1077
|
-
const
|
|
1134
|
+
const seat = env.ELANOUS_HARNESS_SEAT;
|
|
1135
|
+
const jobArmEnv = grounding || credentialRelay || githubRelay || env.ELANOUS_DISPATCH_RECORDED === '1' || options.armEnv || detailUntil || seat
|
|
1078
1136
|
? {
|
|
1079
1137
|
...armEnv,
|
|
1080
1138
|
...(detailUntil ? { ELANOUS_LIVE_DETAIL_UNTIL: detailUntil } : {}),
|
|
1081
1139
|
...(env.ELANOUS_DISPATCH_RECORDED === '1' ? { ELANOUS_DISPATCH_RECORDED: '1' } : {}),
|
|
1140
|
+
...(seat === 'OP' || seat === 'TC' || seat === 'MK' || seat === 'UX' ? { ELANOUS_HARNESS_SEAT: seat } : {}),
|
|
1082
1141
|
...(grounding ? { [GROUNDING_URL_ENV]: groundingUrl! } : {}),
|
|
1083
1142
|
...(credentialRelay ? { [POD_CREDENTIAL_URL_ENV]: `${new URL(groundingUrl!).origin}${POD_CREDENTIAL_GROK_PATH}` } : {}),
|
|
1084
1143
|
...(githubRelay ? { [POD_GITHUB_CREDENTIAL_URL_ENV]: `${new URL(groundingUrl!).origin}${POD_CREDENTIAL_GITHUB_PATH}` } : {}),
|
|
1085
1144
|
}
|
|
1086
1145
|
: undefined;
|
|
1087
|
-
const
|
|
1146
|
+
const measuredRequest = podMemoryRequestFor(requestKind, advice);
|
|
1147
|
+
const requestGi = memoryGi(measuredRequest);
|
|
1148
|
+
const limitGi = memoryGi(memoryLimit);
|
|
1149
|
+
const memoryRequest = requestGi !== null && limitGi !== null && requestGi > limitGi ? memoryLimit : measuredRequest;
|
|
1150
|
+
const job = podJobManifest({ name, namespace, image: jobImage, ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}), repoUrl, ...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), args, passEnv: jobPassEnv, deadlineSeconds: options.deadlineSeconds ?? POD_JOB_DEADLINE_SECONDS, ...(goalDoc ? { goalDoc } : {}), runId: launchRunId, ...(parentRunId ? { parentRunId } : {}), ...(jobArmEnv ? { armEnv: jobArmEnv } : {}), hostId: resolveHostId(env), skillEnvs: Object.keys(skillEnvs), memoryLimit, memoryRequest, imageCommit: options.imageCommit !== undefined ? options.imageCommit : options.kubectl ? null : podImageFreshness({ image }).imageCommit, ...(grok ? { grokCredential: 'grokAuth' in creds && creds.grokAuth ? 'subscription' as const : 'api_key' as const } : {}), ...(codexAccounts ? { codexAccounts } : {}), ...(hostRefresh ? { appCredential: true } : {}), execution: { key: executionKey, attempt: oomRetried ? 2 : 1 }, hostLeaseAdmitted: !!releaseAdmission }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
|
|
1088
1151
|
const a = kubectl(['apply', '-f', '-'], JSON.stringify(job));
|
|
1089
1152
|
if (a.status !== 0) { cleanupSecret(); return { exitCode: 1, output: a.stderr, error: { code: 'pod-apply', message: a.stderr.trim() } }; }
|
|
1090
1153
|
releaseAdmission?.applied?.(name, context, namespace);
|
|
@@ -1267,7 +1330,8 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1267
1330
|
catch (error) { debug.log('harness.host-regate', 'unmeasured', { pr: prNumber, files: [], os: process.platform, failedStep: 'comment', detail: error instanceof Error ? error.message : String(error) }); }
|
|
1268
1331
|
}
|
|
1269
1332
|
const releaseHold = regate.failures[0]?.step === 'release-path-hold';
|
|
1270
|
-
|
|
1333
|
+
const frozen = regate.status === 'frozen';
|
|
1334
|
+
disposition = { ...disposition, stage: frozen || releaseHold ? 'pr-opened' : regate.passed ? 'merged' : 'host-regate-failed', merged: regate.passed && !frozen, hostRegate: regate, ok: regate.passed || releaseHold || frozen };
|
|
1271
1335
|
}
|
|
1272
1336
|
const childFailure = state === 'failed' && !oomKilled && failedReason !== 'DeadlineExceeded'
|
|
1273
1337
|
? lastPodChildFailure(logs) : null;
|
package/src/telegram-commands.ts
CHANGED
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
import type { TgIncoming, TgMessageStreamer } from './telegram.js';
|
|
15
15
|
import type { UserConfig } from './user-config.js';
|
|
16
16
|
import { botCommandsToTelegram } from './bots/command-surface.js';
|
|
17
|
+
import { SLASH_COMMANDS, type SlashCommand } from './chat/index.js';
|
|
18
|
+
import { FEATURE_MATURITY, type Maturity } from './maturity/feature-maturity.js';
|
|
19
|
+
import { buildBotSlashCatalog } from './maturity/bot-slash-catalog.js';
|
|
17
20
|
import {
|
|
18
21
|
appendMessage,
|
|
19
22
|
attachTelegramBinding,
|
|
@@ -46,6 +49,7 @@ import { classifyIntake } from './ad-pipeline/intake.js';
|
|
|
46
49
|
import { createAdPipelineDeps, runAdPipeline } from './ad-pipeline/run.js';
|
|
47
50
|
import { projectCommand } from './telegram-project-command.js';
|
|
48
51
|
import { telegramNowSlash } from './context-bus/context-now-surfaces.js';
|
|
52
|
+
import { telegramLoopsStatus } from './telegram-loops-command.js';
|
|
49
53
|
import { createWishCard } from './intake-plane/wish-card.js';
|
|
50
54
|
import { telegramDecisionOwner } from './decisions/telegram-decision-cards.js';
|
|
51
55
|
import { CardStore } from './task-cards/card-store.js';
|
|
@@ -298,8 +302,12 @@ function dropChatSessionReply(ctx: TgIncoming): string {
|
|
|
298
302
|
/** Build the default command set for a Telegram bot wired via
|
|
299
303
|
* botFromConfig. Returns the array rather than mutating globals so
|
|
300
304
|
* tests can construct a variant set with stubbed handlers. */
|
|
301
|
-
export function defaultTelegramCommands(
|
|
302
|
-
|
|
305
|
+
export function defaultTelegramCommands(
|
|
306
|
+
nowDeps?: ContextNowDeps,
|
|
307
|
+
coreCommands: readonly SlashCommand[] = SLASH_COMMANDS,
|
|
308
|
+
maturity: { tuiSlash: Readonly<Record<string, Maturity>>; telegramCommand: Readonly<Record<string, Maturity>>; discordCommand: Readonly<Record<string, Maturity>> } = FEATURE_MATURITY,
|
|
309
|
+
): TgSlashCommand[] {
|
|
310
|
+
const handled: TgSlashCommand[] = [
|
|
303
311
|
{
|
|
304
312
|
name: 'help',
|
|
305
313
|
description: 'List available commands',
|
|
@@ -316,6 +324,11 @@ export function defaultTelegramCommands(nowDeps?: ContextNowDeps): TgSlashComman
|
|
|
316
324
|
description: 'Show current release, decisions, seats and context: /now [topic]',
|
|
317
325
|
handler: async (args) => telegramNowSlash(args, nowDeps),
|
|
318
326
|
},
|
|
327
|
+
{
|
|
328
|
+
name: 'loops',
|
|
329
|
+
description: '루프·크론 현황 — 늦음·실패를 먼저 보여줍니다',
|
|
330
|
+
handler: async (args) => telegramLoopsStatus(args),
|
|
331
|
+
},
|
|
319
332
|
{
|
|
320
333
|
name: 'status',
|
|
321
334
|
description: 'Show bot identity, current session, provider + model',
|
|
@@ -604,7 +617,8 @@ export function defaultTelegramCommands(nowDeps?: ContextNowDeps): TgSlashComman
|
|
|
604
617
|
if (args.length === 0) return '/wish <소원 한 줄>';
|
|
605
618
|
const store = new CardStore();
|
|
606
619
|
try {
|
|
607
|
-
const card = createWishCard({ text: args.join(' '), source: 'telegram', ref: `${ctx.chatId}:${ctx.messageId}
|
|
620
|
+
const card = createWishCard({ text: args.join(' '), source: 'telegram', ref: `${ctx.chatId}:${ctx.messageId}`,
|
|
621
|
+
replyTo: { surface: 'telegram', chatId: String(ctx.chatId), ...(ctx.threadId != null ? { threadId: String(ctx.threadId) } : {}), ...(ctx.botId ? { botId: ctx.botId } : {}) } }, store);
|
|
608
622
|
return `소원 카드로 남겼습니다 — ${card.title} (카드 ${card.cardId.slice(0, 8)})`;
|
|
609
623
|
} finally {
|
|
610
624
|
store.close();
|
|
@@ -951,6 +965,16 @@ export function defaultTelegramCommands(nowDeps?: ContextNowDeps): TgSlashComman
|
|
|
951
965
|
},
|
|
952
966
|
...botCommandsToTelegram(),
|
|
953
967
|
];
|
|
968
|
+
const catalog = buildBotSlashCatalog({
|
|
969
|
+
surface: 'telegram', coreCommands, maturity,
|
|
970
|
+
handledCommands: handled,
|
|
971
|
+
});
|
|
972
|
+
const handlers = new Map(handled.map((command) => [command.name, command]));
|
|
973
|
+
return catalog.commands.map((entry) => handlers.get(entry.name) ?? {
|
|
974
|
+
name: entry.name,
|
|
975
|
+
description: entry.description,
|
|
976
|
+
handler: async () => catalog.unsupportedReply(entry.name)!,
|
|
977
|
+
});
|
|
954
978
|
}
|
|
955
979
|
|
|
956
980
|
/** Telegram-side adapter over the messenger-agnostic ACP turn runner.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { dashboardLoops } from './domains/dashboard-data.js';
|
|
2
|
+
import { readSchedulesInventory } from './nexus/api/schedules-read.js';
|
|
3
|
+
import { loadLoopRows, LOOP_SCHEDULES_PATH, LOOPS_PATH, type LoopRow } from './loops/status-rows.js';
|
|
4
|
+
|
|
5
|
+
interface LoopsSources {
|
|
6
|
+
schedules: () => unknown;
|
|
7
|
+
loops: () => unknown;
|
|
8
|
+
now: () => number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const defaultSources: LoopsSources = {
|
|
12
|
+
schedules: () => ({ schedules: readSchedulesInventory() }),
|
|
13
|
+
loops: () => ({ loops: dashboardLoops() }),
|
|
14
|
+
now: Date.now,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const clean = (text: string) => text.replace(/[\r\n\t]+/g, ' ').replace(/\s+/g, ' ').trim();
|
|
18
|
+
|
|
19
|
+
/** Read the same two inventories and apply the same row verdicts as PWA /loops. */
|
|
20
|
+
export async function telegramLoopsStatus(args: string[], sources: LoopsSources = defaultSources): Promise<string> {
|
|
21
|
+
if (args.length) return '사용법: /loops';
|
|
22
|
+
try {
|
|
23
|
+
const rows = await loadLoopRows(async (path) => {
|
|
24
|
+
if (path === LOOP_SCHEDULES_PATH) return sources.schedules();
|
|
25
|
+
if (path === LOOPS_PATH) return sources.loops();
|
|
26
|
+
throw new Error('unknown inventory');
|
|
27
|
+
}, sources.now());
|
|
28
|
+
if (!rows.length) return '등록된 루프·크론이 없습니다.';
|
|
29
|
+
const counts = (['늦음', '실패', '살아 있음', '꺼짐', '판정 불가'] as const)
|
|
30
|
+
.map(verdict => `${verdict} ${rows.filter(row => row.verdict === verdict).length}`);
|
|
31
|
+
const priority: Record<LoopRow['verdict'], number> = { '늦음': 0, '실패': 1, '살아 있음': 2, '꺼짐': 3, '판정 불가': 4 };
|
|
32
|
+
const lines = rows.slice().sort((a, b) => priority[a.verdict] - priority[b.verdict]).map(row =>
|
|
33
|
+
`• ${row.verdict === '늦음' ? '🔴' : row.verdict === '실패' ? '❌' : row.verdict === '살아 있음' ? '🟢' : '⚪'} ${row.verdict} · ${clean(row.name)} (${clean(row.layer)}) · ${clean(row.mode)} · 마지막 ${row.lastRun ?? '기록 없음'} · ${clean(row.id)}`);
|
|
34
|
+
let reply = ['루프·크론 현황', counts.join(' · ')].join('\n');
|
|
35
|
+
for (const [index, line] of lines.entries()) {
|
|
36
|
+
// Telegram caps a message at 4096 characters; leave space for an explicit omission count.
|
|
37
|
+
if (reply.length + line.length + 35 > 4000) return `${reply}\n… ${lines.length - index}개 더 (메시지 길이 제한)`;
|
|
38
|
+
reply += `\n${line}`;
|
|
39
|
+
}
|
|
40
|
+
return reply;
|
|
41
|
+
} catch {
|
|
42
|
+
return '루프 현황을 읽지 못했습니다. 일부 레지스트리 조회가 실패했습니다. 데몬 연결을 확인하세요.';
|
|
43
|
+
}
|
|
44
|
+
}
|