elanous 0.2.31 → 0.2.32
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/{1834-50625904996a8fa8.js → 1834-98b64172bc1efe77.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/69-0a2c8209c6d4a158.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8097-9530c03bcc550f02.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-504204b58c82c253.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-3535a4a69f1fafaa.js → page-a9717a67c2eecbaf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-5c9ec30aee62dfbf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-584191533721fa16.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-73c4b4bb3ac7ab5f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/{page-5a503541353e60a8.js → page-c0f1ca371f710b06.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-6025101384e361a5.js → page-46430d45a9e04eac.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-3b2ebf1dc64ff0be.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-a645f00a06483e9a.js → page-a27b1e5a0d1b308f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-25654b82094f7d22.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-e9bab7413b028b28.js → page-8ea49a4c6dd2c6e7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-ce5113aefea0a8d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-e547e6ce55f1fb33.js → page-0e5a41fac1c75396.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-d27e1b9850fd54bf.js → page-31aaf1ed37c6df9a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-3dcc1fa411e1f4e0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-0fec29c7ba01894b.js → page-c69247e7134d329d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-339a07410cd6f36b.js → layout-66dc5cd3a0999abe.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-26ff7ff651a7217d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-617b3976981efa96.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/{page-2fd93ed8c9f16580.js → page-d256f5d5d9552823.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/market/{page-9b564dbff0fefc35.js → page-ce8f1907b8a6101c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-33e182a316263116.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-814ba51385225776.js → page-32866485913d6a04.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-397d479626e5a459.js → not-found-7607cca9b84950c6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/{page-af1bba4e3251b728.js → page-eeab86176b03dc3b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-3ad3be5e7d3fdce9.js → page-5b93dbae3432b996.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-77a6cb90a9cf2b10.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-4da93ad35d4a8d28.js → page-f47e768b5b242b5a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-eb41aa3367fc2505.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-4d13ab0b8a267e3f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-84dad32ff0fc0831.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-3a658d5fa6d16a25.js → page-95306f892aa2d0ee.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-0157de6e85f8bb6b.js → page-940fb6c217f6b470.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-0a07363b130182a1.js → page-8c66a3b24a745630.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-841d73d4ac44ed49.js → page-7b675dfd35c36fc9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-6f43a3d1146e6377.js → page-8bd0d3cf7665bcfc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-521ee23ac8c0abfa.js → page-c219f2ed171131be.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/{page-a9992ee285efaa83.js → page-40f8fa21bdae955b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-3d48e85fa89ec04f.js → page-468f8313e4f1fbd9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-7bbb8fa353a246c4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-04d00ae9b0890349.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/{page-233c71cd5f68f0d7.js → page-4214b855d9233ea8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-70151d78de2f375e.js → page-936752c7aa58762e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-dcac33a3e2cb72c0.js → page-ec052ad359df1797.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-8305570551091338.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-0448145ccd5c45c1.js → page-fad650e4793adb65.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-d90e64743826e1d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-a16bcfd49af86a32.js +1 -0
- package/apps/pwa/out/_next/static/css/b5d88eba78a3d7bf.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 +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 +17 -14
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/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/implement-loop.yaml +5 -0
- package/package.json +1 -1
- package/scripts/ci-typecheck-changed.ts +26 -1
- package/scripts/measure-si-task-cpu.ts +143 -0
- package/scripts/ratchets/test-interference-patterns.allowlist.txt +0 -2
- package/scripts/release-loop/prepare-node.ts +102 -4
- package/src/acp/core-turn-bridge.ts +16 -4
- package/src/acp/elanous-extensions.ts +3 -0
- package/src/acp/server.ts +3 -0
- package/src/acp/tool-trace.ts +186 -11
- package/src/agent/self-ambient-cache.ts +156 -0
- package/src/agent/self-ambient.ts +8 -1
- package/src/agent/task-notification.ts +5 -0
- package/src/agent-substrate/pr-reviewer.ts +3 -1
- package/src/ask-user-question/tool.ts +22 -0
- package/src/autopilot/build/llm-conflict-merge.ts +56 -10
- package/src/autopilot/pr-manager.ts +23 -5
- package/src/capture/frame-state-detect.ts +39 -0
- package/src/capture/tui-self-report.ts +134 -6
- package/src/chat/index.ts +61 -15
- package/src/chat/pickers/modal-runtime.ts +6 -0
- package/src/chat/pickers/modals.ts +8 -2
- package/src/cli/ask-bare.ts +1 -0
- package/src/cli/import-cli.ts +12 -0
- package/src/cli/log-event-names.ts +10 -0
- package/src/cli/msg-cli.ts +1 -0
- package/src/cli/pod-cli.ts +14 -3
- package/src/cli/pr-cli.ts +67 -32
- package/src/cli/pr-land-base-read.ts +34 -0
- package/src/cli/pr-land-heavy-remote.ts +81 -25
- package/src/cli/pr-land-overlap-evidence.ts +46 -2
- package/src/cli/pr-land-pod-shadow-job.ts +26 -3
- package/src/cli/pr-land-refusal.ts +20 -5
- package/src/cli/pty-takeover-cli.ts +88 -14
- package/src/cli/sim-cli.ts +18 -0
- package/src/context-bus/ops-events.ts +6 -2
- package/src/core-turn/run-core-turn.ts +1 -0
- package/src/core-turn/run-goal-loop.ts +21 -0
- package/src/core-turn/types.ts +3 -0
- package/src/dashboard/acp-active-model.ts +42 -1
- package/src/dashboard/background-jobs-status.ts +66 -0
- package/src/dashboard/index.ts +117 -44
- package/src/dashboard/input/agent-wake.ts +242 -0
- package/src/dashboard/input/chat-main-entry-runtime.ts +5 -0
- package/src/dashboard/input/external-input-host.ts +23 -9
- package/src/dashboard/input/mouse-wiring.ts +108 -11
- package/src/dashboard/log-click-runtime.ts +118 -2
- package/src/dashboard/turn-preamble.ts +116 -40
- package/src/dashboard/turn-session-layers.ts +4 -2
- package/src/dashboard/turn-start-segments.ts +222 -0
- package/src/display/prompt-row-owner.ts +146 -0
- package/src/display/resize-listener.ts +33 -2
- package/src/fold-stack.ts +4 -2
- package/src/git-fs/index.ts +263 -36
- package/src/harness/gate-reserve.ts +87 -0
- package/src/harness/harness-incidents.ts +13 -0
- package/src/harness/harness-queue.ts +15 -0
- package/src/index.ts +8 -6
- package/src/input/external-input-queue.ts +34 -9
- package/src/llm/route-decision.ts +36 -0
- package/src/llm/tool-loop-steered-rejection.ts +8 -0
- package/src/llm-request-shape.ts +209 -0
- package/src/llm.ts +166 -267
- package/src/loops/orchestrator/tick.ts +24 -2
- package/src/maturity/feature-maturity.ts +1 -1
- package/src/mss/logging/render-categories.ts +5 -0
- package/src/ops/overview.ts +59 -2
- package/src/pr-tool/census.ts +19 -4
- package/src/pr-tool/inflight-registry.ts +143 -0
- package/src/pty-shell/pty-manifest.ts +12 -3
- package/src/pty-shell/tui-run-state.ts +34 -3
- package/src/seat-loop/traffic-signal.ts +2 -1
- package/src/self-dev/ask-launch-flow.ts +13 -0
- package/src/self-dev/draft-residue.ts +243 -24
- package/src/self-dev/shard-plan.ts +11 -3
- package/src/self-dev/shard-run.ts +103 -10
- package/src/self-implement/elanous-tui-spawn.ts +14 -0
- package/src/self-implement/follow-up-placement.ts +79 -0
- package/src/self-implement/gate-artifacts.ts +15 -4
- package/src/self-implement/gate-baseline.ts +96 -5
- package/src/self-implement/gate-cli.ts +7 -2
- package/src/self-implement/gate-remote.ts +26 -1
- package/src/self-implement/host-regate.ts +36 -4
- package/src/self-implement/orchestrator.ts +101 -11
- package/src/self-implement/run-ledger.ts +16 -14
- package/src/session-runtime/index.ts +22 -267
- package/src/shell-runner/background-surface.ts +15 -5
- package/src/skills/tools/agent.ts +39 -6
- package/src/skills/tools/list-dir.ts +11 -0
- package/src/skills/tools/schedule-wakeup.ts +408 -0
- package/src/skills/tools/shell.ts +245 -3
- package/src/status/bar.ts +26 -0
- package/src/steward/digest-delivery.ts +118 -0
- package/src/steward/hold-hygiene.ts +120 -0
- package/src/steward/triage.ts +10 -0
- package/src/surface/shell-runner-boot.ts +5 -2
- package/src/task-agent/task-hand.ts +24 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +65 -11
- package/src/task-orchestrator/surfaces/pod-lease.ts +98 -0
- package/src/task-orchestrator/surfaces/pod-ledger-events.ts +1 -1
- package/src/task-orchestrator/surfaces/pod-pool.ts +3 -1
- package/src/task-orchestrator/surfaces/pod-reuse-cleanup.ts +10 -6
- package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +525 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-runner.ts +90 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +156 -2
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +7 -5
- package/src/tui/chat/execution-badge.ts +5 -1
- package/src/tui/input-lag-probe.ts +230 -0
- package/src/tui.ts +4 -0
- package/src/ux-sim/scenario.ts +255 -0
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/8097-92f87bd108322f66.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-bf3634a117322df7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b9f51447f757fbf2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-2ebb8fd59b9461f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a2fc4e49a4fa4ba8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-f4b630cc8b5273cd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-61ac22cbab1ee4ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c992eafe028b642e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-805ac2e137b7a121.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-349f2752d8cd37ad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-d5c6dcf31c25cecc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-62f4475d3c38fb9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-f219ccdbc0bf27df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-456666766e5ce6fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-7404e377e8adb8c5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-14456240ed2a36ad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-d2ced71132fac9b2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-f08358bdf251adb9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-149dc1caba411bb9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-9cb0c444903da65d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-17e504c396e0e389.js +0 -1
- package/apps/pwa/out/_next/static/css/3f5fc6f96e2cf7b2.css +0 -1
- /package/apps/pwa/out/_next/static/{LSXfY0u9QtfDbejY0Ukf_ → IVKr3AlRUVLKsw2fh7eXF}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{LSXfY0u9QtfDbejY0Ukf_ → IVKr3AlRUVLKsw2fh7eXF}/_ssgManifest.js +0 -0
|
@@ -79,6 +79,11 @@ export const RENDER_CATEGORY_EXEMPTIONS: ReadonlyMap<string, string> = new Map([
|
|
|
79
79
|
// ⛔ 접두 전체(`dashboard`)를 풀지 «않는다» — 그러면 진짜 렌더 소음이 되살아난다. 이 둘만 뗀다.
|
|
80
80
|
['dashboard.turn-typeahead', 'Mid-turn input queueing decisions — which key was consumed, what was queued, and when it drains. Turn-control judgment, not frame noise.'],
|
|
81
81
|
['dashboard.streaming-key', 'Streaming-window key ownership — whether a key reached the streaming dispatcher at all. This is the probe that answers "is the ladder live?", so it must survive render mute.'],
|
|
82
|
+
// ⛔ 2026-10-17 (`TUI-H8-SEAT-BOOT-LOG-MUTED`) — 자리 부트 카드는 «항상성» 이벤트다(렌더 소음 아님).
|
|
83
|
+
// `/session new` 뒤 카드가 실렸는지를 `logs --all --include-test` 로도 0행으로 만들어
|
|
84
|
+
// «카드가 빠졌다»와 «음소거됐다»를 가를 수 없었다. 접두 전체는 풀지 않고 이것만 뗀다.
|
|
85
|
+
// (`tui.autoturn`·`tui.wakeup`·`tui.autowake` 는 `tui` 가 렌더 접두가 아니라 애초에 안 잘린다.)
|
|
86
|
+
['dashboard.seat-boot', 'Seat boot card lifecycle — conversation-start, card-injected, card-skipped. Homeostasis evidence that the seat card reached the turn, not frame noise.'],
|
|
82
87
|
]);
|
|
83
88
|
|
|
84
89
|
/** Reject an exemption without its required, human-readable rationale. */
|
package/src/ops/overview.ts
CHANGED
|
@@ -96,6 +96,62 @@ export interface VerdictResult { verdict: Verdict; reasons: string[]; nextAction
|
|
|
96
96
|
/** 부하/코어가 이 값을 넘으면 «기계 압박»(흐름 저하의 사유). 디스크 I/O 신호는 OVW-MACHINE-HB 전까지 없다. */
|
|
97
97
|
export const LOAD_PER_CORE_PRESSURE = 3;
|
|
98
98
|
|
|
99
|
+
/** 자원 지도의 축 어휘(닫힌 집합 · RES-FLOOR-BINDING). I/O·게이트 예약은 이 칸에 원천이 없어 늘 제외다. */
|
|
100
|
+
export type ResourceKind = 'memory' | 'cpu' | 'io' | 'slots' | 'gate';
|
|
101
|
+
|
|
102
|
+
/** 구속 조건 — 남은 비율이 최소인 축. 남은 비율 = 남은 양/전체 양(CPU 는 압박선 LOAD_PER_CORE_PRESSURE×코어를 전체로 본다). */
|
|
103
|
+
export interface ResourceBinding {
|
|
104
|
+
kind: ResourceKind;
|
|
105
|
+
label: string;
|
|
106
|
+
remainingRatio: number;
|
|
107
|
+
/** 남은 양/전체 양의 사람 표기 — `5/64GB` · `0/54코어(압박선 3/코어)` · `0/46자리`. */
|
|
108
|
+
remainingText: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function ratio01(n: number): number {
|
|
112
|
+
return Math.min(1, Math.max(0, n));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function remainingAmount(n: number): string {
|
|
116
|
+
return Number.isInteger(n) ? n.toFixed(0) : String(Number(n.toPrecision(3)));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 구속 조건 규칙(RES-FLOOR-BINDING) — 메모리·CPU·I/O·슬롯·게이트 예약 중 «남은 비율 최소»인 축이 흐름을 묶는다.
|
|
121
|
+
* 못 잰 축(하트비트 없는 호스트 · 용량 못 잼 · 원천 없는 I/O·게이트 예약)은 후보에서 뺀다 — «넉넉»으로도 «바닥»으로도 읽지 않는다.
|
|
122
|
+
* 측정 축이 하나도 없으면 null. 동율은 정해진 순서(호스트 순 메모리→CPU, 그 다음 슬롯)의 첫 축이 이긴다.
|
|
123
|
+
*/
|
|
124
|
+
export function bindingResource(s: OverviewSnapshot): ResourceBinding | null {
|
|
125
|
+
const candidates: ResourceBinding[] = [];
|
|
126
|
+
for (const host of s.machines.hosts) {
|
|
127
|
+
const l = host.load.value;
|
|
128
|
+
if (!l) continue;
|
|
129
|
+
if (l.totalGb > 0) {
|
|
130
|
+
const remaining = Math.max(0, l.freeGb);
|
|
131
|
+
candidates.push({ kind: 'memory', label: `${host.name} 메모리`, remainingRatio: ratio01(remaining / l.totalGb), remainingText: `${remainingAmount(remaining)}/${l.totalGb.toFixed(0)}GB` });
|
|
132
|
+
}
|
|
133
|
+
if (l.cpuCount > 0) {
|
|
134
|
+
const cpuCap = l.cpuCount * LOAD_PER_CORE_PRESSURE;
|
|
135
|
+
const remaining = Math.max(0, cpuCap - l.load5);
|
|
136
|
+
candidates.push({ kind: 'cpu', label: `${host.name} CPU`, remainingRatio: ratio01(remaining / cpuCap), remainingText: `${remainingAmount(remaining)}/${cpuCap.toFixed(0)}코어(압박선 ${LOAD_PER_CORE_PRESSURE}/코어)` });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const capacity = s.tasks.capacity.value;
|
|
140
|
+
if (capacity && capacity.podTarget + capacity.localMax > 0) {
|
|
141
|
+
const total = capacity.podTarget + capacity.localMax;
|
|
142
|
+
candidates.push({ kind: 'slots', label: '슬롯', remainingRatio: ratio01(capacity.free / total), remainingText: `${capacity.free}/${total}자리` });
|
|
143
|
+
}
|
|
144
|
+
let binding: ResourceBinding | null = null;
|
|
145
|
+
for (const c of candidates) if (binding === null || c.remainingRatio < binding.remainingRatio) binding = c;
|
|
146
|
+
return binding;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** 판정 띠 문구 — 측정된 축 중 «무엇이 흐름을 묶고 있나»를 이름으로 단다. 측정 축이 없으면 null. */
|
|
150
|
+
export function bindingPhrase(b: ResourceBinding | null): string | null {
|
|
151
|
+
if (!b) return null;
|
|
152
|
+
return `구속 자원 ${b.label} — 남은 비율 최소 ${Math.round(b.remainingRatio * 100)}% (${b.remainingText})`;
|
|
153
|
+
}
|
|
154
|
+
|
|
99
155
|
export function computeVerdict(s: OverviewSnapshot): VerdictResult {
|
|
100
156
|
const hits = new Map<Verdict, { reasons: string[]; actions: string[] }>();
|
|
101
157
|
const hit = (v: Verdict, reason: string, action?: string): void => {
|
|
@@ -155,14 +211,15 @@ export function computeVerdict(s: OverviewSnapshot): VerdictResult {
|
|
|
155
211
|
}
|
|
156
212
|
}
|
|
157
213
|
|
|
214
|
+
const phrase = bindingPhrase(bindingResource(s));
|
|
158
215
|
for (const v of VERDICTS) {
|
|
159
216
|
const row = hits.get(v);
|
|
160
217
|
if (!row) continue;
|
|
161
218
|
// 이유 사슬: 이긴 판정의 사유 먼저, 그 밑에 진 판정들의 사유도 «부가»로 남긴다(한 사슬에 두 원인이 보이게).
|
|
162
219
|
const rest = VERDICTS.filter((o) => o !== v && hits.has(o)).flatMap((o) => hits.get(o)!.reasons.map((r) => `(+${VERDICT_LABEL[o]}) ${r}`));
|
|
163
|
-
return { verdict: v, reasons: [...row.reasons, ...rest], nextActions: row.actions };
|
|
220
|
+
return { verdict: v, reasons: [...row.reasons, ...(phrase ? [phrase] : []), ...rest], nextActions: row.actions };
|
|
164
221
|
}
|
|
165
|
-
return { verdict: 'flowing', reasons: [], nextActions: [] };
|
|
222
|
+
return { verdict: 'flowing', reasons: phrase ? [phrase] : [], nextActions: [] };
|
|
166
223
|
}
|
|
167
224
|
|
|
168
225
|
/** exit code — flowing·degraded 0 · 그 밖의 판정 3(수집기 자체 실패 1 은 호출자 몫). */
|
package/src/pr-tool/census.ts
CHANGED
|
@@ -28,7 +28,9 @@ export type Census = { status: 'checked'; total: number; rows: readonly CensusRo
|
|
|
28
28
|
| { status: 'unknown'; reason: string };
|
|
29
29
|
export interface DiffHunk { path: string; start: number; end: number }
|
|
30
30
|
export type GuardCode = 'inventory-unknown' | 'candidate-unknown' | 'goal-only' | 'file-overlap' | 'hunk-overlap' | 'same-cell' | 'seat-cap' | 'seat-unattributed';
|
|
31
|
-
export interface GuardReason { code: GuardCode; detail: string; prs?: readonly number[] }
|
|
31
|
+
export interface GuardReason { code: GuardCode; detail: string; prs?: readonly number[]; files?: readonly string[] }
|
|
32
|
+
/** PR-GUARD-HUNK-WARN (대표 10-17): 줄 겹침은 기본 경고 — 착지 쪽 스마트 머지가 푼다. 'refuse' = 옛 거부 동작. */
|
|
33
|
+
export type HunkOverlapMode = 'refuse' | 'warn';
|
|
32
34
|
export interface GuardVerdict { status: 'allow' | 'refuse' | 'unknown'; reasons: readonly GuardReason[]; warnings: readonly GuardReason[] }
|
|
33
35
|
|
|
34
36
|
const SEATS = ['OP', 'TC', 'MK', 'UX'] as const;
|
|
@@ -209,7 +211,7 @@ export function censusOpenPrs(inventory: Inventory, now = Date.now()): Census {
|
|
|
209
211
|
return result;
|
|
210
212
|
}
|
|
211
213
|
|
|
212
|
-
export function guardNewPr(input: { files: readonly string[] | null; seat?: Seat | null; seatCap?: number; cellId?: string | null; hunks?: readonly DiffHunk[] }, inventory: Inventory): GuardVerdict {
|
|
214
|
+
export function guardNewPr(input: { files: readonly string[] | null; seat?: Seat | null; seatCap?: number; cellId?: string | null; hunks?: readonly DiffHunk[]; hunkOverlap?: HunkOverlapMode }, inventory: Inventory): GuardVerdict {
|
|
213
215
|
if (!input.files || input.files.length === 0 || input.files.some(file => !file || file.startsWith('/') || file.includes('..'))) {
|
|
214
216
|
const reasons: GuardReason[] = [{ code: 'candidate-unknown', detail: '새 PR 변경 파일을 확인할 수 없음' }];
|
|
215
217
|
debug.log('pr.tool', 'guard-refuse', { status: 'unknown', reasons });
|
|
@@ -226,8 +228,21 @@ export function guardNewPr(input: { files: readonly string[] | null; seat?: Seat
|
|
|
226
228
|
const sameCell = input.cellId ? inventory.prs.filter(pr => pr.cellId === input.cellId) : [];
|
|
227
229
|
if (sameCell.length) reasons.push({ code: 'same-cell', detail: `같은 칸 ${input.cellId} OPEN PR이 있음`, prs: sameCell.map(pr => pr.number) });
|
|
228
230
|
const overlaps = inventory.prs.filter(pr => pr.files.some(file => candidate.has(file)));
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
+
const hunkFiles = new Set<string>();
|
|
232
|
+
const sameHunk = overlaps.filter(pr => {
|
|
233
|
+
const paths = (pr.hunks ?? []).filter(other => input.hunks?.some(own => own.path === other.path && own.start <= other.end && other.start <= own.end)).map(other => other.path);
|
|
234
|
+
for (const path of paths) hunkFiles.add(path);
|
|
235
|
+
return paths.length > 0;
|
|
236
|
+
});
|
|
237
|
+
if (sameHunk.length) {
|
|
238
|
+
const refuseHunk = input.hunkOverlap === 'refuse';
|
|
239
|
+
(refuseHunk ? reasons : warnings).push({
|
|
240
|
+
code: 'hunk-overlap',
|
|
241
|
+
detail: refuseHunk ? '같은 파일의 수정 줄 범위가 겹침' : '같은 파일의 수정 줄 범위가 겹침 · 착지 때 머지로 푼다',
|
|
242
|
+
prs: sameHunk.map(pr => pr.number),
|
|
243
|
+
files: [...hunkFiles],
|
|
244
|
+
});
|
|
245
|
+
}
|
|
231
246
|
if (overlaps.length) warnings.push({ code: 'file-overlap', detail: '같은 파일의 OPEN PR이 있음 · 줄 겹침은 별도 판정', prs: overlaps.map(pr => pr.number) });
|
|
232
247
|
const seat = seatValue(input.seat);
|
|
233
248
|
if (input.seat !== undefined && input.seat !== null && !seat) reasons.push({ code: 'candidate-unknown', detail: '호출자 자리 값 오류' });
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* INFLIGHT-REGISTRY — 비행 중 등록부(대표 10-17 «출발 때 맥락 남기고 머지 때 참조 · 착지하면 지움 · 폭증 막기»).
|
|
3
|
+
*
|
|
4
|
+
* 발사하는 런이 runId·칸·한 줄 의도·대상 파일을 한 줄 등록하고, 머지 순간(스마트 머지 LLM)에 같은 파일을
|
|
5
|
+
* 만지는 줄들의 의도를 읽는다. 착지하면 지운다. 정본은 PR 본문이고 이것은 «색인»이다 — 못 읽으면 빈 목록
|
|
6
|
+
* (머지는 종전 그대로 간다), 못 쓰면 발사·착지를 막지 않는다(호출부가 fail-soft).
|
|
7
|
+
*
|
|
8
|
+
* 폭증 막기: 런당 1줄(같은 칸 재발사는 덮어씀) · 의도 ≤300자 · 파일 ≤20 · 48h 지난 줄은 읽을 때 거르고 쓸 때 걷는다.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { dirname, join } from 'node:path';
|
|
12
|
+
import { debug } from '../debug/log.js';
|
|
13
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
14
|
+
import { withFileLockSync } from '../storage/file-lock.js';
|
|
15
|
+
|
|
16
|
+
export const INFLIGHT_INTENT_MAX_CHARS = 300;
|
|
17
|
+
export const INFLIGHT_FILES_MAX = 20;
|
|
18
|
+
export const INFLIGHT_TTL_MS = 48 * 60 * 60 * 1000;
|
|
19
|
+
export const INFLIGHT_REGISTRY_FILE = 'inflight-registry.json';
|
|
20
|
+
|
|
21
|
+
export interface InflightEntry {
|
|
22
|
+
runId: string;
|
|
23
|
+
/** 칸 id(없으면 카드 id) — 같은 칸 재발사는 이 키로 덮어쓴다. */
|
|
24
|
+
cell: string;
|
|
25
|
+
intent: string;
|
|
26
|
+
files: string[];
|
|
27
|
+
registeredAt: string;
|
|
28
|
+
cardId?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface InflightOptions {
|
|
32
|
+
path?: string;
|
|
33
|
+
now?: () => Date;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 기본 자리 — 과제 카드 상태(`task-agent-actions.json`)와 같은 우주 뿌리. */
|
|
37
|
+
export function inflightRegistryPath(): string {
|
|
38
|
+
return join(effectiveInstanceRoot(), INFLIGHT_REGISTRY_FILE);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readEntries(path: string): InflightEntry[] {
|
|
42
|
+
if (!existsSync(path)) return [];
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8')) as { entries?: unknown };
|
|
45
|
+
return Array.isArray(parsed.entries) ? parsed.entries.filter(isEntry) : [];
|
|
46
|
+
} catch { return []; }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isEntry(value: unknown): value is InflightEntry {
|
|
50
|
+
const entry = value as Partial<InflightEntry> | null;
|
|
51
|
+
return !!entry && typeof entry.runId === 'string' && typeof entry.cell === 'string' && typeof entry.intent === 'string'
|
|
52
|
+
&& Array.isArray(entry.files) && typeof entry.registeredAt === 'string';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isExpired(entry: InflightEntry, now: Date): boolean {
|
|
56
|
+
const at = Date.parse(entry.registeredAt);
|
|
57
|
+
return !Number.isFinite(at) || now.getTime() - at > INFLIGHT_TTL_MS;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** 잠금 안에서 읽고 고쳐 쓴다 — 48h 지난 줄은 쓸 때 걷는다(`expire` 관측). */
|
|
61
|
+
function mutate(opts: InflightOptions, update: (entries: InflightEntry[]) => InflightEntry[]): void {
|
|
62
|
+
const path = opts.path ?? inflightRegistryPath();
|
|
63
|
+
const now = (opts.now ?? (() => new Date()))();
|
|
64
|
+
withFileLockSync(`${path}.lock`, () => {
|
|
65
|
+
const all = readEntries(path);
|
|
66
|
+
const live = all.filter((entry) => !isExpired(entry, now));
|
|
67
|
+
if (live.length < all.length) {
|
|
68
|
+
try { debug.log('pr-tool.inflight', 'expire', { phase: 'write', runIds: all.filter((entry) => isExpired(entry, now)).map((entry) => entry.runId) }); } catch { /* fail-soft */ }
|
|
69
|
+
}
|
|
70
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
71
|
+
writeFileSync(path, JSON.stringify({ entries: update(live) }));
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** 한 줄 등록 — 같은 runId 또는 같은 칸의 옛 줄을 덮어쓴다. 의도·파일은 상한에서 자른다. */
|
|
76
|
+
export function registerInflight(input: { runId: string; cell: string; intent: string; files: readonly string[]; cardId?: string }, opts: InflightOptions = {}): InflightEntry {
|
|
77
|
+
const now = (opts.now ?? (() => new Date()))();
|
|
78
|
+
const files = [...new Set(input.files.map((file) => file.trim()).filter(Boolean))];
|
|
79
|
+
// 한 줄 칸만 — 줄바꿈·제어 문자는 공백으로(머지 프롬프트 인용 블록을 깨지 못하게 · 프롬프트 쪽도 따로 무력화한다).
|
|
80
|
+
const oneLine = (value: string) => value.replace(/[\p{Cc}\s]+/gu, ' ').trim();
|
|
81
|
+
const entry: InflightEntry = {
|
|
82
|
+
runId: oneLine(input.runId),
|
|
83
|
+
cell: oneLine(input.cell),
|
|
84
|
+
intent: oneLine(input.intent).slice(0, INFLIGHT_INTENT_MAX_CHARS),
|
|
85
|
+
files: files.slice(0, INFLIGHT_FILES_MAX),
|
|
86
|
+
registeredAt: now.toISOString(),
|
|
87
|
+
...(input.cardId ? { cardId: input.cardId } : {}),
|
|
88
|
+
};
|
|
89
|
+
let replaced: string[] = [];
|
|
90
|
+
mutate(opts, (entries) => {
|
|
91
|
+
replaced = entries.filter((old) => old.runId === entry.runId || old.cell === entry.cell).map((old) => old.runId);
|
|
92
|
+
return [...entries.filter((old) => old.runId !== entry.runId && old.cell !== entry.cell), entry];
|
|
93
|
+
});
|
|
94
|
+
try { debug.log('pr-tool.inflight', 'register', { runId: entry.runId, cell: entry.cell, files: entry.files.length, filesDropped: files.length - entry.files.length, intentChars: entry.intent.length, replaced }); } catch { /* fail-soft */ }
|
|
95
|
+
return entry;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** 줄을 지운다 — runId 또는 카드 id 로. 지운 수를 돌려준다(0 = 그런 줄 없음). */
|
|
99
|
+
export function removeInflight(key: { runId: string } | { cardId: string }, reason: string, opts: InflightOptions = {}): number {
|
|
100
|
+
let removed: string[] = [];
|
|
101
|
+
const matches = (entry: InflightEntry): boolean => 'runId' in key ? entry.runId === key.runId : entry.cardId === key.cardId;
|
|
102
|
+
mutate(opts, (entries) => {
|
|
103
|
+
removed = entries.filter(matches).map((entry) => entry.runId);
|
|
104
|
+
return entries.filter((entry) => !matches(entry));
|
|
105
|
+
});
|
|
106
|
+
try { debug.log('pr-tool.inflight', 'remove', { ...key, reason, removed }); } catch { /* fail-soft */ }
|
|
107
|
+
return removed.length;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** 살아 있는 줄 — `files` 를 주면 그중 하나라도 만지는 줄만. 48h 지난 줄은 거른다(읽기는 쓰지 않는다). */
|
|
111
|
+
export function listInflight(filter: { files?: readonly string[] } = {}, opts: InflightOptions = {}): InflightEntry[] {
|
|
112
|
+
const now = (opts.now ?? (() => new Date()))();
|
|
113
|
+
const all = readEntries(opts.path ?? inflightRegistryPath());
|
|
114
|
+
const live = all.filter((entry) => !isExpired(entry, now));
|
|
115
|
+
if (live.length < all.length) {
|
|
116
|
+
try { debug.log('pr-tool.inflight', 'expire', { phase: 'read', count: all.length - live.length }); } catch { /* fail-soft */ }
|
|
117
|
+
}
|
|
118
|
+
if (!filter.files) return live;
|
|
119
|
+
const wanted = new Set(filter.files);
|
|
120
|
+
return live.filter((entry) => entry.files.some((file) => wanted.has(file)));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 착지한 PR 의 줄을 지운다 — 그 PR 을 연 과제 카드(`card.pr`)의 카드 id·runId 로 찾는다.
|
|
125
|
+
* 카드가 없으면 디스크를 건드리지 않는다(사람 PR · 다른 우주 카드 = 지울 줄 없음).
|
|
126
|
+
*/
|
|
127
|
+
export function removeInflightForPr(
|
|
128
|
+
pr: number,
|
|
129
|
+
cards: Iterable<{ id: string; runId?: string; pr?: number | { number: number } }>,
|
|
130
|
+
opts: InflightOptions = {},
|
|
131
|
+
): number {
|
|
132
|
+
const owners = [...cards].filter((card) => (typeof card.pr === 'number' ? card.pr : card.pr?.number) === pr);
|
|
133
|
+
if (owners.length === 0) {
|
|
134
|
+
try { debug.log('pr-tool.inflight', 'remove', { pr, reason: 'pr-landed', removed: [], cards: 0 }); } catch { /* fail-soft */ }
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
|
137
|
+
let removed = 0;
|
|
138
|
+
for (const card of owners) {
|
|
139
|
+
removed += removeInflight({ cardId: card.id }, `pr-landed:${pr}`, opts);
|
|
140
|
+
if (card.runId) removed += removeInflight({ runId: card.runId }, `pr-landed:${pr}`, opts);
|
|
141
|
+
}
|
|
142
|
+
return removed;
|
|
143
|
+
}
|
|
@@ -277,6 +277,9 @@ export function upsertPtyManifest(row: {
|
|
|
277
277
|
};
|
|
278
278
|
}): void {
|
|
279
279
|
try {
|
|
280
|
+
// 행을 (다시) 세우면 snapshot·frame 칸이 비워진다 — 같은 프로세스의 쓰기 스로틀도 비워 다음 쓰기가 바로 닿게 한다.
|
|
281
|
+
_lastSnapshotAt.delete(row.id);
|
|
282
|
+
_lastFrameAt.delete(row.id);
|
|
280
283
|
const d = db(); if (!d) return;
|
|
281
284
|
const runId = getHarnessRunId(process.env);
|
|
282
285
|
const runIdSource = getHarnessRunIdSource();
|
|
@@ -800,12 +803,18 @@ export function listPtyManifestByRun(runId: string): PtyManifestRow[] {
|
|
|
800
803
|
}
|
|
801
804
|
}
|
|
802
805
|
|
|
803
|
-
|
|
806
|
+
/** `strict` — 조회 실패(db 못 엶·질의 오류)를 «행 없음»(null)과 가르도록 던진다. 기본은 둘 다 null(fail-soft). */
|
|
807
|
+
export function getPtyManifest(id: string, opts: { readonly strict?: boolean } = {}): PtyManifestRow | null {
|
|
804
808
|
try {
|
|
805
|
-
const d = db();
|
|
809
|
+
const d = db();
|
|
810
|
+
if (!d) {
|
|
811
|
+
if (opts.strict) throw new Error('pty-manifest-db-unavailable');
|
|
812
|
+
return null;
|
|
813
|
+
}
|
|
806
814
|
const row = d.query('SELECT * FROM pty_manifest WHERE id=?').get(id) as Record<string, unknown> | null;
|
|
807
815
|
return row ? mapRow(row) : null;
|
|
808
|
-
} catch {
|
|
816
|
+
} catch (error) {
|
|
817
|
+
if (opts.strict) throw error;
|
|
809
818
|
return null;
|
|
810
819
|
}
|
|
811
820
|
}
|
|
@@ -176,10 +176,9 @@ export function describePtyRunState(
|
|
|
176
176
|
...(checkoutHead ? { checkoutHead } : {}),
|
|
177
177
|
...(row && row.kind === 'tui' ? { startedAt: row.startedAt } : {}),
|
|
178
178
|
});
|
|
179
|
-
const
|
|
179
|
+
const turnView = ptyTurnViewOfRow(row);
|
|
180
180
|
return {
|
|
181
|
-
|
|
182
|
-
...(turn !== 'unknown' && row?.turnAt ? { turnAt: row.turnAt } : {}),
|
|
181
|
+
...turnView,
|
|
183
182
|
build: build ?? null,
|
|
184
183
|
installed: installed.sha ?? null,
|
|
185
184
|
stale: staleness.stale,
|
|
@@ -191,6 +190,38 @@ export function shortSha(sha: string | null | undefined): string {
|
|
|
191
190
|
return sha ? sha.slice(0, 7) : '-';
|
|
192
191
|
}
|
|
193
192
|
|
|
193
|
+
/** 행의 자기보고 턴만 — 설치본·git 판독과 «무관»하다(판독이 실패해도 기록된 턴은 그대로 읽힌다). 죽은 행·기록 없음 = unknown. */
|
|
194
|
+
export function ptyTurnViewOfRow(row: Pick<PtyManifestRow, 'alive' | 'turnState' | 'turnAt'> | null | undefined): Pick<PtyRunStateView, 'turn' | 'turnAt'> {
|
|
195
|
+
const turn: PtyTurnView = row?.alive && row.turnState ? row.turnState : 'unknown';
|
|
196
|
+
return { turn, ...(turn !== 'unknown' && row?.turnAt ? { turnAt: row.turnAt } : {}) };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ── 턴 칸(H10 · `pty list` 가 화면 긁기 없이 «지금 턴 중인가·얼마나 됐나»를 낸다) ──
|
|
200
|
+
|
|
201
|
+
export interface PtyTurnCell {
|
|
202
|
+
readonly state: PtyTurnView;
|
|
203
|
+
/** 지금 턴 상태로 바뀐 시각(epoch ms) — 모르면 null. */
|
|
204
|
+
readonly at: number | null;
|
|
205
|
+
/** 지금 턴 상태에 머문 초(busy 면 «이 턴 경과»). ⛔ 모르면 null — 「0초」와 「못 잼」은 다른 값이다. */
|
|
206
|
+
readonly sinceSec: number | null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export const UNKNOWN_TURN_CELL: PtyTurnCell = { state: 'unknown', at: null, sinceSec: null };
|
|
210
|
+
|
|
211
|
+
/** `turnAt` 은 턴 값이 «바뀔 때만» 적힌다(`reportTuiTurn` 이 같은 값을 거른다) ⇒ now−turnAt = 그 상태의 경과. */
|
|
212
|
+
export function describeTurnCell(view: Pick<PtyRunStateView, 'turn' | 'turnAt'>, now: number): PtyTurnCell {
|
|
213
|
+
if (view.turn === 'unknown' || view.turnAt === undefined || !Number.isFinite(view.turnAt) || view.turnAt <= 0) {
|
|
214
|
+
return { state: view.turn, at: null, sinceSec: null };
|
|
215
|
+
}
|
|
216
|
+
return { state: view.turn, at: view.turnAt, sinceSec: Math.max(0, Math.floor((now - view.turnAt) / 1000)) };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** 표 한 칸 값: `busy,42s` · `idle,300s` · 시각 모름 `busy,?s` · 턴 모름 `?`. */
|
|
220
|
+
export function formatTurnCell(cell: PtyTurnCell): string {
|
|
221
|
+
if (cell.state === 'unknown') return '?';
|
|
222
|
+
return `${cell.state},${cell.sinceSec === null ? '?' : cell.sinceSec}s`;
|
|
223
|
+
}
|
|
224
|
+
|
|
194
225
|
// ── 기록(TUI) ──
|
|
195
226
|
|
|
196
227
|
export interface TuiRunStateRecorderDeps {
|
|
@@ -18,7 +18,8 @@ export function classifySeatTrafficProposal(message: string): 'hold' | 'UNHOLD'
|
|
|
18
18
|
return null;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const
|
|
21
|
+
export const opSeatStatePath = (name: string): string => join(homedir(), 'elanous-hq', 'seat-state', 'OP', name);
|
|
22
|
+
const defaultPath = opSeatStatePath;
|
|
22
23
|
const missing = (error: unknown): boolean => (error as NodeJS.ErrnoException).code === 'ENOENT';
|
|
23
24
|
|
|
24
25
|
/** A missing installation has no signal; an invalid or unreadable one cannot authorize a checklist launch. */
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { linesOutsideFencedCode } from '../self-implement/goal-author.js';
|
|
14
14
|
import { existsSync } from 'node:fs';
|
|
15
15
|
import { debug } from '../debug/log.js';
|
|
16
|
+
import { appendRunLedgerEntry, runLedgerDir } from '../self-implement/run-ledger.js';
|
|
16
17
|
import { recordAuthoredGoal } from './authored-goal-trace.js';
|
|
17
18
|
import { findAskPathCandidates, type AskPathCandidateOptions } from './ask-path-candidates.js';
|
|
18
19
|
import { retiredEntranceNotice, type EntranceDeclaration } from './entrance-registry.js';
|
|
@@ -849,6 +850,15 @@ export async function runAskLaunchFlow(
|
|
|
849
850
|
const entrance = input.entrance;
|
|
850
851
|
const askStartedAt = deps.now();
|
|
851
852
|
const selfResolveClarifications = resolveSelfResolveClarifications(input.selfResolveClarifications);
|
|
853
|
+
// The Pod's logs.db disappears with the Job. Journal the two auto-authoring boundaries in its
|
|
854
|
+
// existing run ledger so the host's terminal collection can reemit them even on a failed Job.
|
|
855
|
+
const recordPodAuthoring = (event: 'ask-auto-target-candidates' | 'ask-auto-reauthor', data: Record<string, unknown>): void => {
|
|
856
|
+
if (process.env.ELANOUS_SUBSTRATE !== 'pod') return;
|
|
857
|
+
const runId = process.env.ELANOUS_RUN_ID ?? input.runId;
|
|
858
|
+
if (!runId) return;
|
|
859
|
+
try { appendRunLedgerEntry({ runId, event, data }, runLedgerDir()); }
|
|
860
|
+
catch (error) { deps.log('ask-auto-author-ledger-unavailable', { event, reason: error instanceof Error ? error.message : String(error) }, 'warn'); }
|
|
861
|
+
};
|
|
852
862
|
deps.log('harness.entrance', {
|
|
853
863
|
entrance: entrance.id,
|
|
854
864
|
entranceStatus: entrance.status,
|
|
@@ -1082,6 +1092,7 @@ export async function runAskLaunchFlow(
|
|
|
1082
1092
|
selection: { path: selectedPath ?? null, reason: selectionReason },
|
|
1083
1093
|
...(candidates.error === undefined ? {} : { error: candidates.error }),
|
|
1084
1094
|
}, candidates.error === undefined && selectedPath ? 'info' : 'warn');
|
|
1095
|
+
recordPodAuthoring('ask-auto-target-candidates', { candidatePathCount: candidates.paths.length, selected: Boolean(selectedPath), outcome: selectionReason });
|
|
1085
1096
|
}
|
|
1086
1097
|
const selectedPaths = candidates.selection?.path
|
|
1087
1098
|
? [candidates.selection.path]
|
|
@@ -1093,6 +1104,7 @@ export async function runAskLaunchFlow(
|
|
|
1093
1104
|
} else {
|
|
1094
1105
|
deps.print('[ask] ⑴ ⚠️ 자동 재저작에 넣을 대상 경로 후보가 없다. 대상 경로를 확인해야 한다');
|
|
1095
1106
|
}
|
|
1107
|
+
recordPodAuthoring('ask-auto-reauthor', { candidatePathCount: candidates.paths.length, firstPathCount: first.decision.result.paths.length, outcome: 'started' });
|
|
1096
1108
|
authored = await authorAndPreflight(selectedPaths.length > 0
|
|
1097
1109
|
? `대상 경로: ${selectedPaths.join(' · ')}\n${input.askText}`
|
|
1098
1110
|
: input.askText, selectedPaths);
|
|
@@ -1104,6 +1116,7 @@ export async function runAskLaunchFlow(
|
|
|
1104
1116
|
secondPathCount: authored.decision.result.paths.length,
|
|
1105
1117
|
helped: authored.decision.shouldLaunch,
|
|
1106
1118
|
}, authored.decision.shouldLaunch ? 'info' : 'warn');
|
|
1119
|
+
recordPodAuthoring('ask-auto-reauthor', { candidatePathCount: candidates.paths.length, firstPathCount: first.decision.result.paths.length, secondPathCount: authored.decision.result.paths.length, helped: authored.decision.shouldLaunch });
|
|
1107
1120
|
}
|
|
1108
1121
|
|
|
1109
1122
|
const { goalFile: effectiveGoalFile, decision } = authored;
|