elanous 0.2.11 → 0.2.13
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/{1394-556de6d71bbdb0fb.js → 1394-931c9827067b2276.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3869-3cd46526879f454c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3992-2e522a8ac5f2afde.js → 3992-fe5c06d6b31f8b25.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8605-ea5bee9dfae512c7.js → 8605-9da4678f8a310d18.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-6a061bd97f0b9398.js → page-e49b9b76c565a0b1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-f14987d5850a31e0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-7c97dcdfe2e37a60.js → page-4c1dbb21861beab2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-5721537ca8a3598c.js → page-f6bc66b919b2ea7d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-b654240a572e4c48.js → page-60603c1e056c9b25.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-b9750abc897508c3.js → page-a0ea8f5a2341ee6b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-c8b1ccda6ba7ae98.js → page-31012264da8a341f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-d6b1d156fb95e74b.js → layout-50309aeb0bdd554a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-9688f5b4f1ff950f.js → page-0696ee3fac9fe56c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66c27608f651b866.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-35414e0a007b4082.js → page-6ddd2109b7fc3a80.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/{page-e001823c35c9dd67.js → page-7a2bf830038a2264.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-371423d1a573d64b.js → page-1f327f18694fabca.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-4b764b125339ac17.js → page-e5cef2fe6e4028df.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-cdf8e4b01067bcf1.js → page-cf596128d2b420ba.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/{page-3a24072b35470157.js → page-f672536f14f0969f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-d37cb0d52f9b6bd5.js → page-29a709616df9b050.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-686da0df9bf23e5c.js → page-fee842d7c2151129.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-4dc614f3ce043e63.js → page-78b40a344ed22f24.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-d02c782fc792e2af.js → page-a011dd5b0bff29c5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-239c17b911b84e72.js → page-d92e32d51ab5157e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/{page-dc6a79fb003f746c.js → page-5f21aed6932ef375.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-0fffb84cafdc9da6.js → page-1e1a53946804debf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-1218753c82d585f6.js → page-a1833cd43d832601.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/{page-4b5935e67531efb5.js → page-63d14a4d8fe1e1c1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/{page-3285bc87caf1eaa5.js → page-2a511d320f762e06.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-ad553b0e06505ecd.js → page-1c3e03f88dac1e21.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-fca096e3e860306f.js → page-c4d5289c2ea97a89.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-9ebfa78f0d9885c3.js → page-b91f8952deaeaf27.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-3dc5f21f7e39622d.js → page-71b43a2588489987.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +1 -0
- package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +14 -14
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +14 -14
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +14 -14
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/examples/teams/personas/backend.yaml +4 -0
- package/examples/teams/personas/pm.yaml +4 -0
- package/examples/teams/personas/qa.yaml +4 -0
- package/examples/teams/small-product.yaml +20 -0
- package/graphs/context/nightly-condense.yaml +21 -0
- package/graphs/context/recipes.yaml +12 -0
- package/graphs/demo/inside-seed.yaml +20 -0
- package/graphs/demo/recipes.yaml +12 -0
- package/graphs/lecture/lecture-note.yaml +28 -0
- package/graphs/lecture/recipes.yaml +17 -0
- package/package.json +2 -1
- package/plugins/card-followup/examples/sample-card.png +0 -0
- package/scripts/context-hooks/emit.ts +36 -14
- package/scripts/coord-channel-watch.sh +1 -0
- package/scripts/discord-seat-probe.ts +144 -0
- package/scripts/hq/hq-standby.sh +65 -0
- package/scripts/hq/standby-promote.ts +173 -0
- package/scripts/hq/standby-role.ts +59 -0
- package/scripts/hq/standby-snapshot.ts +226 -0
- package/scripts/hq/standby-verify.ts +219 -0
- package/scripts/lecture-note/node.ts +256 -0
- package/scripts/lib/telegram-inject-args.ts +31 -0
- package/scripts/measure-pod-memory-by-goal.ts +130 -0
- package/scripts/release-loop/auto-approve-node.ts +1 -1
- package/scripts/release-loop/checklist-gate-node.ts +2 -1
- package/scripts/release-loop/gate-node.ts +61 -17
- package/scripts/release-loop/ops-upgrade-node.ts +59 -6
- package/scripts/release-loop/publish-node.ts +4 -1
- package/scripts/release-loop/version-node.ts +13 -3
- package/scripts/telegram-inject.ts +14 -6
- package/scripts/test-deterministic.ts +53 -3
- package/scripts/test-diet/remeasure-list.ts +112 -0
- package/scripts/ux/demo-rehearsal.ts +17 -13
- package/scripts/ux/demo-seed.ts +108 -0
- package/scripts/ux/lib/rehearsal-verdict.ts +12 -5
- package/skills/omni-crawl/SKILL.md +16 -16
- package/skills/omni-crawl/package-lock.json +1 -544
- package/skills/omni-crawl/package.json +3 -4
- package/skills/omni-crawl/scripts/main.ts +13 -13
- package/skills/omni-crawl/scripts/monitor.ts +8 -8
- package/skills/omni-crawl/src/apify.ts +13 -2
- package/skills/omni-crawl/src/capture.ts +6 -0
- package/skills/omni-crawl/src/registry.ts +2 -2
- package/skills/omni-crawl/src/stdout-json.ts +4 -0
- package/skills/omni-digest/SKILL.md +10 -11
- package/skills/omni-digest/package-lock.json +0 -545
- package/skills/omni-digest/package.json +2 -5
- package/skills/omni-digest/scripts/main.ts +3 -3
- package/skills/omni-digest/src/delegate.ts +5 -5
- package/skills/omni-digest/src/fetch.ts +2 -2
- package/skills/omni-digest/src/media.ts +25 -11
- package/skills/omni-digest/src/visual.ts +3 -2
- package/skills/omni-market/scripts/main.ts +1 -1
- package/skills/omni-market/scripts/stdout-json.ts +4 -0
- package/src/agent/identity-env.ts +11 -0
- package/src/agent-mission/driver.ts +150 -23
- package/src/agent-mission/duo.ts +94 -0
- package/src/agent-mission/pty-decision.ts +2 -2
- package/src/autopilot/pty-control-loop.ts +4 -1
- package/src/autopilot/track-agent-shadow.ts +2 -1
- package/src/autopilot/track-agent.ts +4 -1
- package/src/boot/daemon-prompt-request.ts +3 -0
- package/src/boot/daemon-prompt-turn.ts +6 -2
- package/src/boot/daemon-runtime.ts +48 -16
- package/src/chat/index.ts +1 -0
- package/src/cli/decisions-cli.ts +51 -19
- package/src/cli/outputs-cli.ts +92 -0
- package/src/cli/persona-cli.ts +45 -13
- package/src/cli/pod-cli.ts +27 -3
- package/src/cli/pod-memory-advice.ts +35 -0
- package/src/cli/pr-cli.ts +15 -1
- package/src/cli/release-cli.ts +135 -15
- package/src/context-bus/condense.ts +194 -0
- package/src/context-bus/context-day.ts +82 -0
- package/src/context-bus/context-now-surfaces.ts +32 -2
- package/src/context-bus/context-now.ts +2 -2
- package/src/context-bus/coord-events.ts +2 -2
- package/src/context-bus/long-term-memory.ts +118 -22
- package/src/context-bus/nightly-condense.ts +153 -0
- package/src/context-bus/session-events.ts +35 -0
- package/src/dashboard/dashboard-status-lines.ts +2 -0
- package/src/dashboard/index.ts +8 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +24 -0
- package/src/decisions/decision-cards.ts +13 -7
- package/src/decisions/decision-ledger.ts +43 -17
- package/src/domains/outbound-alert.ts +99 -31
- package/src/domains/outbound-log-probe.ts +8 -0
- package/src/execution-loop/launch-gate.ts +17 -8
- package/src/field-note/telegram.ts +142 -0
- package/src/graph-kinds/registry.ts +2 -2
- package/src/graph-runner/graph-approval-card.ts +2 -2
- package/src/graph-runner/graph-grow-llm.ts +23 -13
- package/src/graph-runner/graph-grow.ts +69 -13
- package/src/graph-runner/graph-tick.ts +9 -2
- package/src/graph-runner/runner.ts +92 -24
- package/src/harness/harness-cli-command.ts +165 -17
- package/src/harness/harness-queue-child.ts +37 -0
- package/src/harness/harness-queue.ts +306 -0
- package/src/hooks/consult-intake.ts +153 -0
- package/src/hooks/github-shadow.ts +72 -2
- package/src/hooks/receiver.ts +82 -3
- package/src/hq/hq.ts +318 -0
- package/src/hq/lease.ts +210 -0
- package/src/hq/ledger-write-target.ts +41 -0
- package/src/index.ts +232 -16
- package/src/intake-plane/discord-seat-work.ts +36 -3
- package/src/intake-plane/linear-wish.ts +44 -0
- package/src/intake-plane/persona-answer.ts +5 -0
- package/src/intake-plane/telegram-seat-work.ts +13 -6
- package/src/intake-plane/wish-card.ts +33 -0
- package/src/llm/difficulty.ts +1 -1
- package/src/loops/checker.ts +178 -0
- package/src/loops/loop-cli.ts +37 -3
- package/src/loops/observe.ts +49 -0
- package/src/loops/orchestrator/overlap.ts +141 -0
- package/src/loops/orchestrator/traffic.ts +150 -0
- package/src/loops/package/verify.ts +125 -0
- package/src/loops/verdict.ts +22 -0
- package/src/maturity/feature-maturity.ts +5 -4
- package/src/memory.ts +37 -4
- package/src/nexus/api/card-followup-route.ts +118 -0
- package/src/nexus/api/decisions-route.ts +68 -0
- package/src/nexus/api/http-server.ts +78 -8
- package/src/nexus/api/outbound-report.ts +40 -7
- package/src/nexus/api/personas.ts +97 -54
- package/src/nexus/api/skill-exec-route.ts +97 -0
- package/src/nexus/api/task-cards-api.ts +19 -0
- package/src/nexus/api/workflow-history.ts +88 -0
- package/src/nexus/api/workflows.ts +52 -0
- package/src/nexus/outbound/delivery-ledger.ts +17 -8
- package/src/nexus/outbound/router.ts +8 -3
- package/src/persona/loader.ts +18 -0
- package/src/persona/persona-todo.ts +14 -9
- package/src/persona/presets.ts +10 -2
- package/src/persona/team.ts +105 -0
- package/src/persona/types.ts +4 -0
- package/src/plugins/maker/market-bundle.ts +2 -1
- package/src/pod-lease/host-lease.ts +130 -20
- package/src/release-loop/checklist.ts +15 -3
- package/src/release-loop/feature-store.ts +24 -10
- package/src/release-loop/landed-but-yellow.ts +60 -0
- package/src/seat-dispatch/seat-ask.ts +57 -12
- package/src/seat-loop/seat-loop.ts +253 -18
- package/src/seat-loop/seat-pack.ts +127 -0
- package/src/self-dev/ask-launch-flow.ts +3 -0
- package/src/self-dev/draft-sweep.ts +44 -10
- package/src/self-dev/draft-triage-rules.ts +54 -2
- package/src/self-implement/budget-gate.ts +29 -12
- package/src/self-implement/gate-candidate-ranking.ts +60 -0
- package/src/self-implement/gate-scope.ts +9 -8
- package/src/self-implement/headless-elanous-driver.ts +7 -2
- package/src/self-implement/l8-merge-queue.ts +157 -1
- package/src/self-implement/orchestrator.ts +15 -0
- package/src/self-implement/test-seams.ts +1 -0
- package/src/steward/launch.ts +5 -1
- package/src/steward/readiness.ts +125 -0
- package/src/steward/rescue.ts +75 -0
- package/src/steward/triage.ts +48 -9
- package/src/task-cards/card-cli.ts +78 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +14 -10
- package/src/task-orchestrator/surfaces/pod-lease.ts +18 -6
- package/src/task-orchestrator/surfaces/pod-pool.ts +27 -10
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +46 -8
- package/src/telegram-commands.ts +21 -0
- package/src/telegram-dispatch-forward.ts +1 -1
- package/src/telegram-fabric-plan.ts +90 -0
- package/src/telegram-project-command.ts +84 -4
- package/src/telegram.ts +37 -5
- package/src/tool-runtime/elanous-control-runtimes.ts +2 -1
- package/src/user-config.ts +101 -13
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/executor.ts +4 -0
- package/src/workflow-runtime/node-catalog.ts +13 -0
- package/src/workflow-runtime/nodes/knowledge.ts +38 -0
- package/src/workflow-runtime/schema.ts +17 -1
- package/src/workflow-runtime/types.ts +13 -1
- package/apps/pwa/out/_next/static/chunks/2310-7a59f043f6db207e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2548-6e316138d347cefe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3869-a39d1d9921e10d43.js +0 -1
- package/apps/pwa/out/_next/static/chunks/507-5acd98bd6dab2d8a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5445-745eb795c5963d79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9906-482c6619b3fdda20.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-1dcdc58fc2de2454.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-cb95a7432c9ee744.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-b12b6d2f8d2d1aea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b9efe868bfd36255.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-8fd36984f549270a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-4cd1de371f8ad048.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-d99f89657828421c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-50cb2a0eb15e6c83.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-2a9932ddb62d76bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-6b15dd6456c5df09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-e7a529e0507ac86c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-94c82eb0ae5abb32.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-0be8d4b2ced75ba0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-53f80edc6b8dc07c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-c6da2b468454532d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-93bc70e36f595414.js +0 -1
- package/apps/pwa/out/_next/static/css/543f56bcb1f79fca.css +0 -1
- /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → D51l8jUBplJrIpWvENsDK}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → D51l8jUBplJrIpWvENsDK}/_ssgManifest.js +0 -0
|
@@ -19,7 +19,7 @@ import { join, resolve } from 'node:path';
|
|
|
19
19
|
import { debug } from '../../debug/log.js';
|
|
20
20
|
import { PodLeaseAdmission, type PodLeasePredecessor, type PodLeaseRelease } from '../../pod-lease/admission.js';
|
|
21
21
|
import { predecessorState, type PredecessorState } from '../../pod-lease/dependency-state.js';
|
|
22
|
-
import { HostPoolLease } from '../../pod-lease/host-lease.js';
|
|
22
|
+
import { HostPoolLease, leaseHasPendingPod } from '../../pod-lease/host-lease.js';
|
|
23
23
|
import { measurePoolLease, recommendConcurrency, type PoolDnsProbe, type PoolLeaseRecommendation } from './pod-lease.js';
|
|
24
24
|
|
|
25
25
|
export interface PodPoolMember {
|
|
@@ -143,6 +143,10 @@ export function genericConfigPodPool(config: { pod?: { pool?: string }; harness?
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
/** 노드 자리 배분 — 우선순위 순서로 첫 빈 자리. 순수(시각·대기는 호출자가). */
|
|
146
|
+
export function podPoolHostLease(members: readonly PodPoolMember[]): HostPoolLease {
|
|
147
|
+
return new HostPoolLease([...members.map((m) => `${m.context}@${m.sshHost ?? ''}`)].sort().join(','));
|
|
148
|
+
}
|
|
149
|
+
|
|
146
150
|
export class PodPoolScheduler {
|
|
147
151
|
private readonly inflight = new Map<string, number>();
|
|
148
152
|
private readonly admission: PodLeaseAdmission;
|
|
@@ -157,7 +161,7 @@ export class PodPoolScheduler {
|
|
|
157
161
|
constructor(readonly members: readonly PodPoolMember[], options: { status?: () => PoolLeaseRecommendation | Promise<PoolLeaseRecommendation>; kubectl?: PoolKubectl; dns?: (context: string) => PoolDnsProbe; pollMs?: number; hostLease?: HostPoolLease; dependencyMerged?: (after: PodLeasePredecessor, signal?: AbortSignal) => PredecessorState | boolean | Promise<PredecessorState | boolean> } = {}) {
|
|
158
162
|
this.pollMs = options.pollMs ?? 15_000;
|
|
159
163
|
// One lease directory per pool (contexts, not caps) — every CLI process launching into the same clusters shares it.
|
|
160
|
-
this.hostLease = options.hostLease ??
|
|
164
|
+
this.hostLease = options.hostLease ?? podPoolHostLease(members);
|
|
161
165
|
const measureStatus = options.status ?? (() => {
|
|
162
166
|
const measure = measurePoolLease(members, { ...(options.kubectl ? { kubectl: options.kubectl } : {}), ...(options.dns ? { dns: options.dns } : {}) });
|
|
163
167
|
return recommendConcurrency(measure, { capacity: members.reduce((n, m) => n + m.capacity, 0), accounts: 0, perAccount: 0 });
|
|
@@ -166,9 +170,13 @@ export class PodPoolScheduler {
|
|
|
166
170
|
this.admission = new PodLeaseAdmission({ status: async () => {
|
|
167
171
|
const measured = await measureStatus();
|
|
168
172
|
this.lastRaw = measured;
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
const
|
|
173
|
+
const pendingJobs = measured.pendingJobs ?? [];
|
|
174
|
+
const ownStart = this.hostLease.selfStart();
|
|
175
|
+
const transferId = process.env.ELANOUS_POD_RESERVED_LEASE;
|
|
176
|
+
const transferIsOther = !!transferId && this.hostLease.reservationIsOther(transferId);
|
|
177
|
+
const others = measured.recommended === null ? 0 : this.hostLease.live().filter((r) =>
|
|
178
|
+
(r.pid !== process.pid || r.startedAt !== ownStart) && !leaseHasPendingPod(r, pendingJobs)).length;
|
|
179
|
+
const decision = measured.recommended === null ? measured : { ...measured, recommended: Math.max(0, measured.recommended - others + (transferIsOther ? 1 : 0)) };
|
|
172
180
|
// An unknown reading cannot erase permits already granted: on recovery,
|
|
173
181
|
// the first healthy recommendation must still account for their slots.
|
|
174
182
|
if (decision.recommended === null) return decision;
|
|
@@ -181,19 +189,28 @@ export class PodPoolScheduler {
|
|
|
181
189
|
}, ...(options.pollMs ? { pollMs: options.pollMs } : {}), dependencyMerged: options.dependencyMerged ?? ((after, signal) => predecessorState(after, {}, signal)) });
|
|
182
190
|
}
|
|
183
191
|
/** FIFO gate within this process, then a host lease so independent CLI processes cannot over-admit the same pool. */
|
|
184
|
-
async acquireAdmission(signal?: AbortSignal, after?: PodLeasePredecessor): Promise<PodLeaseRelease & { applied: () => void }> {
|
|
192
|
+
async acquireAdmission(signal?: AbortSignal, after?: PodLeasePredecessor): Promise<PodLeaseRelease & { applied: (job: string, context: string, namespace: string) => void; observed: () => void }> {
|
|
185
193
|
const local = await this.acquireLocalAdmission(signal, after);
|
|
186
194
|
try {
|
|
187
195
|
for (;;) {
|
|
188
196
|
if (signal?.aborted) throw new Error('pod lease admission aborted');
|
|
189
197
|
const free = this.lastRaw?.recommended ?? null;
|
|
190
|
-
// Same check as the local gate, but atomic across processes: other
|
|
191
|
-
const
|
|
198
|
+
// Same check as the local gate, but atomic across processes: other authoring and pre-Running leases hold slots.
|
|
199
|
+
const reservedId = process.env.ELANOUS_POD_RESERVED_LEASE;
|
|
200
|
+
const transferred = reservedId ? this.hostLease.claim(reservedId) : null;
|
|
201
|
+
if (reservedId && !transferred) throw new Error('pod lease: authoring reservation could not be transferred');
|
|
202
|
+
if (transferred) delete process.env.ELANOUS_POD_RESERVED_LEASE;
|
|
203
|
+
const pendingJobs = this.lastRaw?.pendingJobs ?? [];
|
|
204
|
+
const lease = transferred ?? (free !== null && free > 0 ? this.hostLease.tryReserve((others) =>
|
|
205
|
+
others - this.hostLease.live().filter((r) => leaseHasPendingPod(r, pendingJobs) &&
|
|
206
|
+
(r.pid !== process.pid || r.startedAt !== this.hostLease.selfStart())).length < free) : null);
|
|
192
207
|
if (lease) {
|
|
193
208
|
let hostReleased = false;
|
|
194
209
|
const releaseHost = () => { if (!hostReleased) { hostReleased = true; lease(); } };
|
|
195
|
-
|
|
196
|
-
|
|
210
|
+
return Object.assign(() => { releaseHost(); local(); }, {
|
|
211
|
+
applied: (job: string, context: string, namespace: string) => lease.applied(job, context, namespace),
|
|
212
|
+
observed: releaseHost,
|
|
213
|
+
});
|
|
197
214
|
}
|
|
198
215
|
await new Promise<void>((resolve, reject) => {
|
|
199
216
|
const timer = setTimeout(() => { signal?.removeEventListener('abort', onAbort); resolve(); }, this.pollMs);
|
|
@@ -58,6 +58,9 @@ 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';
|
|
62
|
+
import { readPodMemoryAdvice, type PodMemoryAdvice } from '../../cli/pod-memory-advice.js';
|
|
63
|
+
import { getUserConfig } from '../../user-config.js';
|
|
61
64
|
|
|
62
65
|
export type Kubectl = (args: readonly string[], input?: string) => { status: number | null; stdout: string; stderr: string };
|
|
63
66
|
|
|
@@ -132,6 +135,10 @@ export interface PodSpawnOptions {
|
|
|
132
135
|
env?: NodeJS.ProcessEnv;
|
|
133
136
|
/** Detail switch file (test injection; defaults to the host's local/production selection). */
|
|
134
137
|
liveDetailFile?: string;
|
|
138
|
+
/** Test seam for the opt-in user configuration; otherwise read the active user config. */
|
|
139
|
+
adviseDefaults?: boolean;
|
|
140
|
+
/** Test seam for the read-only measured advice. */
|
|
141
|
+
memoryAdvice?: () => PodMemoryAdvice;
|
|
135
142
|
}
|
|
136
143
|
|
|
137
144
|
export function defaultKubectl(args: readonly string[], input?: string): { status: number | null; stdout: string; stderr: string } {
|
|
@@ -411,7 +418,7 @@ export const POD_MEMORY_HIGH_DEFAULT = '32Gi';
|
|
|
411
418
|
export const POD_MEMORY_LITE_DEFAULT = '2Gi';
|
|
412
419
|
export const POD_MEMORY_TIERS = ['lite', 'standard', 'high'] as const;
|
|
413
420
|
export type PodMemoryTier = (typeof POD_MEMORY_TIERS)[number];
|
|
414
|
-
export type PodMemorySource = 'option' | 'goal-line' | 'pwa-auto' | 'default' | 'parent-goal-line' | 'parent-pwa-auto';
|
|
421
|
+
export type PodMemorySource = 'option' | 'goal-line' | 'pwa-auto' | 'default' | 'parent-goal-line' | 'parent-pwa-auto' | 'advise';
|
|
415
422
|
const PWA_PATH = /(?:^|[\s`'"(,·])apps\/pwa\//m;
|
|
416
423
|
const GOAL_LINE = /^[ \t]*(?:Pod 메모리|pod-memory)[ \t]*:[ \t]*(lite|standard|high)[ \t]*\r?$/im;
|
|
417
424
|
|
|
@@ -436,6 +443,12 @@ export function goalTouchesPwa(feature: string): boolean {
|
|
|
436
443
|
return PWA_PATH.test(feature);
|
|
437
444
|
}
|
|
438
445
|
|
|
446
|
+
/** A launch-time warning, not a memory-tier override or a test-command parser. */
|
|
447
|
+
export function broadPodTestWarning(goal: string): string | null {
|
|
448
|
+
const broad = /(?:전체\s*(?:시험|테스트)|(?:full|whole|entire|all)\s+(?:test|suite)|(?:전체|모든)\s*(?:test|suite)|\bbun\s+test\s+(?:[\w./-]+\/|[\w./-]+(?<!\.[cm]?[jt]sx?))(?:\s|$)|\bbun\s+test\s*(?:$|[;&|]))/imu.test(goal);
|
|
449
|
+
return broad ? '[pod] 넓은 시험 골 경고: 10-04 W10b 32Gi OOM — 넓은 bun test 한 프로세스가 16GB+ 사용; 바꾼 시험 파일만 지정해 실행하세요.' : null;
|
|
450
|
+
}
|
|
451
|
+
|
|
439
452
|
export function parsePodMemoryTier(v: string | undefined): PodMemoryTier | null {
|
|
440
453
|
const t = v?.trim().toLowerCase();
|
|
441
454
|
return t && (POD_MEMORY_TIERS as readonly string[]).includes(t) ? (t as PodMemoryTier) : null;
|
|
@@ -447,14 +460,21 @@ function memoryGi(v: string): number | null {
|
|
|
447
460
|
return m[2] === 'Gi' ? Number(m[1]) : Number(m[1]) / 1024;
|
|
448
461
|
}
|
|
449
462
|
|
|
450
|
-
export function podMemoryLimitFor(feature: string, env: NodeJS.ProcessEnv | Readonly<Record<string, string | undefined>>, parentGoal?: string): { limit: string; tier: PodMemoryTier; source: PodMemorySource } {
|
|
463
|
+
export function podMemoryLimitFor(feature: string, env: NodeJS.ProcessEnv | Readonly<Record<string, string | undefined>>, parentGoal?: string, advice?: PodMemoryAdvice): { limit: string; tier: PodMemoryTier; source: PodMemorySource } {
|
|
451
464
|
const option = parsePodMemoryTier(env.ELANOUS_POD_MEMORY_TIER);
|
|
452
465
|
const line = parsePodMemoryTier(GOAL_LINE.exec(feature)?.[1]);
|
|
453
|
-
const parentLine = !option && !line
|
|
466
|
+
const parentLine = !option && !line ? parsePodMemoryTier(GOAL_LINE.exec(parentGoal ?? '')?.[1]) : null;
|
|
467
|
+
const goalType = advice && !option && !line && !parentLine
|
|
468
|
+
? (broadPodTestWarning(feature) ? 'test' : goalTypeOf(feature))
|
|
469
|
+
?? (parentGoal ? (broadPodTestWarning(parentGoal) ? 'test' : goalTypeOf(parentGoal)) : null)
|
|
470
|
+
: null;
|
|
471
|
+
const recommendation = advice?.byGoalType.find((entry) => entry.goalType === goalType && entry.evidence.runs > 0)?.recommended;
|
|
454
472
|
const [tier, source]: [PodMemoryTier, PodMemorySource] = option ? [option, 'option']
|
|
455
473
|
: line ? [line, 'goal-line']
|
|
474
|
+
// 조각 자신의 PWA 경로가 부모 줄·실측 권고보다 먼저다 — 부모 «standard» 가 PWA 조각을 16Gi 로 내리면 OOM(POD7 · #23569 순서 회귀)
|
|
456
475
|
: goalTouchesPwa(feature) ? ['high', 'pwa-auto']
|
|
457
476
|
: parentLine ? [parentLine, 'parent-goal-line']
|
|
477
|
+
: recommendation ? [recommendation, 'advise']
|
|
458
478
|
: parentGoal && goalTouchesPwa(parentGoal) ? ['high', 'parent-pwa-auto']
|
|
459
479
|
: ['standard', 'default'];
|
|
460
480
|
if (tier === 'lite') return { limit: env.ELANOUS_POD_MEMORY_LITE?.trim() || POD_MEMORY_LITE_DEFAULT, tier, source };
|
|
@@ -634,7 +654,6 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
634
654
|
const hostSupervised = options.hostSupervised ?? true;
|
|
635
655
|
return (input) => {
|
|
636
656
|
const name = podJobName(input.spaceId);
|
|
637
|
-
const featureMemory = podMemoryLimitFor(input.feature, env);
|
|
638
657
|
let parentGoal: string | undefined;
|
|
639
658
|
let parentGoalUnreadable = false;
|
|
640
659
|
let inheritedFrom: string | undefined;
|
|
@@ -652,7 +671,14 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
652
671
|
inheritedFrom = withinRoot;
|
|
653
672
|
} catch { parentGoalUnreadable = true; }
|
|
654
673
|
}
|
|
655
|
-
|
|
674
|
+
let advice: PodMemoryAdvice | undefined;
|
|
675
|
+
const explicitMemory = Boolean(parsePodMemoryTier(env.ELANOUS_POD_MEMORY_TIER) || GOAL_LINE.test(input.feature) || GOAL_LINE.test(parentGoal ?? ''));
|
|
676
|
+
const useAdvice = options.adviseDefaults ?? getUserConfig().pod?.memory?.adviseDefaults === true;
|
|
677
|
+
if (useAdvice && !explicitMemory) {
|
|
678
|
+
try { advice = (options.memoryAdvice ?? readPodMemoryAdvice)(); }
|
|
679
|
+
catch (error) { debug.log('self-implement.pod', 'memory-advice-unavailable', { spaceId: input.spaceId, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
|
|
680
|
+
}
|
|
681
|
+
const { limit: selectedMemoryLimit, tier: memoryTier, source: memorySource } = podMemoryLimitFor(input.feature, env, parentGoal, advice);
|
|
656
682
|
for (const [where, text] of [['feature', input.feature], ['parent-goal', parentGoal]] as const) {
|
|
657
683
|
if (text && text.split(/\r?\n/u).some((line) => /Pod 메모리/u.test(line) && !GOAL_LINE.test(line))) {
|
|
658
684
|
const reason = '한 줄 단독 `Pod 메모리: high|standard` 형식이 아님';
|
|
@@ -660,6 +686,8 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
660
686
|
debug.log('self-implement.pod', 'memory-directive-ignored', { spaceId: input.spaceId, where, reason }, { level: 'warn' });
|
|
661
687
|
}
|
|
662
688
|
}
|
|
689
|
+
const broadTestWarning = broadPodTestWarning(`${input.feature}\n${parentGoal ?? ''}`);
|
|
690
|
+
if (broadTestWarning) console.warn(broadTestWarning);
|
|
663
691
|
let memoryLimit = selectedMemoryLimit;
|
|
664
692
|
const after = input.after ?? podPredecessorFor(input.feature, env, parentGoal);
|
|
665
693
|
if (after !== undefined) debug.log('self-implement.pod', 'predecessor', { spaceId: input.spaceId, after, source: input.after !== undefined ? 'input' : 'goal-or-option' });
|
|
@@ -708,7 +736,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
708
736
|
if (recoveryError) return { exitCode: 1, output: '', error: { code: 'pod-oom-recovery', message: recoveryError } };
|
|
709
737
|
// Each Job attempt has its own host lease; releasing the first after OOM lets the retry
|
|
710
738
|
// compete with other processes for the newly measured capacity before applying another Job.
|
|
711
|
-
let releaseAdmission: ((() => void) & { applied?: () => void }) | undefined;
|
|
739
|
+
let releaseAdmission: ((() => void) & { applied?: (job: string, context: string, namespace: string) => void; observed?: () => void }) | undefined;
|
|
712
740
|
try {
|
|
713
741
|
// Every attempt, including an OOM retry, must acquire a pool slot before applying a Job.
|
|
714
742
|
const acquireSlot = async (): Promise<PodPoolMember | null> => {
|
|
@@ -1059,6 +1087,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1059
1087
|
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, 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 를 부르지 않는다
|
|
1060
1088
|
const a = kubectl(['apply', '-f', '-'], JSON.stringify(job));
|
|
1061
1089
|
if (a.status !== 0) { cleanupSecret(); return { exitCode: 1, output: a.stderr, error: { code: 'pod-apply', message: a.stderr.trim() } }; }
|
|
1090
|
+
releaseAdmission?.applied?.(name, context, namespace);
|
|
1062
1091
|
if (options.source?.kind === 'bundle') {
|
|
1063
1092
|
const podName = await waitForRunningPod(kubectl, namespace, name, sleep);
|
|
1064
1093
|
if (!podName) {
|
|
@@ -1066,6 +1095,8 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1066
1095
|
cleanupSecret();
|
|
1067
1096
|
return { exitCode: 1, output: '', error: { code: 'pod-source', message: `Pod for ${name} did not become Running` } };
|
|
1068
1097
|
}
|
|
1098
|
+
releaseAdmission?.observed?.();
|
|
1099
|
+
if (releaseAdmission) delete releaseAdmission.observed;
|
|
1069
1100
|
const copied = kubectl(['cp', options.source.bundlePath, `${podName}:/tmp/source.bundle`, '-c', 'child', '-n', namespace]);
|
|
1070
1101
|
if (copied.status !== 0) {
|
|
1071
1102
|
debug.log('self-implement.pod', 'source-mismatch', { job: name, kind: 'bundle', headCommit: options.source.headCommit });
|
|
@@ -1098,7 +1129,6 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1098
1129
|
}
|
|
1099
1130
|
writePodFragment({ spaceId: input.spaceId, context, namespace, job: name, inboxDir: POD_CONTROL_INBOX_DIR, ...(liveChildRunId === normalizeRunId(liveChildRunId) ? { runId: liveChildRunId } : {}), ...(parentRunId && normalizeRunId(parentRunId) === parentRunId ? { parentRunId } : {}) }, env);
|
|
1100
1131
|
recorded = true;
|
|
1101
|
-
releaseAdmission?.applied?.();
|
|
1102
1132
|
debug.log('self-implement.pod', 'job-applied', { job: name, namespace, ...(member ? { context: member.context } : {}), image: jobImage, spaceId: input.spaceId, passEnv, extraArgs: options.extraArgs ?? [], ...(options.armEnv?.ELANOUS_ARM_ID ? { armId: options.armEnv.ELANOUS_ARM_ID } : {}) });
|
|
1103
1133
|
let state: 'complete' | 'failed' | 'aborted' = 'failed';
|
|
1104
1134
|
let failedReason = '';
|
|
@@ -1114,8 +1144,9 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1114
1144
|
}
|
|
1115
1145
|
const g = kubectl(['-n', namespace, 'get', 'job', name, '-o', 'jsonpath={.status.conditions[*].type}']);
|
|
1116
1146
|
const types = g.stdout;
|
|
1117
|
-
if (/Complete|SuccessCriteriaMet/.test(types)) { state = 'complete'; break; }
|
|
1147
|
+
if (/Complete|SuccessCriteriaMet/.test(types)) { releaseAdmission?.observed?.(); state = 'complete'; break; }
|
|
1118
1148
|
if (/Failed|FailureTarget/.test(types)) {
|
|
1149
|
+
releaseAdmission?.observed?.();
|
|
1119
1150
|
state = 'failed';
|
|
1120
1151
|
const reason = kubectl(['-n', namespace, 'get', 'job', name, '-o', 'jsonpath={.status.conditions[?(@.type=="Failed")].reason}']);
|
|
1121
1152
|
failedReason = reason.status === 0 ? reason.stdout.trim() : '';
|
|
@@ -1135,6 +1166,13 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1135
1166
|
debug.log('self-implement.pod', 'container-terminated', { job: name, container: 'child', reason: containerReason, exitCode: containerExitCode, jobReason: failedReason || null, memoryLimit });
|
|
1136
1167
|
break;
|
|
1137
1168
|
}
|
|
1169
|
+
if (releaseAdmission?.observed) {
|
|
1170
|
+
const podPhase = kubectl(['-n', namespace, 'get', 'pods', '-l', `job-name=${name}`, '-o', 'jsonpath={.items[*].status.phase}']);
|
|
1171
|
+
if (podPhase.status === 0 && /\b(?:Running|Succeeded|Failed)\b/.test(podPhase.stdout)) {
|
|
1172
|
+
releaseAdmission.observed();
|
|
1173
|
+
delete releaseAdmission.observed;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1138
1176
|
if (ghTokenExpiresAt !== null && ghTokenExpiresAt - now() <= 10 * 60_000) {
|
|
1139
1177
|
try {
|
|
1140
1178
|
const fresh = await issueVerifiedAppCredential();
|
package/src/telegram-commands.ts
CHANGED
|
@@ -46,6 +46,9 @@ import { classifyIntake } from './ad-pipeline/intake.js';
|
|
|
46
46
|
import { createAdPipelineDeps, runAdPipeline } from './ad-pipeline/run.js';
|
|
47
47
|
import { projectCommand } from './telegram-project-command.js';
|
|
48
48
|
import { telegramNowSlash } from './context-bus/context-now-surfaces.js';
|
|
49
|
+
import { createWishCard } from './intake-plane/wish-card.js';
|
|
50
|
+
import { telegramDecisionOwner } from './decisions/telegram-decision-cards.js';
|
|
51
|
+
import { CardStore } from './task-cards/card-store.js';
|
|
49
52
|
import type { ContextNowDeps } from './context-bus/context-now.js';
|
|
50
53
|
|
|
51
54
|
/** How many prior user/assistant turns to pass into `executeSkill` as
|
|
@@ -155,6 +158,7 @@ export interface TgCommandContext {
|
|
|
155
158
|
userConfig: UserConfig;
|
|
156
159
|
/** All registered commands — used by `/help` to enumerate. */
|
|
157
160
|
allCommands: TgSlashCommand[];
|
|
161
|
+
sendButtons?: (text: string, buttons: Array<Array<{ text: string; data: string }>>) => Promise<void>;
|
|
158
162
|
/** When the caller posted a placeholder for a streaming command,
|
|
159
163
|
* the streamer edits it in-place as the handler accumulates
|
|
160
164
|
* output. Undefined for instant commands. Handlers must tolerate
|
|
@@ -590,6 +594,23 @@ export function defaultTelegramCommands(nowDeps?: ContextNowDeps): TgSlashComman
|
|
|
590
594
|
},
|
|
591
595
|
},
|
|
592
596
|
projectCommand(),
|
|
597
|
+
{
|
|
598
|
+
name: 'wish',
|
|
599
|
+
description: '소원을 카드로 남기기',
|
|
600
|
+
handler: async (args, ctx, { userConfig }) => {
|
|
601
|
+
if (String(ctx.userId) !== telegramDecisionOwner(userConfig) || ctx.chatId !== ctx.userId || !ctx.isDm) {
|
|
602
|
+
return '대표만 쓸 수 있습니다';
|
|
603
|
+
}
|
|
604
|
+
if (args.length === 0) return '/wish <소원 한 줄>';
|
|
605
|
+
const store = new CardStore();
|
|
606
|
+
try {
|
|
607
|
+
const card = createWishCard({ text: args.join(' '), source: 'telegram', ref: `${ctx.chatId}:${ctx.messageId}` }, store);
|
|
608
|
+
return `소원 카드로 남겼습니다 — ${card.title} (카드 ${card.cardId.slice(0, 8)})`;
|
|
609
|
+
} finally {
|
|
610
|
+
store.close();
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
},
|
|
593
614
|
{
|
|
594
615
|
name: 'fork',
|
|
595
616
|
description: 'Fork the current chat session (or /fork <prefix>) and continue HERE on the fork · time-travel: /fork before:N',
|
|
@@ -42,7 +42,7 @@ export function defaultBaseUrl(deps: { runtime?: () => ReturnType<typeof readNex
|
|
|
42
42
|
return `http://${host}:${rt.httpPort}`;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
function defaultToken(): string | null {
|
|
45
|
+
export function defaultToken(): string | null {
|
|
46
46
|
const p = join(getElanousConfigDir(), 'acp-token');
|
|
47
47
|
try { return existsSync(p) ? readFileSync(p, 'utf-8').trim() || null : null; } catch { return null; }
|
|
48
48
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { defaultBaseUrl, defaultToken } from './telegram-dispatch-forward.js';
|
|
2
|
+
import type { FabricPlan } from './self-dev/fabric-plan-core.js';
|
|
3
|
+
import type { TelegramBot, TgIncoming } from './telegram.js';
|
|
4
|
+
|
|
5
|
+
export interface TelegramFabricPlanDeps {
|
|
6
|
+
fetchImpl?: typeof fetch;
|
|
7
|
+
baseUrl?: () => string | null;
|
|
8
|
+
token?: () => string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Only a successful daemon draft is offered for approval; no execution endpoint is called here. */
|
|
12
|
+
export function attachTelegramFabricPlan(
|
|
13
|
+
bot: Pick<TelegramBot, 'isOwnerAllowed' | 'sendMessage' | 'sendInlineKeyboard' | 'onCallbackQuery' | 'answerCallbackQuery'>,
|
|
14
|
+
deps: TelegramFabricPlanDeps = {},
|
|
15
|
+
): (ctx: TgIncoming) => Promise<boolean> {
|
|
16
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
17
|
+
const pending = new Map<string, { chatId: number; userId: number; threadId?: number; messageId: number }>();
|
|
18
|
+
const endpoint = (path: string): { url: string; token: string | null } => {
|
|
19
|
+
const base = (deps.baseUrl ?? defaultBaseUrl)();
|
|
20
|
+
if (!base) throw new Error('데몬에 연결할 수 없습니다');
|
|
21
|
+
return { url: `${base}${path}`, token: (deps.token ?? defaultToken)() };
|
|
22
|
+
};
|
|
23
|
+
const post = async (path: string, body?: unknown): Promise<unknown> => {
|
|
24
|
+
const { url, token } = endpoint(path);
|
|
25
|
+
const response = await fetchImpl(url, {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}) },
|
|
28
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
29
|
+
});
|
|
30
|
+
if (!response.ok) throw new Error(`데몬 응답 ${response.status}`);
|
|
31
|
+
return response.json();
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
bot.onCallbackQuery(async (q) => {
|
|
35
|
+
const match = /^fabric:(approve|edit):([a-f0-9-]{36})$/i.exec(q.data);
|
|
36
|
+
if (!match) return;
|
|
37
|
+
const planId = match[2]!;
|
|
38
|
+
const original = pending.get(planId);
|
|
39
|
+
if (!original || !bot.isOwnerAllowed(q.userId) || q.userId !== original.userId
|
|
40
|
+
|| q.chatId !== original.chatId || q.threadId !== original.threadId || q.messageId !== original.messageId) {
|
|
41
|
+
await bot.answerCallbackQuery(q.id, { text: '이 초안의 요청자만 선택할 수 있습니다' });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (match[1] === 'edit') {
|
|
45
|
+
pending.delete(planId);
|
|
46
|
+
await bot.answerCallbackQuery(q.id);
|
|
47
|
+
await bot.sendMessage(original.chatId, '고칠 내용을 반영해 «아크: <복합 문장>»으로 다시 보내세요. 이 초안은 승인되지 않았습니다.', { threadId: original.threadId });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
await post(`/v1/fabric/plans/${planId}/approve`);
|
|
52
|
+
pending.delete(planId);
|
|
53
|
+
await bot.answerCallbackQuery(q.id, { text: '승인되었습니다' });
|
|
54
|
+
await bot.sendMessage(original.chatId, `아크 초안 ${planId} 승인됨 — 실행은 별도 단계입니다.`, { threadId: original.threadId });
|
|
55
|
+
} catch (error) {
|
|
56
|
+
await bot.answerCallbackQuery(q.id, { text: `승인 실패: ${error instanceof Error ? error.message : String(error)}` });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return async (ctx) => {
|
|
61
|
+
if (ctx.attachments.length || ctx.video || !ctx.text.startsWith('아크:')) return false;
|
|
62
|
+
const request = ctx.text.slice('아크:'.length).trim();
|
|
63
|
+
if (!request || request.includes('\n')) {
|
|
64
|
+
await bot.sendMessage(ctx.chatId, '«아크: <복합 문장>» 한 줄로 보내세요.', { replyTo: ctx.messageId, threadId: ctx.threadId });
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const body = await post('/v1/fabric/decompose', { request }) as { plan?: FabricPlan };
|
|
69
|
+
const plan = body.plan;
|
|
70
|
+
if (!plan || plan.status !== 'draft' || !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(plan.id)
|
|
71
|
+
|| !Array.isArray(plan.nodes) || plan.nodes.length === 0) throw new Error('초안 형식 오류');
|
|
72
|
+
const steps = plan.nodes.map((node, i) => `${i + 1}. ${node.title}`).join('\n');
|
|
73
|
+
const preview = `아크 초안 · 실행 전 검토\n${steps}`;
|
|
74
|
+
const reply = { replyTo: ctx.messageId, threadId: ctx.threadId };
|
|
75
|
+
for (let offset = 0; offset + 4000 < preview.length; offset += 4000) {
|
|
76
|
+
const sent = await bot.sendMessage(ctx.chatId, preview.slice(offset, offset + 4000), reply);
|
|
77
|
+
if (sent?.messageId === undefined) throw new Error('단계 미리보기를 전부 전달하지 못했습니다 — 승인 불가');
|
|
78
|
+
}
|
|
79
|
+
const finalOffset = Math.floor((preview.length - 1) / 4000) * 4000;
|
|
80
|
+
const sent = await bot.sendInlineKeyboard(ctx.chatId, preview.slice(finalOffset), [[
|
|
81
|
+
{ text: '승인', data: `fabric:approve:${plan.id}` },
|
|
82
|
+
{ text: '고치기', data: `fabric:edit:${plan.id}` },
|
|
83
|
+
]], reply);
|
|
84
|
+
if (sent?.messageId !== undefined) pending.set(plan.id, { chatId: ctx.chatId, userId: ctx.userId, threadId: ctx.threadId, messageId: sent.messageId });
|
|
85
|
+
} catch (error) {
|
|
86
|
+
await bot.sendMessage(ctx.chatId, `아크 초안 생성 실패: ${error instanceof Error ? error.message : String(error)}`, { replyTo: ctx.messageId, threadId: ctx.threadId });
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { debug } from './debug/log.js';
|
|
2
2
|
import { getProject, listProjects, type Project } from './project/project-store.js';
|
|
3
3
|
import { findSessionByTelegramChat, updateSessionMeta } from './session/index.js';
|
|
4
|
+
import { telegramDecisionOwner } from './decisions/telegram-decision-cards.js';
|
|
5
|
+
import type { TelegramBot } from './telegram.js';
|
|
4
6
|
import type { TgSlashCommand } from './telegram-commands.js';
|
|
7
|
+
import type { UserConfig } from './user-config.js';
|
|
5
8
|
|
|
6
9
|
export interface TelegramProjectCommandDeps {
|
|
7
10
|
list: () => Project[];
|
|
@@ -19,7 +22,7 @@ export function projectCommand(deps: TelegramProjectCommandDeps = {
|
|
|
19
22
|
return {
|
|
20
23
|
name: 'project',
|
|
21
24
|
description: '이 대화의 프로젝트 보기·옮기기',
|
|
22
|
-
handler: async (args, ctx) => {
|
|
25
|
+
handler: async (args, ctx, ctxOptions) => {
|
|
23
26
|
const session = deps.findSession(ctx.chatId, ctx.threadId, ctx.botId);
|
|
24
27
|
if (!session) {
|
|
25
28
|
debug.log('telegram.project', 'rejected', { projects: [], reason: 'no-session' });
|
|
@@ -28,13 +31,38 @@ export function projectCommand(deps: TelegramProjectCommandDeps = {
|
|
|
28
31
|
|
|
29
32
|
if (args.length === 0) {
|
|
30
33
|
const current = session.projectId ? deps.find(session.projectId) : null;
|
|
31
|
-
const projects = deps.list().sort((a, b) => a.name.localeCompare(b.name))
|
|
32
|
-
debug.log('telegram.project', 'listed', { projects: projects.map(p => ({ id: p.id, name: p.name })) });
|
|
34
|
+
const projects = deps.list().sort((a, b) => a.name.localeCompare(b.name));
|
|
35
|
+
debug.log('telegram.project', 'listed', { projects: projects.slice(0, 20).map(p => ({ id: p.id, name: p.name })) });
|
|
36
|
+
if (ctx.isDm && ctx.chatId === ctx.userId
|
|
37
|
+
&& String(ctx.userId) === telegramDecisionOwner(ctxOptions.userConfig) && ctxOptions.sendButtons) {
|
|
38
|
+
const eligible = projects.filter(p => {
|
|
39
|
+
const bytes = Buffer.byteLength(`prj:${p.id}`, 'utf8');
|
|
40
|
+
if (bytes <= 64) return true;
|
|
41
|
+
debug.log('telegram.project', 'button-rejected', { reason: 'callback-data-too-long', projectId: p.id, bytes });
|
|
42
|
+
return false;
|
|
43
|
+
});
|
|
44
|
+
const visible = eligible.slice(0, 7);
|
|
45
|
+
if (current && eligible.some(p => p.id === current.id) && !visible.some(p => p.id === current.id)) {
|
|
46
|
+
visible[visible.length - 1] = current;
|
|
47
|
+
}
|
|
48
|
+
const buttons = visible.map(p => ({ text: `${p.id === current?.id ? '✓ ' : ''}${p.name}`, data: `prj:${p.id}` }));
|
|
49
|
+
buttons.push({ text: `${session.projectId ? '' : '✓ '}받은 대화로`, data: 'prj:-' });
|
|
50
|
+
const rows: Array<Array<{ text: string; data: string }>> = [];
|
|
51
|
+
for (let i = 0; i < buttons.length; i += 2) rows.push(buttons.slice(i, i + 2));
|
|
52
|
+
try {
|
|
53
|
+
await ctxOptions.sendButtons('프로젝트를 선택하세요', rows);
|
|
54
|
+
return;
|
|
55
|
+
} catch (err) {
|
|
56
|
+
debug.log('telegram.project', 'button-rejected', {
|
|
57
|
+
reason: 'send-failed', error: err instanceof Error ? err.message : String(err),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
33
61
|
return [
|
|
34
62
|
`지금 대화의 프로젝트: ${current?.name ?? '받은 대화(프로젝트 없음)'}`,
|
|
35
63
|
'',
|
|
36
64
|
'프로젝트 목록:',
|
|
37
|
-
...projects.map(p => `• ${p.name}`),
|
|
65
|
+
...projects.slice(0, 20).map(p => `• ${p.name}`),
|
|
38
66
|
'',
|
|
39
67
|
'옮기려면 /project <이름>',
|
|
40
68
|
].join('\n');
|
|
@@ -69,3 +97,55 @@ export function projectCommand(deps: TelegramProjectCommandDeps = {
|
|
|
69
97
|
},
|
|
70
98
|
};
|
|
71
99
|
}
|
|
100
|
+
|
|
101
|
+
export function attachTelegramProjectButtons(
|
|
102
|
+
bot: Pick<TelegramBot, 'botId' | 'onCallbackQuery' | 'sendMessage' | 'answerCallbackQuery'>,
|
|
103
|
+
cfg: UserConfig,
|
|
104
|
+
deps: TelegramProjectCommandDeps = {
|
|
105
|
+
list: listProjects,
|
|
106
|
+
find: getProject,
|
|
107
|
+
update: updateSessionMeta,
|
|
108
|
+
findSession: findSessionByTelegramChat,
|
|
109
|
+
},
|
|
110
|
+
): () => void {
|
|
111
|
+
const owner = telegramDecisionOwner(cfg);
|
|
112
|
+
return bot.onCallbackQuery(async q => {
|
|
113
|
+
if (!q.data.startsWith('prj:')) return;
|
|
114
|
+
if (!owner || String(q.userId) !== owner) {
|
|
115
|
+
debug.log('telegram.project', 'button-rejected', { reason: 'not-owner', userId: q.userId, chatId: q.chatId });
|
|
116
|
+
await bot.answerCallbackQuery(q.id, { text: '권한이 없습니다' });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (q.chatId === undefined || q.chatId !== q.userId || q.threadId !== undefined) {
|
|
120
|
+
debug.log('telegram.project', 'button-rejected', { reason: 'not-private-chat', userId: q.userId, chatId: q.chatId });
|
|
121
|
+
await bot.answerCallbackQuery(q.id, { text: '개인 대화에서만 고를 수 있습니다' });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const chatId = q.chatId;
|
|
125
|
+
const session = deps.findSession(chatId, q.threadId, bot.botId);
|
|
126
|
+
if (!session) {
|
|
127
|
+
debug.log('telegram.project', 'button-rejected', { reason: 'no-session', chatId });
|
|
128
|
+
await bot.answerCallbackQuery(q.id);
|
|
129
|
+
await bot.sendMessage(chatId, '아직 이 채팅에 대화가 없습니다 — 한 마디 보낸 뒤 다시');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (q.data === 'prj:-') {
|
|
133
|
+
deps.update(session.id, m => { delete m.projectId; });
|
|
134
|
+
debug.log('telegram.project', 'button-cleared', { chatId, sessionId: session.id });
|
|
135
|
+
await bot.answerCallbackQuery(q.id);
|
|
136
|
+
await bot.sendMessage(chatId, '받은 대화로 옮겼습니다');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const project = deps.find(q.data.slice(4));
|
|
140
|
+
if (!project) {
|
|
141
|
+
debug.log('telegram.project', 'button-rejected', { reason: 'not-found', chatId, projectId: q.data.slice(4) });
|
|
142
|
+
await bot.answerCallbackQuery(q.id);
|
|
143
|
+
await bot.sendMessage(chatId, '그 이름의 프로젝트가 없습니다 — /project 로 목록 보기');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
deps.update(session.id, m => { m.projectId = project.id; });
|
|
147
|
+
debug.log('telegram.project', 'button-moved', { chatId, sessionId: session.id, projectId: project.id });
|
|
148
|
+
await bot.answerCallbackQuery(q.id);
|
|
149
|
+
await bot.sendMessage(chatId, `지금 프로젝트: ${project.name}`);
|
|
150
|
+
});
|
|
151
|
+
}
|