elanous 0.2.16 → 0.2.18
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/P6YTVgzuZz5oTZhY4Din_/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1307-b46d45b705e8c9ae.js → 1307-30dff22c9e1f3073.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6158.9c0449a9a8d0064b.js → 6158.af2e93b7adff709b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d92f7ee3994e84c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-3cb52e5ea8fc2fa5.js → page-4733c967b56a4277.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-d22fd849cc22f104.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-c1e20d7d338713f1.js → page-bb742f995c6769e7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-482c5dbde699947c.js → page-02b073a2ac793b83.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-8a9b3594bc23289b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-a0eb02638b1e6f31.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-a0e2a6d1bfb52fc5.js → page-308111abadf35670.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-c53598cfce6156f2.js → page-5d97b2901e0d1ea6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-13a63953f53c6a23.js → page-c39554c321576982.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-5d3c8429d04f3831.js → page-8c3d8e63ae2e1c98.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-3193bdc0bbab047a.js → page-9b72a07d424701c6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-6107ca8ddd5e3bdd.js → page-a572f58cd3ca5864.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-be5ea965a07fbc41.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-c130b63862437814.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-fa244bd146a00ae8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-44d56d6391965a6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-4167348049166852.js → page-83a700d4c754d802.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-de52e8fdf9095f7a.js → not-found-7b3170f374f77b9d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-ca3952cd78a83ca2.js → page-a3c58f5c72a5f3f9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-e8e1ecaea7cd71d2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-1ebfe049c640e171.js → page-1467047104141297.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-102310930791197a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-6ed0b73bae8351d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-abe8e3b74bea1ffc.js → page-44d5f767491692bf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-43336055451f6e37.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-d28573d6792b4275.js → page-3cc47dca0922ab9a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a77ee355a2304ff0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-d751de5f8062ff5e.js → page-b17cd46cf0e9b8cf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/{page-fe1759f94003b32b.js → page-61553851aa606146.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/{page-d64c984e855c8c68.js → page-a1f585e154e61c59.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-7bf54c9bb9274af8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-9afdee66b820c76e.js → page-5150ef17b5383a57.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-3544f154c87b43ee.js → page-134da468538baf48.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-539cde99554be20f.js → webpack-c2782b330447ffb6.js} +1 -1
- package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.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/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/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/decisions/index.html +1 -0
- package/apps/pwa/out/decisions/index.txt +30 -0
- 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/loops/index.html +1 -1
- package/apps/pwa/out/loops/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/graphs/intake/intake-daily.yaml +9 -2
- package/graphs/intake/news-sources.yaml +4 -0
- package/graphs/intake/recipes.yaml +6 -1
- package/package.json +2 -1
- package/samples/workflows/templates/approval-gated-deploy.yaml +30 -0
- package/samples/workflows/templates/chat-driven-research.yaml +35 -0
- package/samples/workflows/templates/discord-listen-and-respond.yaml +29 -0
- package/samples/workflows/templates/http-poll-then-alert.yaml +30 -0
- package/samples/workflows/templates/manual-quickstart.yaml +21 -0
- package/samples/workflows/templates/scheduled-daily-digest.yaml +30 -0
- package/samples/workflows/templates/webhook-deploy-hook.yaml +27 -0
- package/scripts/__fixtures__/intake-news.xml +7 -0
- package/scripts/ci-public-export-import-gate.ts +7 -2
- package/scripts/ci-typecheck-changed.ts +21 -6
- package/scripts/discord-seat-probe.ts +3 -2
- package/scripts/docs/generate-cli-anatomy.ts +162 -0
- package/scripts/goals/authoring-metrics.ts +73 -0
- package/scripts/intake-news.ts +169 -0
- package/scripts/landing-heal/must-fix-match.ts +7 -7
- package/scripts/landing-heal/run-mission.ts +3 -2
- package/scripts/public-export-test-run.ts +37 -2
- package/scripts/release-loop/checklist-gate-node.ts +13 -11
- package/scripts/release-loop/cut-branch.ts +2 -1
- package/scripts/release-loop/cutoff.ts +4 -2
- package/scripts/release-loop/docs-land-node.ts +77 -11
- package/scripts/release-loop/docs-node.ts +1 -1
- package/scripts/release-loop/gate-node.ts +99 -14
- package/scripts/release-loop/node-verdict.ts +2 -1
- package/scripts/release-loop/npm-publish-node.ts +11 -7
- package/scripts/release-loop/ops-upgrade-node.ts +8 -1
- package/scripts/release-loop/preflight.ts +246 -0
- package/scripts/release-loop/publish-node.ts +10 -3
- package/scripts/release-loop/release-docs.ts +134 -0
- package/scripts/release-loop/release-readiness.ts +11 -6
- package/scripts/release-loop/release-version.ts +48 -0
- package/scripts/release-loop/resume-release.ts +115 -0
- package/scripts/release-loop/unattended-release.ts +43 -7
- package/scripts/release-loop/version-node.ts +149 -41
- package/scripts/release-story/draft.ts +42 -14
- package/scripts/rhythm/daily.ts +25 -4
- package/scripts/skills/public-check.ts +62 -12
- package/scripts/test-deterministic.ts +51 -33
- package/scripts/test-diet/effectiveness.ts +62 -1
- package/scripts/test-diet/lib.ts +1 -1
- package/scripts/test-diet/node.ts +8 -1
- package/src/agent-mission/driver.ts +53 -10
- package/src/agent-mission/review-loop.ts +4 -3
- package/src/agent-substrate/pr-comment-meta.ts +20 -0
- package/src/autopilot/build/llm-conflict-merge.ts +28 -8
- package/src/autopilot/track-agent.ts +5 -3
- package/src/briefing/brief-items.ts +86 -17
- package/src/cli/autopilot-cli.ts +86 -1
- package/src/cli/brief-cli.ts +36 -2
- package/src/cli/decisions-cli.ts +4 -1
- package/src/cli/freeze-cli.ts +3 -2
- package/src/cli/intake-cli.ts +74 -2
- package/src/cli/log-event-names.ts +1 -0
- package/src/cli/notify-cli.ts +75 -0
- package/src/cli/pr-cli.ts +12 -1
- package/src/cli/provider-cli.ts +24 -0
- package/src/cli/release-cli.ts +101 -17
- package/src/cli/self-update-canary.ts +46 -0
- package/src/cli/self-update.ts +200 -1
- package/src/cli/tasks-cli.ts +309 -3
- package/src/context-bus/condense.ts +19 -3
- package/src/context-bus/context-day.ts +77 -24
- package/src/context-bus/context-first.ts +33 -0
- package/src/context-card/index.ts +45 -0
- package/src/context-card/task.ts +76 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -0
- package/src/decisions/decision-ledger.ts +10 -1
- package/src/discord-self-message.ts +20 -2
- package/src/discord-slash-wire.ts +1 -0
- package/src/domains/outbound-alert.ts +200 -27
- package/src/domains/outbound-log-probe.ts +16 -1
- package/src/domains/release-tool.ts +2 -2
- package/src/execution-loop/gates.ts +11 -2
- package/src/flow-loop/split.ts +8 -4
- package/src/git-fs/worktree.ts +23 -3
- package/src/harness/harness-cli-command.ts +151 -11
- package/src/harness/harness-pod-dispatch.ts +11 -0
- package/src/harness/harness-queue.ts +466 -32
- package/src/harness/helper-scan.ts +10 -3
- package/src/harness/nested-elanous-policy.ts +23 -4
- package/src/hq/fence-wrapper.ts +123 -0
- package/src/hq/hq.ts +8 -0
- package/src/hq/seats.ts +369 -0
- package/src/index.ts +190 -23
- package/src/intake-plane/adopt-gate.ts +49 -0
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/collect-github.ts +57 -25
- package/src/intake-plane/digest.ts +39 -4
- package/src/intake-plane/discord-seat-work.ts +8 -2
- package/src/intake-plane/requirement-funnel.ts +316 -0
- package/src/knowledge/know-find.ts +10 -1
- package/src/loops/budget.ts +36 -0
- package/src/loops/orchestrator/finish-rate.ts +73 -1
- package/src/loops/orchestrator/grid-place.ts +11 -0
- package/src/loops/orchestrator/tick.ts +295 -20
- package/src/maturity/feature-maturity.ts +3 -2
- package/src/nexus/api/decisions-route.ts +11 -1
- package/src/nexus/api/grid.ts +61 -0
- package/src/nexus/api/http-server.ts +19 -1
- package/src/nexus/api/loop-edges.ts +69 -13
- package/src/nexus/api/ops-seats-sources.ts +2 -2
- package/src/nexus/api/task-cards-api.ts +19 -0
- package/src/nexus/api/workflow-templates.ts +3 -0
- package/src/oauth/codex.ts +120 -2
- package/src/onboarding/first-chat-gate.ts +29 -0
- package/src/release-loop/checklist.ts +104 -6
- package/src/release-loop/feature-store.ts +10 -9
- package/src/release-loop/landing-freeze.ts +24 -14
- package/src/release-loop/placement.ts +13 -3
- package/src/release-loop/rubric.ts +80 -0
- package/src/seat-dispatch/seat-ask.ts +79 -16
- package/src/seat-loop/seat-loop.ts +107 -37
- package/src/self-dev/ask-launch-flow.ts +50 -10
- package/src/self-dev/ask-path-candidates.ts +68 -0
- package/src/self-dev/dev-pipeline.ts +4 -0
- package/src/self-dev/draft-triage-rules.ts +1 -1
- package/src/self-dev/orchestrate-cli.ts +22 -0
- package/src/self-dev/round-pr-supersede.ts +244 -0
- package/src/self-dev/run-store.ts +5 -1
- package/src/self-dev/run-supervisor.ts +21 -1
- package/src/self-dev/supervisor-verdict-edges.ts +46 -0
- package/src/self-implement/follow-up-goals.ts +2 -2
- package/src/self-implement/frozen-merges.ts +25 -20
- package/src/self-implement/gate-remote.ts +427 -0
- package/src/self-implement/goal-author-clarification.ts +81 -0
- package/src/self-implement/goal-author-cli.ts +33 -5
- package/src/self-implement/goal-author.ts +93 -12
- package/src/self-implement/goal-clarification-escalation.ts +37 -1
- package/src/self-implement/goal-document-write.ts +28 -0
- package/src/self-implement/heal-intake.ts +4 -0
- package/src/self-implement/host-regate.ts +3 -1
- package/src/self-implement/l8-merge-queue.ts +29 -5
- package/src/self-implement/orchestrator.ts +78 -13
- package/src/self-implement/prior-draft-findings.ts +4 -3
- package/src/skills/skill-feedback.ts +76 -0
- package/src/task-agent/actions.ts +151 -0
- package/src/task-agent/judge.ts +122 -0
- package/src/task-agent/mission.ts +423 -0
- package/src/task-agent/shadow.ts +90 -0
- package/src/task-agent/task-hand.ts +235 -0
- package/src/task-cards/card-store.ts +4 -1
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +43 -4
- package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
- package/src/task-orchestrator/surfaces/pod-pool.ts +81 -13
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +225 -35
- package/src/telegram-commands.ts +1 -1
- package/src/telegram-loops-command.ts +22 -15
- package/src/telegram.ts +5 -20
- package/src/user-config.ts +86 -4
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4614-21403aac730d4586.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8949-ecb4128a0858b911.js +0 -1
- package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9247-29d889b809288e65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1e962b31d1f11081.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-8c2d9526164f5d09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-76270efaee638f17.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-dce686e582c23937.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d3d53b146416e422.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-1a61c7ca1ceb3ae7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-51f811fa119308b6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-dacff46049e1557e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-36a7d994f4ca0299.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-5b042736aa113756.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-acb749f1e432bcf3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-57924393e3e4e539.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-31fa1ce4c4aff9ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-0f5cd57ca928f42b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-59f03fda76c03d22.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d4aef67abf264874.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-1ea64acadd704ace.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-30c2073bfc21c004.js +0 -1
- package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +0 -1
- package/apps/pwa/out/_next/static/rv9cierV0jSOvDBm1g0XB/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{rv9cierV0jSOvDBm1g0XB → P6YTVgzuZz5oTZhY4Din_}/_ssgManifest.js +0 -0
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// send.py 와 동일 동작을 TS 로 포팅(완전 elanous 소유). sync(curl) 계약.
|
|
6
6
|
|
|
7
7
|
import * as childProcess from 'node:child_process';
|
|
8
|
-
import { existsSync, readFileSync, appendFileSync, mkdirSync, unlinkSync } from 'node:fs';
|
|
9
|
-
import { dirname, join, relative } from 'node:path';
|
|
8
|
+
import { existsSync, readFileSync, appendFileSync, mkdirSync, unlinkSync, readdirSync, renameSync, openSync, closeSync, statSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { basename, dirname, join, relative } from 'node:path';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
11
|
import { getElanousConfigDir } from '../elanous-config-dir.js';
|
|
12
12
|
import { conatusEnv } from './conatus-env.js';
|
|
@@ -138,11 +138,11 @@ export function userRecentlyActive(now: Date = new Date()): boolean {
|
|
|
138
138
|
|
|
139
139
|
/** 야간 보류 적재 (jsonl append — 크론 동시 실행에 안전). origin 있으면 함께 적재 —
|
|
140
140
|
* 아침 flush 가 그 origin(발신 채널)으로 되돌려 발송(없으면 report 묶음). */
|
|
141
|
-
function deferOutbound(text: string, kind: string, origin?: MissionOrigin | null, path = DEFERRED_PATH): boolean {
|
|
141
|
+
export function deferOutbound(text: string, kind: string, origin?: MissionOrigin | null, path = DEFERRED_PATH): boolean {
|
|
142
142
|
try {
|
|
143
143
|
mkdirSync(dirname(path), { recursive: true });
|
|
144
144
|
const rec = { ts: new Date().toISOString(), kind, text, ...(origin ? { origin } : {}) };
|
|
145
|
-
appendFileSync(path, JSON.stringify(rec) + '\n');
|
|
145
|
+
withQueueLock(path, () => appendFileSync(path, JSON.stringify(rec) + '\n'));
|
|
146
146
|
console.error(`[outbound] 야간 무음(00:00~06:30 KST) — 보류 적재 (${kind})`);
|
|
147
147
|
return true;
|
|
148
148
|
} catch (e) {
|
|
@@ -198,32 +198,195 @@ function deliverToOrigin(origin: MissionOrigin, text: string, onBot?: (bot: stri
|
|
|
198
198
|
try { return sendTelegramRaw(token, origin.chatId, text, origin.threadId); } catch { return false; }
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
/**
|
|
202
|
-
|
|
203
|
-
|
|
201
|
+
/** 보류 큐 재시도 상한 env — config `outbound.deferredMaxAttempts` 가 없을 때만 읽는다. */
|
|
202
|
+
export const DEFERRED_MAX_ATTEMPTS_ENV = 'ELANOUS_OUTBOUND_DEFERRED_MAX_ATTEMPTS';
|
|
203
|
+
|
|
204
|
+
/** 보류 항목이 격리되기 전까지 허용하는 실패 flush 수 — config > env > 5. */
|
|
205
|
+
export function deferredMaxAttempts(config?: Pick<UserConfig, 'outbound'>): number {
|
|
206
|
+
try {
|
|
207
|
+
const v = (config ?? getUserConfig()).outbound?.deferredMaxAttempts;
|
|
208
|
+
if (typeof v === 'number' && Number.isSafeInteger(v) && v > 0) return v;
|
|
209
|
+
} catch { /* unreadable config: env, then default */ }
|
|
210
|
+
const raw = Number(process.env[DEFERRED_MAX_ATTEMPTS_ENV]?.trim() || NaN);
|
|
211
|
+
if (Number.isSafeInteger(raw) && raw > 0) return raw;
|
|
212
|
+
return 5;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** 격리 파일 — 보류 큐 옆 `<name>.failed.jsonl`. */
|
|
216
|
+
export function deferredQuarantinePath(path: string): string {
|
|
217
|
+
return path.endsWith('.jsonl') ? `${path.slice(0, -'.jsonl'.length)}.failed.jsonl` : `${path}.failed`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function pidAlive(pid: number): boolean {
|
|
221
|
+
try { process.kill(pid, 0); return true; } catch (e) { return (e as NodeJS.ErrnoException).code === 'EPERM'; }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** 큐 적재·claim 이 서로를 가로지르지 않게 하는 짧은 배타 잠금(`<queue>.lock`, O_EXCL).
|
|
225
|
+
* 적재는 open→write→close 를 잠금 안에서 하므로 claim 의 rename 이 «열린 채 쓰기 전»인 적재를 가져가지 않는다.
|
|
226
|
+
* 잠금을 못 얻으면(2초 · 30초 넘은 잔여 잠금은 걷는다) 그대로 진행한다 — 알림을 잃는 것보다 낫다. */
|
|
227
|
+
function withQueueLock<T>(path: string, fn: () => T): T {
|
|
228
|
+
const lock = `${path}.lock`;
|
|
229
|
+
const deadline = Date.now() + 2_000;
|
|
230
|
+
let fd: number | null = null;
|
|
231
|
+
while (fd === null && Date.now() <= deadline) {
|
|
232
|
+
try { fd = openSync(lock, 'wx'); break; } catch (e) {
|
|
233
|
+
const code = (e as NodeJS.ErrnoException).code;
|
|
234
|
+
if (code === 'ENOENT') { try { mkdirSync(dirname(path), { recursive: true }); } catch { break; } continue; }
|
|
235
|
+
if (code !== 'EEXIST') break;
|
|
236
|
+
try { if (Date.now() - statSync(lock).mtimeMs > 30_000) { unlinkSync(lock); continue; } } catch { continue; }
|
|
237
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (fd === null) { try { debug.log('outbound.send', 'deferred-lock-unavailable', { lock: basename(lock) }); } catch { /* */ } }
|
|
241
|
+
try { return fn(); } finally {
|
|
242
|
+
if (fd !== null) { try { closeSync(fd); } catch { /* */ } try { unlinkSync(lock); } catch { /* */ } }
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** 이보다 오래된 claim 은 소유 pid 가 살아 있어도 회수한다(pid 재사용 대비 · 정상 flush 는 이보다 훨씬 짧다). */
|
|
247
|
+
const STALE_CLAIM_MS = 15 * 60_000;
|
|
248
|
+
|
|
249
|
+
let claimSeq = 0;
|
|
250
|
+
/** 큐를 «가져간다» — 원자적 rename 으로 이 flush 만의 파일로 옮긴다. flush 도중 적재되는 항목은 새 큐 파일에
|
|
251
|
+
* 쌓이므로 잃지 않는다. 죽은 flusher(또는 이 프로세스의 앞 호출)가 남긴 claim 도 같이 회수한다. */
|
|
252
|
+
function claimDeferred(path: string): string[] {
|
|
253
|
+
const claims: string[] = [];
|
|
254
|
+
const next = (): string => `${path}.flushing-${process.pid}-${Date.now()}-${claimSeq++}`;
|
|
255
|
+
const dir = dirname(path);
|
|
256
|
+
const prefix = `${basename(path)}.flushing-`;
|
|
257
|
+
try {
|
|
258
|
+
for (const name of readdirSync(dir)) {
|
|
259
|
+
if (!name.startsWith(prefix)) continue;
|
|
260
|
+
const m = /^(\d+)-(\d+)-/.exec(name.slice(prefix.length));
|
|
261
|
+
const pid = Number(m?.[1]);
|
|
262
|
+
const at = Number(m?.[2]);
|
|
263
|
+
if (!Number.isSafeInteger(pid)) continue;
|
|
264
|
+
// flushDeferred 는 동기라 같은 pid 의 claim 은 «지난 호출의 잔여»다. 남의 것은 죽었거나 오래됐을 때만.
|
|
265
|
+
const stale = Number.isFinite(at) && Date.now() - at > STALE_CLAIM_MS;
|
|
266
|
+
if (pid !== process.pid && !stale && pidAlive(pid)) continue;
|
|
267
|
+
const own = next();
|
|
268
|
+
try { renameSync(join(dir, name), own); claims.push(own); } catch { /* another flusher took it */ }
|
|
269
|
+
}
|
|
270
|
+
} catch { /* no directory yet */ }
|
|
271
|
+
withQueueLock(path, () => {
|
|
272
|
+
if (!existsSync(path)) return;
|
|
273
|
+
const own = next();
|
|
274
|
+
try { renameSync(path, own); claims.push(own); } catch { /* another flusher took it */ }
|
|
275
|
+
});
|
|
276
|
+
return claims;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** flushDeferred 주입점 — 시험은 실제 발송 대신 sendBatch 를 준다. */
|
|
280
|
+
export type FlushDeferredDeps = {
|
|
281
|
+
sendBatch?: (text: string, kind: string, origin?: MissionOrigin) => boolean;
|
|
282
|
+
maxAttempts?: number;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
/** 보류분 일괄 발송 — 묶음 단위. 배달 건수 반환.
|
|
286
|
+
* 무음 창 밖 첫 sendOutbound 가 자동 호출 + 06:31 플러시 크론이 보장.
|
|
287
|
+
* ⭐ 실패한 묶음의 항목만 큐에 남긴다(성공 묶음은 다시 보내지 않는다 · 10-07 야간 11회 재발송 사고).
|
|
288
|
+
* 실패가 `maxAttempts` 번 쌓인 항목은 `<name>.failed.jsonl` 로 격리한다. */
|
|
289
|
+
export function flushDeferred(path = DEFERRED_PATH, deps: FlushDeferredDeps = {}): number {
|
|
204
290
|
ensureOutboundLogSink();
|
|
205
|
-
const observeFlush = (
|
|
291
|
+
const observeFlush = (
|
|
292
|
+
count: number, lagMin: number, kinds: string[],
|
|
293
|
+
outcome: { sent: number; kept: number; quarantined: number },
|
|
294
|
+
): void => {
|
|
206
295
|
const lagWarnMin = flushLagWarnMin();
|
|
207
296
|
const over = count > 0 && lagMin > lagWarnMin;
|
|
208
297
|
logSend(
|
|
209
298
|
'flush',
|
|
210
299
|
'deferred-batch',
|
|
211
|
-
{ count, lagMin, kinds: kinds.map(safeObservationLabel), path, lagWarnMin },
|
|
300
|
+
{ count, lagMin, kinds: kinds.map(safeObservationLabel), path, lagWarnMin, ...outcome },
|
|
212
301
|
over ? { level: 'warn' } : undefined,
|
|
213
302
|
);
|
|
214
303
|
};
|
|
215
|
-
|
|
216
|
-
|
|
304
|
+
const none = { sent: 0, kept: 0, quarantined: 0 };
|
|
305
|
+
const claims = claimDeferred(path);
|
|
306
|
+
if (claims.length === 0) {
|
|
307
|
+
observeFlush(0, 0, [], none);
|
|
217
308
|
return 0;
|
|
218
309
|
}
|
|
219
|
-
type Item = { ts: string; kind: string; text: string; origin?: MissionOrigin };
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
310
|
+
type Item = { ts: string; kind: string; text: string; origin?: MissionOrigin; attempts?: number };
|
|
311
|
+
const items: Item[] = [];
|
|
312
|
+
const corrupt: string[] = [];
|
|
313
|
+
const readClaims: string[] = [];
|
|
314
|
+
for (const claim of claims) {
|
|
315
|
+
let body = '';
|
|
316
|
+
try { body = readFileSync(claim, 'utf-8'); } catch (e) {
|
|
317
|
+
// 못 읽은 claim 은 지우지 않는다 — 다음 flush 가 다시 회수한다.
|
|
318
|
+
try { debug.log('outbound.send', 'deferred-claim-unreadable', { error: e instanceof Error ? e.message : String(e) }, { level: 'warn' }); } catch { /* */ }
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
readClaims.push(claim);
|
|
322
|
+
for (const line of body.split('\n')) {
|
|
323
|
+
if (!line.trim()) continue;
|
|
324
|
+
try {
|
|
325
|
+
const rec = JSON.parse(line) as Item;
|
|
326
|
+
if (rec && typeof rec === 'object' && typeof rec.text === 'string') items.push(rec);
|
|
327
|
+
else corrupt.push(line);
|
|
328
|
+
} catch { corrupt.push(line); }
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const qPath = deferredQuarantinePath(path);
|
|
332
|
+
const settleFailed = (stage: string, e: unknown): void => {
|
|
333
|
+
try { debug.log('outbound.send', 'deferred-settle-failed', { stage, error: e instanceof Error ? e.message : String(e) }, { level: 'warn' }); } catch { /* */ }
|
|
334
|
+
};
|
|
335
|
+
/** 읽은 claim 을 정산한다. 격리 기록이 실패하면 그 줄은 큐로 돌린다(다음에 다시 격리).
|
|
336
|
+
* 큐 기록이 실패하면 claim 하나를 «남길 줄만»으로 바꿔 두어 다음 flush 가 배달분을 다시 보내지 않게 한다. */
|
|
337
|
+
/** 정산된 claim 을 걷는다. 삭제가 실패하면 비워 둔다 — 남은 claim 이 다음 flush 에 배달분을 다시 내지 않게. */
|
|
338
|
+
const retireClaim = (claim: string): void => {
|
|
339
|
+
try { unlinkSync(claim); return; } catch (e) { settleFailed('claim-unlink', e); }
|
|
340
|
+
try { writeFileSync(claim, ''); } catch (e) { settleFailed('claim-truncate', e); }
|
|
341
|
+
};
|
|
342
|
+
type Quarantine = { line: string; kind?: string; attempts?: number };
|
|
343
|
+
/** 반환 = 실제 정산 결과(관측은 이것으로 센다). */
|
|
344
|
+
const settle = (keep: string[], quarantine: Quarantine[]): { kept: number; quarantined: number } => {
|
|
345
|
+
let back = keep;
|
|
346
|
+
let quarantined = 0;
|
|
347
|
+
if (quarantine.length) {
|
|
348
|
+
try {
|
|
349
|
+
appendFileSync(qPath, quarantine.map(q => q.line).join('\n') + '\n');
|
|
350
|
+
quarantined = quarantine.length;
|
|
351
|
+
for (const q of quarantine) {
|
|
352
|
+
if (q.kind === undefined) continue;
|
|
353
|
+
try { debug.log('outbound.send', 'deferred-quarantined', { kind: safeObservationLabel(q.kind), attempts: q.attempts }); } catch { /* */ }
|
|
354
|
+
}
|
|
355
|
+
} catch (e) { settleFailed('quarantine', e); back = [...keep, ...quarantine.map(q => q.line)]; }
|
|
356
|
+
}
|
|
357
|
+
const result = { kept: back.length, quarantined };
|
|
358
|
+
if (readClaims.length === 0) return result;
|
|
359
|
+
let wrote = back.length === 0;
|
|
360
|
+
if (!wrote) {
|
|
361
|
+
try { withQueueLock(path, () => appendFileSync(path, back.join('\n') + '\n')); wrote = true; } catch (e) { settleFailed('queue', e); }
|
|
362
|
+
}
|
|
363
|
+
if (!wrote) {
|
|
364
|
+
const first = readClaims[0]!;
|
|
365
|
+
const tmp = join(dirname(first), `.${basename(first)}.tmp`);
|
|
366
|
+
try {
|
|
367
|
+
writeFileSync(tmp, back.join('\n') + '\n');
|
|
368
|
+
renameSync(tmp, first);
|
|
369
|
+
for (const claim of readClaims.slice(1)) retireClaim(claim);
|
|
370
|
+
} catch (e) { settleFailed('claim-rewrite', e); }
|
|
371
|
+
return result;
|
|
372
|
+
}
|
|
373
|
+
for (const claim of readClaims) retireClaim(claim);
|
|
374
|
+
return result;
|
|
375
|
+
};
|
|
376
|
+
const maxAttempts = deps.maxAttempts ?? deferredMaxAttempts();
|
|
377
|
+
const priorAttempts = (it: Item): number =>
|
|
378
|
+
typeof it.attempts === 'number' && Number.isSafeInteger(it.attempts) && it.attempts > 0 ? it.attempts : 0;
|
|
379
|
+
const quarantine: Quarantine[] = corrupt.map(line => ({ line }));
|
|
380
|
+
// 이미 상한에 닿은 줄(앞 flush 의 격리 기록이 실패해 큐로 돌아온 것)은 보내지 않고 다시 격리만 시도한다.
|
|
381
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
382
|
+
const it = items[i]!;
|
|
383
|
+
if (priorAttempts(it) < maxAttempts) continue;
|
|
384
|
+
quarantine.push({ line: JSON.stringify(it), kind: it.kind, attempts: priorAttempts(it) });
|
|
385
|
+
items.splice(i, 1);
|
|
386
|
+
}
|
|
224
387
|
if (items.length === 0) {
|
|
225
|
-
|
|
226
|
-
observeFlush(0, 0, []);
|
|
388
|
+
const settled = settle([], quarantine);
|
|
389
|
+
observeFlush(0, 0, [], { ...none, ...settled });
|
|
227
390
|
return 0;
|
|
228
391
|
}
|
|
229
392
|
const kst = (iso: string) => {
|
|
@@ -236,8 +399,7 @@ export function flushDeferred(path = DEFERRED_PATH): number {
|
|
|
236
399
|
`🌙 야간 보류 알림 ${list.length}건 (00:00~06:30 KST 무음 · ⏳ 최대 ${lagMin}분 지연 — 일괄 전달)`,
|
|
237
400
|
...list.map(i => `\n── [${kst(i.ts)} · ${i.kind}] ──\n${i.text}`),
|
|
238
401
|
].join('\n');
|
|
239
|
-
|
|
240
|
-
const sendBatch = (text: string, kind: string, origin?: MissionOrigin): boolean => {
|
|
402
|
+
const sendBatch = deps.sendBatch ?? ((text: string, kind: string, origin?: MissionOrigin): boolean => {
|
|
241
403
|
let bot = 'unknown';
|
|
242
404
|
const path = origin
|
|
243
405
|
? (deliverToOrigin(origin, text, label => { bot = label; }) ? 'origin' : false)
|
|
@@ -250,15 +412,17 @@ export function flushDeferred(path = DEFERRED_PATH): number {
|
|
|
250
412
|
});
|
|
251
413
|
} catch { /* observation must not change delivery */ }
|
|
252
414
|
return path !== false;
|
|
415
|
+
});
|
|
416
|
+
const failed: Item[] = [];
|
|
417
|
+
const send = (batch: Item[], kind: string, origin?: MissionOrigin): void => {
|
|
418
|
+
if (!sendBatch(fmt(batch), kind, origin)) failed.push(...batch);
|
|
253
419
|
};
|
|
254
|
-
let delivered = 0;
|
|
255
420
|
// origin 없는 매매 알림만 report 묶음. 그 외는 kind 별로 운영 봇 경계를 유지한다.
|
|
256
421
|
const noOrigin = items.filter(i => !i.origin);
|
|
257
422
|
const tradingBatch = noOrigin.filter(i => !isOperationalKind(i.kind));
|
|
258
|
-
if (tradingBatch.length
|
|
423
|
+
if (tradingBatch.length) send(tradingBatch, 'report');
|
|
259
424
|
for (const kind of new Set(noOrigin.filter(i => isOperationalKind(i.kind)).map(i => i.kind))) {
|
|
260
|
-
|
|
261
|
-
if (sendBatch(fmt(batch), kind)) delivered += batch.length;
|
|
425
|
+
send(noOrigin.filter(i => i.kind === kind), kind);
|
|
262
426
|
}
|
|
263
427
|
// origin 있는 것 = 발신 채널·스레드별로 묶어 서로 다른 수신자에게 섞이지 않게 발송.
|
|
264
428
|
const groups = new Map<string, { origin: MissionOrigin; list: Item[] }>();
|
|
@@ -272,10 +436,19 @@ export function flushDeferred(path = DEFERRED_PATH): number {
|
|
|
272
436
|
}
|
|
273
437
|
for (const g of groups.values()) {
|
|
274
438
|
const kinds = new Set(g.list.map(i => i.kind));
|
|
275
|
-
|
|
439
|
+
send(g.list, kinds.size === 1 ? g.list[0]!.kind : 'mixed', g.origin);
|
|
440
|
+
}
|
|
441
|
+
// 실패 묶음의 항목만 되돌린다(시도 수 +1). 상한에 닿은 항목은 격리 — 영원히 재시도하지 않는다.
|
|
442
|
+
const keep: string[] = [];
|
|
443
|
+
for (const it of failed) {
|
|
444
|
+
const attempts = priorAttempts(it) + 1;
|
|
445
|
+
const line = JSON.stringify({ ...it, attempts });
|
|
446
|
+
if (attempts >= maxAttempts) quarantine.push({ line, kind: it.kind, attempts });
|
|
447
|
+
else keep.push(line);
|
|
276
448
|
}
|
|
277
|
-
|
|
278
|
-
|
|
449
|
+
const settled = settle(keep, quarantine);
|
|
450
|
+
const delivered = items.length - failed.length;
|
|
451
|
+
observeFlush(items.length, lagMin, [...new Set(items.map(i => i.kind))], { sent: delivered, ...settled });
|
|
279
452
|
return delivered;
|
|
280
453
|
}
|
|
281
454
|
|
|
@@ -1,8 +1,23 @@
|
|
|
1
|
-
import { sendOutbound, setInProcessOutbound } from './outbound-alert.js';
|
|
1
|
+
import { inQuietHours, kstMinutes, sendOutbound, setInProcessOutbound } from './outbound-alert.js';
|
|
2
2
|
import { setUserConfigOverlay } from '../user-config.js';
|
|
3
3
|
|
|
4
4
|
// Focused subprocess fixture: isolated state/config roots and a fake curl on PATH.
|
|
5
5
|
setUserConfigOverlay(cfg => ({ ...cfg, telegram: { ...cfg.telegram, botToken: '123456:private-secret', homeChannel: 98765 } }));
|
|
6
6
|
process.env.SEND_VIA_ELANOUS = '0';
|
|
7
|
+
// Quiet hours (00:00~06:30 KST) defer instead of sending, so a gate run at night saw no `sent` row.
|
|
8
|
+
// Shift this fixture's clock back to 23:59 KST of the previous day; the shift stays inside this process.
|
|
9
|
+
const quietShiftMs = inQuietHours() ? (kstMinutes() + 1) * 60_000 : 0;
|
|
10
|
+
if (quietShiftMs > 0) {
|
|
11
|
+
const RealDate = Date;
|
|
12
|
+
const shifted = () => RealDate.now() - quietShiftMs;
|
|
13
|
+
class ShiftedDate extends RealDate {
|
|
14
|
+
constructor(...args: unknown[]) {
|
|
15
|
+
if (args.length === 0) super(shifted());
|
|
16
|
+
else super(...(args as [string | number | Date]));
|
|
17
|
+
}
|
|
18
|
+
static override now(): number { return shifted(); }
|
|
19
|
+
}
|
|
20
|
+
globalThis.Date = ShiftedDate as DateConstructor;
|
|
21
|
+
}
|
|
7
22
|
setInProcessOutbound(null);
|
|
8
23
|
if (!sendOutbound('SECRET-BODY-DO-NOT-LOG', 'ops-alert', { channel: 'cli', surface: process.env.OUTBOUND_PROBE_SOURCE ?? 'outbound-log-probe' })) process.exitCode = 1;
|
|
@@ -2,7 +2,7 @@ import type { LLMToolSpec } from '../llm.js';
|
|
|
2
2
|
import type { ToolRuntimeContext } from '../tool-runtime/types.js';
|
|
3
3
|
import { debug } from '../debug/log.js';
|
|
4
4
|
import { requestConfirmation, type ConfirmOpts, type ConfirmResult } from '../hitl/confirm.js';
|
|
5
|
-
import { devVersion, listChecklist, summarizeChecklist, type Checklist } from '../release-loop/checklist.js';
|
|
5
|
+
import { devVersion, checklistDevVersion, listChecklist, summarizeChecklist, type Checklist } from '../release-loop/checklist.js';
|
|
6
6
|
import { add, move, validateVersion } from '../release-loop/feature-store.js';
|
|
7
7
|
import { formatKst, getSchedule, setSchedule, type ReleaseSchedule } from '../release-loop/release-schedule.js';
|
|
8
8
|
import { latestGraphRun, type GraphRunState } from '../graph-runner/runner.js';
|
|
@@ -64,7 +64,7 @@ function dDay(days: number): string {
|
|
|
64
64
|
export function dispatchReleaseStatus(args: Record<string, unknown>, deps: ReleaseToolDeps = {}): { text: string; structured: Record<string, unknown> } {
|
|
65
65
|
const requested = args.version;
|
|
66
66
|
if (requested !== undefined && (typeof requested !== 'string' || !requested.trim())) throw new Error('판 이름이 필요합니다');
|
|
67
|
-
const version = requested ? null : (deps.devVersion?.() ?? devVersion());
|
|
67
|
+
const version = requested ? null : checklistDevVersion(deps.devVersion?.() ?? devVersion());
|
|
68
68
|
const versions = requested ? [requested as string] : [version!, nextVersion(version!)];
|
|
69
69
|
const now = deps.now ?? new Date();
|
|
70
70
|
const run = (deps.latestRun ?? latestGraphRun)('release-loop');
|
|
@@ -3,6 +3,7 @@ import { decideBudget, readBudgetInputsLive, type BudgetDecision, type BudgetInp
|
|
|
3
3
|
import { resolveHarnessSubstrate, type ResolvedHarnessSubstrate } from '../harness/harness-substrate-default.js';
|
|
4
4
|
import { podMemoryLimitFor } from '../task-orchestrator/surfaces/self-implement-pod.js';
|
|
5
5
|
import { recallMemoryContext } from '../agent-substrate/execution/memory-context.js';
|
|
6
|
+
import { CONTEXT_CARD_SCREEN_CHARS } from '../context-card/index.js';
|
|
6
7
|
|
|
7
8
|
export interface GateResult<T> {
|
|
8
9
|
decision: T;
|
|
@@ -169,6 +170,7 @@ export async function relationGate(
|
|
|
169
170
|
export interface MemoryGateDeps {
|
|
170
171
|
recall: (query: string) => Promise<string>;
|
|
171
172
|
priorAbandonment: (goalId: string) => Promise<string | undefined> | string | undefined;
|
|
173
|
+
priorMustFix?: (goalId: string) => readonly string[];
|
|
172
174
|
}
|
|
173
175
|
|
|
174
176
|
export interface MemoryDecision {
|
|
@@ -183,15 +185,22 @@ export async function memoryGate(
|
|
|
183
185
|
card: Pick<TaskCard, 'goalId' | 'title'>,
|
|
184
186
|
deps: MemoryGateDeps = { recall: recallMemoryContext, priorAbandonment: () => undefined },
|
|
185
187
|
): Promise<GateResult<MemoryDecision>> {
|
|
186
|
-
const [
|
|
188
|
+
const [recall, priorAbandonment] = await Promise.all([
|
|
187
189
|
deps.recall(card.title), deps.priorAbandonment(card.goalId),
|
|
188
190
|
]);
|
|
191
|
+
// must-fix 와 recall 은 서로 독립이다 — recall 이 비어도 앞선 must-fix 는 맥락에 남는다.
|
|
192
|
+
const mustFix = deps.priorMustFix?.(card.goalId) ?? [];
|
|
193
|
+
const mustFixBlock = mustFix.length ? `앞선 must-fix:\n${mustFix.map((item) => `- ${item}`).join('\n')}` : '';
|
|
194
|
+
const combined = [mustFixBlock, recall].filter(Boolean).join('\n');
|
|
195
|
+
const codePoints = [...combined];
|
|
196
|
+
const truncated = codePoints.length > CONTEXT_CARD_SCREEN_CHARS;
|
|
197
|
+
const context = truncated ? codePoints.slice(0, CONTEXT_CARD_SCREEN_CHARS).join('') : combined;
|
|
189
198
|
const decision: MemoryDecision = {
|
|
190
199
|
action: 'record', context, fragmentIds: [],
|
|
191
200
|
...(priorAbandonment === undefined ? {} : { priorAbandonment }),
|
|
192
201
|
};
|
|
193
202
|
return {
|
|
194
203
|
decision,
|
|
195
|
-
explanation: `memory=${
|
|
204
|
+
explanation: `memory=${recall ? 'recalled' : 'none or unavailable'}; prior abandonment=${priorAbandonment ?? 'unknown'}; fragment IDs unavailable from recall API${truncated ? `; truncated; chars=${[...context].length}/${CONTEXT_CARD_SCREEN_CHARS}` : ''}`,
|
|
196
205
|
};
|
|
197
206
|
}
|
package/src/flow-loop/split.ts
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
import type { TaskCard } from '../task-cards/card-store.js';
|
|
2
2
|
|
|
3
|
-
export interface SplitCell { id: string; title: string; seat?: string; host?: string }
|
|
3
|
+
export interface SplitCell { id: string; title: string; kind?: string; seat?: string; host?: string }
|
|
4
4
|
|
|
5
5
|
/** FLOW1: only explicitly assigned work may enter a seat's queue. The same proposal is used in shadow and live. */
|
|
6
6
|
export function splitCard(card: TaskCard, _opts: { shadow?: boolean } = {}): SplitCell[] {
|
|
7
7
|
const intake = [...card.sections].reverse().find(section => section.key.startsWith('intake:wish:'));
|
|
8
8
|
let text = card.title;
|
|
9
|
+
let kind: string | undefined;
|
|
9
10
|
if (intake) {
|
|
10
11
|
let body: unknown;
|
|
11
12
|
try { body = JSON.parse(intake.content); } catch { body = intake.content; }
|
|
12
13
|
if (typeof body === 'string') text = body;
|
|
13
|
-
else if (body && typeof body === 'object'
|
|
14
|
+
else if (body && typeof body === 'object') {
|
|
15
|
+
if (typeof (body as { text?: unknown }).text === 'string') text = (body as { text: string }).text;
|
|
16
|
+
if (typeof (body as { kind?: unknown }).kind === 'string') kind = (body as { kind: string }).kind;
|
|
17
|
+
}
|
|
14
18
|
}
|
|
15
19
|
const lines = text.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
|
|
16
20
|
const cells: SplitCell[] = lines.map((line, index) => {
|
|
17
21
|
const match = /^(?:[-*]|\d+[.)])?\s*\[(OP|MK|TC|UX)\]\s*(.+)$/u.exec(line);
|
|
18
|
-
if (match) return { id: `${card.id}-${index + 1}`, title: match[2]!.trim(), seat: match[1]
|
|
22
|
+
if (match) return { id: `${card.id}-${index + 1}`, title: match[2]!.trim(), seat: match[1]!, ...(kind !== undefined ? { kind } : {}) };
|
|
19
23
|
// 담당 없는 행도 칸으로 남긴다 — seat 가 없으니 대기열엔 못 들어가고 tick 이 unplaced-no-owner 로 센다.
|
|
20
|
-
return { id: `${card.id}-${index + 1}`, title: line.replace(/^(?:[-*]|\d+[.)])\s*/u, '') };
|
|
24
|
+
return { id: `${card.id}-${index + 1}`, title: line.replace(/^(?:[-*]|\d+[.)])\s*/u, ''), ...(kind !== undefined ? { kind } : {}) };
|
|
21
25
|
});
|
|
22
26
|
// 의뢰 본문이 있으면 담당이 하나도 없어도 행마다 칸으로 남긴다. 본문 없이 제목뿐일 때만 카드 한 칸.
|
|
23
27
|
return intake || cells.some(cell => cell.seat) ? cells : [{ id: card.id, title: card.title }];
|
package/src/git-fs/worktree.ts
CHANGED
|
@@ -20,6 +20,9 @@ import { debug } from '../debug/log.js';
|
|
|
20
20
|
import { getUserConfig } from '../user-config.js';
|
|
21
21
|
|
|
22
22
|
const GIT_TIMEOUT_MS = 30_000;
|
|
23
|
+
/** 얕은 클론(Pod `git clone --depth 50` · docker/harness/job.yaml)에서 base 를 가져올 깊이 — 클론과 같은 값.
|
|
24
|
+
* ⛔ 깊이 없이 얕은 저장소로 다른 브랜치를 fetch 하면 그 브랜치의 «전 이력»을 받는다(실측 193s vs 4s · 30s 에 SIGTERM). */
|
|
25
|
+
const SHALLOW_BASE_FETCH_DEPTH = 50;
|
|
23
26
|
const WORKTREE_ADD_TIMEOUT_MS = 5 * 60_000;
|
|
24
27
|
|
|
25
28
|
export interface WorktreeAddDeps {
|
|
@@ -439,14 +442,31 @@ export function syncBaseWithRemote(
|
|
|
439
442
|
// 원격이 "그런 브랜치 없다" 고 답했다 ⇒ 축약 hex 여도 모호하지 않다.
|
|
440
443
|
if (!hasRemote) return asSha(false) ?? { checkout: base, freshness: 'local-only' };
|
|
441
444
|
|
|
445
|
+
// ⭐ 얕은 저장소면 깊이를 묶는다 — 아니면 그 브랜치의 전 이력을 받느라 GIT_TIMEOUT_MS 를 넘긴다
|
|
446
|
+
// (Pod 클론은 `--depth 50` · 비-main base 런이 전부 여기서 SIGTERM 으로 죽었다). 받은 tip 에서 갈리므로
|
|
447
|
+
// 이후 merge-base(HEAD ↔ 그 tip)는 tip 자체라 깊이 50 으로 족하다. 얕지 않으면 종전 인자 그대로다.
|
|
448
|
+
// probe 를 못 읽으면 종전 경로로 간다(값은 'unknown' 으로 관측에 남긴다).
|
|
449
|
+
const shallowProbe = git('rev-parse', '--is-shallow-repository');
|
|
450
|
+
const shallowAnswer = shallowProbe.status === 0 ? (shallowProbe.stdout || '').trim() : '';
|
|
451
|
+
const shallow: boolean | 'unknown' = shallowAnswer === 'true' ? true : shallowAnswer === 'false' ? false : 'unknown';
|
|
452
|
+
const depth = shallow === true ? SHALLOW_BASE_FETCH_DEPTH : null;
|
|
453
|
+
const fetchArgs = depth === null ? ['fetch', 'origin', wanted] : ['fetch', `--depth=${depth}`, 'origin', wanted];
|
|
454
|
+
const fetchStartedAt = Date.now();
|
|
442
455
|
// Only the no-runner fallback joins the shared retry seam; injected runners remain
|
|
443
456
|
// an exact test/caller seam with their prior command count.
|
|
444
457
|
const fetched = runner
|
|
445
|
-
? git(
|
|
446
|
-
: runGitCommand(repoRoot,
|
|
458
|
+
? git(...fetchArgs)
|
|
459
|
+
: runGitCommand(repoRoot, fetchArgs, { encoding: 'utf8', timeout: GIT_TIMEOUT_MS });
|
|
460
|
+
const fetchMs = Date.now() - fetchStartedAt;
|
|
461
|
+
debug.log('git-fs.worktree', 'base.fetch', { base, shallow, depth, ms: fetchMs, ok: fetched.status === 0, signal: fetched.signal ?? null }, fetched.status === 0 ? undefined : { level: 'warn' });
|
|
447
462
|
if (fetched.status !== 0) {
|
|
448
463
|
// origin 에 있는 것을 확인했는데 못 가져왔다 ⇒ 낡은 로컬로 진행하면 조용히 틀린다.
|
|
449
|
-
|
|
464
|
+
// spawnSync 의 timeout 은 SIGTERM 으로 끝난다 — 「실패」와 「시간 초과」를 문면에서 가른다.
|
|
465
|
+
const timedOut = fetched.status === null && fetched.signal === 'SIGTERM';
|
|
466
|
+
const what = timedOut
|
|
467
|
+
? `fetch timed out after ${GIT_TIMEOUT_MS / 1000}s (shallow=${shallow}${depth === null ? '' : `, depth=${depth}`})`
|
|
468
|
+
: 'fetch failed';
|
|
469
|
+
throw new Error(`git worktree base sync failed — origin/${remoteBranch} exists but ${what}: status=${fetched.status}, signal=${fetched.signal ?? 'none'}, stdout=${JSON.stringify((fetched.stdout || '').trim().slice(0, 200))}, stderr=${JSON.stringify((fetched.stderr || '').trim().slice(0, 200))}`);
|
|
450
470
|
}
|
|
451
471
|
// ⭐ **가져온 것**에서 갈린다(리뷰 must-fix ②) — ls-remote 로 먼저 읽은 SHA 를 쓰면 그 사이 원격이
|
|
452
472
|
// 전진했을 때 "원격 tip" 계약이 깨진다. FETCH_HEAD 가 방금 받은 tip 이다.
|