elanous 0.2.18 → 0.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/{1307-30dff22c9e1f3073.js → 1307-77fad80cf54477c6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2489-ff55a35b202c0f94.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7748-6e51bfe927019f5c.js +25 -0
- package/apps/pwa/out/_next/static/chunks/8401-35e94e9091f5eb8d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +1 -0
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-33c0689b70ed8cf3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/{page-d92f7ee3994e84c7.js → page-c855c24de7db74fe.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-4733c967b56a4277.js → page-85b316bb4235980b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-d22fd849cc22f104.js → page-9b2b26a33cb2cd42.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-bb742f995c6769e7.js → page-c9955fde67b1bcb5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-02b073a2ac793b83.js → page-7a852956c74015a3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-8a9b3594bc23289b.js → page-4ba4abe096b6c2a8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-575d4a8b4f7becb2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-308111abadf35670.js → page-f5d56ff5f45ad164.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-2260e9eef0357421.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-5d97b2901e0d1ea6.js → page-7c058069c5e7fa63.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-8c3d8e63ae2e1c98.js → page-466bf0ee16ad1432.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-9b72a07d424701c6.js → page-a23bc71713bcc08f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ed4a6f799f005d09.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-a572f58cd3ca5864.js → page-9cd565005b93aca6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/{page-be5ea965a07fbc41.js → page-474cb5888ea0e8f3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-35bfec2e631d3eae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-fa244bd146a00ae8.js → page-9ca21f9ad3208a61.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-44d56d6391965a6a.js → page-f9ccb1b1adbea14d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-83a700d4c754d802.js → page-ec102c9b8b691e37.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-7b3170f374f77b9d.js → not-found-a9c2cfa8dda922a5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-f6055495e9ed7ea8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-1467047104141297.js → page-db03ae3589a81c9e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4a6463b97f0ad94f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-102310930791197a.js → page-75bffecc9aa1004e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-6ed0b73bae8351d8.js → page-31d5b875760bb62d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c26fa10aac6c21c4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-43336055451f6e37.js → page-68793c6e10331d66.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/{page-3cc47dca0922ab9a.js → page-46d5201d2207a511.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-a77ee355a2304ff0.js → page-d81c07a7b4f618ae.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-b17cd46cf0e9b8cf.js → page-be1c4e8697f22360.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-cf0a4b4abead71ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-7bf54c9bb9274af8.js → page-0fd895fab9b62c1b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-c6e4364d9de8f717.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-0acc8e198f7b0dfb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-134da468538baf48.js → page-34882c84f84021c5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-c2782b330447ffb6.js → webpack-c260571513bf43c2.js} +1 -1
- package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
- package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +1 -0
- package/apps/pwa/out/_next/static/css/d3427996da11e480.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 -1
- package/apps/pwa/out/decisions/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +16 -15
- 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 +16 -15
- 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 +24 -23
- 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/demo/editor-recipes.yaml +22 -0
- package/graphs/ops/guardian.yaml +26 -0
- package/graphs/ops/recipes.yaml +3 -0
- package/graphs/retro/daily.yaml +25 -0
- package/graphs/retro/recipes.yaml +24 -0
- package/package.json +1 -1
- package/scripts/ask-marker-check.ts +3 -1
- package/scripts/coord-channel-watch.sh +2 -2
- package/scripts/discord-seat-probe.ts +3 -1
- package/scripts/land-global-checks.ts +89 -0
- package/scripts/lib/pch-regress-map.ts +24 -0
- package/scripts/morning-report.ts +18 -4
- package/scripts/pch-regress.ts +88 -0
- package/scripts/release-loop/gate-diff.ts +18 -0
- package/scripts/release-loop/gate-node.ts +628 -65
- package/scripts/release-loop/gate-partial.ts +106 -0
- package/scripts/release-loop/gate-timing.ts +229 -0
- package/scripts/release-loop/preflight.ts +6 -4
- package/scripts/release-loop/publish-window.ts +141 -0
- package/scripts/release-loop/release-docs.ts +1 -4
- package/scripts/release-loop/release-universe.ts +38 -0
- package/scripts/release-loop/resume-release.ts +67 -5
- package/scripts/release-loop/shard-plan.ts +65 -0
- package/scripts/release-loop/unattended-release.ts +12 -2
- package/scripts/release-story/draft.ts +5 -1
- package/scripts/retro/shadow-compare.ts +71 -0
- package/scripts/rhythm/daily.ts +6 -1
- package/scripts/video/scene-table.ts +159 -0
- package/src/acp/core-turn-bridge.ts +39 -2
- package/src/agent-mission/driver.ts +34 -6
- package/src/autopilot/build/llm-conflict-merge.ts +132 -7
- package/src/autopilot/build/merge-intent.ts +135 -7
- package/src/away/presence.ts +40 -0
- package/src/away/release-watch.ts +272 -0
- package/src/chat/index.ts +2 -1
- package/src/chat/short-question.ts +3 -40
- package/src/cli/away-cli.ts +68 -0
- package/src/cli/config-drift.ts +513 -0
- package/src/cli/guardian-cli.ts +49 -0
- package/src/cli/intake-cli.ts +13 -7
- package/src/cli/log-event-names.ts +5 -0
- package/src/cli/pod-cli.ts +5 -1
- package/src/cli/pr-cli.ts +20 -0
- package/src/cli/pty-drive-cli.ts +14 -3
- package/src/cli/release-cli.ts +52 -12
- package/src/cli/retro-cli.ts +78 -0
- package/src/cli/setup-cli.ts +31 -1
- package/src/cli/tasks-cli.ts +193 -14
- package/src/context-bus/context-now-public.ts +1 -1
- package/src/context-bus/context-now-surfaces.ts +16 -6
- package/src/context-bus/context-now.ts +97 -3
- package/src/core-turn/short-question.ts +100 -0
- package/src/dashboard/dashboard-status-lines.ts +24 -4
- package/src/dashboard/index.ts +50 -5
- package/src/dashboard/input/slash-executor.ts +6 -2
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +68 -40
- package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +62 -23
- package/src/dashboard/slash-runtime/help-groups.ts +44 -0
- package/src/dashboard/slash-runtime/model-choices.ts +125 -0
- package/src/domains/guardian-tick.ts +138 -0
- package/src/domains/outbound-alert.ts +44 -13
- package/src/domains/outbound-log-probe.ts +6 -1
- package/src/domains/schedule-registry.ts +23 -0
- package/src/domains/telegram-kind-route.ts +39 -0
- package/src/domains/watch-default-scheduler.ts +95 -0
- package/src/esc-abort-gate.ts +44 -0
- package/src/graph-runner/graph-run-api.ts +205 -0
- package/src/graph-runner/runner.ts +36 -6
- package/src/harness/harness-cli-command.ts +420 -51
- package/src/harness/harness-queue.ts +315 -25
- package/src/harness/harness-salvage-cli.ts +140 -0
- package/src/harness/harness-worktree-add.ts +68 -0
- package/src/harness/ledger-write-guard.ts +6 -16
- package/src/hq/fence-outcomes.ts +63 -0
- package/src/hq/fence-wrapper.ts +33 -0
- package/src/hq/hq.ts +17 -2
- package/src/index.ts +118 -6
- package/src/instance/test-write-guard-spy-first.fixture.ts +13 -0
- package/src/instance/test-write-guard.ts +200 -0
- package/src/intake-plane/digest.ts +39 -15
- package/src/intake-plane/harness-target.ts +4 -3
- package/src/intake-plane/intake-sources.ts +1 -0
- package/src/intake-plane/items.ts +1 -0
- package/src/loops/orchestrator/degradation.ts +57 -0
- package/src/loops/orchestrator/finish-rate.ts +33 -3
- package/src/loops/orchestrator/tick.ts +166 -45
- package/src/maturity/feature-maturity.ts +6 -4
- package/src/maturity/tui-slash-maturity.ts +1 -0
- package/src/mss/logging/log-store.ts +3 -0
- package/src/nexus/api/graph-kinds.ts +10 -4
- package/src/nexus/api/graphs-api.ts +178 -6
- package/src/nexus/api/http-server.ts +54 -8
- package/src/nexus/api/loop-edges.ts +292 -17
- package/src/nexus/api/ops-api.ts +112 -29
- package/src/nexus/api/ops-board-route.ts +48 -0
- package/src/nexus/index.ts +7 -0
- package/src/onboarding/entry-hints.ts +5 -0
- package/src/onboarding.ts +24 -6
- package/src/release-loop/checklist.ts +2 -0
- package/src/release-loop/feature-store.ts +68 -18
- package/src/release-loop/gate-shards.ts +158 -0
- package/src/release-loop/light-rc.ts +173 -0
- package/src/release-loop/placement.ts +51 -17
- package/src/release-loop/release-schedule.ts +6 -2
- package/src/release-loop/rubric.ts +57 -1
- package/src/retro/daily.ts +372 -0
- package/src/seat-loop/op-decide.ts +173 -0
- package/src/seat-loop/seat-loop.ts +60 -3
- package/src/seat-loop/seat-report.ts +58 -3
- package/src/self-dev/ask-launch-flow.ts +5 -4
- package/src/self-dev/ask-launch-io.ts +49 -1
- package/src/self-dev/ask-path-candidates.ts +40 -2
- package/src/self-dev/dev-cli.ts +36 -0
- package/src/self-dev/draft-sweep.ts +371 -16
- package/src/self-dev/draft-triage-rules.ts +19 -4
- package/src/self-dev/orchestrate.ts +1 -0
- package/src/self-dev/pr-preview.ts +247 -0
- package/src/self-dev/shard-run.ts +23 -7
- package/src/self-implement/blocked-draft-policy.ts +101 -0
- package/src/self-implement/execution-clarification.ts +11 -0
- package/src/self-implement/gate-cli.ts +29 -8
- package/src/self-implement/gate-env-retry.ts +83 -0
- package/src/self-implement/goal-author-clarification.ts +88 -13
- package/src/self-implement/goal-clarification-escalation.ts +2 -0
- package/src/self-implement/orchestrator.ts +220 -6
- package/src/self-implement/run-stop.ts +123 -0
- package/src/self-implement/run-ttl.ts +258 -0
- package/src/self-implement/salvage-branches.ts +254 -0
- package/src/self-implement/salvage-retention.ts +123 -0
- package/src/self-implement/seams.ts +4 -0
- package/src/self-implement/self-implement-cli.ts +2 -0
- package/src/self-implement/sibling-resync.ts +223 -0
- package/src/self-implement/stop-autoheal.ts +204 -0
- package/src/task-agent/actions.ts +63 -2
- package/src/task-agent/board.ts +132 -0
- package/src/task-agent/card-evidence.ts +292 -0
- package/src/task-agent/completion-evidence.ts +126 -0
- package/src/task-agent/judge.ts +25 -0
- package/src/task-agent/live-moves.ts +276 -0
- package/src/task-agent/mission.ts +33 -7
- package/src/task-agent/shadow.ts +192 -15
- package/src/task-agent/task-hand.ts +124 -17
- package/src/task-orchestrator/surfaces/pod-command-job.ts +82 -8
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +4 -0
- package/src/task-orchestrator/surfaces/pod-install-slots.ts +89 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +233 -21
- package/src/task-orchestrator/surfaces/pod-pool.ts +155 -34
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +21 -5
- package/src/telegram-away-command.ts +38 -0
- package/src/telegram-commands.ts +52 -0
- package/src/telegram-report-log-probe.ts +21 -0
- package/src/telegram-report.ts +27 -3
- package/src/testing/real-cli-spawn.ts +106 -0
- package/src/tui-client/dashboard-session.ts +1 -0
- package/src/user-config.ts +190 -5
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
- package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-a0eb02638b1e6f31.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c39554c321576982.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-c130b63862437814.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-a3c58f5c72a5f3f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-e8e1ecaea7cd71d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-44d5f767491692bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-61553851aa606146.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-a1f585e154e61c59.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-5150ef17b5383a57.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +0 -1
- package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +0 -1
- /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → Bam1YziK2iH9bN4K0utGk}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → Bam1YziK2iH9bN4K0utGk}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Child fixture for test-write-guard.test.ts: a bare spy installed BEFORE outbound-alert is imported must not
|
|
2
|
+
// make the captured «real» curl look like a fake. Prints {refused, curlCalls}.
|
|
3
|
+
import { spyOn } from 'bun:test';
|
|
4
|
+
import * as childProcess from 'node:child_process';
|
|
5
|
+
|
|
6
|
+
const bare = spyOn(childProcess, 'execFileSync');
|
|
7
|
+
const { sendTelegramDirect } = await import('../domains/outbound-alert.js');
|
|
8
|
+
const { getUserConfig } = await import('../user-config.js');
|
|
9
|
+
const base = getUserConfig();
|
|
10
|
+
const config = { ...base, telegram: { ...base.telegram, channels: undefined, botToken: '2:fake', homeChannel: 2 } } as typeof base;
|
|
11
|
+
let refused = '';
|
|
12
|
+
try { sendTelegramDirect('private-alert-body', 'ops-alert', { config }); } catch (e) { refused = e instanceof Error ? e.name : String(e); }
|
|
13
|
+
console.log(JSON.stringify({ refused, curlCalls: bare.mock.calls.length }));
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// Last line of defense: a test process never writes ops state or sends real alerts, even without the test preload.
|
|
2
|
+
// 10-07 incident: the public-export tree ran `bun test` without bunfig.toml (#24629), so tests wrote the ops deferred
|
|
3
|
+
// queue, ops logs.db and sent real Telegram messages. This guard does not depend on the preload or on HOME.
|
|
4
|
+
import { lstatSync, readFileSync, readlinkSync } from 'node:fs';
|
|
5
|
+
import { homedir, userInfo } from 'node:os';
|
|
6
|
+
import { delimiter, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
7
|
+
import { debug } from '../debug/log.js';
|
|
8
|
+
|
|
9
|
+
export class TestOpsWriteRefusedError extends Error {
|
|
10
|
+
constructor(what: string, path: string, signal: string) {
|
|
11
|
+
super(`[test-guard] refused: a test process (${signal}) tried to ${what} in the operational root (${path}). `
|
|
12
|
+
+ 'Point the test at an isolated root (ELANOUS_STATE_DIR / --config-dir tmp) or inject a fake sender.');
|
|
13
|
+
this.name = 'TestOpsWriteRefusedError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const TEST_FILE = /\.(test|spec)\.[cm]?[jt]sx?$/;
|
|
18
|
+
|
|
19
|
+
/** Which signal marks this process as a test, or null for a real run.
|
|
20
|
+
* Measured under bun 1.4.2: `bun test` sets NODE_ENV=test unless the caller set another value, and `Bun.main` is the
|
|
21
|
+
* running test file (argv carries no «test» word). ELANOUS_TEST_HOME is set by the deterministic runner and inherited
|
|
22
|
+
* by children it spawns. A real CLI/daemon has none of the three. */
|
|
23
|
+
export function testProcessSignal(env: NodeJS.ProcessEnv = process.env, main: string | undefined = bunMain()): string | null {
|
|
24
|
+
if (env.NODE_ENV === 'test') return 'NODE_ENV=test';
|
|
25
|
+
if (env.ELANOUS_TEST_HOME?.trim()) return 'ELANOUS_TEST_HOME';
|
|
26
|
+
if (main && TEST_FILE.test(main)) return 'Bun.main=test-file';
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isTestProcess(): boolean { return testProcessSignal() !== null; }
|
|
31
|
+
|
|
32
|
+
function bunMain(): string | undefined {
|
|
33
|
+
try { return (globalThis as { Bun?: { main?: string } }).Bun?.main; } catch { return undefined; }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Real path of `path` with the kernel's lookup order: components left to right, each symlink (live or dangling)
|
|
37
|
+
* expanded before a later `..` applies — `alias/../x` with `alias → ops/child` is `ops/x`. Missing tails are kept. */
|
|
38
|
+
export function physicalPath(path: string): string {
|
|
39
|
+
const pending = (isAbsolute(path) ? path : `${process.cwd()}/${path}`).split('/').filter(Boolean);
|
|
40
|
+
let current = '/';
|
|
41
|
+
let links = 0;
|
|
42
|
+
while (pending.length) {
|
|
43
|
+
const part = pending.shift()!;
|
|
44
|
+
if (part === '.') continue;
|
|
45
|
+
if (part === '..') { current = dirname(current); continue; }
|
|
46
|
+
const next = current === '/' ? `/${part}` : `${current}/${part}`;
|
|
47
|
+
let target: string | null = null;
|
|
48
|
+
try { if (lstatSync(next).isSymbolicLink()) target = readlinkSync(next); } catch { /* missing: keep the name */ }
|
|
49
|
+
if (target !== null && links++ < 40) {
|
|
50
|
+
pending.unshift(...target.split('/').filter(Boolean));
|
|
51
|
+
if (isAbsolute(target)) current = '/';
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
current = next;
|
|
55
|
+
}
|
|
56
|
+
return current;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type SpawnSync = (cmd: string[]) => { exitCode: number; stdout: { toString(): string } };
|
|
60
|
+
|
|
61
|
+
/** The OS account's home from the account database, not HOME — test runners redirect HOME. Null when unresolvable. */
|
|
62
|
+
function lookupAccountHome(): string | null {
|
|
63
|
+
try {
|
|
64
|
+
const spawnSync = (globalThis as { Bun?: { spawnSync: SpawnSync } }).Bun?.spawnSync;
|
|
65
|
+
const user = userInfo().username;
|
|
66
|
+
const uid = process.getuid?.();
|
|
67
|
+
const valid = (home: string | undefined): string | null => (home && isAbsolute(home) ? home : null);
|
|
68
|
+
if (process.platform === 'darwin' && spawnSync) {
|
|
69
|
+
const r = spawnSync(['/usr/bin/dscl', '.', '-read', `/Users/${user}`, 'NFSHomeDirectory']);
|
|
70
|
+
const home = valid(r.exitCode === 0 ? r.stdout.toString().trim().split(/\s+/).at(-1) : undefined);
|
|
71
|
+
if (home) return home;
|
|
72
|
+
}
|
|
73
|
+
if (process.platform !== 'darwin' && uid !== undefined) {
|
|
74
|
+
try {
|
|
75
|
+
const home = valid(readFileSync('/etc/passwd', 'utf8').split('\n')
|
|
76
|
+
.map((line) => line.split(':')).find((f) => Number(f[2]) === uid)?.[5]);
|
|
77
|
+
if (home) return home;
|
|
78
|
+
} catch { /* try getent */ }
|
|
79
|
+
if (spawnSync) {
|
|
80
|
+
const r = spawnSync(['getent', 'passwd', String(uid)]);
|
|
81
|
+
const home = valid(r.exitCode === 0 ? r.stdout.toString().trim().split(':')[5] : undefined);
|
|
82
|
+
if (home) return home;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch { /* unresolvable */ }
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let accountHomeLookup: () => string | null = lookupAccountHome;
|
|
90
|
+
let opsRootsOverride: string[] | null = null; // added roots, never a replacement
|
|
91
|
+
let cachedOpsRoots: string[] | null = null;
|
|
92
|
+
|
|
93
|
+
/** Tests add «ops-like» roots to prove the refusal; the real account root stays protected. null clears them. */
|
|
94
|
+
export function setOpsRootsForTesting(roots: string[] | null): void { opsRootsOverride = roots ? roots.map(physicalPath) : null; }
|
|
95
|
+
|
|
96
|
+
/** Tests inject a failing account lookup to prove the fallback; null restores the real one. Clears the cache. */
|
|
97
|
+
export function setAccountHomeLookupForTesting(lookup: (() => string | null) | null): void {
|
|
98
|
+
accountHomeLookup = lookup ?? lookupAccountHome;
|
|
99
|
+
cachedOpsRoots = null;
|
|
100
|
+
accountHomeUnresolved = false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** The operational instance root(s) the guard protects — the account's real `~/.elanous`.
|
|
104
|
+
* When the account database cannot be read, fail toward protection: every conventional home of this user plus HOME. */
|
|
105
|
+
export function opsRoots(): string[] {
|
|
106
|
+
// Widening only: injected roots and the env list add to the real account root; nothing can remove it.
|
|
107
|
+
const extra = (process.env[EXTRA_OPS_ROOTS_ENV] ?? '').split(delimiter).filter((p) => isAbsolute(p)).map(physicalPath);
|
|
108
|
+
const added = [...(opsRootsOverride ?? []), ...extra];
|
|
109
|
+
return added.length ? [...new Set([...accountOpsRoots(), ...added])] : accountOpsRoots();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Extra roots the guard protects in this process (path-delimited). Used to prove the guard in a real child entry. */
|
|
113
|
+
export const EXTRA_OPS_ROOTS_ENV = 'ELANOUS_TEST_GUARD_EXTRA_OPS_ROOTS';
|
|
114
|
+
|
|
115
|
+
function accountOpsRoots(): string[] {
|
|
116
|
+
if (cachedOpsRoots) return cachedOpsRoots;
|
|
117
|
+
const home = accountHomeLookup();
|
|
118
|
+
let homes: string[];
|
|
119
|
+
if (home) homes = [home];
|
|
120
|
+
else {
|
|
121
|
+
let user = '';
|
|
122
|
+
try { user = userInfo().username; } catch { /* unknown user */ }
|
|
123
|
+
homes = [...(user ? [`/Users/${user}`, `/home/${user}`] : []), ...(process.getuid?.() === 0 ? ['/root'] : []), homedir()];
|
|
124
|
+
try { debug.log('test-guard', 'account-home-unresolved', { protected: homes.length }); } catch { /* observation only */ }
|
|
125
|
+
}
|
|
126
|
+
accountHomeUnresolved = !home;
|
|
127
|
+
cachedOpsRoots = [...new Set(homes.map((h) => physicalPath(join(h, '.elanous'))))];
|
|
128
|
+
return cachedOpsRoots;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let accountHomeUnresolved = false;
|
|
132
|
+
|
|
133
|
+
export function isInsideOpsRoot(path: string, roots: string[] = opsRoots()): boolean {
|
|
134
|
+
const target = physicalPath(path);
|
|
135
|
+
// Unknown account home (non-standard, unreadable): any `<dir>/.elanous` tree may be the ops root — protect them all.
|
|
136
|
+
// Isolated test roots are temp dirs or `<root>/state`, never a directory named `.elanous` under a home.
|
|
137
|
+
// Both spellings: a `.elanous` that is itself a symlink to an oddly named directory is still that tree.
|
|
138
|
+
if (accountHomeUnresolved && [resolve(path), target].some((p) => p.split(sep).includes('.elanous'))) return true;
|
|
139
|
+
return roots.some((root) => {
|
|
140
|
+
const rel = relative(root, target);
|
|
141
|
+
return rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function refuse(what: string, path: string, signal: string): never {
|
|
146
|
+
try { debug.log('test-guard', 'ops-write-refused', { what, path, signal }); } catch { /* the refusal stands */ }
|
|
147
|
+
throw new TestOpsWriteRefusedError(what, path, signal);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Throw when a test process is about to write `path` inside the operational root. No-op in real runs. */
|
|
151
|
+
export function assertNotTestWritingOps(path: string, what: string): void {
|
|
152
|
+
const signal = testProcessSignal();
|
|
153
|
+
if (!signal) return;
|
|
154
|
+
if (path === ':memory:' || !isInsideOpsRoot(path)) return;
|
|
155
|
+
refuse(what, path, signal);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Throw when a test process is about to perform a network send it must not make.
|
|
159
|
+
* - Routing (config dir · state root · conatus dir · acp-token dir) inside the ops root: always refused, faked or
|
|
160
|
+
* not — a test that resolved the ops universe is the leak itself, whatever its transport does.
|
|
161
|
+
* - `external` (a third-party bot API — Telegram, Discord): refused unless the transport is a test double that
|
|
162
|
+
* replaces the real one (`transportFaked`), whatever root the credentials came from. A double that deliberately
|
|
163
|
+
* calls the original is an explicit choice to send and is out of this guard's reach. */
|
|
164
|
+
export function assertNoRealSendFromTest(what: string, opts: {
|
|
165
|
+
transportFaked: boolean; routingRoots: string[]; external?: boolean;
|
|
166
|
+
}): void {
|
|
167
|
+
const signal = testProcessSignal();
|
|
168
|
+
if (!signal) return;
|
|
169
|
+
const opsRoot = opts.routingRoots.find((root) => root && isInsideOpsRoot(root));
|
|
170
|
+
if (opsRoot) refuse(what, opsRoot, signal);
|
|
171
|
+
if (opts.external && !opts.transportFaked) refuse(what, 'a real third-party bot API', signal);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** True when `fn` is a bun:test/jest double whose behaviour replaces `real`. A bare `spyOn` keeps calling the
|
|
175
|
+
* original (its implementation IS `real`), so it is not a fake. */
|
|
176
|
+
export function isTestDouble(fn: unknown, real?: unknown): boolean {
|
|
177
|
+
if (!isMockFn(fn)) return false;
|
|
178
|
+
const impl = fn.getMockImplementation?.();
|
|
179
|
+
return real === undefined || impl !== unwrapMock(real);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
type MockFn = { mock: object; getMockImplementation?: () => unknown };
|
|
183
|
+
function isMockFn(fn: unknown): fn is MockFn {
|
|
184
|
+
return typeof fn === 'function' && typeof (fn as { mock?: unknown }).mock === 'object' && (fn as { mock?: unknown }).mock !== null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** The function under a chain of spies — a «real» captured after a bare spy was installed is the spy itself.
|
|
188
|
+
* Unwrapping a fake yields the fake, so a capture taken under a fake refuses rather than allows. */
|
|
189
|
+
function unwrapMock(fn: unknown): unknown {
|
|
190
|
+
let current = fn;
|
|
191
|
+
for (let i = 0; i < 8 && isMockFn(current); i++) {
|
|
192
|
+
const impl: unknown = current.getMockImplementation?.();
|
|
193
|
+
if (typeof impl !== 'function' || impl === (current as unknown)) break;
|
|
194
|
+
current = impl;
|
|
195
|
+
}
|
|
196
|
+
return current;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Resolve the account home while no test has mocked anything yet; real runs never pay the lookup.
|
|
200
|
+
try { if (typeof process !== 'undefined' && isTestProcess()) opsRoots(); } catch { /* resolved lazily */ }
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
// 흡수 하루 다이제스트 — 그날 흡수한 것을 사람이 «발견»하는 자리. 노트 절(마크다운) ⊕ 텔레그램 짧은 판.
|
|
2
2
|
// 결정론만: 요약 한 줄은 각 노트가 이미 쓴 「한 줄 결론」을 뽑는다(LLM 을 다시 부르지 않는다).
|
|
3
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
-
import { basename, join } from 'node:path';
|
|
3
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { basename, dirname, join } from 'node:path';
|
|
5
5
|
import { loadIntakeLedger, type IntakeItem } from './items.js';
|
|
6
|
+
import { canonicalSeat } from './intake-sources.js';
|
|
6
7
|
import { readSavedCursorAt } from './collect-telegram-saved.js';
|
|
7
8
|
import { intakeOutboxDir, kstDay } from './route.js';
|
|
8
9
|
|
|
9
10
|
export type DigestLensVerdict = '대체 후보' | '보강' | '경쟁 대조';
|
|
10
11
|
export interface DigestEntry { id: string; sources: string[]; url?: string; note?: string; noteName?: string; oneLiner?: string; axis: string; impact?: { verdict: DigestLensVerdict; why: string; target: string } }
|
|
11
|
-
export interface IntakeDigest { day: string; absorbed: DigestEntry[]; goals: { fact: string; url?: string }[]; news?: { title: string; url: string; summary: string[]; implication: string[] }[]; shadowSuggestions?: { fact: string; url?: string; verdict: string }[]; review?: { fact: string; note?: string }[]; grounding: number; release: number; manual: number; savedSilence?: { days: number; lastNewAt: string } }
|
|
12
|
+
export interface IntakeDigest { day: string; seat?: string; absorbed: DigestEntry[]; goals: { fact: string; url?: string }[]; news?: { title: string; url: string; summary: string[]; implication: string[] }[]; shadowSuggestions?: { fact: string; url?: string; verdict: string }[]; review?: { fact: string; note?: string }[]; grounding: number; release: number; manual: number; savedSilence?: { days: number; lastNewAt: string } }
|
|
12
13
|
|
|
13
14
|
const AXIS_LABEL: Record<string, string> = {
|
|
14
15
|
video_automation: '영상 자동화', agent_basics: '에이전트 기본', agent_applied: '에이전트 응용', unrelated: '그 밖',
|
|
@@ -51,9 +52,27 @@ export function pathOnly(why: string): boolean {
|
|
|
51
52
|
return rest.length < 4;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
/** Seat briefing receipts — `outbox/brief/<seat>.jsonl` rows `{ id, day }`: an item already sent on an earlier day is not «new» again. */
|
|
56
|
+
export function seatBriefFile(root: string, seat: string): string { return join(intakeOutboxDir(root), 'brief', `${seat}.jsonl`); }
|
|
57
|
+
|
|
58
|
+
/** Record the items a seat briefing delivered (called only after a successful send). */
|
|
59
|
+
export function recordSeatBriefed(root: string, seat: string, day: string, ids: readonly string[]): void {
|
|
60
|
+
if (!ids.length) return;
|
|
61
|
+
const file = seatBriefFile(root, seat);
|
|
62
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
63
|
+
appendFileSync(file, ids.map((id) => JSON.stringify({ id, day })).join('\n') + '\n');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function buildIntakeDigest(root: string, day: string, readFile: (p: string) => string | undefined = (p) => (existsSync(p) ? readFileSync(p, 'utf8') : undefined), now: Date = new Date(), seat?: string): IntakeDigest {
|
|
67
|
+
const selectedSeat = seat === undefined ? undefined : canonicalSeat(seat);
|
|
68
|
+
// Re-collecting the same search result moves lastSeenAt to today; without receipts it would be «new» every morning.
|
|
69
|
+
const briefedEarlier = new Set(selectedSeat
|
|
70
|
+
? readJsonl(seatBriefFile(root, selectedSeat)).filter((r) => typeof r.id === 'string' && r.day !== day).map((r) => r.id as string)
|
|
71
|
+
: []);
|
|
55
72
|
const items = [...loadIntakeLedger(root).items.values()]
|
|
56
|
-
.filter((i) => (
|
|
73
|
+
.filter((i) => (selectedSeat === undefined || (i.seat === selectedSeat && !briefedEarlier.has(i.id)))
|
|
74
|
+
&& (selectedSeat ? i.status !== 'discarded' : (i.status === 'absorbed' || i.status === 'routed') && i.outputs.some((o) => o.kind === 'note'))
|
|
75
|
+
&& kstDay(i.lastSeenAt) === day);
|
|
57
76
|
const out = intakeOutboxDir(root);
|
|
58
77
|
const relevant = new Map<string, NonNullable<DigestEntry['impact']>>();
|
|
59
78
|
// The lens (src/intake-plane/lens.ts) writes its verdicts to outbox/lens/<day>.jsonl; route folders carry none.
|
|
@@ -76,13 +95,13 @@ export function buildIntakeDigest(root: string, day: string, readFile: (p: strin
|
|
|
76
95
|
return {
|
|
77
96
|
id: i.id, sources: i.sources, ...(i.url ? { url: i.url } : {}),
|
|
78
97
|
...(note ? { note, noteName: basename(note, '.md') } : {}),
|
|
79
|
-
...(md ? { oneLiner: noteOneLiner(md) } : {}),
|
|
98
|
+
...(md ? { oneLiner: noteOneLiner(md) } : selectedSeat && i.title ? { oneLiner: i.title } : {}),
|
|
80
99
|
axis: AXIS_LABEL[i.judgement?.axis ?? i.axis ?? ''] ?? (i.sources.includes('telegram-saved') ? '내가 저장한 것' : '그 밖'),
|
|
81
100
|
...(impact ? { impact } : {}),
|
|
82
101
|
};
|
|
83
102
|
});
|
|
84
|
-
const goals = readJsonl(join(out, 'goals', `${day}.jsonl`)).map((g) => ({ fact: String(g.fact ?? ''), ...(g.url ? { url: String(g.url) } : {}) }));
|
|
85
|
-
const lensRows = readJsonl(join(out, 'lens', `${day}.jsonl`));
|
|
103
|
+
const goals = selectedSeat ? [] : readJsonl(join(out, 'goals', `${day}.jsonl`)).map((g) => ({ fact: String(g.fact ?? ''), ...(g.url ? { url: String(g.url) } : {}) }));
|
|
104
|
+
const lensRows = selectedSeat ? [] : readJsonl(join(out, 'lens', `${day}.jsonl`));
|
|
86
105
|
const news = lensRows.flatMap((row) =>
|
|
87
106
|
typeof row.title === 'string' && typeof row.url === 'string' && Array.isArray(row.summary) && Array.isArray(row.implication)
|
|
88
107
|
? [{ title: row.title, url: row.url, summary: row.summary.filter((s): s is string => typeof s === 'string'), implication: row.implication.filter((s): s is string => typeof s === 'string') }]
|
|
@@ -94,15 +113,15 @@ export function buildIntakeDigest(root: string, day: string, readFile: (p: strin
|
|
|
94
113
|
if (suggestion.status !== '제안' || typeof suggestion.fact !== 'string' || !suggestion.fact.trim()) return [];
|
|
95
114
|
return [{ fact: suggestion.fact, ...(typeof row.url === 'string' ? { url: row.url } : {}), verdict: typeof suggestion.verdict === 'string' ? suggestion.verdict : '판단 필요' }];
|
|
96
115
|
}) : []);
|
|
97
|
-
const review = readJsonl(join(out, 'review', `${day}.jsonl`)).map((r) => ({ fact: String(r.fact ?? ''), ...(r.note ? { note: String(r.note) } : {}) }));
|
|
98
|
-
const grounding = readJsonl(join(out, 'grounding.jsonl')).filter((g) => typeof g.at === 'string' && kstDay(g.at) === day).length;
|
|
99
|
-
const lastNewAt = readSavedCursorAt(root);
|
|
116
|
+
const review = selectedSeat ? [] : readJsonl(join(out, 'review', `${day}.jsonl`)).map((r) => ({ fact: String(r.fact ?? ''), ...(r.note ? { note: String(r.note) } : {}) }));
|
|
117
|
+
const grounding = selectedSeat ? 0 : readJsonl(join(out, 'grounding.jsonl')).filter((g) => typeof g.at === 'string' && kstDay(g.at) === day).length;
|
|
118
|
+
const lastNewAt = selectedSeat ? undefined : readSavedCursorAt(root);
|
|
100
119
|
const silentMs = lastNewAt ? now.getTime() - Date.parse(lastNewAt) : NaN;
|
|
101
120
|
return {
|
|
102
|
-
day, absorbed, goals, ...(news.length ? { news } : {}), ...(shadowSuggestions.length ? { shadowSuggestions } : {}), review, grounding,
|
|
103
|
-
release: readJsonl(join(out, 'release', `${day}.jsonl`)).length,
|
|
104
|
-
manual: readJsonl(join(out, 'manual', `${day}.jsonl`)).length,
|
|
105
|
-
...(lastNewAt && silentMs > SAVED_SILENCE_MS ? { savedSilence: { days: Math.floor(silentMs / 86_400_000), lastNewAt } } : {}),
|
|
121
|
+
day, ...(selectedSeat ? { seat: selectedSeat } : {}), absorbed, goals, ...(news.length ? { news } : {}), ...(shadowSuggestions.length ? { shadowSuggestions } : {}), review, grounding,
|
|
122
|
+
release: selectedSeat ? 0 : readJsonl(join(out, 'release', `${day}.jsonl`)).length,
|
|
123
|
+
manual: selectedSeat ? 0 : readJsonl(join(out, 'manual', `${day}.jsonl`)).length,
|
|
124
|
+
...(!selectedSeat && lastNewAt && silentMs > SAVED_SILENCE_MS ? { savedSilence: { days: Math.floor(silentMs / 86_400_000), lastNewAt } } : {}),
|
|
106
125
|
};
|
|
107
126
|
}
|
|
108
127
|
|
|
@@ -146,6 +165,11 @@ export function renderDigestMarkdown(d: IntakeDigest): string {
|
|
|
146
165
|
|
|
147
166
|
/** 텔레그램 — 흡수 렌즈가 대조한 항목만 SCQA 짧은 판으로 낸다. */
|
|
148
167
|
export function renderDigestTelegram(d: IntakeDigest, _opts: { vaultRoot?: string; notePath?: string } = {}): string {
|
|
168
|
+
if (d.seat && !d.absorbed.length) return '오늘 새 소식 없음';
|
|
169
|
+
if (d.seat) return [
|
|
170
|
+
`흡수 ${d.absorbed.length}`,
|
|
171
|
+
...d.absorbed.map((item) => `- ${item.oneLiner ?? item.noteName ?? item.id}${item.url ? ` · ${item.url}` : ''}`),
|
|
172
|
+
].join('\n');
|
|
149
173
|
// «Touching» is decided by the lens verdict alone; a missing note summary only changes how S reads (ACP must-fix).
|
|
150
174
|
const touching = d.absorbed.filter((e) => e.impact);
|
|
151
175
|
const L = [`흡수 ${d.absorbed.length} → 우리에게 닿는 것 ${touching.length}`];
|
|
@@ -3,14 +3,15 @@ export type DaemonHarnessTarget =
|
|
|
3
3
|
| { ok: false; reason: string };
|
|
4
4
|
|
|
5
5
|
/** Read-only target selection. An explicitly configured path must never fall back to the daemon cwd. */
|
|
6
|
-
export function resolveDaemonHarnessTarget({ configured, cwd, isGitRepo }: {
|
|
6
|
+
export function resolveDaemonHarnessTarget({ configured, cwd, isGitRepo, allowNonGit = false }: {
|
|
7
7
|
configured?: string;
|
|
8
8
|
cwd: string;
|
|
9
9
|
isGitRepo: (path: string) => boolean;
|
|
10
|
+
allowNonGit?: boolean;
|
|
10
11
|
}): DaemonHarnessTarget {
|
|
11
12
|
if (configured !== undefined) {
|
|
12
|
-
if (isGitRepo(configured)) return { ok: true, repo: configured, source: 'config' };
|
|
13
|
-
} else if (isGitRepo(cwd)) {
|
|
13
|
+
if (allowNonGit || isGitRepo(configured)) return { ok: true, repo: configured, source: 'config' };
|
|
14
|
+
} else if (allowNonGit || isGitRepo(cwd)) {
|
|
14
15
|
return { ok: true, repo: cwd, source: 'cwd' };
|
|
15
16
|
}
|
|
16
17
|
return {
|
|
@@ -20,6 +20,7 @@ export interface RegisteredIntakeSource {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function canonicalSeat(value: string): string {
|
|
23
|
+
if (value.trim() === 'user') return 'user';
|
|
23
24
|
const seat = findTrack(value.trim());
|
|
24
25
|
if (!seat) throw new Error(`알 수 없는 자리: ${value}`);
|
|
25
26
|
return seat.id;
|
|
@@ -84,6 +84,7 @@ export function canonicalUrl(raw: string): string | undefined {
|
|
|
84
84
|
function normalizedSeat(seat: unknown): string | undefined {
|
|
85
85
|
if (seat === undefined) return undefined;
|
|
86
86
|
if (typeof seat !== 'string') throw new Error(`알 수 없는 자리: ${String(seat)}`);
|
|
87
|
+
if (seat.trim() === 'user') return 'user';
|
|
87
88
|
const track = findTrack(seat.trim());
|
|
88
89
|
if (!track) throw new Error(`알 수 없는 자리: ${seat}`);
|
|
89
90
|
return track.id;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// COORD-HA (0.2.19) — the signal the orchestrator tick reads for its degradation ladder (RFC loop map §A8b ④⑤):
|
|
2
|
+
// L1 = no usable LLM (rules only) · L2 = the task board cannot take a write (no new assignment). The tick already acts on
|
|
3
|
+
// `TickDeps.degradation`; until now nothing supplied it in production. Each probe answers «false» only on a measured
|
|
4
|
+
// failure — an unknown is not a degradation.
|
|
5
|
+
import { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { randomUUID } from 'node:crypto';
|
|
8
|
+
import { Database } from 'bun:sqlite';
|
|
9
|
+
import { cardIndexPath, taskCardsDir } from '../../task-cards/card-store.js';
|
|
10
|
+
import { resolveUsableLlm } from '../../llm/usable-llm.js';
|
|
11
|
+
|
|
12
|
+
export interface DegradationSignal { llm: boolean; board: boolean; why: string[] }
|
|
13
|
+
|
|
14
|
+
/** The task board takes a write: its folder is a directory a file can be created in (created and removed — a permission
|
|
15
|
+
* bit is not proof · review r1), and its index can take a write lock (busy 2 s · rolled back). */
|
|
16
|
+
export function probeBoardWritable(root: string): { ok: boolean; why?: string } {
|
|
17
|
+
const dir = taskCardsDir(root);
|
|
18
|
+
try {
|
|
19
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
20
|
+
if (!statSync(dir).isDirectory()) return { ok: false, why: 'board dir not writable: not a directory' };
|
|
21
|
+
// A fresh name each time — a leftover from a crashed probe can never make a writable board look full (review r2).
|
|
22
|
+
const probe = join(dir, `.degradation-probe-${randomUUID()}`);
|
|
23
|
+
try { writeFileSync(probe, '', { flag: 'wx', mode: 0o600 }); }
|
|
24
|
+
finally { rmSync(probe, { force: true }); }
|
|
25
|
+
} catch (error) { return { ok: false, why: `board dir not writable: ${(error as NodeJS.ErrnoException).code ?? String(error)}` }; }
|
|
26
|
+
const index = cardIndexPath(root);
|
|
27
|
+
// No index yet: the card store creates it on first open — a writable folder is enough.
|
|
28
|
+
if (!existsSync(index)) return { ok: true };
|
|
29
|
+
let db: Database | undefined;
|
|
30
|
+
try {
|
|
31
|
+
db = new Database(index, { readwrite: true, create: false });
|
|
32
|
+
db.exec('PRAGMA busy_timeout = 2000');
|
|
33
|
+
db.exec('BEGIN IMMEDIATE');
|
|
34
|
+
db.exec('ROLLBACK');
|
|
35
|
+
return { ok: true };
|
|
36
|
+
} catch (error) {
|
|
37
|
+
return { ok: false, why: `board index write lock: ${String(error instanceof Error ? error.message : error).slice(0, 160)}` };
|
|
38
|
+
} finally { try { db?.close(); } catch { /* closing a failed handle is best effort */ } }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface DegradationDeps {
|
|
42
|
+
usableLlm?: () => { usable: boolean; why: string };
|
|
43
|
+
board?: (root: string) => { ok: boolean; why?: string };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function probeDegradation(root: string, deps: DegradationDeps = {}): DegradationSignal {
|
|
47
|
+
const why: string[] = [];
|
|
48
|
+
let llm = true;
|
|
49
|
+
try {
|
|
50
|
+
const usable = (deps.usableLlm ?? resolveUsableLlm)();
|
|
51
|
+
llm = usable.usable;
|
|
52
|
+
if (!llm) why.push(`llm: ${usable.why}`);
|
|
53
|
+
} catch (error) { why.push(`llm unknown: ${String(error).slice(0, 120)}`); }
|
|
54
|
+
const board = (deps.board ?? probeBoardWritable)(root);
|
|
55
|
+
if (!board.ok) why.push(board.why ?? 'board not writable');
|
|
56
|
+
return { llm, board: board.ok, why };
|
|
57
|
+
}
|
|
@@ -15,6 +15,8 @@ export type FinishMetric = 'launched' | 'landed' | 'landingRate' | 'staleDrafts'
|
|
|
15
15
|
export type FinishMetrics = {
|
|
16
16
|
launched: number | null;
|
|
17
17
|
launchedUnreadable?: number;
|
|
18
|
+
/** Launched Pod runs whose child ledger never came back (`pod-ledger-incomplete`) — counted in `launched`, result unknown. */
|
|
19
|
+
launchedIncomplete?: number;
|
|
18
20
|
landed: number | null;
|
|
19
21
|
landingRate: number | null;
|
|
20
22
|
staleDrafts: number | null;
|
|
@@ -97,7 +99,9 @@ export function recordFinishHistory(root: string, metrics: FinishMetrics, now: D
|
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
type Start = { runId: string; startedAt: string };
|
|
100
|
-
type StartObservation = { starts: readonly Start[]; unreadable: readonly { runId: string; reason: string }[]; unreadableDirectories?: number
|
|
102
|
+
type StartObservation = { starts: readonly Start[]; unreadable: readonly { runId: string; reason: string }[]; unreadableDirectories?: number;
|
|
103
|
+
/** Runs counted as launched although their child ledger never came back (reason = the host marker's data.reason). */
|
|
104
|
+
incomplete?: readonly { runId: string; reason: string }[] };
|
|
101
105
|
type Pr = { number: number; headRefName: string; mergedAt?: string; createdAt?: string; mergeable?: string; labels?: { name: string }[] };
|
|
102
106
|
export interface MeasureFinishDeps {
|
|
103
107
|
listStarts?: (cutoff: number) => StartObservation;
|
|
@@ -113,6 +117,7 @@ function defaultListStarts(cutoff: number, targets?: readonly LogTarget[]): Star
|
|
|
113
117
|
let unreadableDirectories = 0;
|
|
114
118
|
let unresolvedDirectories = 0;
|
|
115
119
|
const launchOnlyIds = new Set<string>();
|
|
120
|
+
const incomplete = new Map<string, { runId: string; reason: string }>();
|
|
116
121
|
for (const { ledgerDirectory } of resolveFederatedRunLedgerTargets({ includeTest: true, ...(targets ? { targets } : {}) })) {
|
|
117
122
|
let dir: string;
|
|
118
123
|
try {
|
|
@@ -143,6 +148,20 @@ function defaultListStarts(cutoff: number, targets?: readonly LogTarget[]): Star
|
|
|
143
148
|
continue;
|
|
144
149
|
}
|
|
145
150
|
const start = entries.find(entry => entry.event === 'start');
|
|
151
|
+
// ORCH-LIVE-1008 ①: the host writes `pod-ledger-incomplete` on a Pod child run's ledger when the child's own ledger
|
|
152
|
+
// never came back (OOM · logs unavailable · partial collection). The Job did run — it is a launch with an unknown
|
|
153
|
+
// result, not a ledger without a start. Its time is the marker's timestamp, else the ledger mtime.
|
|
154
|
+
const incompleteMarker = !start ? entries.find(entry => entry.event === 'pod-ledger-incomplete') : undefined;
|
|
155
|
+
if (incompleteMarker) {
|
|
156
|
+
// Only a real `start` (from any directory) outranks the marker; a host launch-only record does not — the same
|
|
157
|
+
// run read as launch-only first is still a launch whose result never came back.
|
|
158
|
+
if (starts.has(runId) && !launchOnlyIds.has(runId)) continue;
|
|
159
|
+
if (!starts.has(runId)) starts.set(runId, { runId, startedAt: incompleteMarker.timestamp ?? new Date(statSync(path).mtimeMs).toISOString() });
|
|
160
|
+
const why = (incompleteMarker.data as { reason?: unknown } | undefined)?.reason;
|
|
161
|
+
incomplete.set(runId, { runId, reason: typeof why === 'string' && why ? why : 'unknown' });
|
|
162
|
+
unreadable.delete(runId);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
146
165
|
// A Pod-dispatched launch leaves only a host launch record (launch-quota-policy · author-on-pod-receipt) — the run
|
|
147
166
|
// itself starts inside the Pod. That is still a launch: its time is the record's timestamp, else the ledger mtime.
|
|
148
167
|
const launchRecord = !start ? entries.find(entry => LAUNCH_ONLY_EVENTS.has(entry.event)) : undefined;
|
|
@@ -157,12 +176,14 @@ function defaultListStarts(cutoff: number, targets?: readonly LogTarget[]): Star
|
|
|
157
176
|
if (launchRecord) launchOnlyIds.add(runId); else launchOnlyIds.delete(runId);
|
|
158
177
|
starts.set(runId, { runId, startedAt });
|
|
159
178
|
unreadable.delete(runId);
|
|
179
|
+
incomplete.delete(runId);
|
|
160
180
|
}
|
|
161
181
|
}
|
|
162
182
|
debug.log('loop.orchestrator', 'finish-launch-population', {
|
|
163
183
|
directories: directories.size + unresolvedDirectories, launched: starts.size, launchOnly: launchOnlyIds.size, childLedgers: childLedgers.size, unreadable: unreadable.size,
|
|
184
|
+
...(incomplete.size > 0 ? { incomplete: incomplete.size } : {}),
|
|
164
185
|
});
|
|
165
|
-
return { starts: [...starts.values()], unreadable: [...unreadable.values()], unreadableDirectories };
|
|
186
|
+
return { starts: [...starts.values()], unreadable: [...unreadable.values()], unreadableDirectories, incomplete: [...incomplete.values()] };
|
|
166
187
|
}
|
|
167
188
|
|
|
168
189
|
function defaultRunGh(args: string[]): string {
|
|
@@ -184,6 +205,7 @@ export function measureFinish(deps: MeasureFinishDeps = {}, now: Date = new Date
|
|
|
184
205
|
const cutoff = now.getTime() - DAY_MS;
|
|
185
206
|
let launched: number | null = null;
|
|
186
207
|
let launchedUnreadable = 0;
|
|
208
|
+
let launchedIncomplete = 0;
|
|
187
209
|
let landed: number | null = null;
|
|
188
210
|
let staleDrafts: number | null = null;
|
|
189
211
|
let conflictRatio: number | null = null;
|
|
@@ -196,6 +218,14 @@ export function measureFinish(deps: MeasureFinishDeps = {}, now: Date = new Date
|
|
|
196
218
|
|| observation.unreadable.some(entry => !entry || typeof entry.runId !== 'string' || typeof entry.reason !== 'string')) throw new Error('invalid run start observation');
|
|
197
219
|
launched = new Set(observation.starts.filter(start => Date.parse(start.startedAt) >= cutoff && Date.parse(start.startedAt) <= now.getTime()).map(start => start.runId)).size;
|
|
198
220
|
launchedUnreadable = observation.unreadable.length;
|
|
221
|
+
const incompleteRuns = (observation.incomplete ?? []).filter(entry => observation.starts.some(start => start.runId === entry.runId
|
|
222
|
+
&& Date.parse(start.startedAt) >= cutoff && Date.parse(start.startedAt) <= now.getTime()));
|
|
223
|
+
launchedIncomplete = incompleteRuns.length;
|
|
224
|
+
if (launchedIncomplete > 0) {
|
|
225
|
+
const byReason: Record<string, number> = {};
|
|
226
|
+
for (const entry of incompleteRuns) byReason[entry.reason] = (byReason[entry.reason] ?? 0) + 1;
|
|
227
|
+
debug.log('loop.orchestrator', 'finish-ledger-incomplete', { count: launchedIncomplete, byReason, sample: incompleteRuns.slice(0, 5) });
|
|
228
|
+
}
|
|
199
229
|
if (launchedUnreadable > 0) {
|
|
200
230
|
reasons.launched = `${launchedUnreadable}개 원장 시작 줄 없음(하한값)`;
|
|
201
231
|
debug.log('loop.orchestrator', 'finish-ledger-unreadable', { count: launchedUnreadable, sample: observation.unreadable.slice(0, 5) });
|
|
@@ -234,7 +264,7 @@ export function measureFinish(deps: MeasureFinishDeps = {}, now: Date = new Date
|
|
|
234
264
|
: 'no harness runs launched in the last 24 hours';
|
|
235
265
|
else if (landed > launched) reasons.landingRate = `landed exceeds launched — population mismatch (launched=${launched}, landed=${landed})`;
|
|
236
266
|
else landingRate = landed / launched;
|
|
237
|
-
return { launched, ...(launchedUnreadable > 0 ? { launchedUnreadable } : {}), landed, landingRate, staleDrafts, conflictRatio, unknownMergeable, reasons };
|
|
267
|
+
return { launched, ...(launchedUnreadable > 0 ? { launchedUnreadable } : {}), ...(launchedIncomplete > 0 ? { launchedIncomplete } : {}), landed, landingRate, staleDrafts, conflictRatio, unknownMergeable, reasons };
|
|
238
268
|
}
|
|
239
269
|
|
|
240
270
|
/** Advice only: no launch cap, PR, or config is changed. */
|