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
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TASK-AGENT-MISSION ① — `elanous tasks hand --mission "<칸 문면>"`: 미션을 조각 카드 ⊕ 선후 간선으로 쪼개 적고,
|
|
3
|
+
* 선행이 없는 조각만 기존 `handTask` 로 넘긴다(기본 shadow · `--live` 면 발사).
|
|
4
|
+
*
|
|
5
|
+
* - 쪼개기: 문면이 이미 조각을 줄로 나열하면(`- …` · `① …` · `1. …` + `after: 1, ②` 힌트) LLM 없이 결정론으로.
|
|
6
|
+
* 아니면 기존 분해기 `decomposeSelfDevGoal`(self orchestrate --decompose 와 같은 것)을 주입 가능한 자리로 부른다.
|
|
7
|
+
* - 저장: 과제 카드와 «같은» 상태 파일(`taskAgentStatePath()`) — 미션 카드(`pieces`) ⊕ 조각 카드(`mission`·`after`).
|
|
8
|
+
* - ② `elanous tasks advance`: 조각의 착지 근거(카드의 PR 번호·골 id → PR 병합)로 착지를 가르고(`landedPieces`),
|
|
9
|
+
* 새로 준비된 조각만 같은 `handTask` 로 «한 번» 넘긴다 · 모두 착지하면 미션 카드에 green «제안»만 적는다.
|
|
10
|
+
* - 관측: `debug.log('task-agent', 'mission-split' | 'mission-piece' | 'mission-advance' | 'mission-piece-handed' | 'mission-green-proposed', …)`.
|
|
11
|
+
*/
|
|
12
|
+
import { randomBytes } from 'node:crypto';
|
|
13
|
+
import { debug } from '../debug/log.js';
|
|
14
|
+
import type { SelfDevDecomposition } from '../self-dev/decompose.js';
|
|
15
|
+
import {
|
|
16
|
+
handTask,
|
|
17
|
+
nextMoveFor,
|
|
18
|
+
readTaskAgentState,
|
|
19
|
+
readTaskCard,
|
|
20
|
+
TaskCardSupersededError,
|
|
21
|
+
TASK_CARD_PREFIX,
|
|
22
|
+
TASK_SEATS,
|
|
23
|
+
taskAgentStatePath,
|
|
24
|
+
updateTaskCard,
|
|
25
|
+
writeTaskCards,
|
|
26
|
+
type TaskCard,
|
|
27
|
+
type TaskCardMove,
|
|
28
|
+
type TaskLauncher,
|
|
29
|
+
type TaskSeat,
|
|
30
|
+
} from './task-hand.js';
|
|
31
|
+
|
|
32
|
+
/** 쪼갠 조각 — `after` 는 같은 목록 안의 0-기반 위치. */
|
|
33
|
+
export interface MissionPieceDraft { text: string; after: number[] }
|
|
34
|
+
export interface MissionSplit { pieces: MissionPieceDraft[]; source: string }
|
|
35
|
+
|
|
36
|
+
/** 주입 가능한 분해기 — 기본은 `decomposeSelfDevGoal`(LLM). 시험은 가짜를 준다. */
|
|
37
|
+
export type MissionDecompose = (text: string) => Promise<SelfDevDecomposition>;
|
|
38
|
+
|
|
39
|
+
const CIRCLED_FIRST = 0x2460; // ①
|
|
40
|
+
const CIRCLED_LAST = 0x2473; // ⑳
|
|
41
|
+
const ITEM = /^(\s*)(?:([-*•])|([①-⑳])|(\d{1,3})[.)])\s+(.*)$/u;
|
|
42
|
+
const AFTER_HINT = /\s*[([]?\s*(?:after|선행)\s*[::]\s*([^)\]]*?)\s*[)\]]?\s*$/iu;
|
|
43
|
+
const EMPTY_ITEM = /^\s*(?:[-*•]|[\u2460-\u2473]|\d{1,3}[.)])\s*$/u;
|
|
44
|
+
const AFTER_LINE = /^\s*(?:after|선행)\s*[::]\s*(.*?)\s*$/iu;
|
|
45
|
+
|
|
46
|
+
function circledNumber(ch: string): number | null {
|
|
47
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
48
|
+
return code >= CIRCLED_FIRST && code <= CIRCLED_LAST ? code - CIRCLED_FIRST + 1 : null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function refLabel(raw: string): string | null {
|
|
52
|
+
const ref = raw.trim().replace(/^#/, '');
|
|
53
|
+
if (!ref) return null;
|
|
54
|
+
if (/^\d{1,3}$/.test(ref)) return String(Number(ref));
|
|
55
|
+
const circled = [...ref].length === 1 ? circledNumber(ref) : null;
|
|
56
|
+
return circled === null ? ref : String(circled);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** 문면이 이미 조각을 나열했을 때의 결정론 분해 — 조각이 2장 미만이면 null(분해기로 간다). */
|
|
60
|
+
export function parseListedMission(text: string): MissionSplit | null {
|
|
61
|
+
interface Item { label: string; lines: string[]; afterRefs: string[] }
|
|
62
|
+
const items: Item[] = [];
|
|
63
|
+
let baseIndent = 0; // 첫 조각의 들여쓰기 — 같은 수준(±1칸)만 조각, 더 깊으면 그 조각의 하위 줄
|
|
64
|
+
for (const line of text.split(/\r?\n/)) {
|
|
65
|
+
if (EMPTY_ITEM.test(line)) throw new Error(`빈 조각 표식이 있다: «${line.trim()}»`);
|
|
66
|
+
const item = line.match(ITEM);
|
|
67
|
+
if (item && items.length === 0) baseIndent = item[1]!.length;
|
|
68
|
+
if (item && (items.length === 0 || item[1]!.length <= baseIndent + 1)) {
|
|
69
|
+
const marker = item[3] ? String(circledNumber(item[3])) : item[4] ? String(Number(item[4])) : String(items.length + 1);
|
|
70
|
+
let body = item[5]!;
|
|
71
|
+
const afterRefs: string[] = [];
|
|
72
|
+
const hint = body.match(AFTER_HINT);
|
|
73
|
+
if (hint) { afterRefs.push(...hint[1]!.split(/[,\s]+/).filter(Boolean)); body = body.slice(0, hint.index).trimEnd(); }
|
|
74
|
+
items.push({ label: marker, lines: [body], afterRefs });
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const current = items.at(-1);
|
|
78
|
+
if (!current) continue; // 머리말 — 미션 카드 원문에 남는다
|
|
79
|
+
const afterLine = line.match(AFTER_LINE);
|
|
80
|
+
if (afterLine) { current.afterRefs.push(...afterLine[1]!.split(/[,\s]+/).filter(Boolean)); continue; }
|
|
81
|
+
if (line.trim()) current.lines.push(line.trim());
|
|
82
|
+
}
|
|
83
|
+
if (items.length < 2) return null;
|
|
84
|
+
const byLabel = new Map<string, number>();
|
|
85
|
+
items.forEach((item, index) => {
|
|
86
|
+
if (byLabel.has(item.label)) throw new Error(`조각 번호가 겹친다: ${item.label}`);
|
|
87
|
+
byLabel.set(item.label, index);
|
|
88
|
+
});
|
|
89
|
+
const pieces = items.map((item, index) => {
|
|
90
|
+
const after = item.afterRefs.map((raw) => {
|
|
91
|
+
const label = refLabel(raw);
|
|
92
|
+
const target = label === null ? undefined : byLabel.get(label);
|
|
93
|
+
if (target === undefined) throw new Error(`조각 ${item.label} 의 after 가 없는 조각을 가리킨다: ${raw}`);
|
|
94
|
+
if (target === index) throw new Error(`조각 ${item.label} 이 자기 자신 뒤에 올 수 없다`);
|
|
95
|
+
return target;
|
|
96
|
+
});
|
|
97
|
+
return { text: item.lines.join('\n').trim(), after: [...new Set(after)] };
|
|
98
|
+
});
|
|
99
|
+
if (pieces.some((piece) => !piece.text)) throw new Error('빈 조각이 있다');
|
|
100
|
+
return { pieces, source: 'listed' };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** 분해기 결과(`SelfDevGoal[]`)를 조각으로 — dependsOn 의 id 를 위치로 바꾼다. 없는 id·자기 참조는 버리지 않고 멈춘다. */
|
|
104
|
+
export function piecesFromDecomposition(result: SelfDevDecomposition): MissionSplit {
|
|
105
|
+
const ids = result.goals.map((goal, index) => goal.id ?? String(index));
|
|
106
|
+
if (new Set(ids).size !== ids.length) throw new Error(`분해기 조각 id 가 겹친다: ${ids.join(', ')}`);
|
|
107
|
+
const pieces = result.goals.map((goal, index) => ({
|
|
108
|
+
text: (goal.feature ?? '').trim(),
|
|
109
|
+
after: [...new Set((goal.dependsOn ?? []).map((id) => {
|
|
110
|
+
const at = ids.indexOf(id);
|
|
111
|
+
if (at < 0) throw new Error(`분해기 조각 ${ids[index]} 의 dependsOn 이 없는 조각을 가리킨다: ${id}`);
|
|
112
|
+
if (at === index) throw new Error(`분해기 조각 ${ids[index]} 이 자기 자신 뒤에 올 수 없다`);
|
|
113
|
+
return at;
|
|
114
|
+
}))],
|
|
115
|
+
}));
|
|
116
|
+
return { pieces, source: `decompose:${result.decomposition.outcome}` };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** 선후에 고리가 있으면 던진다 — 고리 속 조각은 영영 발사되지 않는다. */
|
|
120
|
+
export function assertAcyclic(pieces: readonly MissionPieceDraft[]): void {
|
|
121
|
+
const state = new Array<0 | 1 | 2>(pieces.length).fill(0);
|
|
122
|
+
const visit = (at: number): void => {
|
|
123
|
+
if (state[at] === 2) return;
|
|
124
|
+
if (state[at] === 1) throw new Error(`조각 선후에 고리가 있다 (조각 ${at + 1})`);
|
|
125
|
+
state[at] = 1;
|
|
126
|
+
for (const before of pieces[at]!.after) visit(before);
|
|
127
|
+
state[at] = 2;
|
|
128
|
+
};
|
|
129
|
+
pieces.forEach((_piece, at) => visit(at));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function defaultMissionDecompose(text: string): Promise<SelfDevDecomposition> {
|
|
133
|
+
const { decomposeSelfDevGoal } = await import('../self-dev/decompose.js');
|
|
134
|
+
return decomposeSelfDevGoal(text, { observation: { category: 'task-agent', event: 'mission-decompose' } });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function splitMission(text: string, decompose: MissionDecompose = defaultMissionDecompose): Promise<MissionSplit> {
|
|
138
|
+
const split = parseListedMission(text) ?? piecesFromDecomposition(await decompose(text));
|
|
139
|
+
if (split.pieces.length === 0) throw new Error('미션을 조각으로 쪼개지 못했다');
|
|
140
|
+
const empty = split.pieces.findIndex((piece) => !piece.text.trim());
|
|
141
|
+
if (empty >= 0) throw new Error(`조각 ${empty + 1} 의 문면이 비어 있다 (${split.source})`);
|
|
142
|
+
assertAcyclic(split.pieces);
|
|
143
|
+
return split;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* ② 의 자리(순수): 아직 착지하지 않았고 선행이 모두 착지한 조각 id.
|
|
148
|
+
* `landed` 는 착지한 조각 id 집합 — 감지(PR 병합 등)는 이 함수 밖의 몫이다.
|
|
149
|
+
*/
|
|
150
|
+
export function readyPieces(pieces: readonly Pick<TaskCard, 'id' | 'after'>[], landed: ReadonlySet<string>): string[] {
|
|
151
|
+
return pieces.filter((piece) => !landed.has(piece.id) && (piece.after ?? []).every((id) => landed.has(id))).map((piece) => piece.id);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** 모든 조각이 착지했나 — 미션 칸 green 제안의 조건(② 이후에 쓴다). */
|
|
155
|
+
export function missionLanded(mission: Pick<TaskCard, 'pieces'>, landed: ReadonlySet<string>): boolean {
|
|
156
|
+
return (mission.pieces ?? []).length > 0 && (mission.pieces ?? []).every((id) => landed.has(id));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface HandMissionOptions {
|
|
160
|
+
text: string;
|
|
161
|
+
seat?: TaskSeat;
|
|
162
|
+
checklistId?: string;
|
|
163
|
+
live?: boolean;
|
|
164
|
+
statePath?: string;
|
|
165
|
+
launcher?: TaskLauncher;
|
|
166
|
+
decompose?: MissionDecompose;
|
|
167
|
+
now?: () => Date;
|
|
168
|
+
id?: string;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface MissionPieceResult { card: TaskCard; move: TaskCardMove; launched: boolean; error?: string }
|
|
172
|
+
export interface HandMissionResult {
|
|
173
|
+
mission: TaskCard;
|
|
174
|
+
pieces: MissionPieceResult[];
|
|
175
|
+
edges: Array<[string, string]>;
|
|
176
|
+
source: string;
|
|
177
|
+
mode: 'shadow' | 'live';
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function handMission(opts: HandMissionOptions): Promise<HandMissionResult> {
|
|
181
|
+
const text = opts.text.trim();
|
|
182
|
+
if (!text) throw new Error('미션 문면이 비어 있다');
|
|
183
|
+
if (opts.seat !== undefined && !TASK_SEATS.includes(opts.seat)) throw new Error(`자리는 ${TASK_SEATS.join('|')} 중 하나다: ${opts.seat}`);
|
|
184
|
+
const split = await splitMission(text, opts.decompose);
|
|
185
|
+
const now = (opts.now ?? (() => new Date()))();
|
|
186
|
+
const missionId = opts.id ?? `${TASK_CARD_PREFIX}${now.toISOString().slice(0, 10).replaceAll('-', '')}-${randomBytes(3).toString('hex')}`;
|
|
187
|
+
const mode = opts.live ? 'live' : 'shadow';
|
|
188
|
+
const path = opts.statePath ?? taskAgentStatePath();
|
|
189
|
+
const pieceIds = split.pieces.map((_piece, index) => `${missionId}-${index + 1}`);
|
|
190
|
+
const common = { ...(opts.seat ? { seat: opts.seat } : {}), ...(opts.checklistId ? { checklistId: opts.checklistId } : {}) };
|
|
191
|
+
const mission: TaskCard = { id: missionId, text, ...common, createdAt: now.toISOString(), status: 'handed', history: [], pieces: pieceIds, splitSource: split.source };
|
|
192
|
+
const drafts: TaskCard[] = split.pieces.map((piece, index) => ({
|
|
193
|
+
id: pieceIds[index]!, text: piece.text, ...common, createdAt: now.toISOString(), status: 'handed', history: [],
|
|
194
|
+
mission: missionId, after: piece.after.map((at) => pieceIds[at]!),
|
|
195
|
+
}));
|
|
196
|
+
const edges = drafts.flatMap((card) => (card.after ?? []).map((before) => [before, card.id] as [string, string]));
|
|
197
|
+
// 미션 ⊕ 조각을 한 잠금 안에서 먼저 적는다 — 발사 도중 실패해도 선후 기록은 남는다.
|
|
198
|
+
writeTaskCards(path, [mission, ...drafts]);
|
|
199
|
+
try { debug.log('task-agent', 'mission-split', { missionId, pieces: drafts.length, edges: edges.map(([a, b]) => `${a}->${b}`), source: split.source, seat: opts.seat ?? null, checklistId: opts.checklistId ?? null, mode }); } catch { /* fail-soft */ }
|
|
200
|
+
// 방금 만든 조각이다 — 착지한 것이 «없음»을 안다(모름이 아니다).
|
|
201
|
+
const landedNone: ReadonlySet<string> = new Set();
|
|
202
|
+
const ready = new Set(readyPieces(drafts, landedNone));
|
|
203
|
+
const results: MissionPieceResult[] = [];
|
|
204
|
+
for (const draft of drafts) {
|
|
205
|
+
let result: MissionPieceResult;
|
|
206
|
+
if (!ready.has(draft.id)) {
|
|
207
|
+
result = { card: draft, move: nextMoveFor(draft, landedNone), launched: false };
|
|
208
|
+
} else {
|
|
209
|
+
try {
|
|
210
|
+
const handed = await handTask({
|
|
211
|
+
text: draft.text, id: draft.id, mission: missionId, after: draft.after ?? [],
|
|
212
|
+
...(opts.seat ? { seat: opts.seat } : {}), ...(opts.checklistId ? { checklistId: opts.checklistId } : {}),
|
|
213
|
+
live: opts.live === true, statePath: path, ...(opts.launcher ? { launcher: opts.launcher } : {}), ...(opts.now ? { now: opts.now } : {}),
|
|
214
|
+
});
|
|
215
|
+
result = { card: handed.card, move: handed.move, launched: handed.launched };
|
|
216
|
+
} catch (error) {
|
|
217
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
218
|
+
const card = readTaskCard(draft.id, path) ?? draft; // launch-failed 면 handTask 가 이미 적었다
|
|
219
|
+
result = { card, move: nextMoveFor(card, landedNone), launched: false, error: reason };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
results.push(result);
|
|
223
|
+
try { debug.log('task-agent', 'mission-piece', { missionId, pieceId: draft.id, after: draft.after ?? [], ready: ready.has(draft.id), move: result.move.kind, reason: result.move.reason, launched: result.launched, mode, ...(result.error ? { error: result.error } : {}) }); } catch { /* fail-soft */ }
|
|
224
|
+
}
|
|
225
|
+
return { mission, pieces: results, edges, source: split.source, mode };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* ② 착지 근거 — 조각 하나의 PR 상태. `merged` 만 착지다.
|
|
231
|
+
* pending = 열림·못 읽음·원장에 PR 없음 · closed = 병합 없이 닫힘 · no-ref = 카드에 PR 번호도 골 id 도 없다.
|
|
232
|
+
*/
|
|
233
|
+
export type PieceEvidence = 'merged' | 'pending' | 'closed' | 'no-ref';
|
|
234
|
+
|
|
235
|
+
/** ② 의 판정(순수): 근거가 `merged` 인 조각만 착지다 — 모름(근거 없음·no-ref·pending)은 «미착지»(착지를 추측하지 않는다). */
|
|
236
|
+
export function landedPieces(mission: Pick<TaskCard, 'pieces'>, evidence: Readonly<Record<string, PieceEvidence | undefined>>): Set<string> {
|
|
237
|
+
return new Set((mission.pieces ?? []).filter((id) => evidence[id] === 'merged'));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** 조각 근거 하나를 읽는 자리(시험 주입) — 기본은 `predecessorState`(PR 번호 → gh · 골 id → 런 원장 `pr-opened` → gh). */
|
|
241
|
+
export type PieceEvidenceReader = (ref: number | string) => Promise<'merged' | 'waiting' | 'blocked'>;
|
|
242
|
+
|
|
243
|
+
async function defaultEvidenceReader(ref: number | string): Promise<'merged' | 'waiting' | 'blocked'> {
|
|
244
|
+
const { predecessorState } = await import('../pod-lease/dependency-state.js');
|
|
245
|
+
return predecessorState(ref);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** 조각 카드마다 근거를 모은다 — PR 번호가 있으면 그것, 없으면 골 id. 읽기 실패는 pending(미착지). */
|
|
249
|
+
export async function collectPieceEvidence(pieces: readonly TaskCard[], read: PieceEvidenceReader = defaultEvidenceReader): Promise<Record<string, PieceEvidence>> {
|
|
250
|
+
const out: Record<string, PieceEvidence> = {};
|
|
251
|
+
for (const piece of pieces) {
|
|
252
|
+
const ref = piece.pr ?? piece.goalId;
|
|
253
|
+
if (ref === undefined) { out[piece.id] = 'no-ref'; continue; }
|
|
254
|
+
let state: 'merged' | 'waiting' | 'blocked';
|
|
255
|
+
try { state = await read(ref); } catch { state = 'waiting'; }
|
|
256
|
+
out[piece.id] = state === 'merged' ? 'merged' : state === 'blocked' ? 'closed' : 'pending';
|
|
257
|
+
}
|
|
258
|
+
return out;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const GOAL_ID = /^[a-f0-9]{16}$/;
|
|
262
|
+
|
|
263
|
+
/** `--pr <조각>=<N>` · `--goal <조각>=<골id>` — 조각 카드에 착지 근거를 잠금 안에서 적는다(미션 조각만 · 근거는 하나 · 새 것이 교체). */
|
|
264
|
+
export function recordPieceRef(pieceId: string, ref: { pr?: number; goalId?: string }, statePath = taskAgentStatePath(), expectedMission?: string): TaskCard {
|
|
265
|
+
if ((ref.pr === undefined) === (ref.goalId === undefined)) throw new Error('근거는 PR 번호 «또는» 골 id 하나다');
|
|
266
|
+
if (ref.pr !== undefined && !(Number.isSafeInteger(ref.pr) && ref.pr > 0)) throw new Error(`PR 번호가 아니다: ${ref.pr}`);
|
|
267
|
+
if (ref.goalId !== undefined && !GOAL_ID.test(ref.goalId)) throw new Error(`골 id 는 16자 hex 다: ${ref.goalId}`);
|
|
268
|
+
const refused: { reason?: string } = {};
|
|
269
|
+
const card = updateTaskCard(statePath, pieceId, (current, tasks) => {
|
|
270
|
+
if (!current?.mission) { refused.reason = `미션 조각 카드가 아니다: ${pieceId}`; return undefined; }
|
|
271
|
+
// 미션을 지정했으면 그 미션(실재하는 미션 카드)의 조각에만 적는다 — 미션 A 호출이 미션 B 근거를 바꾸지 않게.
|
|
272
|
+
if (expectedMission !== undefined && (current.mission !== expectedMission || !tasks[expectedMission]?.pieces?.includes(pieceId))) {
|
|
273
|
+
refused.reason = `조각 ${pieceId} 은 미션 ${expectedMission} 의 조각이 아니다`; return undefined;
|
|
274
|
+
}
|
|
275
|
+
// green 제안 뒤 근거를 바꾸면 제안이 바뀐 근거와 어긋난다 — 제안된 미션의 조각 근거는 잠근다.
|
|
276
|
+
if (tasks[current.mission]?.greenProposal) { refused.reason = `미션 ${current.mission} 은 이미 green 제안됐다 — 근거를 바꾸지 않는다`; return undefined; }
|
|
277
|
+
// 근거는 하나만 — 새 근거가 옛 근거를 «교체»한다(정정한 골 id 뒤에 옛 PR 이 남아 우선되지 않게).
|
|
278
|
+
const { pr: _oldPr, goalId: _oldGoal, ...rest } = current;
|
|
279
|
+
return { ...rest, ...(ref.pr !== undefined ? { pr: ref.pr } : { goalId: ref.goalId! }) };
|
|
280
|
+
});
|
|
281
|
+
if (refused.reason || !card) throw new Error(refused.reason ?? `미션 조각 카드가 아니다: ${pieceId}`);
|
|
282
|
+
try { debug.log('task-agent', 'mission-piece-ref', { pieceId, missionId: card.mission ?? null, pr: ref.pr ?? null, goalId: ref.goalId ?? null }); } catch { /* fail-soft */ }
|
|
283
|
+
return card;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** 이미 넘긴 조각인가 — live 는 live 넘김·발사·발사 실패면 다시 안 넘긴다 · shadow 는 어떤 넘김이든 있으면 안 넘긴다. */
|
|
287
|
+
function alreadyHanded(card: TaskCard, mode: 'shadow' | 'live'): boolean {
|
|
288
|
+
if (card.status === 'launched' || card.status === 'launch-failed') return true;
|
|
289
|
+
if (!card.handed) return false;
|
|
290
|
+
return mode === 'shadow' || card.handed.mode === 'live';
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export interface AdvanceMissionOptions {
|
|
294
|
+
live?: boolean;
|
|
295
|
+
statePath?: string;
|
|
296
|
+
launcher?: TaskLauncher;
|
|
297
|
+
readEvidence?: PieceEvidenceReader;
|
|
298
|
+
now?: () => Date;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export interface AdvancedPiece { pieceId: string; launched: boolean; move: TaskCardMove['kind']; error?: string }
|
|
302
|
+
export interface AdvanceMissionResult {
|
|
303
|
+
missionId: string;
|
|
304
|
+
mode: 'shadow' | 'live';
|
|
305
|
+
evidence: Record<string, PieceEvidence>;
|
|
306
|
+
landed: string[];
|
|
307
|
+
ready: string[];
|
|
308
|
+
handed: AdvancedPiece[];
|
|
309
|
+
/** 준비됐지만 이미 넘겨서 건너뛴 조각(선행 없는 첫 조각 포함). */
|
|
310
|
+
skipped: string[];
|
|
311
|
+
green: 'proposed' | 'already-proposed' | 'not-yet';
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* ② 한 걸음: 근거 → 착지 → 준비된 조각 → 새로 준비된 조각만 `handTask` 로 한 번 → 모두 착지면 green 제안.
|
|
316
|
+
* 넘김은 같은 상태 파일 잠금 안에서 «먼저» 표지를 적고(claim) 넘긴다 — 동시에 두 번 불러도 한 번만 넘긴다.
|
|
317
|
+
* 선행이 없는 조각은 `tasks hand --mission` 이 이미 넘겼다 — ② 는 다시 넘기지 않는다.
|
|
318
|
+
*/
|
|
319
|
+
export async function advanceMission(missionId: string, opts: AdvanceMissionOptions = {}): Promise<AdvanceMissionResult> {
|
|
320
|
+
const started = Date.now();
|
|
321
|
+
try {
|
|
322
|
+
const result = await advanceMissionOnce(missionId, opts);
|
|
323
|
+
try { debug.log('task-agent', 'mission-advance', { missionId, mode: result.mode, landed: result.landed, ready: result.ready, handed: result.handed.map((piece) => piece.pieceId), skipped: result.skipped, green: result.green, evidence: result.evidence, ms: Date.now() - started }); } catch { /* fail-soft */ }
|
|
324
|
+
return result;
|
|
325
|
+
} catch (error) {
|
|
326
|
+
// 실패한 호출도 한 줄 — 관측이 «호출마다» 남는다.
|
|
327
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
328
|
+
try { debug.log('task-agent', 'mission-advance', { missionId, mode: opts.live ? 'live' : 'shadow', error: reason, ms: Date.now() - started }); } catch { /* fail-soft */ }
|
|
329
|
+
throw new MissionAdvanceError(reason);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** `advanceMission` 이 실패했다 — `mission-advance` 관측은 이미 남겼다. */
|
|
334
|
+
export class MissionAdvanceError extends Error {
|
|
335
|
+
constructor(reason: string) {
|
|
336
|
+
super(reason);
|
|
337
|
+
this.name = 'MissionAdvanceError';
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function advanceMissionOnce(missionId: string, opts: AdvanceMissionOptions): Promise<AdvanceMissionResult> {
|
|
342
|
+
const path = opts.statePath ?? taskAgentStatePath();
|
|
343
|
+
const mode = opts.live ? 'live' : 'shadow';
|
|
344
|
+
const clock = opts.now ?? (() => new Date());
|
|
345
|
+
// live 인데 발사기가 없으면 claim 을 적기 «전»에 멈춘다 — live 표지가 발사 없이 남아 영영 안 넘겨지지 않게.
|
|
346
|
+
if (mode === 'live' && !opts.launcher) throw new Error('--live 인데 launcher 가 없다');
|
|
347
|
+
const all = readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(path).tasks ?? {};
|
|
348
|
+
const mission = all[missionId];
|
|
349
|
+
if (!mission?.pieces) throw new Error(`미션 카드가 아니다: ${missionId}`);
|
|
350
|
+
const missing = mission.pieces.filter((id) => !all[id]);
|
|
351
|
+
if (missing.length) throw new Error(`조각 카드 없음: ${missing.join(', ')} — 선후 기록이 불완전하다`);
|
|
352
|
+
const pieces = mission.pieces.map((id) => all[id]!);
|
|
353
|
+
if (mission.greenProposal) {
|
|
354
|
+
// 제안이 끝난 미션은 다시 조회하지도 넘기지도 않는다 — 재조회 실패(pending)가 착지한 조각을 재발사하게 하지 않는다.
|
|
355
|
+
return { missionId, mode, evidence: {}, landed: [], ready: [], handed: [], skipped: [], green: 'already-proposed' };
|
|
356
|
+
}
|
|
357
|
+
const evidence = await collectPieceEvidence(pieces, opts.readEvidence);
|
|
358
|
+
const landed = landedPieces(mission, evidence);
|
|
359
|
+
const ready = readyPieces(pieces, landed);
|
|
360
|
+
const handed: AdvancedPiece[] = [];
|
|
361
|
+
const skipped: string[] = [];
|
|
362
|
+
for (const pieceId of ready) {
|
|
363
|
+
const card = all[pieceId]!;
|
|
364
|
+
// 자기 PR·골 근거가 이미 있는 조각은 런이 있었다 — 재조회가 pending 이어도 다시 넘기지 않는다.
|
|
365
|
+
if (!(card.after ?? []).length || alreadyHanded(card, mode) || card.pr !== undefined || card.goalId !== undefined) { skipped.push(pieceId); continue; }
|
|
366
|
+
const mark: NonNullable<TaskCard['handed']> = { at: clock().toISOString(), mode, claim: randomBytes(6).toString('hex') };
|
|
367
|
+
const claim: { card?: TaskCard } = {};
|
|
368
|
+
updateTaskCard(path, pieceId, (current) => {
|
|
369
|
+
if (!current || alreadyHanded(current, mode) || current.pr !== undefined || current.goalId !== undefined) return undefined;
|
|
370
|
+
claim.card = { ...current, handed: mark };
|
|
371
|
+
return claim.card;
|
|
372
|
+
});
|
|
373
|
+
const claimed = claim.card;
|
|
374
|
+
if (!claimed) { skipped.push(pieceId); continue; }
|
|
375
|
+
let result: AdvancedPiece;
|
|
376
|
+
try {
|
|
377
|
+
const out = await handTask({
|
|
378
|
+
text: claimed.text, id: pieceId, mission: missionId, after: claimed.after ?? [], landed,
|
|
379
|
+
...(claimed.seat ? { seat: claimed.seat } : {}), ...(claimed.checklistId ? { checklistId: claimed.checklistId } : {}),
|
|
380
|
+
cardFields: { handed: mark, ...(claimed.pr !== undefined ? { pr: claimed.pr } : {}), ...(claimed.goalId !== undefined ? { goalId: claimed.goalId } : {}) },
|
|
381
|
+
live: mode === 'live', statePath: path, ...(opts.launcher ? { launcher: opts.launcher } : {}), ...(opts.now ? { now: opts.now } : {}),
|
|
382
|
+
// claim 을 가진 동안만 쓴다 — 늦게 끝난 shadow 넘김이 live 표지·발사 기록을 덮지 않게.
|
|
383
|
+
writeGuard: (current) => current?.handed?.claim === mark.claim,
|
|
384
|
+
});
|
|
385
|
+
result = { pieceId, launched: out.launched, move: out.move.kind };
|
|
386
|
+
} catch (error) {
|
|
387
|
+
if (error instanceof TaskCardSupersededError) { skipped.push(pieceId); continue; }
|
|
388
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
389
|
+
// 발사 실패면 handTask 가 launch-failed 로 적었다 — 표지에 사유를 덧붙여 다시 넘기지 않는다(판단부 몫).
|
|
390
|
+
updateTaskCard(path, pieceId, (current) => current && current.handed?.claim === mark.claim ? { ...current, handed: { ...mark, error: reason } } : undefined);
|
|
391
|
+
result = { pieceId, launched: false, move: 'launch', error: reason };
|
|
392
|
+
}
|
|
393
|
+
handed.push(result);
|
|
394
|
+
try { debug.log('task-agent', 'mission-piece-handed', { missionId, pieceId, after: claimed.after ?? [], mode, launched: result.launched, ...(result.error ? { error: result.error } : {}) }); } catch { /* fail-soft */ }
|
|
395
|
+
}
|
|
396
|
+
let green: AdvanceMissionResult['green'] = 'not-yet';
|
|
397
|
+
if (missionLanded(mission, landed)) {
|
|
398
|
+
const proposal = {
|
|
399
|
+
at: clock().toISOString(),
|
|
400
|
+
checklistId: mission.checklistId ?? null,
|
|
401
|
+
evidence: Object.fromEntries(pieces.map((piece) => [piece.id, piece.pr !== undefined ? `#${piece.pr}` : `goal:${piece.goalId}`])),
|
|
402
|
+
};
|
|
403
|
+
const wrote = { proposed: false, stale: false };
|
|
404
|
+
updateTaskCard(path, missionId, (current, tasks) => {
|
|
405
|
+
if (!current || current.greenProposal) return undefined;
|
|
406
|
+
// 조회에 쓴 근거가 잠금 안에서도 그대로일 때만 — 조회 도중 근거가 바뀌었으면 다음 호출이 다시 본다.
|
|
407
|
+
if (pieces.some((piece) => tasks[piece.id]?.pr !== piece.pr || tasks[piece.id]?.goalId !== piece.goalId)) { wrote.stale = true; return undefined; }
|
|
408
|
+
wrote.proposed = true;
|
|
409
|
+
return { ...current, greenProposal: proposal };
|
|
410
|
+
});
|
|
411
|
+
green = wrote.proposed ? 'proposed' : wrote.stale ? 'not-yet' : 'already-proposed';
|
|
412
|
+
if (wrote.proposed) {
|
|
413
|
+
try { debug.log('task-agent', 'mission-green-proposed', { missionId, checklist: mission.checklistId ?? null, evidence: proposal.evidence }); } catch { /* fail-soft */ }
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return { missionId, mode, evidence, landed: [...landed], ready, handed, skipped, green };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** `--all` 의 대상 — green 제안이 아직 없는 미션 카드 id. */
|
|
420
|
+
export function openMissionIds(statePath = taskAgentStatePath()): string[] {
|
|
421
|
+
const all = readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(statePath).tasks ?? {};
|
|
422
|
+
return Object.values(all).filter((card) => card.pieces && !card.greenProposal).map((card) => card.id).sort();
|
|
423
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TASK-AGENT-SHADOW — 슈퍼바이저가 런의 멈춤을 확정한 자리에서 판단부를 «그림자»로 부른다.
|
|
3
|
+
*
|
|
4
|
+
* RFC-task-agent-any-task-to-completion-2026-10-06 §A9 «판단부 호출부 0»을 메운다.
|
|
5
|
+
* - 입력은 슈퍼바이저 결과에 «이미 실려 있는» 값만 쓴다(네트워크 호출 0): 종료 어휘 ·
|
|
6
|
+
* PR 번호/병합 여부(`prNumber`·`merged`). 리뷰 판정·must-fix 는 결과에 없으므로 비운다
|
|
7
|
+
* («통과»로 읽지 않는다 — 판단부가 review 수를 고른다).
|
|
8
|
+
* - 실행부는 언제나 shadow 로 부른다: «했을 수»만 기록하고 명령은 하나도 돌리지 않는다.
|
|
9
|
+
* 설정 `taskAgent.mode=live` 도 이 경로에는 먹지 않는다(실행은 `autopilot task-agent-action` 몫).
|
|
10
|
+
* - 관측: `debug.log('task-agent', 'shadow-move', …)` → `elanous logs --category task-agent`.
|
|
11
|
+
*/
|
|
12
|
+
import { debug } from '../debug/log.js';
|
|
13
|
+
import type { SupervisorJobResult, SupervisorStopReason } from '../self-dev/run-supervisor.js';
|
|
14
|
+
import { executeNextAction, type NextAction } from './actions.js';
|
|
15
|
+
import { judgeNextMove, type TaskJudgeInput, type TaskJudgement } from './judge.js';
|
|
16
|
+
|
|
17
|
+
export interface TaskAgentShadowInput {
|
|
18
|
+
runId: string | null;
|
|
19
|
+
stopReason: SupervisorStopReason;
|
|
20
|
+
results: readonly SupervisorJobResult[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TaskAgentShadowMove {
|
|
24
|
+
runId: string | null;
|
|
25
|
+
stopReason: SupervisorStopReason;
|
|
26
|
+
move: TaskJudgement['move'];
|
|
27
|
+
executorKind: TaskJudgement['executorKind'] | null;
|
|
28
|
+
variant: TaskJudgement['executorVariant'] | null;
|
|
29
|
+
reason: string;
|
|
30
|
+
/** 실행부 결과 — shadow 경로에서는 늘 'shadow'(실행부 밖의 수면 null). */
|
|
31
|
+
executorResult: string | null;
|
|
32
|
+
/** 실행부가 남긴 «했을 수» 문면. */
|
|
33
|
+
wouldDo: string | null;
|
|
34
|
+
pr: number | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface TaskAgentShadowDeps {
|
|
38
|
+
log?: (category: string, event: string, data: Record<string, unknown>) => void;
|
|
39
|
+
/** 시험용 — shadow 실행부가 명령을 부르면 여기로 온다(정상이면 0회). */
|
|
40
|
+
command?: (args: string[]) => Promise<{ status: number; stdout: string; stderr?: string }>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 슈퍼바이저 결과에서 판단부 입력을 만든다 — 추가 조회 없이 이미 실린 값만. */
|
|
44
|
+
export function shadowJudgeInput(stopReason: SupervisorStopReason, results: readonly SupervisorJobResult[]): TaskJudgeInput {
|
|
45
|
+
const withPr = results.filter((result) => typeof result.prNumber === 'number' && result.prNumber > 0);
|
|
46
|
+
const input: TaskJudgeInput = { stopReason };
|
|
47
|
+
if (withPr.length > 0) {
|
|
48
|
+
input.pr = withPr[0]!.prNumber!;
|
|
49
|
+
// 병합 여부만 결과에 실린다 — 닫힘(CLOSED)은 여기서 모른다. 병합이 아니면 OPEN 으로 둔다(판단부는 MERGED 만 본다).
|
|
50
|
+
input.prState = withPr.every((result) => result.merged === true) ? 'MERGED' : 'OPEN';
|
|
51
|
+
}
|
|
52
|
+
return input;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function recordTaskAgentShadowMove(input: TaskAgentShadowInput, deps: TaskAgentShadowDeps = {}): Promise<TaskAgentShadowMove> {
|
|
56
|
+
const judgeInput = shadowJudgeInput(input.stopReason, input.results);
|
|
57
|
+
const judgement = judgeNextMove(judgeInput);
|
|
58
|
+
let executorResult: string | null = null;
|
|
59
|
+
let wouldDo: string | null = null;
|
|
60
|
+
if (judgement.executorKind) {
|
|
61
|
+
const action: NextAction = {
|
|
62
|
+
kind: judgement.executorKind,
|
|
63
|
+
...(judgement.executorVariant ? { variant: judgement.executorVariant } : {}),
|
|
64
|
+
taskId: input.runId ?? 'run-unknown',
|
|
65
|
+
rationale: judgement.reason,
|
|
66
|
+
pr: typeof judgeInput.pr === 'number' ? judgeInput.pr : 0,
|
|
67
|
+
runId: input.runId ?? '',
|
|
68
|
+
original: [...new Set(input.results.map((result) => result.feature))].join(' · '),
|
|
69
|
+
checklistId: '',
|
|
70
|
+
};
|
|
71
|
+
executorResult = await executeNextAction(action, {
|
|
72
|
+
mode: 'shadow',
|
|
73
|
+
command: deps.command ?? (async () => { throw new Error('task-agent shadow path must not run commands'); }),
|
|
74
|
+
observe: (_event, data) => { wouldDo = data.reason; },
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const move: TaskAgentShadowMove = {
|
|
78
|
+
runId: input.runId,
|
|
79
|
+
stopReason: input.stopReason,
|
|
80
|
+
move: judgement.move,
|
|
81
|
+
executorKind: judgement.executorKind ?? null,
|
|
82
|
+
variant: judgement.executorVariant ?? null,
|
|
83
|
+
reason: judgement.reason,
|
|
84
|
+
executorResult,
|
|
85
|
+
wouldDo,
|
|
86
|
+
pr: typeof judgeInput.pr === 'number' ? judgeInput.pr : null,
|
|
87
|
+
};
|
|
88
|
+
(deps.log ?? ((category, event, data) => debug.log(category, event, data)))('task-agent', 'shadow-move', { ...move });
|
|
89
|
+
return move;
|
|
90
|
+
}
|