elanous 0.2.27 → 0.2.28
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/{1454-a9c338f5bb556f42.js → 1454-29d1d773d5996a49.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{599-a914cc0468f56143.js → 599-56f2c0a5cc4a7295.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-5ab1e47978577c5e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-07110da72c45e68f.js → page-24453f6a302816b4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-d2d76d4a7eae91bd.js → page-6c131d30bb1cf871.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-fa46d4588e8442c2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-e5a9302c105725a3.js → page-dc0772b11861af51.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-66ad46ed0279feb9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-5159dc8752c6cd29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-c5a2478861c08b87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-f722a9dec084e08b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/{page-6a16a48d1aa6e91f.js → page-256ffd365d32e100.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e0de78ae7b99f01c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-cfbdc44b708d9801.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-39f82acf619cca23.js → page-4c741eb760f2f77c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-7c2c8f81b185f9c0.js → page-e9ba98f5f1440a21.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/{page-ae7aff07e32676bb.js → page-dafc901bb1b72b5f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-482c60c1bc540d0a.js → page-2f6f303fa114ec24.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-7cb996c1aa08b6f5.js → layout-1634081c5272006c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/{page-623e08fe4477d606.js → page-ced0bcfc6542b3fc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/{page-db460cd8aecb8490.js → page-bb1f05cd51c38944.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/{page-ff27902d0b11ef4f.js → page-646b50f63b6fe732.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-db069b48a59c8c00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-1917ea87ea71ab6b.js → page-f736324afa2117c1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-25a302273b054fd7.js → page-6f131b51193a36f9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-670510eda8b9443f.js → not-found-435f5e2faf02bc01.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-497e0eedf20e7890.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-a8d28fdf995e16ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-4bb7556e2e97a750.js → page-8966452228753c5e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fd5d18656cb432b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-5663779cb1a37609.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/{page-4eb96fc7241c2456.js → page-66a67eb19ebd976b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-3b492aa78cf3c4a6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-0d64ff212dc55d03.js → page-c71267e7681da982.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-7f096248993fc2d4.js → page-a21d1e1797172692.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-e47fc72049149a38.js → page-cb249e81ed339318.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-f7d808e31e005895.js → page-781cfc5114e097da.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-5c2b24b5496dad73.js → page-478157214db6a023.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-76cdb531668b5191.js → page-86141f12cf090b7f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-7256c73bb1c36751.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-5c738ca7adef3de5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-0138c8091ec17224.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-cc2f24f16d85e735.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/{page-dab163b338e3b966.js → page-979d318a534e92da.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-182d394f7e6e096b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-0260ffc7eda50db1.js → page-21050928a697ed7a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-44b9d807005a9030.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-99c4e6c7345a9156.js → page-601fb77340e63c9a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-75d3fb5d47af0edf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-11bf9e5725a72c22.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +13 -13
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +13 -13
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +13 -13
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +13 -13
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +13 -13
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +13 -13
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +13 -13
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +13 -13
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +13 -13
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +13 -13
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +13 -13
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +13 -13
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +13 -13
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +13 -13
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +13 -13
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +13 -13
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +13 -13
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +13 -13
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +13 -13
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +13 -13
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +13 -13
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +13 -13
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +13 -13
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +13 -13
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +13 -13
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +13 -13
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +13 -13
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +13 -13
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +14 -14
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +13 -13
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +13 -13
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +13 -13
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +13 -13
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +13 -13
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +13 -13
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +13 -13
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +13 -13
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +13 -13
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +13 -13
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +13 -13
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +13 -13
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +13 -13
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +13 -13
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +13 -13
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +13 -13
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +13 -13
- package/graphs/implement-loop.yaml +38 -1
- package/graphs/steward/recipes.yaml +6 -0
- package/graphs/steward/release-day.yaml +23 -0
- package/graphs/steward/steward.yaml +9 -1
- package/graphs/work/code-pr.yaml +23 -0
- package/package.json +1 -1
- package/scripts/browser-observe-cycle.ts +76 -2
- package/scripts/ci-typecheck-changed.ts +50 -17
- package/scripts/detect-test-interference.ts +31 -1
- package/scripts/release-loop/docs-node.ts +51 -22
- package/scripts/release-loop/gate-node.ts +37 -4
- package/scripts/release-loop/node-verdict.ts +3 -1
- package/scripts/release-loop/publish-node.ts +3 -0
- package/scripts/release-loop/tui-node-checks.ts +5 -2
- package/scripts/skills/public-check.ts +5 -2
- package/scripts/test-deterministic.ts +520 -37
- package/scripts/ux-sim.ts +7 -1
- package/scripts/verify-kpack6-live.ts +230 -0
- package/src/acp/codex-plugins.ts +17 -0
- package/src/agent/task-notification.ts +2 -1
- package/src/agent-mission/in-agent-plugin.ts +8 -3
- package/src/agent-substrate/self-review-cli.ts +34 -10
- package/src/briefing/brief-items.ts +151 -35
- package/src/budget/llm-cost.ts +25 -2
- package/src/budget/usage-outcomes.ts +32 -0
- package/src/claims/claims-ledger.ts +23 -0
- package/src/claims/claims-render.ts +22 -2
- package/src/cli/brief-cli.ts +131 -10
- package/src/cli/claims-cli.ts +24 -4
- package/src/cli/msg-cli.ts +165 -9
- package/src/cli/plugin-cli.ts +11 -3
- package/src/cli/pr-cli.ts +80 -18
- package/src/cli/pr-land-refusal.ts +20 -0
- package/src/cli/release-cli.ts +2 -0
- package/src/cli/rfc-cli.ts +24 -0
- package/src/cli/tailscale-serve.ts +5 -8
- package/src/cli/tasks-cli.ts +393 -26
- package/src/cli/usage-cli.ts +9 -4
- package/src/dashboard/first-screen-band.ts +63 -13
- package/src/dashboard/index.ts +8 -3
- package/src/dashboard/skill-detection-runtime.ts +24 -4
- package/src/domains/morning-report.ts +3 -1
- package/src/github/pr-labels.ts +10 -4
- package/src/graph-runner/runner.ts +101 -14
- package/src/harness/harness-clean.ts +21 -3
- package/src/harness/harness-cli-command.ts +22 -6
- package/src/harness/harness-pod-dispatch.ts +11 -4
- package/src/hq/seats.ts +17 -3
- package/src/index.ts +34 -5
- package/src/intelligence-map/model-bench-scores.ts +102 -0
- package/src/loops/orchestrator/inbox.ts +73 -0
- package/src/loops/orchestrator/resync-shadow.ts +100 -12
- package/src/loops/orchestrator/tick.ts +73 -2
- package/src/maturity/feature-maturity.ts +1 -1
- package/src/msg/msg-sse-server.ts +141 -0
- package/src/msg/msg-store.ts +84 -14
- package/src/patents/ledger.ts +128 -0
- package/src/plugins/install/market-fetch.ts +32 -1
- package/src/pod-lease/admission.ts +116 -41
- package/src/release-loop/checklist.ts +11 -0
- package/src/release-loop/merged-pr-checklist.ts +24 -5
- package/src/release-loop/rubric.ts +70 -0
- package/src/rfc-registry/registry.ts +88 -0
- package/src/seat-dispatch/seat-request-ledger.ts +4 -0
- package/src/seat-loop/knowhow-lint.ts +233 -0
- package/src/seat-loop/seat-pack.ts +16 -4
- package/src/seat-loop/traffic-signal.ts +7 -0
- package/src/self-dev/ask-launch-flow.ts +48 -8
- package/src/self-dev/ask-path-candidates.ts +69 -14
- package/src/self-dev/decompose-proposal.ts +43 -1
- package/src/self-dev/graph-journey-nodes.ts +2 -0
- package/src/self-dev/launch-preflight-text.ts +5 -1
- package/src/self-dev/launch-preflight.ts +143 -10
- package/src/self-implement/gate-cli.ts +6 -2
- package/src/self-implement/goal-author.ts +2 -1
- package/src/self-implement/goal-new-paths.ts +4 -5
- package/src/self-implement/graph-authority.ts +17 -3
- package/src/self-implement/graph-yaml.ts +15 -7
- package/src/self-implement/headless-elanous-driver.ts +16 -1
- package/src/self-implement/host-regate.ts +44 -9
- package/src/self-implement/orchestrator.ts +3 -10
- package/src/self-implement/rework-policy.ts +80 -2
- package/src/self-implement/speed-claims.ts +145 -6
- package/src/skills/tools/persistent-grounding.ts +59 -4
- package/src/skills/tools/shell.ts +79 -1
- package/src/steward/rejudge-tick.ts +12 -1
- package/src/steward/release-day.ts +158 -0
- package/src/steward/resync-tick.ts +123 -0
- package/src/task-agent/actions.ts +1 -0
- package/src/task-agent/adopt-pr.ts +140 -0
- package/src/task-agent/board.ts +11 -1
- package/src/task-agent/card-evidence.ts +37 -5
- package/src/task-agent/cover.ts +84 -17
- package/src/task-agent/failure-notify.ts +61 -0
- package/src/task-agent/green-sweep.ts +170 -0
- package/src/task-agent/land-worktree.ts +52 -5
- package/src/task-agent/live-move-history.ts +1 -0
- package/src/task-agent/live-moves.ts +43 -12
- package/src/task-agent/parent-host.ts +1 -1
- package/src/task-agent/postland-card.ts +139 -0
- package/src/task-agent/rejudge-on-head.ts +106 -7
- package/src/task-agent/remote-rejudge-bind.ts +62 -0
- package/src/task-agent/repair-branch-cas.ts +182 -0
- package/src/task-agent/repair-cycle.ts +275 -0
- package/src/task-agent/repair-next-move.ts +31 -0
- package/src/task-agent/repair-review.ts +24 -0
- package/src/task-agent/repair-worker.ts +87 -0
- package/src/task-agent/review-result-capture.ts +2 -1
- package/src/task-agent/shadow.ts +13 -7
- package/src/task-agent/task-hand.ts +115 -6
- package/src/task-orchestrator/surfaces/pod-repair-commit.ts +96 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +70 -12
- package/src/telegram.ts +30 -1
- package/src/user-config.ts +8 -1
- package/src/ux-sim/fault-sqlite-busy.ts +50 -0
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-a1ce306364f08da9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-30007380f2e2a3c1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-b93c04b5311c97ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-718a49e86bf25f9c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-20662900d6b4b455.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-f707a509515ac50c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-fcb06e305f515527.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-6acf5ec4e6c6d1af.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-8397a5c6ca569bf4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-d73c3f1e00d93986.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-65caacf54389081e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-77e04b4f8762c666.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-15c0a4095baddfba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-63e272a4cd3b6634.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e2b63345bba1a11f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-d376e0c83edc041a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-d6db55fe2c04d128.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-87b88f551f421d8b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-08019efef111ed2e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-980ce6fe36ad95eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-0ab42e0f05823013.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ad9220422d20ba33.js +0 -1
- /package/apps/pwa/out/_next/static/{Ev2jivDMTSoEc-5rIXdcy → y1ltt7oCZxVpC6zVPhZ2O}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{Ev2jivDMTSoEc-5rIXdcy → y1ltt7oCZxVpC6zVPhZ2O}/_ssgManifest.js +0 -0
|
@@ -106,6 +106,8 @@ export interface FirstScreenBandInput {
|
|
|
106
106
|
readonly width: number;
|
|
107
107
|
readonly daemon: boolean;
|
|
108
108
|
readonly link?: TuiDaemonLink;
|
|
109
|
+
/** Renders the three available-action rows and the example row in this locale; `undefined` keeps the historical band. */
|
|
110
|
+
readonly locale?: 'ko' | 'en';
|
|
109
111
|
readonly pwa?: {
|
|
110
112
|
readonly tailnet?: string | null;
|
|
111
113
|
readonly lan?: string | null;
|
|
@@ -138,10 +140,37 @@ function fitCells(text: string, limit: number): string {
|
|
|
138
140
|
return result;
|
|
139
141
|
}
|
|
140
142
|
|
|
143
|
+
// First-run guidance: three rows of available actions plus one immediately-typeable
|
|
144
|
+
// example row, per locale. Every row names something the empty TUI can really do.
|
|
145
|
+
const GUIDANCE: Record<'ko' | 'en', {
|
|
146
|
+
readonly actions: readonly [string, string, string];
|
|
147
|
+
readonly example: string;
|
|
148
|
+
}> = {
|
|
149
|
+
ko: {
|
|
150
|
+
actions: [
|
|
151
|
+
'· 대화 — 무엇이든 자연어로 물어보고 시켜보세요',
|
|
152
|
+
'· 명령 — /help 로 전체 명령을 보세요',
|
|
153
|
+
'· 스킬 — /run-skill <이름> 으로 설치된 스킬을 돌리세요',
|
|
154
|
+
],
|
|
155
|
+
example: '바로 입력해 보세요: 무엇을 할 수 있어?',
|
|
156
|
+
},
|
|
157
|
+
en: {
|
|
158
|
+
actions: [
|
|
159
|
+
'· Chat — ask for anything in plain language',
|
|
160
|
+
'· Commands — /help lists every command',
|
|
161
|
+
'· Skills — /run-skill <name> runs an installed skill',
|
|
162
|
+
],
|
|
163
|
+
example: 'Try typing: what can you do?',
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
141
167
|
/** A startup transcript band; never includes credentials or a partial address. */
|
|
142
168
|
export function buildFirstScreenBand(input: FirstScreenBandInput): string[] {
|
|
143
169
|
const width = Number.isFinite(input.width) ? Math.max(0, Math.floor(input.width)) : 0;
|
|
144
|
-
|
|
170
|
+
// The historical band is mixed: the link row speaks Korean, the remaining lines English.
|
|
171
|
+
// An explicit locale unifies them; `undefined` must keep every pinned line byte-identical.
|
|
172
|
+
const linkKorean = input.locale !== 'en';
|
|
173
|
+
const plainKorean = input.locale === 'ko';
|
|
145
174
|
const address = [input.pwa?.tailnet, input.pwa?.lan, input.pwa?.loopback]
|
|
146
175
|
.map((candidate) => {
|
|
147
176
|
if (!candidate) return null;
|
|
@@ -154,32 +183,53 @@ export function buildFirstScreenBand(input: FirstScreenBandInput): string[] {
|
|
|
154
183
|
}
|
|
155
184
|
})
|
|
156
185
|
.find((candidate) => candidate !== null);
|
|
157
|
-
const fallback = input.pwa === undefined
|
|
186
|
+
const fallback = input.pwa === undefined
|
|
187
|
+
? (plainKorean ? 'PWA: 확인 안 함' : 'PWA: not checked')
|
|
188
|
+
: (plainKorean ? 'PWA: 사용할 수 없음' : 'PWA: unavailable');
|
|
158
189
|
const addressLine = address ? `PWA: ${address}` : fallback;
|
|
159
190
|
|
|
160
191
|
const daemonLine = (() => {
|
|
161
192
|
const link = input.link;
|
|
162
|
-
if (!link) return
|
|
163
|
-
|
|
193
|
+
if (!link) return plainKorean
|
|
194
|
+
? `elanous | 데몬: ${input.daemon ? '켜짐' : '꺼짐'}`
|
|
195
|
+
: `elanous | daemon: ${input.daemon ? 'online' : 'offline'}`;
|
|
196
|
+
if (link.status === 'checking') return linkKorean ? 'elanous | 데몬: 확인 중' : 'elanous | daemon: checking';
|
|
164
197
|
if (link.status === 'connected') {
|
|
165
198
|
let address: string;
|
|
166
199
|
try {
|
|
167
200
|
const url = new URL(link.address);
|
|
168
201
|
address = ['http:', 'https:'].includes(url.protocol) && url.hostname ? url.origin : '';
|
|
169
202
|
} catch { address = ''; }
|
|
170
|
-
const
|
|
171
|
-
|
|
203
|
+
const bare = linkKorean ? 'elanous | 데몬: 연결됨' : 'elanous | daemon: connected';
|
|
204
|
+
const line = `${bare}${address ? ` (${address})` : ''}`;
|
|
205
|
+
return cellWidth(line) <= width ? line : bare;
|
|
206
|
+
}
|
|
207
|
+
if (link.status === 'error') {
|
|
208
|
+
return linkKorean ? `elanous | 데몬: ${link.stage} 조회 오류` : `elanous | daemon: ${link.stage} lookup error`;
|
|
172
209
|
}
|
|
173
|
-
|
|
174
|
-
|
|
210
|
+
const bare = linkKorean
|
|
211
|
+
? `elanous | 데몬: ${link.status === 'absent' ? '없음' : '응답 없음'}`
|
|
212
|
+
: `elanous | daemon: ${link.status === 'absent' ? 'absent' : 'unhealthy'}`;
|
|
175
213
|
if (!link.startCommand || CONTROL_CHARS.test(link.startCommand)) return bare;
|
|
176
|
-
const line = `${bare} ·
|
|
214
|
+
const line = `${bare} · ${linkKorean ? '시작' : 'start'}: ${link.startCommand}`;
|
|
177
215
|
return cellWidth(line) <= width ? line : bare;
|
|
178
216
|
})();
|
|
179
217
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
218
|
+
const hintLine = 'Type a message to begin | /help for commands';
|
|
219
|
+
|
|
220
|
+
const statusRows = [
|
|
221
|
+
fitCells(daemonLine, width),
|
|
222
|
+
cellWidth(addressLine) <= width
|
|
223
|
+
? addressLine
|
|
224
|
+
: fitCells(address ? (plainKorean ? 'PWA: 주소 확인됨' : 'PWA: address available') : fallback, width),
|
|
184
225
|
];
|
|
226
|
+
// The historical band folds its guidance into one mixed-language hint row.
|
|
227
|
+
// An explicit locale renders the same guidance as three available-action rows
|
|
228
|
+
// plus one immediately-typeable example row, in that locale — on every screen
|
|
229
|
+
// state (empty, checking, absent, unhealthy, error) because none of these
|
|
230
|
+
// rows depends on the daemon link or the PWA lookup.
|
|
231
|
+
const guidanceRows = input.locale
|
|
232
|
+
? [...GUIDANCE[input.locale].actions, GUIDANCE[input.locale].example].map((row) => fitCells(row, width))
|
|
233
|
+
: [fitCells(hintLine, width)];
|
|
234
|
+
return [...statusRows, ...guidanceRows];
|
|
185
235
|
}
|
package/src/dashboard/index.ts
CHANGED
|
@@ -643,6 +643,7 @@ import { activeCodexAccountView } from '../oauth/codex-account-store.js';
|
|
|
643
643
|
import { buildDashboardStatusLines } from './dashboard-status-lines.js';
|
|
644
644
|
import { readSlashContextNow, seatsNowLine } from '../context-bus/context-now-surfaces.js';
|
|
645
645
|
import { buildFirstScreenBand, resolveTuiDaemonLink, type TuiDaemonLink } from './first-screen-band.js';
|
|
646
|
+
import { detectLocale } from '../expression/locale.js';
|
|
646
647
|
import { runNexusShow, type NexusShowResult } from '../cli/nexus-show.js';
|
|
647
648
|
import {
|
|
648
649
|
workingDirSegment, sessionCwdSegment, modelSegment, gitSegment,
|
|
@@ -1896,18 +1897,20 @@ export function renderDashboardFirstScreenBand(input: {
|
|
|
1896
1897
|
readonly history: ReadonlyArray<{ role: string }>;
|
|
1897
1898
|
readonly lines: string[];
|
|
1898
1899
|
readonly width: number;
|
|
1900
|
+
/** Renders the localized guidance rows; `undefined` keeps the historical mixed band. */
|
|
1901
|
+
readonly locale?: 'ko' | 'en';
|
|
1899
1902
|
readonly draw: () => void;
|
|
1900
1903
|
readonly observe: (data: { daemon: boolean; hasPwa: boolean; hasTailnet: boolean }) => void;
|
|
1901
1904
|
readonly show?: () => Promise<Pick<NexusShowResult, 'status' | 'urls'>>;
|
|
1902
1905
|
readonly daemonLink?: () => Promise<TuiDaemonLink>;
|
|
1903
1906
|
}): Promise<void> {
|
|
1904
1907
|
if (input.history.some((message) => message.role !== 'system')) return Promise.resolve();
|
|
1905
|
-
const band = buildFirstScreenBand({ width: input.width, daemon: false, link: { status: 'checking' } });
|
|
1908
|
+
const band = buildFirstScreenBand({ width: input.width, daemon: false, link: { status: 'checking' }, locale: input.locale });
|
|
1906
1909
|
input.lines.unshift(...band);
|
|
1907
1910
|
const daemonLink = input.daemonLink ?? resolveTuiDaemonLink;
|
|
1908
1911
|
const linkPending = Promise.resolve().then(() => daemonLink()).catch((): TuiDaemonLink => ({ status: 'error', stage: 'health' })).then((link) => {
|
|
1909
1912
|
if (input.lines[0] !== band[0]) return;
|
|
1910
|
-
input.lines[0] = buildFirstScreenBand({ width: input.width, daemon: false, link })[0]!;
|
|
1913
|
+
input.lines[0] = buildFirstScreenBand({ width: input.width, daemon: false, link, locale: input.locale })[0]!;
|
|
1911
1914
|
try { input.draw(); } catch { /* screen may have closed */ }
|
|
1912
1915
|
});
|
|
1913
1916
|
const show = input.show ?? (() => runNexusShow({ format: 'json', out: { log: () => {}, error: () => {} } }));
|
|
@@ -1921,7 +1924,7 @@ export function renderDashboardFirstScreenBand(input: {
|
|
|
1921
1924
|
const hasTailnet = !!pwa?.tailnet;
|
|
1922
1925
|
observe({ daemon, hasPwa, hasTailnet });
|
|
1923
1926
|
if (input.lines[1] !== band[1]) return;
|
|
1924
|
-
input.lines[1] = buildFirstScreenBand({ width: input.width, daemon, pwa })[1]!;
|
|
1927
|
+
input.lines[1] = buildFirstScreenBand({ width: input.width, daemon, pwa, locale: input.locale })[1]!;
|
|
1925
1928
|
try { input.draw(); } catch { /* screen may have closed */ }
|
|
1926
1929
|
}).catch(() => {
|
|
1927
1930
|
observe({ daemon: false, hasPwa: false, hasTailnet: false });
|
|
@@ -15703,6 +15706,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
15703
15706
|
history: chat.history,
|
|
15704
15707
|
lines: chatLines,
|
|
15705
15708
|
width: Math.max(0, termSize().cols - 4),
|
|
15709
|
+
// The band ships ko/en only; any other detected locale keeps the English rows.
|
|
15710
|
+
locale: detectLocale() === 'ko' ? 'ko' : 'en',
|
|
15706
15711
|
draw: () => draw(),
|
|
15707
15712
|
observe: (data) => { try { debug.log('dashboard.first-screen-band', 'resolved', data); } catch { /* observation is best-effort */ } },
|
|
15708
15713
|
});
|
|
@@ -7,16 +7,26 @@ import {
|
|
|
7
7
|
} from '../skills/router.js';
|
|
8
8
|
import type { SkillIndexEntry } from '../skills/index.js';
|
|
9
9
|
import type { SkillTier } from '../skills/runner.js';
|
|
10
|
-
import
|
|
10
|
+
import * as llm from '../llm.js';
|
|
11
|
+
import { resolveRoleLlm } from '../user-config.js';
|
|
12
|
+
import { debug } from '../debug/log.js';
|
|
11
13
|
|
|
12
14
|
export interface DashboardSkillDetectionRuntimeDeps {
|
|
13
15
|
llmFallback: boolean;
|
|
14
16
|
keywordScoreThreshold: number;
|
|
15
17
|
llmConfidenceThreshold: number;
|
|
16
18
|
streamLLM: (
|
|
17
|
-
messages: LLMMessage[],
|
|
19
|
+
messages: llm.LLMMessage[],
|
|
18
20
|
onDelta: (chunk: string, full: string) => void,
|
|
19
|
-
opts: {
|
|
21
|
+
opts: {
|
|
22
|
+
signal?: AbortSignal;
|
|
23
|
+
maxTokens?: number;
|
|
24
|
+
temperature?: number;
|
|
25
|
+
/** classify role pin — resolveRoleLlm('classify')가 고른 모델·프로바이더. */
|
|
26
|
+
model?: string;
|
|
27
|
+
provider?: (typeof llm.PROVIDERS)[string];
|
|
28
|
+
usageRole?: 'classify';
|
|
29
|
+
},
|
|
20
30
|
) => Promise<string>;
|
|
21
31
|
/** Active model tier — enables tier-aware full-menu LLM routing in
|
|
22
32
|
* detectLLM (T1 frontier models route the whole menu when keyword is
|
|
@@ -69,10 +79,20 @@ export async function detectDashboardSkillRoute(
|
|
|
69
79
|
if (!deps.llmFallback) return keywordDetection;
|
|
70
80
|
|
|
71
81
|
const classify = async (prompt: string, signal?: AbortSignal): Promise<LLMClassifyResult> => {
|
|
82
|
+
const resolved = resolveRoleLlm('classify');
|
|
83
|
+
const provider = llm.PROVIDERS[resolved.provider];
|
|
84
|
+
if (!provider) throw new Error(`Unknown classify provider: ${resolved.provider}`);
|
|
85
|
+
debug.log('llm.role-pin', 'resolved', {
|
|
86
|
+
site: 'skill-detection-runtime',
|
|
87
|
+
role: 'classify',
|
|
88
|
+
model: resolved.model,
|
|
89
|
+
provider: resolved.provider,
|
|
90
|
+
source: resolved.source,
|
|
91
|
+
});
|
|
72
92
|
const raw = await deps.streamLLM(
|
|
73
93
|
[{ role: 'user', content: prompt }],
|
|
74
94
|
() => {},
|
|
75
|
-
{ signal, maxTokens: 120, temperature: 0 },
|
|
95
|
+
{ signal, maxTokens: 120, temperature: 0, model: resolved.model, provider, usageRole: 'classify' },
|
|
76
96
|
);
|
|
77
97
|
return parseClassifier(raw);
|
|
78
98
|
};
|
|
@@ -22,6 +22,8 @@ export interface MorningReportOpts {
|
|
|
22
22
|
heatmapImageUrl?: string;
|
|
23
23
|
/** S3 상세 리포트 업로드 여부(기본 true). 테스트는 false 로 부작용 차단. */
|
|
24
24
|
upload?: boolean;
|
|
25
|
+
/** 시장서사 LLM model 명시(A/B). 미지정이면 지금과 같은 기본 provider 경로(providerFor(cfg)). */
|
|
26
|
+
model?: string;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/** 밤사이 소스를 종합한 아침 에센셜(텔레그램) 조립. 상세는 S3 링크로. */
|
|
@@ -38,7 +40,7 @@ export async function composeMorningReport(now: Date = new Date(), opts: Morning
|
|
|
38
40
|
catch { uploaded = null; }
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
const narrative = opts.narrate ? await synthesizeEssentialBrief(md) : '';
|
|
43
|
+
const narrative = opts.narrate ? await synthesizeEssentialBrief(md, { model: opts.model }) : '';
|
|
42
44
|
|
|
43
45
|
// 결론-우선 상단 블록(매력도 Δ · 섹터순환 · 캡스톤) — PWA 대시보드 데이터 재사용. fail-soft.
|
|
44
46
|
let heatmap = null, capstone = null;
|
package/src/github/pr-labels.ts
CHANGED
|
@@ -3,6 +3,12 @@ export const IDEA_APPROVAL_LIMIT = 10;
|
|
|
3
3
|
export const IDEA_STALE_DAYS = 7;
|
|
4
4
|
export const STALLED_DRAFT_HOURS = 24;
|
|
5
5
|
export const CLAIM_IDLE_HOURS = 6;
|
|
6
|
+
export const PR_LABEL_NAMES = {
|
|
7
|
+
running: 'elanous:running',
|
|
8
|
+
stalled: 'elanous:stalled',
|
|
9
|
+
superseded: 'elanous:superseded',
|
|
10
|
+
needsRebase: 'elanous:needs-rebase',
|
|
11
|
+
} as const;
|
|
6
12
|
|
|
7
13
|
export type PrLabelAxis = 'state' | 'addon' | 'origin' | 'risk';
|
|
8
14
|
export type PrLabelApplier = 'harness' | 'launcher' | 'sweeper' | 'session' | 'human';
|
|
@@ -24,14 +30,14 @@ export interface PrLabelDefinition {
|
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
export const PR_LABELS = [
|
|
27
|
-
{ name:
|
|
28
|
-
{ name:
|
|
33
|
+
{ name: PR_LABEL_NAMES.running, axis: 'state', color: '1D76DB', description: 'Harness run or session is actively handling a draft PR', appliedBy: ['harness', 'session'], sweep: { action: 'mark-stalled', when: 'run-ended' } },
|
|
34
|
+
{ name: PR_LABEL_NAMES.stalled, axis: 'state', color: 'D93F0B', description: 'Incomplete draft requiring human attention', appliedBy: ['harness', 'sweeper'], sweep: { action: 'close', afterHours: STALLED_DRAFT_HOURS } },
|
|
29
35
|
{ name: 'elanous:idea-approval', axis: 'state', color: '0E8A16', description: 'Completed, ready PR awaiting human approval', appliedBy: ['launcher'], sweep: { action: 'none' } },
|
|
30
|
-
{ name:
|
|
36
|
+
{ name: PR_LABEL_NAMES.superseded, axis: 'state', color: '6A737D', description: 'Another PR has already merged the same work', appliedBy: ['sweeper', 'session'], sweep: { action: 'close' } },
|
|
31
37
|
{ name: 'elanous:keep', axis: 'addon', color: 'BFDADC', description: 'Human requested that the PR be kept open', appliedBy: ['human'], sweep: { action: 'exclude' } },
|
|
32
38
|
{ name: 'elanous:release-hold', axis: 'addon', color: 'D4C5F9', description: 'Exclude from release notes until verified in production', appliedBy: ['launcher', 'session', 'human'], sweep: { action: 'none' } },
|
|
33
39
|
{ name: 'elanous:idea-stale', axis: 'addon', color: 'FBCA04', description: 'Approval pending beyond the notification threshold', appliedBy: ['sweeper'], sweep: { action: 'notify', afterDays: IDEA_STALE_DAYS } },
|
|
34
|
-
{ name:
|
|
40
|
+
{ name: PR_LABEL_NAMES.needsRebase, axis: 'addon', color: 'E99695', description: 'Approval blocked by conflicts or failing checks', appliedBy: ['sweeper', 'session'], sweep: { action: 'block-approval' } },
|
|
35
41
|
{ name: 'elanous:bench', axis: 'addon', color: 'C5DEF5', description: 'Bench arm output: never landed or swept; the owner harvests the winner onto main', appliedBy: ['harness'], sweep: { action: 'none' } },
|
|
36
42
|
{ name: 'elanous:harvestable', axis: 'addon', color: 'FBCA04', description: 'Pod terminal marked this PR for harvest', appliedBy: ['harness'], sweep: { action: 'close', afterHours: STALLED_DRAFT_HOURS } },
|
|
37
43
|
{ name: 'elanous:from-harness', axis: 'origin', color: 'C5DEF5', description: 'Launched from a harness goal', appliedBy: ['harness'], sweep: { action: 'none' } },
|
|
@@ -27,6 +27,43 @@ import { releaseResumeAcceptedRegressions, releaseResumeGateExecutor, releaseRes
|
|
|
27
27
|
|
|
28
28
|
type BashRun = WorkflowDeps['runBash'];
|
|
29
29
|
const DEFAULT_BASH_TIMEOUT_MS = 5 * 60 * 1_000;
|
|
30
|
+
// Independent of graph YAML and recipe snapshots: an already-wrong saved binding must fail closed.
|
|
31
|
+
const RELEASE_NODE_COMMANDS: Readonly<Record<string, { recipe: string; command?: string }>> = {
|
|
32
|
+
'version-release': { recipe: 'cmd:version-release', command: 'bun scripts/release-loop/version-node.ts release --json' },
|
|
33
|
+
cutoff: { recipe: 'cmd:cutoff', command: 'bun scripts/release-loop/node-verdict.ts cutoff' },
|
|
34
|
+
'checklist-gate': { recipe: 'cmd:checklist-gate', command: 'bun scripts/release-loop/checklist-gate-node.ts' },
|
|
35
|
+
prefetch: { recipe: 'cmd:docs', command: 'bun scripts/release-loop/docs-node.ts --json' },
|
|
36
|
+
gate: { recipe: 'cmd:gate', command: 'bun scripts/release-loop/gate-node.ts --json' },
|
|
37
|
+
'mac-smoke': { recipe: 'cmd:mac-smoke', command: 'bun scripts/release-loop/mac-smoke-node.ts' },
|
|
38
|
+
'export-check': { recipe: 'cmd:export-check', command: 'bun scripts/release-loop/export-check-node.ts' },
|
|
39
|
+
pwa: { recipe: 'cmd:pwa', command: 'bun scripts/release-loop/node-verdict.ts pwa' },
|
|
40
|
+
prepare: { recipe: 'cmd:prepare', command: 'bun scripts/release-loop/prepare-node.ts' },
|
|
41
|
+
upgrade: { recipe: 'cmd:upgrade', command: 'bun scripts/release-loop/upgrade-node.ts' },
|
|
42
|
+
tui: { recipe: 'cmd:docs', command: 'bun scripts/release-loop/docs-node.ts --json' },
|
|
43
|
+
docs: { recipe: 'cmd:docs', command: 'bun scripts/release-loop/docs-node.ts --json' },
|
|
44
|
+
'known-issues': { recipe: 'cmd:known-issues', command: 'bun scripts/release-loop/known-issues-node.ts' },
|
|
45
|
+
'notes-check': { recipe: 'cmd:notes-check', command: 'bun scripts/announce-loop/check-node.ts --json' },
|
|
46
|
+
'auto-approve': { recipe: 'cmd:auto-approve', command: 'bun scripts/release-loop/auto-approve-node.ts' },
|
|
47
|
+
'approve-publish': { recipe: 'approval:publish' },
|
|
48
|
+
publish: { recipe: 'cmd:publish-command', command: 'bun scripts/release-loop/publish-node.ts' },
|
|
49
|
+
'npm-publish': { recipe: 'cmd:npm-publish', command: 'bun scripts/release-loop/npm-publish-node.ts' },
|
|
50
|
+
'docs-land': { recipe: 'cmd:docs-land', command: 'bun scripts/release-loop/docs-land-node.ts' },
|
|
51
|
+
verify: { recipe: 'cmd:verify', command: 'bun scripts/release-loop/verify-node.ts' },
|
|
52
|
+
'vault-note': { recipe: 'cmd:vault-note', command: 'bun scripts/release-loop/vault-note-node.ts' },
|
|
53
|
+
'release-story': { recipe: 'cmd:release-story', command: 'bun scripts/release-story/draft.ts --graph' },
|
|
54
|
+
'ops-upgrade': { recipe: 'cmd:ops-upgrade', command: 'bun scripts/release-loop/ops-upgrade-node.ts' },
|
|
55
|
+
'version-dev-bump': { recipe: 'cmd:version-dev-bump', command: 'bun scripts/release-loop/version-node.ts dev-bump --json' },
|
|
56
|
+
done: { recipe: 'none' },
|
|
57
|
+
failed: { recipe: 'none' },
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function assertReleaseCommandBinding(node: GraphNodeSpec, command: string | undefined, terminal: boolean): void {
|
|
61
|
+
const expected = RELEASE_NODE_COMMANDS[node.nodeId];
|
|
62
|
+
if (!expected || node.recipe !== expected.recipe || command !== expected.command ||
|
|
63
|
+
terminal !== (node.nodeId === 'done' || node.nodeId === 'failed')) {
|
|
64
|
+
throw new Error(`release node dispatch mismatch before execution: ${node.nodeId}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
30
67
|
|
|
31
68
|
export interface GraphRunState {
|
|
32
69
|
graphId: string;
|
|
@@ -54,7 +91,9 @@ export interface GraphRunState {
|
|
|
54
91
|
graphPath?: string;
|
|
55
92
|
variant?: { overlayId: string; goal: string; plan: GraphVariantPlan };
|
|
56
93
|
parentRunId?: string;
|
|
57
|
-
|
|
94
|
+
/** The entry chosen by a parent call; absent for existing single-entry runs. */
|
|
95
|
+
entryNodeId?: string;
|
|
96
|
+
unitCalls?: Array<{ node: string; unit: string; version: number; hash: string; childRunId: string; exit: string; steps: number; entry?: string }>;
|
|
58
97
|
growth?: GraphGrowth[];
|
|
59
98
|
growthRejections?: Array<{ from: string; outcome: string; reason: string }>;
|
|
60
99
|
growthPark?: { from: string; outcome: string; reason: string; decisionId: string; decisionRef: string; growth: GraphGrowth; command?: GrowthCommand; approval?: string };
|
|
@@ -84,7 +123,7 @@ export interface GraphRunOptions {
|
|
|
84
123
|
releaseGateExecutor?: { executor: string; host?: string };
|
|
85
124
|
useCurrentGraph?: boolean;
|
|
86
125
|
/** Internal unit invocation context, propagated only by runGraph. */
|
|
87
|
-
unitContext?: { parentRunId: string; ancestors: readonly string[]; remainingSteps: number };
|
|
126
|
+
unitContext?: { parentRunId: string; ancestors: readonly string[]; remainingSteps: number; entryNodeId?: string };
|
|
88
127
|
/**
|
|
89
128
|
* RELEASE-LEDGER-UNIVERSE — stamp every cmd: child with this run's ledger root (`ELANOUS_STATE_DIR`).
|
|
90
129
|
* Without it a child launched from a non-leader tree (`bun scripts/...` with cwd = that tree) re-resolves
|
|
@@ -404,7 +443,14 @@ function nextNode(edges: readonly GraphEdgeSpec[], nodeId: string, exitOutcome:
|
|
|
404
443
|
|
|
405
444
|
type ResolvedUnit = { path: string; template: GraphTemplateSpec; hash: string };
|
|
406
445
|
|
|
407
|
-
|
|
446
|
+
export class UnitNotFoundError extends Error {
|
|
447
|
+
constructor(call: string) {
|
|
448
|
+
super(`unit not found: ${call}`);
|
|
449
|
+
this.name = 'UnitNotFoundError';
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export function resolveUnit(call: string, callerPath: string): ResolvedUnit {
|
|
408
454
|
const [id, requested] = call.split('@');
|
|
409
455
|
const repo = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
410
456
|
const directories = [join(dirname(callerPath), 'units'), dirname(callerPath), join(repo, 'graphs', 'units'), join(getElanousConfigDir(), 'graphs', 'units')];
|
|
@@ -424,16 +470,24 @@ function resolveUnit(call: string, callerPath: string): ResolvedUnit {
|
|
|
424
470
|
if (matches.length > 1) throw new Error(`ambiguous unit: ${call}`);
|
|
425
471
|
if (matches.length) return matches[0]!;
|
|
426
472
|
}
|
|
427
|
-
throw new
|
|
473
|
+
throw new UnitNotFoundError(call);
|
|
428
474
|
}
|
|
429
475
|
|
|
430
|
-
function validateUnitCalls(graph: GraphTemplateSpec, path: string, ancestors: readonly string[] = []): Map<string, ResolvedUnit> {
|
|
476
|
+
export function validateUnitCalls(graph: GraphTemplateSpec, path: string, ancestors: readonly string[] = [], onMissing?: (call: string) => void): Map<string, ResolvedUnit> {
|
|
431
477
|
if (ancestors.includes(graph.graphId)) throw new Error(`unit cycle: ${[...ancestors, graph.graphId].join(' -> ')}`);
|
|
432
478
|
if (ancestors.length > 3) throw new Error('unit nesting exceeds depth 3');
|
|
433
479
|
const result = new Map<string, ResolvedUnit>();
|
|
434
480
|
for (const node of graph.nodes) {
|
|
435
481
|
if (node.kind !== 'subgraph' || node.call === undefined) continue;
|
|
436
|
-
|
|
482
|
+
let unit: ResolvedUnit;
|
|
483
|
+
try { unit = resolveUnit(node.call!, path); } catch (error) {
|
|
484
|
+
if (error instanceof UnitNotFoundError && onMissing) { onMissing(node.call!); continue; }
|
|
485
|
+
throw error;
|
|
486
|
+
}
|
|
487
|
+
const selectedEntry = node.callEntry ?? unit.template.entryNode;
|
|
488
|
+
if (!(unit.template.unit?.entries ?? [unit.template.entryNode]).includes(selectedEntry)) {
|
|
489
|
+
throw new Error(`unit entry mismatch: ${node.nodeId} -> ${selectedEntry}`);
|
|
490
|
+
}
|
|
437
491
|
const contract = node.contract!;
|
|
438
492
|
const edge = graph.edges.find(e => e.from === node.nodeId);
|
|
439
493
|
if (!unit.template.unit || contract.inputs.length !== unit.template.unit.inputs.length ||
|
|
@@ -445,7 +499,7 @@ function validateUnitCalls(graph: GraphTemplateSpec, path: string, ancestors: re
|
|
|
445
499
|
debug.log('graph.unit', 'contract-mismatch', { graphId: graph.graphId, nodeId: node.nodeId, call: node.call });
|
|
446
500
|
throw new Error(`unit contract mismatch: ${node.nodeId} -> ${node.call}`);
|
|
447
501
|
}
|
|
448
|
-
validateUnitCalls(unit.template, unit.path, [...ancestors, graph.graphId]);
|
|
502
|
+
validateUnitCalls(unit.template, unit.path, [...ancestors, graph.graphId], onMissing);
|
|
449
503
|
result.set(node.nodeId, unit);
|
|
450
504
|
}
|
|
451
505
|
return result;
|
|
@@ -492,6 +546,13 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
492
546
|
if (variant && !variant.ok) throw new Error(`graph variant rejected: ${JSON.stringify(variant.rejections)}`);
|
|
493
547
|
if (variant?.ok) graph = variant.template;
|
|
494
548
|
const units = validateUnitCalls(graph, path, options.unitContext?.ancestors);
|
|
549
|
+
const unitEntryNode = options.unitContext?.entryNodeId ?? saved?.entryNodeId ?? graph.entryNode;
|
|
550
|
+
if (saved?.entryNodeId !== undefined && options.unitContext?.entryNodeId !== undefined && saved.entryNodeId !== options.unitContext.entryNodeId) {
|
|
551
|
+
throw new Error('unit entry changed on resume');
|
|
552
|
+
}
|
|
553
|
+
if ((options.unitContext || saved?.entryNodeId !== undefined) && !(graph.unit?.entries ?? [graph.entryNode]).includes(unitEntryNode)) {
|
|
554
|
+
throw new Error(`unit entry mismatch: ${unitEntryNode}`);
|
|
555
|
+
}
|
|
495
556
|
const variantSource = variant?.ok ? stringifyYaml({ ...document,
|
|
496
557
|
nodes: graph.nodes.map((node, index) => ({
|
|
497
558
|
...((document.nodes as Record<string, unknown>[])[index] ?? {}), node_id: node.nodeId, kind: node.kind,
|
|
@@ -537,6 +598,14 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
537
598
|
if (!resolved.command && !resolved.prompt && !resolved.approval) throw new Error(`unknown command recipe for ${node.nodeId}: ${node.recipe}`);
|
|
538
599
|
}
|
|
539
600
|
const graphId = safeSegment(graph.graphId);
|
|
601
|
+
const isReleaseLoop = graphId === 'release-loop';
|
|
602
|
+
// A copied or saved release graph is still a release graph. Its path cannot grant or revoke
|
|
603
|
+
// permission to dispatch a command; only the graph identity and this independent table can.
|
|
604
|
+
const enforceReleaseCommandBindings = isReleaseLoop;
|
|
605
|
+
if (enforceReleaseCommandBindings) {
|
|
606
|
+
for (const node of graph.nodes) assertReleaseCommandBinding(node,
|
|
607
|
+
resolveNodeRecipe(node, recipes, catalogRoles).command?.command, graph.terminalNodes.includes(node.nodeId));
|
|
608
|
+
}
|
|
540
609
|
if (saved && graphId !== saved.graphId) {
|
|
541
610
|
if (options.fromNodeId && graphMode === 'current') throw new Error('graph or recipes changed since failed run');
|
|
542
611
|
throw new Error(`run identity mismatch: ${saved.graphId}/${saved.runId}`);
|
|
@@ -562,7 +631,9 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
562
631
|
: { graphId, runId, startedAt: new Date().toISOString(), ...runnerOwner, status: 'running', path: [], nodes: [], sourceHash: approvalSourceHash,
|
|
563
632
|
graphSnapshot: { graphSha: createHash('sha256').update(variantSource).digest('hex'), recipesSha: createHash('sha256').update(recipeSource).digest('hex') },
|
|
564
633
|
graphPath: resolve(path), ...(variant?.ok ? { variant: { overlayId: variant.overlay.overlayId, ...options.variant! } } : {}),
|
|
565
|
-
...(options.input === undefined ? {} : { input: options.input }),
|
|
634
|
+
...(options.input === undefined ? {} : { input: options.input }),
|
|
635
|
+
...(options.unitContext?.entryNodeId === undefined ? {} : { entryNodeId: options.unitContext.entryNodeId }),
|
|
636
|
+
executed: 0, dryRun: options.dryRun === true, statePath };
|
|
566
637
|
if (options.resumeRunId && graphMode === 'snapshot' && state.sourceHash && state.sourceHash !== approvalSourceHash) {
|
|
567
638
|
throw new Error('graph snapshot changed since run started');
|
|
568
639
|
}
|
|
@@ -594,7 +665,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
594
665
|
const reported = lastJsonObject(prior.output)?.outcome;
|
|
595
666
|
const next = nextNode(graph.edges, prior.nodeId, prior.ok ? 'ok' : 'fail', typeof reported === 'string' ? reported : undefined);
|
|
596
667
|
if (next !== recorded.nodeId) throw new Error('saved run path does not follow graph edges');
|
|
597
|
-
} else if (recorded.nodeId !==
|
|
668
|
+
} else if (recorded.nodeId !== unitEntryNode) throw new Error('saved run path does not start at entry node');
|
|
598
669
|
if (i < from && !recorded.ok) throw new Error('cannot preserve a failed predecessor when resuming');
|
|
599
670
|
const approved = resolveNodeRecipe(spec, recipes, catalogRoles).approval;
|
|
600
671
|
if (approved && i >= from) throw new Error('cannot restart across an approval decision');
|
|
@@ -736,7 +807,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
736
807
|
if (options.resumeRunId && !options.fromNodeId) {
|
|
737
808
|
const previousStatus = state.status;
|
|
738
809
|
Object.assign(state, runnerOwner);
|
|
739
|
-
state.resume = { from: state.pending?.nodeId ?? state.path.at(-1) ??
|
|
810
|
+
state.resume = { from: state.pending?.nodeId ?? state.path.at(-1) ?? unitEntryNode, at: new Date().toISOString(), previousStatus, graph: graphMode };
|
|
740
811
|
persistOwnedRun(state, saved!);
|
|
741
812
|
}
|
|
742
813
|
if (options.resumeRunId) debug.log('graph.runs', 'resume', { graphId, runId, from: state.resume?.from, graph: graphMode });
|
|
@@ -751,7 +822,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
751
822
|
: baseLog;
|
|
752
823
|
const visits = new Map<string, number>();
|
|
753
824
|
for (const nodeId of state.path) visits.set(nodeId, (visits.get(nodeId) ?? 0) + 1);
|
|
754
|
-
let current: string | undefined = options.fromNodeId ?? state.pending?.nodeId ?? state.path.at(-1) ??
|
|
825
|
+
let current: string | undefined = options.fromNodeId ?? state.pending?.nodeId ?? state.path.at(-1) ?? unitEntryNode;
|
|
755
826
|
if (!options.resumeRunId) {
|
|
756
827
|
mkdirSync(dirname(statePath), { recursive: true });
|
|
757
828
|
if (readdirSync(dirname(statePath)).some((file) => file.startsWith(`${runId}.json.`) && file.endsWith('.decision.json'))) {
|
|
@@ -788,6 +859,7 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
788
859
|
}
|
|
789
860
|
const resolved = resolveNodeRecipe(node, recipes, catalogRoles);
|
|
790
861
|
const command = resolved.command;
|
|
862
|
+
if (enforceReleaseCommandBindings) assertReleaseCommandBinding(node, command?.command, graph.terminalNodes.includes(current));
|
|
791
863
|
const prompt = resolved.prompt;
|
|
792
864
|
const approval = resolved.approval ? { approval: approvalMessage(resolved.approval.approval, state.input) } : undefined;
|
|
793
865
|
if (resumingPending && (!approval || approval.approval !== state.pending?.message)) {
|
|
@@ -837,7 +909,13 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
837
909
|
const redactCredentials = (text: string) => Object.values(credentials).filter(value => value.length > 0)
|
|
838
910
|
.reduce((safe, value) => safe.replaceAll(value, '[REDACTED]'), text);
|
|
839
911
|
const runBash: BashRun = async (body, opts) => {
|
|
912
|
+
// Hash the actual /bin/bash argv after interpolation. Keep the command text (which may contain
|
|
913
|
+
// credentials) out of logs, but leave enough to distinguish a replayed publish from npm/docs.
|
|
914
|
+
const argvSha256 = createHash('sha256').update(JSON.stringify(['/bin/bash', '-c', body])).digest('hex');
|
|
915
|
+
const fingerprint = { graphId, runId, nodeId: current!, recipe: node.recipe, argvSha256 };
|
|
916
|
+
debug.log('graph.run', 'command-dispatch', { ...fingerprint, pid: null });
|
|
840
917
|
const execute = options.deps?.runBash ?? realRunBash((pid) => {
|
|
918
|
+
debug.log('graph.run', 'command-spawn', { ...fingerprint, pid });
|
|
841
919
|
// Publish the child before its start time is known, so a concurrent stop refuses instead of failing
|
|
842
920
|
// the ledger while an untracked node keeps working (REL8b review round 3).
|
|
843
921
|
state.activeNode = { nodeId: current!, pid, pidStartedAt: UNVERIFIED_NODE_START };
|
|
@@ -906,7 +984,10 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
906
984
|
const alreadySpent = state.path.length + (state.unitCalls ?? []).reduce((sum, call) => sum + call.steps, 0);
|
|
907
985
|
const remaining = options.unitContext ? options.unitContext.remainingSteps - alreadySpent : Infinity;
|
|
908
986
|
const childRunId = randomUUID();
|
|
909
|
-
const
|
|
987
|
+
const selectedEntry = node.callEntry ?? unit.template.entryNode;
|
|
988
|
+
const recordEntry = node.callEntry !== undefined || unit.template.unit?.entries !== undefined;
|
|
989
|
+
const info = { graphId, runId, nodeId: current, unit: unit.template.graphId, childRunId, parentRunId: runId,
|
|
990
|
+
...(recordEntry ? { entry: selectedEntry } : {}) };
|
|
910
991
|
let entered = false;
|
|
911
992
|
try {
|
|
912
993
|
for (const key of contract.inputs) if (!Object.hasOwn(inputs, key)) throw new Error(`unit input missing: ${current}.${key}`);
|
|
@@ -918,7 +999,8 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
918
999
|
const child = await runGraph(unit.path, { runId: childRunId, input: inputs, dryRun: false,
|
|
919
1000
|
pinChildUniverse: options.pinChildUniverse,
|
|
920
1001
|
unitContext: { parentRunId: runId, ancestors: [...(options.unitContext?.ancestors ?? []), graphId],
|
|
921
|
-
remainingSteps: Math.min(node.budget!.innerSteps, remaining)
|
|
1002
|
+
remainingSteps: Math.min(node.budget!.innerSteps, remaining),
|
|
1003
|
+
...(recordEntry ? { entryNodeId: selectedEntry } : {}) }, deps: options.deps });
|
|
922
1004
|
const steps = child.path.length + (child.unitCalls ?? []).reduce((sum, call) => sum + call.steps, 0);
|
|
923
1005
|
const childExit = child.status === 'done' || child.status === 'failed' ? child.path.at(-1) : undefined;
|
|
924
1006
|
// The last successful record carrying every contracted output is the source;
|
|
@@ -932,7 +1014,8 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
932
1014
|
}, undefined) ?? {};
|
|
933
1015
|
state.unitCalls ??= [];
|
|
934
1016
|
state.unitCalls.push({ node: current, unit: unit.template.graphId, version: unit.template.version,
|
|
935
|
-
hash: unit.hash, childRunId, exit: childExit ?? child.status, steps
|
|
1017
|
+
hash: unit.hash, childRunId, exit: childExit ?? child.status, steps,
|
|
1018
|
+
...(recordEntry ? { entry: selectedEntry } : {}) });
|
|
936
1019
|
if (child.status === 'awaiting-approval') throw new Error(`unit cannot pause for approval: ${current}`);
|
|
937
1020
|
if (child.status === 'budget-exceeded' || steps > node.budget!.innerSteps || steps > remaining) {
|
|
938
1021
|
debug.log('graph.unit', 'budget-exhausted', { ...info, steps, innerSteps: node.budget!.innerSteps });
|
|
@@ -1005,6 +1088,10 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
1005
1088
|
...(approval && state.pending ? { ...(state.pending.decidedBy === undefined ? {} : { decidedBy: state.pending.decidedBy }), decidedAt: state.pending.decidedAt } : {}),
|
|
1006
1089
|
startedAt: nodeStartedAt, endedAt: nodeEndedAt, seconds });
|
|
1007
1090
|
}
|
|
1091
|
+
if (isReleaseLoop && graph.terminalNodes.includes(current) && node.recipe === 'none' &&
|
|
1092
|
+
(exit !== null || output !== undefined || error !== undefined || state.nodes.at(-1)?.executed)) {
|
|
1093
|
+
throw new Error(`release terminal received command output: ${current}`);
|
|
1094
|
+
}
|
|
1008
1095
|
if (approval) {
|
|
1009
1096
|
delete state.pending;
|
|
1010
1097
|
delete state.approvalSourceHash;
|
|
@@ -201,6 +201,8 @@ export function planHarnessClean(input: {
|
|
|
201
201
|
readWorktreeProvenance?: WorktreeProvenanceReader;
|
|
202
202
|
/** provenance reader가 쓰는 git runner. planner 단독 테스트에서는 반드시 명시한다. */
|
|
203
203
|
run?: GitRunner;
|
|
204
|
+
/** Allow reclaiming unowned worktrees only for merged-clean branches; default remains preservation. */
|
|
205
|
+
includeUnownedMergedClean?: boolean;
|
|
204
206
|
/** 조회별 소요 시간(밀리초). 키는 `queryStatus` 조회 칸과 같다. 못 잰 칸은 생략한다. */
|
|
205
207
|
queryTimings?: HarnessCleanQueryTimings;
|
|
206
208
|
}): HarnessCleanPlan {
|
|
@@ -353,7 +355,15 @@ export function planHarnessClean(input: {
|
|
|
353
355
|
continue;
|
|
354
356
|
}
|
|
355
357
|
const ladderReason = `assessment=${assessment.disposition}:${assessment.reason}`;
|
|
356
|
-
|
|
358
|
+
const includeUnownedMergedClean = input.includeUnownedMergedClean === true
|
|
359
|
+
&& mode === 'merged'
|
|
360
|
+
&& mergedPr.has(branch)
|
|
361
|
+
&& !openPr.has(branch)
|
|
362
|
+
&& assessment.disposition === 'reclaim-safe'
|
|
363
|
+
&& assessment.reason === 'merged-clean'
|
|
364
|
+
&& !ownership.recorded
|
|
365
|
+
&& ownership.status.startsWith('not-recorded:');
|
|
366
|
+
if (assessment.disposition !== 'reclaim-safe' || (!ownership.recorded && !includeUnownedMergedClean)) {
|
|
357
367
|
preserve.push({ ...base, reason: `${ladderReason}; ownership=${ownership.status}` });
|
|
358
368
|
continue;
|
|
359
369
|
}
|
|
@@ -992,6 +1002,7 @@ export function execHarnessClean(opts: {
|
|
|
992
1002
|
mode: HarnessCleanMode;
|
|
993
1003
|
dryRun?: boolean;
|
|
994
1004
|
force?: boolean;
|
|
1005
|
+
includeUnownedMergedClean?: boolean;
|
|
995
1006
|
branchPrefix?: string;
|
|
996
1007
|
repo?: string;
|
|
997
1008
|
window?: { offset: number; size: number };
|
|
@@ -1087,6 +1098,7 @@ export function execHarnessClean(opts: {
|
|
|
1087
1098
|
readWorktreeProvenance: opts.readWorktreeProvenance,
|
|
1088
1099
|
run,
|
|
1089
1100
|
mode: opts.mode,
|
|
1101
|
+
...(opts.includeUnownedMergedClean ? { includeUnownedMergedClean: true } : {}),
|
|
1090
1102
|
queryStatus,
|
|
1091
1103
|
queryTimings: {
|
|
1092
1104
|
worktrees: worktreesTimed.ms,
|
|
@@ -1286,10 +1298,16 @@ export function renderHarnessCleanReport(
|
|
|
1286
1298
|
item.reason.includes('reclaim-safe:merged-no-worktree'),
|
|
1287
1299
|
).length;
|
|
1288
1300
|
const branchOnlyReclaimedCount = res.plan.remove.filter((item) => item.reason.includes('branch-only')).length;
|
|
1301
|
+
const isUnownedMergedClean = (item: CleanPlanItem): boolean =>
|
|
1302
|
+
item.reason.includes('assessment=reclaim-safe:merged-clean') && item.reason.includes('ownership=not-recorded:');
|
|
1303
|
+
const plannedUnownedMergedCleanCount = res.plan.remove.filter(isUnownedMergedClean).length;
|
|
1304
|
+
const unownedMergedCleanCount = (res.dryRun ? res.plan.remove : res.removed).filter(isUnownedMergedClean).length;
|
|
1305
|
+
const unownedMergedCleanSummary = plannedUnownedMergedCleanCount > 0
|
|
1306
|
+
? ` · 미소유 merged-clean ${res.dryRun ? '제거 예정' : '회수'} ${unownedMergedCleanCount}` : '';
|
|
1289
1307
|
L.push(
|
|
1290
1308
|
omittedPreserve > 0
|
|
1291
|
-
? `규모 매칭 ${matchedTotal} · 제거 계획 ${res.plan.remove.length} · 보존 ${res.plan.preserve.length} · 화면 ${displayedCount} (보존 ${omittedPreserve}개는 생략) · 브랜치만 회수 ${branchOnlyReclaimedCount} · 워크트리 없이 안전 ${mergedNoWorktreeCount}`
|
|
1292
|
-
: `규모 매칭 ${matchedTotal} · 제거 계획 ${res.plan.remove.length} · 보존 ${res.plan.preserve.length} · 화면 ${displayedCount} · 브랜치만 회수 ${branchOnlyReclaimedCount} · 워크트리 없이 안전 ${mergedNoWorktreeCount}`,
|
|
1309
|
+
? `규모 매칭 ${matchedTotal} · 제거 계획 ${res.plan.remove.length} · 보존 ${res.plan.preserve.length} · 화면 ${displayedCount} (보존 ${omittedPreserve}개는 생략) · 브랜치만 회수 ${branchOnlyReclaimedCount} · 워크트리 없이 안전 ${mergedNoWorktreeCount}${unownedMergedCleanSummary}`
|
|
1310
|
+
: `규모 매칭 ${matchedTotal} · 제거 계획 ${res.plan.remove.length} · 보존 ${res.plan.preserve.length} · 화면 ${displayedCount} · 브랜치만 회수 ${branchOnlyReclaimedCount} · 워크트리 없이 안전 ${mergedNoWorktreeCount}${unownedMergedCleanSummary}`,
|
|
1293
1311
|
);
|
|
1294
1312
|
if (res.plan.remove.length === 0) L.push('정리 대상 없음.');
|
|
1295
1313
|
else {
|