elanous 0.2.11 → 0.2.13
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/{1394-556de6d71bbdb0fb.js → 1394-931c9827067b2276.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3869-3cd46526879f454c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3992-2e522a8ac5f2afde.js → 3992-fe5c06d6b31f8b25.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8605-ea5bee9dfae512c7.js → 8605-9da4678f8a310d18.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-6a061bd97f0b9398.js → page-e49b9b76c565a0b1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-f14987d5850a31e0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-7c97dcdfe2e37a60.js → page-4c1dbb21861beab2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-5721537ca8a3598c.js → page-f6bc66b919b2ea7d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-b654240a572e4c48.js → page-60603c1e056c9b25.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-b9750abc897508c3.js → page-a0ea8f5a2341ee6b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-c8b1ccda6ba7ae98.js → page-31012264da8a341f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-d6b1d156fb95e74b.js → layout-50309aeb0bdd554a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-9688f5b4f1ff950f.js → page-0696ee3fac9fe56c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66c27608f651b866.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-35414e0a007b4082.js → page-6ddd2109b7fc3a80.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/{page-e001823c35c9dd67.js → page-7a2bf830038a2264.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-371423d1a573d64b.js → page-1f327f18694fabca.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-4b764b125339ac17.js → page-e5cef2fe6e4028df.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-cdf8e4b01067bcf1.js → page-cf596128d2b420ba.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/{page-3a24072b35470157.js → page-f672536f14f0969f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-d37cb0d52f9b6bd5.js → page-29a709616df9b050.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-686da0df9bf23e5c.js → page-fee842d7c2151129.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-4dc614f3ce043e63.js → page-78b40a344ed22f24.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-d02c782fc792e2af.js → page-a011dd5b0bff29c5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-239c17b911b84e72.js → page-d92e32d51ab5157e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/{page-dc6a79fb003f746c.js → page-5f21aed6932ef375.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-0fffb84cafdc9da6.js → page-1e1a53946804debf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-1218753c82d585f6.js → page-a1833cd43d832601.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/{page-4b5935e67531efb5.js → page-63d14a4d8fe1e1c1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/{page-3285bc87caf1eaa5.js → page-2a511d320f762e06.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-ad553b0e06505ecd.js → page-1c3e03f88dac1e21.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-fca096e3e860306f.js → page-c4d5289c2ea97a89.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-9ebfa78f0d9885c3.js → page-b91f8952deaeaf27.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-3dc5f21f7e39622d.js → page-71b43a2588489987.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +1 -0
- package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.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/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/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +14 -14
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +14 -14
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/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/examples/teams/personas/backend.yaml +4 -0
- package/examples/teams/personas/pm.yaml +4 -0
- package/examples/teams/personas/qa.yaml +4 -0
- package/examples/teams/small-product.yaml +20 -0
- package/graphs/context/nightly-condense.yaml +21 -0
- package/graphs/context/recipes.yaml +12 -0
- package/graphs/demo/inside-seed.yaml +20 -0
- package/graphs/demo/recipes.yaml +12 -0
- package/graphs/lecture/lecture-note.yaml +28 -0
- package/graphs/lecture/recipes.yaml +17 -0
- package/package.json +2 -1
- package/plugins/card-followup/examples/sample-card.png +0 -0
- package/scripts/context-hooks/emit.ts +36 -14
- package/scripts/coord-channel-watch.sh +1 -0
- package/scripts/discord-seat-probe.ts +144 -0
- package/scripts/hq/hq-standby.sh +65 -0
- package/scripts/hq/standby-promote.ts +173 -0
- package/scripts/hq/standby-role.ts +59 -0
- package/scripts/hq/standby-snapshot.ts +226 -0
- package/scripts/hq/standby-verify.ts +219 -0
- package/scripts/lecture-note/node.ts +256 -0
- package/scripts/lib/telegram-inject-args.ts +31 -0
- package/scripts/measure-pod-memory-by-goal.ts +130 -0
- package/scripts/release-loop/auto-approve-node.ts +1 -1
- package/scripts/release-loop/checklist-gate-node.ts +2 -1
- package/scripts/release-loop/gate-node.ts +61 -17
- package/scripts/release-loop/ops-upgrade-node.ts +59 -6
- package/scripts/release-loop/publish-node.ts +4 -1
- package/scripts/release-loop/version-node.ts +13 -3
- package/scripts/telegram-inject.ts +14 -6
- package/scripts/test-deterministic.ts +53 -3
- package/scripts/test-diet/remeasure-list.ts +112 -0
- package/scripts/ux/demo-rehearsal.ts +17 -13
- package/scripts/ux/demo-seed.ts +108 -0
- package/scripts/ux/lib/rehearsal-verdict.ts +12 -5
- package/skills/omni-crawl/SKILL.md +16 -16
- package/skills/omni-crawl/package-lock.json +1 -544
- package/skills/omni-crawl/package.json +3 -4
- package/skills/omni-crawl/scripts/main.ts +13 -13
- package/skills/omni-crawl/scripts/monitor.ts +8 -8
- package/skills/omni-crawl/src/apify.ts +13 -2
- package/skills/omni-crawl/src/capture.ts +6 -0
- package/skills/omni-crawl/src/registry.ts +2 -2
- package/skills/omni-crawl/src/stdout-json.ts +4 -0
- package/skills/omni-digest/SKILL.md +10 -11
- package/skills/omni-digest/package-lock.json +0 -545
- package/skills/omni-digest/package.json +2 -5
- package/skills/omni-digest/scripts/main.ts +3 -3
- package/skills/omni-digest/src/delegate.ts +5 -5
- package/skills/omni-digest/src/fetch.ts +2 -2
- package/skills/omni-digest/src/media.ts +25 -11
- package/skills/omni-digest/src/visual.ts +3 -2
- package/skills/omni-market/scripts/main.ts +1 -1
- package/skills/omni-market/scripts/stdout-json.ts +4 -0
- package/src/agent/identity-env.ts +11 -0
- package/src/agent-mission/driver.ts +150 -23
- package/src/agent-mission/duo.ts +94 -0
- package/src/agent-mission/pty-decision.ts +2 -2
- package/src/autopilot/pty-control-loop.ts +4 -1
- package/src/autopilot/track-agent-shadow.ts +2 -1
- package/src/autopilot/track-agent.ts +4 -1
- package/src/boot/daemon-prompt-request.ts +3 -0
- package/src/boot/daemon-prompt-turn.ts +6 -2
- package/src/boot/daemon-runtime.ts +48 -16
- package/src/chat/index.ts +1 -0
- package/src/cli/decisions-cli.ts +51 -19
- package/src/cli/outputs-cli.ts +92 -0
- package/src/cli/persona-cli.ts +45 -13
- package/src/cli/pod-cli.ts +27 -3
- package/src/cli/pod-memory-advice.ts +35 -0
- package/src/cli/pr-cli.ts +15 -1
- package/src/cli/release-cli.ts +135 -15
- package/src/context-bus/condense.ts +194 -0
- package/src/context-bus/context-day.ts +82 -0
- package/src/context-bus/context-now-surfaces.ts +32 -2
- package/src/context-bus/context-now.ts +2 -2
- package/src/context-bus/coord-events.ts +2 -2
- package/src/context-bus/long-term-memory.ts +118 -22
- package/src/context-bus/nightly-condense.ts +153 -0
- package/src/context-bus/session-events.ts +35 -0
- package/src/dashboard/dashboard-status-lines.ts +2 -0
- package/src/dashboard/index.ts +8 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +24 -0
- package/src/decisions/decision-cards.ts +13 -7
- package/src/decisions/decision-ledger.ts +43 -17
- package/src/domains/outbound-alert.ts +99 -31
- package/src/domains/outbound-log-probe.ts +8 -0
- package/src/execution-loop/launch-gate.ts +17 -8
- package/src/field-note/telegram.ts +142 -0
- package/src/graph-kinds/registry.ts +2 -2
- package/src/graph-runner/graph-approval-card.ts +2 -2
- package/src/graph-runner/graph-grow-llm.ts +23 -13
- package/src/graph-runner/graph-grow.ts +69 -13
- package/src/graph-runner/graph-tick.ts +9 -2
- package/src/graph-runner/runner.ts +92 -24
- package/src/harness/harness-cli-command.ts +165 -17
- package/src/harness/harness-queue-child.ts +37 -0
- package/src/harness/harness-queue.ts +306 -0
- package/src/hooks/consult-intake.ts +153 -0
- package/src/hooks/github-shadow.ts +72 -2
- package/src/hooks/receiver.ts +82 -3
- package/src/hq/hq.ts +318 -0
- package/src/hq/lease.ts +210 -0
- package/src/hq/ledger-write-target.ts +41 -0
- package/src/index.ts +232 -16
- package/src/intake-plane/discord-seat-work.ts +36 -3
- package/src/intake-plane/linear-wish.ts +44 -0
- package/src/intake-plane/persona-answer.ts +5 -0
- package/src/intake-plane/telegram-seat-work.ts +13 -6
- package/src/intake-plane/wish-card.ts +33 -0
- package/src/llm/difficulty.ts +1 -1
- package/src/loops/checker.ts +178 -0
- package/src/loops/loop-cli.ts +37 -3
- package/src/loops/observe.ts +49 -0
- package/src/loops/orchestrator/overlap.ts +141 -0
- package/src/loops/orchestrator/traffic.ts +150 -0
- package/src/loops/package/verify.ts +125 -0
- package/src/loops/verdict.ts +22 -0
- package/src/maturity/feature-maturity.ts +5 -4
- package/src/memory.ts +37 -4
- package/src/nexus/api/card-followup-route.ts +118 -0
- package/src/nexus/api/decisions-route.ts +68 -0
- package/src/nexus/api/http-server.ts +78 -8
- package/src/nexus/api/outbound-report.ts +40 -7
- package/src/nexus/api/personas.ts +97 -54
- package/src/nexus/api/skill-exec-route.ts +97 -0
- package/src/nexus/api/task-cards-api.ts +19 -0
- package/src/nexus/api/workflow-history.ts +88 -0
- package/src/nexus/api/workflows.ts +52 -0
- package/src/nexus/outbound/delivery-ledger.ts +17 -8
- package/src/nexus/outbound/router.ts +8 -3
- package/src/persona/loader.ts +18 -0
- package/src/persona/persona-todo.ts +14 -9
- package/src/persona/presets.ts +10 -2
- package/src/persona/team.ts +105 -0
- package/src/persona/types.ts +4 -0
- package/src/plugins/maker/market-bundle.ts +2 -1
- package/src/pod-lease/host-lease.ts +130 -20
- package/src/release-loop/checklist.ts +15 -3
- package/src/release-loop/feature-store.ts +24 -10
- package/src/release-loop/landed-but-yellow.ts +60 -0
- package/src/seat-dispatch/seat-ask.ts +57 -12
- package/src/seat-loop/seat-loop.ts +253 -18
- package/src/seat-loop/seat-pack.ts +127 -0
- package/src/self-dev/ask-launch-flow.ts +3 -0
- package/src/self-dev/draft-sweep.ts +44 -10
- package/src/self-dev/draft-triage-rules.ts +54 -2
- package/src/self-implement/budget-gate.ts +29 -12
- package/src/self-implement/gate-candidate-ranking.ts +60 -0
- package/src/self-implement/gate-scope.ts +9 -8
- package/src/self-implement/headless-elanous-driver.ts +7 -2
- package/src/self-implement/l8-merge-queue.ts +157 -1
- package/src/self-implement/orchestrator.ts +15 -0
- package/src/self-implement/test-seams.ts +1 -0
- package/src/steward/launch.ts +5 -1
- package/src/steward/readiness.ts +125 -0
- package/src/steward/rescue.ts +75 -0
- package/src/steward/triage.ts +48 -9
- package/src/task-cards/card-cli.ts +78 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +14 -10
- package/src/task-orchestrator/surfaces/pod-lease.ts +18 -6
- package/src/task-orchestrator/surfaces/pod-pool.ts +27 -10
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +46 -8
- package/src/telegram-commands.ts +21 -0
- package/src/telegram-dispatch-forward.ts +1 -1
- package/src/telegram-fabric-plan.ts +90 -0
- package/src/telegram-project-command.ts +84 -4
- package/src/telegram.ts +37 -5
- package/src/tool-runtime/elanous-control-runtimes.ts +2 -1
- package/src/user-config.ts +101 -13
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/executor.ts +4 -0
- package/src/workflow-runtime/node-catalog.ts +13 -0
- package/src/workflow-runtime/nodes/knowledge.ts +38 -0
- package/src/workflow-runtime/schema.ts +17 -1
- package/src/workflow-runtime/types.ts +13 -1
- package/apps/pwa/out/_next/static/chunks/2310-7a59f043f6db207e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2548-6e316138d347cefe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3869-a39d1d9921e10d43.js +0 -1
- package/apps/pwa/out/_next/static/chunks/507-5acd98bd6dab2d8a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5445-745eb795c5963d79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9906-482c6619b3fdda20.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-1dcdc58fc2de2454.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-cb95a7432c9ee744.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-b12b6d2f8d2d1aea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b9efe868bfd36255.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-8fd36984f549270a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-4cd1de371f8ad048.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-d99f89657828421c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-50cb2a0eb15e6c83.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-2a9932ddb62d76bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-6b15dd6456c5df09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-e7a529e0507ac86c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-94c82eb0ae5abb32.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-0be8d4b2ced75ba0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-53f80edc6b8dc07c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-c6da2b468454532d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-93bc70e36f595414.js +0 -1
- package/apps/pwa/out/_next/static/css/543f56bcb1f79fca.css +0 -1
- /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → D51l8jUBplJrIpWvENsDK}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → D51l8jUBplJrIpWvENsDK}/_ssgManifest.js +0 -0
|
@@ -88,7 +88,7 @@ export type ControlDecision =
|
|
|
88
88
|
| { readonly action: 'handoff'; readonly to: 'codex' | 'claude' | 'elanous'; readonly mission: string; readonly carry?: 'diff' | 'summary' }
|
|
89
89
|
| { readonly action: 'ask-human'; readonly reason: string; readonly url?: string; readonly code?: string }
|
|
90
90
|
/** `no-progress` reports observed lack of progress without claiming completion. */
|
|
91
|
-
| { readonly action: 'no-progress'; readonly reason: string };
|
|
91
|
+
| { readonly action: 'no-progress'; readonly reason: string; readonly terminal?: boolean };
|
|
92
92
|
|
|
93
93
|
export interface RunSupervisor {
|
|
94
94
|
/** run supervisor의 관측 → 다음 행동. 동기/비동기 모두 허용(LLM 은 async). `signal` 이 abort 되면(사람 takeover
|
|
@@ -664,6 +664,9 @@ export async function runPtyControlLoop(
|
|
|
664
664
|
previousIntervention = { ...intervention, sameScreenCount: 1 };
|
|
665
665
|
}
|
|
666
666
|
} else if (decision.action === 'wait' || decision.action === 'no-progress') {
|
|
667
|
+
if (decision.action === 'no-progress' && decision.terminal) {
|
|
668
|
+
return finish({ kind: 'stuck', iteration: step, reason: decision.reason }, step, intervention, true);
|
|
669
|
+
}
|
|
667
670
|
// wait/no-progress — 종료된 자식은 새 출력을 낼 수 없으므로 대기 없이 즉시 수렴한다.
|
|
668
671
|
if (deps.isAlive?.() === false) {
|
|
669
672
|
debug.log('autopilot.control', 'child-exited', { step });
|
|
@@ -2,6 +2,7 @@ import { execFile } from 'node:child_process';
|
|
|
2
2
|
import { appendFileSync, mkdirSync, readFileSync, rmdirSync } from 'node:fs';
|
|
3
3
|
import { dirname, join, resolve } from 'node:path';
|
|
4
4
|
import { promisify } from 'node:util';
|
|
5
|
+
import { stateDirSourceForChild } from '../agent/identity-env.js';
|
|
5
6
|
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
6
7
|
import type { TrackAgentDecision, TrackAgentInput, TrackAgentResult } from './track-agent.js';
|
|
7
8
|
|
|
@@ -127,7 +128,7 @@ export async function launchTrackAgentShadow(input: TrackAgentInput, options: Sh
|
|
|
127
128
|
const instanceRoot = effectiveInstanceRoot();
|
|
128
129
|
const { stdout } = await exec('bun', ['bin/elanous.mjs', '--config-dir', instanceRoot, ...values], {
|
|
129
130
|
cwd: process.env.ELANOUS_TOOL_CWD?.trim() || resolve(import.meta.dir, '../..'),
|
|
130
|
-
env: { ...process.env, ELANOUS_STATE_DIR: instanceRoot },
|
|
131
|
+
env: { ...process.env, ELANOUS_STATE_DIR: instanceRoot, ELANOUS_STATE_DIR_SOURCE: stateDirSourceForChild(instanceRoot) },
|
|
131
132
|
encoding: 'utf8', maxBuffer: 1024 * 1024,
|
|
132
133
|
});
|
|
133
134
|
return { stdout };
|
|
@@ -2,6 +2,7 @@ import { execFile, spawn } from 'node:child_process';
|
|
|
2
2
|
import { readdirSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
import { promisify } from 'node:util';
|
|
5
|
+
import { stateDirSourceForChild } from '../agent/identity-env.js';
|
|
5
6
|
import { debug, redactSecretText } from '../debug/log.js';
|
|
6
7
|
import { getOrCreatePersonaSession } from '../session/index.js';
|
|
7
8
|
import { getUserConfig } from '../user-config.js';
|
|
@@ -38,7 +39,9 @@ const log = (event: string, data: Record<string, unknown>) => {
|
|
|
38
39
|
};
|
|
39
40
|
/** The daemon's own universe, resolved once and pinned on the child (config ⊕ state) — the harness isolates its own workers. */
|
|
40
41
|
export function universeLaunch(args: readonly string[], root: string = effectiveInstanceRoot()): { argv: string[]; env: NodeJS.ProcessEnv } {
|
|
41
|
-
return { argv: ['bin/elanous.mjs', '--config-dir', root, ...args], env: {
|
|
42
|
+
return { argv: ['bin/elanous.mjs', '--config-dir', root, ...args], env: {
|
|
43
|
+
...process.env, ELANOUS_STATE_DIR: root, ELANOUS_STATE_DIR_SOURCE: stateDirSourceForChild(root),
|
|
44
|
+
} };
|
|
42
45
|
}
|
|
43
46
|
const runCli = async (args: string[]): Promise<{ stdout: string }> => {
|
|
44
47
|
const { argv, env } = universeLaunch(args);
|
|
@@ -38,6 +38,7 @@ export interface DaemonPromptBody {
|
|
|
38
38
|
* (`none` · `readonly` · `chat` · `webterm`); invalid values reject
|
|
39
39
|
* the request rather than silently falling back. */
|
|
40
40
|
tools?: DaemonToolSurfaceKind;
|
|
41
|
+
personaId?: string;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
export interface DaemonPromptRequest {
|
|
@@ -53,6 +54,7 @@ export interface DaemonPromptRequest {
|
|
|
53
54
|
* not opt in; the caller-side (`handlePromptStreamPost`) interprets
|
|
54
55
|
* null as "use the daemon's boot-time surface". */
|
|
55
56
|
tools: DaemonToolSurfaceKind | null;
|
|
57
|
+
personaId?: string;
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
export type DaemonPromptRequestParseResult =
|
|
@@ -117,6 +119,7 @@ export function parseDaemonPromptBody(
|
|
|
117
119
|
body.source ? writeInputSourceMeta(body.source) : undefined,
|
|
118
120
|
),
|
|
119
121
|
tools: body.tools ?? null,
|
|
122
|
+
...(typeof body.personaId === 'string' && body.personaId.trim() ? { personaId: body.personaId } : {}),
|
|
120
123
|
},
|
|
121
124
|
};
|
|
122
125
|
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
appendUserPromptBlocksAndBuildMessages,
|
|
11
11
|
} from './daemon-history-helper.js';
|
|
12
12
|
import type { ContentBlock as AcpContentBlock } from '@agentclientprotocol/sdk';
|
|
13
|
-
import type
|
|
13
|
+
import { personaPromptForChat, type DaemonSessionHistory } from './daemon-runtime.js';
|
|
14
14
|
import type { DaemonPromptRequest } from './daemon-prompt-request.js';
|
|
15
15
|
import type { DaemonToolSurface } from './daemon-tools/types.js';
|
|
16
16
|
import type { ConfirmChannel } from '../hitl/confirm.js';
|
|
@@ -199,8 +199,9 @@ export async function runDaemonPromptTurn(opts: {
|
|
|
199
199
|
// …" + active terminals) to the system prompt so the LLM has the
|
|
200
200
|
// session id + terminal ids without an extra discovery call. Helper
|
|
201
201
|
// returns the original prompt unchanged when there's nothing to add.
|
|
202
|
+
const personaPrompt = await personaPromptForChat(request.personaId);
|
|
202
203
|
const augmentedSystemPrompt = appendWebtermContext(
|
|
203
|
-
request.effectiveSystemPrompt,
|
|
204
|
+
personaPrompt ? [personaPrompt, request.effectiveSystemPrompt].filter(Boolean).join('\n\n') : request.effectiveSystemPrompt,
|
|
204
205
|
request.sessionId,
|
|
205
206
|
activeToolSurface,
|
|
206
207
|
);
|
|
@@ -291,6 +292,9 @@ export async function runDaemonPromptTurn(opts: {
|
|
|
291
292
|
if (!isFirstTurn && opts.projectInstructions) {
|
|
292
293
|
messages.unshift({ role: 'system', content: opts.projectInstructions });
|
|
293
294
|
}
|
|
295
|
+
if (!isFirstTurn && personaPrompt) {
|
|
296
|
+
messages.unshift({ role: 'system', content: personaPrompt });
|
|
297
|
+
}
|
|
294
298
|
const result = await runCoreTurn({
|
|
295
299
|
sessionId: request.sessionId,
|
|
296
300
|
userText: request.userText,
|
|
@@ -55,6 +55,33 @@ import { elanousSelfAccessPrompt } from '../agent/self-ambient.js';
|
|
|
55
55
|
import { createToolCwdResolver, type ToolCwdResolver } from './tool-cwd.js';
|
|
56
56
|
import { resolveSessionProjectContext } from '../project/session-context.js';
|
|
57
57
|
import { debug } from '../debug/log.js';
|
|
58
|
+
import { resolvePersonaAddress } from '../intake-plane/persona-answer.js';
|
|
59
|
+
import { awaitGlobalPersonaLoad, getGlobalPersonaRegistry } from '../persona/global-registry.js';
|
|
60
|
+
|
|
61
|
+
const turnPersonaPrompt = new AsyncLocalStorage<string | undefined>();
|
|
62
|
+
|
|
63
|
+
export function withChatPersonaPrompt<T>(prompt: string | undefined, run: () => T): T {
|
|
64
|
+
return turnPersonaPrompt.run(prompt, run);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function prependChatPersonaOnLaterTurn(
|
|
68
|
+
messages: LLMMessage[], isFirstTurn: boolean,
|
|
69
|
+
): LLMMessage[] {
|
|
70
|
+
const prompt = turnPersonaPrompt.getStore();
|
|
71
|
+
if (!isFirstTurn && prompt) messages.unshift({ role: 'system', content: prompt });
|
|
72
|
+
return messages;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function personaPromptForChat(
|
|
76
|
+
id: unknown,
|
|
77
|
+
deps: { load: () => Promise<unknown>; registry: () => Parameters<typeof resolvePersonaAddress>[1] } = {
|
|
78
|
+
load: awaitGlobalPersonaLoad, registry: getGlobalPersonaRegistry,
|
|
79
|
+
},
|
|
80
|
+
): Promise<string | undefined> {
|
|
81
|
+
if (typeof id !== 'string' || !id.trim()) return undefined;
|
|
82
|
+
await deps.load();
|
|
83
|
+
return resolvePersonaAddress(id, deps.registry())?.systemPrompt;
|
|
84
|
+
}
|
|
58
85
|
|
|
59
86
|
/** C4 (2026-07-12) — arm the non-detached-PTY kill ONCE per turn signal.
|
|
60
87
|
* Multiple tool calls in one turn share the same AbortSignal instance;
|
|
@@ -135,7 +162,7 @@ export function composeDaemonSystemPrompt(
|
|
|
135
162
|
promptMeta: Readonly<Record<string, unknown>> | undefined,
|
|
136
163
|
sessionId?: string,
|
|
137
164
|
): string {
|
|
138
|
-
return [basePrompt, buildDaemonInputSourceLine(promptMeta), elanousSelfAccessPrompt(sessionId)]
|
|
165
|
+
return [turnPersonaPrompt.getStore(), basePrompt, buildDaemonInputSourceLine(promptMeta), elanousSelfAccessPrompt(sessionId)]
|
|
139
166
|
.filter((part): part is string => typeof part === 'string' && part.trim().length > 0)
|
|
140
167
|
.join('\n\n');
|
|
141
168
|
}
|
|
@@ -604,12 +631,11 @@ export function createDaemonRunTurn(
|
|
|
604
631
|
const hasNonTextBlock = !!promptBlocks?.some(
|
|
605
632
|
(b) => (b as { type?: string }).type !== 'text',
|
|
606
633
|
);
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
);
|
|
611
|
-
|
|
612
|
-
return appendUserAndBuildMessages(history, sessionId, userText, systemPrompt);
|
|
634
|
+
const isFirstTurn = history.get(sessionId).length === 0;
|
|
635
|
+
const messages = hasNonTextBlock
|
|
636
|
+
? appendUserPromptBlocksAndBuildMessages(history, sessionId, promptBlocks!, systemPrompt)
|
|
637
|
+
: appendUserAndBuildMessages(history, sessionId, userText, systemPrompt);
|
|
638
|
+
return prependChatPersonaOnLaterTurn(messages, isFirstTurn);
|
|
613
639
|
},
|
|
614
640
|
getTools: () => surface.specs,
|
|
615
641
|
dispatchTool: terminalCapableDispatch(surfaceHasPtyShell, async (name, args, ctx) => {
|
|
@@ -723,15 +749,21 @@ export function createDaemonRunTurn(
|
|
|
723
749
|
});
|
|
724
750
|
debugBridge.activate();
|
|
725
751
|
}
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
752
|
+
const elanousMeta = turnCtx.promptMeta?.elanous;
|
|
753
|
+
const personaId = elanousMeta && typeof elanousMeta === 'object' && !Array.isArray(elanousMeta)
|
|
754
|
+
&& 'pwaPersonaId' in elanousMeta ? elanousMeta.pwaPersonaId : undefined;
|
|
755
|
+
const personaPrompt = await personaPromptForChat(personaId);
|
|
756
|
+
await withChatPersonaPrompt(personaPrompt, async () => {
|
|
757
|
+
const projectCwd = resolveSessionProjectContext(turnCtx.sessionId)?.cwd;
|
|
758
|
+
if (projectCwd || opts.acpSessionCwd) {
|
|
759
|
+
// An ACP session's explicit cwd wins; the project folder is only the default when none was given (IA3 review).
|
|
760
|
+
const explicitCwd = opts.acpSessionCwd && turnCtx.cwd ? turnCtx.cwd : undefined;
|
|
761
|
+
const resolver = createToolCwdResolver({ tools: opts.tools ?? 'none', toolCwd: explicitCwd ?? projectCwd ?? turnCtx.cwd });
|
|
762
|
+
await turnToolCwd.run(resolver, () => inner(turnCtx));
|
|
763
|
+
} else {
|
|
764
|
+
await inner(turnCtx);
|
|
765
|
+
}
|
|
766
|
+
});
|
|
735
767
|
} finally {
|
|
736
768
|
debugBridge?.dispose();
|
|
737
769
|
}
|
package/src/chat/index.ts
CHANGED
|
@@ -438,6 +438,7 @@ export const SLASH_COMMANDS: SlashCommand[] = [
|
|
|
438
438
|
{ name: 'clear', aliases: ['cls'], description: '대화 기록 지우기' },
|
|
439
439
|
{ name: 'status', aliases: ['st'], description: '지금 상태 — 모델 · 연결 · 계정' },
|
|
440
440
|
{ name: 'now', aliases: [], description: '현재 맥락과 출처 표 — /now [주제]' },
|
|
441
|
+
{ name: 'wish', aliases: [], description: '소원을 카드로 남기기 — /wish <소원 한 줄>' },
|
|
441
442
|
{ name: 'remaining', aliases: [], description: '계정별 남은 사용량' },
|
|
442
443
|
{ name: 'setup', aliases: [], description: '상세 설정 — /setup · /setup reset(다음 실행 때 마법사 다시)', subcommands: ['help', 'reset'] },
|
|
443
444
|
{ name: 'quit', aliases: ['q', 'exit'], description: '나가기' },
|
package/src/cli/decisions-cli.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { Option, type Command } from 'commander';
|
|
2
|
+
import { hqCliWriteAllowed, type HqDeps } from '../hq/hq.js';
|
|
3
|
+
import { fileLeaseStore } from '../hq/lease.js';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { getElanousConfigDirOverride } from '../elanous-config-dir.js';
|
|
6
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
7
|
+
import { getUserConfig } from '../user-config.js';
|
|
2
8
|
import { projectDecisionsToLinear } from '../decisions/decision-linear-projection.js';
|
|
3
9
|
import { DecisionLedger, importDecisionMarkdown, type DecisionCategory, type DecisionLedgerOptions, type DecisionOption, type DecisionTrack, type DecisionEntry } from '../decisions/decision-ledger.js';
|
|
4
10
|
|
|
@@ -46,13 +52,19 @@ export function formatDecisionDetail(e: DecisionEntry): string {
|
|
|
46
52
|
...(e.refs?.length ? [`참조: ${e.refs.join(' · ')}`] : [])].join('\n');
|
|
47
53
|
}
|
|
48
54
|
|
|
49
|
-
export function registerDecisionsCommands(program: Command, config: DecisionLedgerOptions = {}, out: Pick<Console, 'log'> = console): void {
|
|
50
|
-
const
|
|
55
|
+
export function registerDecisionsCommands(program: Command, config: DecisionLedgerOptions = {}, out: Pick<Console, 'log'> = console, hqDeps: HqDeps = {}): void {
|
|
56
|
+
const noXcheck = new Option('--no-xcheck <이유>', '교차 확인 생략 이유');
|
|
57
|
+
// This flag takes a reason, not Commander's negated boolean default.
|
|
58
|
+
noXcheck.negate = false;
|
|
59
|
+
const root = program.command('decisions').description('대표 결정 원장 · 로컬 전용').option('--hq-override', '본부 임대 거부를 관측하며 수동 우회');
|
|
60
|
+
const mayWrite = (command: string, override?: boolean) => hqCliWriteAllowed(`decisions ${command}`, Boolean(override || root.opts().hqOverride),
|
|
61
|
+
(getElanousConfigDirOverride() || config.stateDir) && !hqDeps.store && !getUserConfig().hq?.arbiter ? { ...hqDeps, store: fileLeaseStore(join(config.stateDir ?? effectiveInstanceRoot(), 'hq', 'lease.json')), seenPath: join(config.stateDir ?? effectiveInstanceRoot(), 'hq', 'seen-generation'), localPath: join(config.stateDir ?? effectiveInstanceRoot(), 'hq', 'local.json') } : hqDeps);
|
|
51
62
|
const emit = (value: unknown, json?: boolean, text?: string) => out.log(json ? JSON.stringify(value) : text ?? JSON.stringify(value));
|
|
52
63
|
const fail = (action: () => void) => { try { action(); } catch (e) { throw new Error(`decisions: ${e instanceof Error ? e.message : String(e)}`); } };
|
|
53
64
|
root.command('linear-sync').description('열린 결정을 COO Linear 프로젝트에 투영하고 닫힌 결정을 동기화한다')
|
|
54
|
-
.option('--dry-run').option('--json')
|
|
55
|
-
.action(async (o: { dryRun?: boolean; json?: boolean }) => {
|
|
65
|
+
.option('--dry-run').option('--json').option('--hq-override')
|
|
66
|
+
.action(async (o: { dryRun?: boolean; json?: boolean; hqOverride?: boolean }) => {
|
|
67
|
+
if (!o.dryRun && !mayWrite('linear-sync', o.hqOverride)) return;
|
|
56
68
|
const result = await projectDecisionsToLinear({ stateDir: config.stateDir, dryRun: o.dryRun });
|
|
57
69
|
emit(result, o.json, result.reason ?? `생성 ${result.created} · 닫음 ${result.closed} · 건너뜀 ${result.skipped} · 실패 ${result.failed}${result.plan?.length ? `\n${result.plan.map(p => `${p.action} ${p.decisionId}${p.issue ? ` ${p.issue}` : ''}`).join('\n')}` : ''}`);
|
|
58
70
|
});
|
|
@@ -61,21 +73,36 @@ export function registerDecisionsCommands(program: Command, config: DecisionLedg
|
|
|
61
73
|
.requiredOption('--s <text>').requiredOption('--c <text>').option('--q <text>').option('--a <text>')
|
|
62
74
|
.requiredOption('--option <key=label:consequence>', '선택지 (두 번 이상)', repeat, [] as string[])
|
|
63
75
|
.option('--recommend <key>').option('--why <text>').option('--skip-recommend <reason>')
|
|
76
|
+
.option('--xcheck <SEAT:메모>', '교차 확인 (반복)', repeat, [] as string[])
|
|
77
|
+
.addOption(noXcheck)
|
|
78
|
+
.option('--alternative <key>').option('--dissent <text>')
|
|
64
79
|
.option('--track <track>').option('--agent <agent>').option('--session <id>')
|
|
65
80
|
.option('--resume-question <qid>').option('--run <runId>')
|
|
66
81
|
.option('--ref <url>', '참조 (반복)', repeat, [] as string[])
|
|
67
82
|
.option('--due <when>', '기한 — UTC ISO(2026-10-04T09:00:00Z) 또는 +Nh(지금부터 N시간) · 기한 2시간 전 텔레그램·디스코드로 다시 알린다')
|
|
68
|
-
.option('--json')
|
|
69
|
-
.action((o: { due?: string; title: string; category: DecisionCategory; s: string; c: string; q?: string; a?: string; option: string[]; recommend?: string; why?: string; skipRecommend?: string; track?: DecisionTrack; agent?: string; session?: string; resumeQuestion?: string; run?: string; ref: string[]; json?: boolean }) => fail(() => {
|
|
83
|
+
.option('--json').option('--hq-override')
|
|
84
|
+
.action((o: { due?: string; title: string; category: DecisionCategory; s: string; c: string; q?: string; a?: string; option: string[]; recommend?: string; why?: string; skipRecommend?: string; xcheck: string[]; noXcheck?: string; alternative?: string; dissent?: string; track?: DecisionTrack; agent?: string; session?: string; resumeQuestion?: string; run?: string; ref: string[]; json?: boolean; hqOverride?: boolean }) => fail(() => {
|
|
70
85
|
if (o.run !== undefined && o.resumeQuestion === undefined) throw new Error('--run requires --resume-question');
|
|
71
86
|
if (o.skipRecommend !== undefined && (o.recommend !== undefined || o.why !== undefined)) throw new Error('choose recommendation or skip, not both');
|
|
72
87
|
if (o.skipRecommend === undefined && (!o.recommend || !o.why)) throw new Error('--recommend and --why required, or --skip-recommend <reason>');
|
|
73
88
|
const options: DecisionOption[] = o.option.map(parseOption);
|
|
89
|
+
if (o.xcheck.length && o.noXcheck !== undefined) throw new Error('choose --xcheck or --no-xcheck, not both');
|
|
90
|
+
const crossCheck = o.xcheck.map(raw => {
|
|
91
|
+
const match = /^([^:\s]+):(.+)$/.exec(raw);
|
|
92
|
+
if (!match || !match[2]!.trim()) throw new Error(`invalid --xcheck: ${raw} (expected SEAT:메모)`);
|
|
93
|
+
return { seat: match[1]!, at: new Date().toISOString(), note: match[2]!.trim() };
|
|
94
|
+
});
|
|
95
|
+
if (!crossCheck.length && o.noXcheck === undefined && getUserConfig().decisions?.requireCrossCheck) throw new Error('교차 확인 없음 — --xcheck SEAT:메모 또는 --no-xcheck 이유');
|
|
96
|
+
const who = agent(o.agent);
|
|
97
|
+
if (!mayWrite('raise', o.hqOverride)) return;
|
|
74
98
|
const entry = new DecisionLedger(config).raise({ title: o.title, category: o.category, scqa: { s: o.s, c: o.c, ...(o.q ? { q: o.q } : {}), ...(o.a ? { a: o.a } : {}) }, options,
|
|
99
|
+
...(crossCheck.length ? { crossCheck } : { crossCheckSkipped: o.noXcheck ?? 'missing' }),
|
|
100
|
+
...(o.alternative !== undefined ? { alternative: o.alternative } : {}), ...(o.dissent !== undefined ? { dissent: o.dissent } : {}),
|
|
75
101
|
recommendation: o.skipRecommend !== undefined ? { skipped: true, reason: o.skipRecommend } : { option: o.recommend!, why: o.why! },
|
|
76
|
-
raisedBy: { agent:
|
|
102
|
+
raisedBy: { agent: who, ...(o.track ? { track: o.track } : {}), ...(o.session ? { session: o.session } : {}) }, ...(o.ref.length ? { refs: o.ref } : {}),
|
|
77
103
|
...(o.resumeQuestion !== undefined ? { resume: { questionId: o.resumeQuestion, ...(o.run !== undefined ? { runId: o.run } : {}) } } : {}),
|
|
78
104
|
...(o.due ? { dueAt: dueAt(o.due) } : {}) });
|
|
105
|
+
if (entry.crossCheckSkipped === 'missing') console.error('교차 확인 없음 — --xcheck SEAT:메모 또는 --no-xcheck 이유');
|
|
79
106
|
emit(entry, o.json, `올림: ${formatDecisionRow(entry)}`);
|
|
80
107
|
}));
|
|
81
108
|
root.command('list').description('결정 목록 (기본 열린 것)')
|
|
@@ -88,11 +115,12 @@ export function registerDecisionsCommands(program: Command, config: DecisionLedg
|
|
|
88
115
|
root.command('show <id>').description('SCQA · 선택지 · 권고 · 이력 · 판').option('--json')
|
|
89
116
|
.action((id: string, o: { json?: boolean }) => fail(() => { const entry = new DecisionLedger(config).show(id); emit(entry, o.json, formatDecisionDetail(entry)); }));
|
|
90
117
|
root.command('decide <id> <option>').description('사람 또는 AUTO 위임 결정 기록')
|
|
91
|
-
.option('--note <text>').option('--auto').option('--delegation <reason>').option('--track <track>').option('--agent <agent>').option('--json')
|
|
92
|
-
.action((id: string, choice: string, o: { note?: string; auto?: boolean; delegation?: string; track?: DecisionTrack; agent?: string; json?: boolean }) => fail(() => {
|
|
118
|
+
.option('--note <text>').option('--auto').option('--delegation <reason>').option('--track <track>').option('--agent <agent>').option('--json').option('--hq-override')
|
|
119
|
+
.action((id: string, choice: string, o: { note?: string; auto?: boolean; delegation?: string; track?: DecisionTrack; agent?: string; json?: boolean; hqOverride?: boolean }) => fail(() => {
|
|
93
120
|
if (o.auto && !o.delegation?.trim()) throw new Error('--auto requires --delegation');
|
|
94
121
|
if (!o.auto && (o.delegation || o.track || o.agent)) throw new Error('delegation/track/agent require --auto');
|
|
95
122
|
const by = o.auto ? { kind: 'auto' as const, agent: agent(o.agent), delegation: o.delegation!, ...(o.track ? { track: o.track } : {}) } : { kind: 'human' as const };
|
|
123
|
+
if (!mayWrite('decide', o.hqOverride)) return;
|
|
96
124
|
const { entry, delivery } = new DecisionLedger(config).decideWithDelivery(id, choice, by, o.note);
|
|
97
125
|
if (delivery && !delivery.ok) {
|
|
98
126
|
// The decision is recorded; only the answer to the waiting run failed — say so and how to retry, never «success».
|
|
@@ -103,22 +131,26 @@ export function registerDecisionsCommands(program: Command, config: DecisionLedg
|
|
|
103
131
|
}
|
|
104
132
|
emit(delivery ? { ...entry, delivery } : entry, o.json, `결정: ${formatDecisionDetail(entry)}`);
|
|
105
133
|
}));
|
|
106
|
-
root.command('retry-answer <id>').description('기록된 결정의 대기 질문 답 전달만 재시도한다').option('--json')
|
|
107
|
-
.action((id: string, o: { json?: boolean }) => fail(() => {
|
|
134
|
+
root.command('retry-answer <id>').description('기록된 결정의 대기 질문 답 전달만 재시도한다').option('--json').option('--hq-override')
|
|
135
|
+
.action((id: string, o: { json?: boolean; hqOverride?: boolean }) => fail(() => {
|
|
136
|
+
if (!mayWrite('retry-answer', o.hqOverride)) return;
|
|
108
137
|
const entry = new DecisionLedger(config).retryAnswer(id);
|
|
109
138
|
emit(entry, o.json, `답 전달: ${entry.id} → ${entry.resume?.questionId}`);
|
|
110
139
|
}));
|
|
111
140
|
root.command('add-options <id>').description('선택지가 미기재된 과거 항목에 확인된 선택지를 추가')
|
|
112
141
|
.requiredOption('--option <key=label:consequence>', '선택지 (두 번 이상)', repeat, [] as string[])
|
|
113
|
-
.option('--agent <agent>').option('--json')
|
|
114
|
-
.action((id: string, o: { option: string[]; agent?: string; json?: boolean }) => fail(() => {
|
|
115
|
-
const
|
|
142
|
+
.option('--agent <agent>').option('--json').option('--hq-override')
|
|
143
|
+
.action((id: string, o: { option: string[]; agent?: string; json?: boolean; hqOverride?: boolean }) => fail(() => {
|
|
144
|
+
const who = agent(o.agent);
|
|
145
|
+
if (!mayWrite('add-options', o.hqOverride)) return;
|
|
146
|
+
const entry = new DecisionLedger(config).addOptions(id, o.option.map(parseOption), who);
|
|
116
147
|
emit(entry, o.json, formatDecisionDetail(entry));
|
|
117
148
|
}));
|
|
118
|
-
root.command('withdraw <id>').description('결정 철회도 원장에 남긴다').requiredOption('--reason <reason>').option('--json')
|
|
119
|
-
.action((id: string, o: { reason: string; json?: boolean }) => fail(() => { const entry = new DecisionLedger(config).withdraw(id, o.reason); emit(entry, o.json, `철회: ${formatDecisionDetail(entry)}`); }));
|
|
120
|
-
root.command('import-markdown <file>').description('기존 대표 결정 문서 씨앗을 가져온다 (재실행 안전)').option('--json')
|
|
121
|
-
.action((file: string, o: { json?: boolean }) => fail(() => {
|
|
149
|
+
root.command('withdraw <id>').description('결정 철회도 원장에 남긴다').requiredOption('--reason <reason>').option('--json').option('--hq-override')
|
|
150
|
+
.action((id: string, o: { reason: string; json?: boolean; hqOverride?: boolean }) => fail(() => { if (!mayWrite('withdraw', o.hqOverride)) return; const entry = new DecisionLedger(config).withdraw(id, o.reason); emit(entry, o.json, `철회: ${formatDecisionDetail(entry)}`); }));
|
|
151
|
+
root.command('import-markdown <file>').description('기존 대표 결정 문서 씨앗을 가져온다 (재실행 안전)').option('--json').option('--hq-override')
|
|
152
|
+
.action((file: string, o: { json?: boolean; hqOverride?: boolean }) => fail(() => {
|
|
153
|
+
if (!mayWrite('import-markdown', o.hqOverride)) return;
|
|
122
154
|
const result = importDecisionMarkdown(new DecisionLedger(config), file);
|
|
123
155
|
emit(result, o.json, `가져옴 ${result.imported.length} · 기존 ${result.existing.length} · 불완전 ${result.incomplete.length} · 못 읽음 ${result.unread.length}${result.incomplete.length ? `\n${result.incomplete.join('\n')}` : ''}${result.unread.length ? `\n${result.unread.join('\n')}` : ''}`);
|
|
124
156
|
if (result.unread.length) process.exitCode = 1;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { accessSync, constants, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import type { Command } from 'commander';
|
|
4
|
+
import { debug } from '../debug/log.js';
|
|
5
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
6
|
+
import type { OutputEntry } from '../outputs/ledger.js';
|
|
7
|
+
import { writeStdoutJson } from './stdout-json.js';
|
|
8
|
+
|
|
9
|
+
type ListOptions = { since?: string; source?: string; limit?: string; json?: boolean };
|
|
10
|
+
|
|
11
|
+
function sinceIso(value: string): string {
|
|
12
|
+
if (value === '24h' || value === '7d') {
|
|
13
|
+
return new Date(Date.now() - (value === '24h' ? 24 : 7 * 24) * 60 * 60 * 1000).toISOString();
|
|
14
|
+
}
|
|
15
|
+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/.test(value)
|
|
16
|
+
|| !Number.isFinite(Date.parse(value))
|
|
17
|
+
|| new Date(`${value.slice(0, 10)}T00:00:00Z`).toISOString().slice(0, 10) !== value.slice(0, 10)) {
|
|
18
|
+
throw new Error(`invalid --since: ${value} (ISO|24h|7d)`);
|
|
19
|
+
}
|
|
20
|
+
return new Date(value).toISOString();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// listOutputs intentionally tolerates unreadable months. Detect inaccessible ledger files here so the CLI
|
|
24
|
+
// does not report an incomplete read as a genuine zero (including when run as root under chmod 000).
|
|
25
|
+
function assertReadableLedger(root: string): void {
|
|
26
|
+
const dir = join(root, 'outputs');
|
|
27
|
+
let names: string[];
|
|
28
|
+
try {
|
|
29
|
+
const info = statSync(dir);
|
|
30
|
+
if (!info.isDirectory() || (info.mode & 0o444) === 0 || (info.mode & 0o111) === 0) {
|
|
31
|
+
throw new Error(`outputs ledger directory unreadable: ${dir}`);
|
|
32
|
+
}
|
|
33
|
+
accessSync(dir, constants.R_OK | constants.X_OK);
|
|
34
|
+
names = readdirSync(dir);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return;
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
for (const name of names.filter(name => /^outputs-\d{4}-(0[1-9]|1[0-2])\.jsonl$/.test(name) || name === 'outputs.jsonl')) {
|
|
40
|
+
const file = join(dir, name);
|
|
41
|
+
const info = statSync(file);
|
|
42
|
+
if (!info.isFile() || (info.mode & 0o444) === 0) throw new Error(`outputs ledger file unreadable: ${file}`);
|
|
43
|
+
accessSync(file, constants.R_OK);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function registerOutputsCommands(program: Command): void {
|
|
48
|
+
program.command('outputs').description('산출물 원장 조회 (읽기 전용)')
|
|
49
|
+
.command('list').description('산출물 최신순 목록과 종류별 수')
|
|
50
|
+
.option('--since <ISO|24h|7d>', '이 시각 이후')
|
|
51
|
+
.option('--source <source>', 'exec|field-reel|field-feed')
|
|
52
|
+
.option('--limit <N>', '최대 건수 (기본 50)', '50')
|
|
53
|
+
.option('--json', 'JSON 출력')
|
|
54
|
+
.action(async (options: ListOptions) => {
|
|
55
|
+
try {
|
|
56
|
+
if (options.source !== undefined && !['exec', 'field-reel', 'field-feed'].includes(options.source)) {
|
|
57
|
+
throw new Error(`invalid --source: ${options.source} (exec|field-reel|field-feed)`);
|
|
58
|
+
}
|
|
59
|
+
if (!options.limit || !/^[1-9]\d*$/.test(options.limit) || !Number.isSafeInteger(Number(options.limit))) {
|
|
60
|
+
throw new Error(`invalid --limit: ${options.limit}`);
|
|
61
|
+
}
|
|
62
|
+
const scope = {
|
|
63
|
+
since: options.since === undefined ? undefined : sinceIso(options.since),
|
|
64
|
+
source: options.source as OutputEntry['source'] | undefined,
|
|
65
|
+
limit: Number(options.limit),
|
|
66
|
+
};
|
|
67
|
+
const displayedScope = { since: scope.since ?? null, source: scope.source ?? null, limit: scope.limit };
|
|
68
|
+
const root = effectiveInstanceRoot();
|
|
69
|
+
assertReadableLedger(root);
|
|
70
|
+
const { listOutputs } = await import('../outputs/ledger.js');
|
|
71
|
+
const items = listOutputs(scope, root);
|
|
72
|
+
const byKind: Record<string, number> = {};
|
|
73
|
+
for (const item of items) byKind[item.kind] = (byKind[item.kind] ?? 0) + 1;
|
|
74
|
+
debug.log('outputs.cli', 'list', { count: items.length, source: scope.source, since: scope.since });
|
|
75
|
+
if (options.json) {
|
|
76
|
+
await writeStdoutJson(`${JSON.stringify({ items, count: items.length, byKind, scope: displayedScope })}\n`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
for (const item of items) {
|
|
80
|
+
const at = Number.isFinite(Date.parse(item.at))
|
|
81
|
+
? new Intl.DateTimeFormat('sv-SE', { timeZone: 'Asia/Seoul', dateStyle: 'short', timeStyle: 'medium', hourCycle: 'h23' }).format(new Date(item.at))
|
|
82
|
+
: item.at;
|
|
83
|
+
console.log(`${at} KST · ${item.kind} · ${item.title} · ${item.source}/${item.sourceId} · ${item.seat ?? '—'} · ${item.path ?? item.url ?? '—'}`);
|
|
84
|
+
}
|
|
85
|
+
if (!items.length) console.log(`scope: ${JSON.stringify(displayedScope)}`);
|
|
86
|
+
console.log(`${items.length}개 · 종류별 수 ${JSON.stringify(byKind)}`);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(`outputs list: ${error instanceof Error ? error.message : String(error)}`);
|
|
89
|
+
process.exitCode = 1;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
package/src/cli/persona-cli.ts
CHANGED
|
@@ -7,8 +7,10 @@ import { parse as parseYaml } from 'yaml';
|
|
|
7
7
|
import { debug } from '../debug/log.js';
|
|
8
8
|
import { parsePersonaYaml } from '../persona/loader.js';
|
|
9
9
|
import { resolveRepositoryPersonaDir, resolveStatePersonaDir } from '../persona/global-registry.js';
|
|
10
|
-
import { choosePersonaId, clonePreset, describePreset, editPersona, findPreset, loadPresetIndex, loadPresets, presetToProfileYaml } from '../persona/presets.js';
|
|
10
|
+
import { UnknownPersonaSchemaKeyError, choosePersonaId, clonePreset, describePreset, editPersona, findPreset, loadPresetIndex, loadPresets, presetToProfileYaml } from '../persona/presets.js';
|
|
11
11
|
import { addTeamSet, listTeamSets, removeTeamSet } from '../persona/team-set.js';
|
|
12
|
+
import { validateTeamFile } from '../persona/team.js';
|
|
13
|
+
import { runPersonaLoopOnce } from '../seat-loop/seat-loop.js';
|
|
12
14
|
|
|
13
15
|
interface StoredPersona { personaId: string; displayName: string; description?: string; preset?: string; title?: string; path: string }
|
|
14
16
|
|
|
@@ -43,11 +45,11 @@ function mapping(value: unknown): value is Record<string, unknown> {
|
|
|
43
45
|
|
|
44
46
|
function setEdit(edits: Record<string, unknown>, assignment: string): void {
|
|
45
47
|
const equals = assignment.indexOf('=');
|
|
46
|
-
if (equals <
|
|
48
|
+
if (equals < 0) throw new Error(`--set requires key=value: ${assignment}`);
|
|
47
49
|
const key = assignment.slice(0, equals);
|
|
48
50
|
const parts = key.split('.');
|
|
49
51
|
if (parts.some((part) => !part || ['__proto__', 'prototype', 'constructor'].includes(part))) {
|
|
50
|
-
throw new
|
|
52
|
+
throw new UnknownPersonaSchemaKeyError(key);
|
|
51
53
|
}
|
|
52
54
|
const value: unknown = parseYaml(assignment.slice(equals + 1));
|
|
53
55
|
let target = edits;
|
|
@@ -68,6 +70,18 @@ function editOptions(fromFile: string | undefined, assignments: readonly string[
|
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
export function registerPersonaCommands(program: Command): void {
|
|
73
|
+
program.command('team').description('Validate a YAML team definition')
|
|
74
|
+
.command('validate <file>').description('Check team schema, persona references and handoff order')
|
|
75
|
+
.action((file: string) => {
|
|
76
|
+
try {
|
|
77
|
+
const definition = validateTeamFile(file);
|
|
78
|
+
console.log(`Valid team ${definition.name}`);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error(error instanceof Error ? error.message.replace(/\s+/g, ' ').trim() : String(error));
|
|
81
|
+
process.exitCode = 2;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
71
85
|
const team = program.command('team-set').description('Install, remove and list persona team sets');
|
|
72
86
|
team.command('add <file>').description('Install a YAML team set').action((file: string) => {
|
|
73
87
|
try { const result = addTeamSet(file); console.log(`Installed team-set ${result['team-set']}`); }
|
|
@@ -94,7 +108,7 @@ export function registerPersonaCommands(program: Command): void {
|
|
|
94
108
|
if (opts.json) { console.log(JSON.stringify({ presets: presets.map((p) => ({ id: p.personaId, displayName: p.displayName, role: p.role, oneLine: p.oneLine })), mine }, null, 2)); return; }
|
|
95
109
|
if (!presets.length) console.log('페르소나 프리셋을 찾지 못했습니다(persona-presets/ 가 이 설치본에 없습니다).');
|
|
96
110
|
else {
|
|
97
|
-
console.log('프리셋 — 골라서 내 페르소나로 만들기: elanous persona add
|
|
111
|
+
console.log('프리셋 — 골라서 내 페르소나로 만들기: elanous persona add <이름> [--from <프리셋>]');
|
|
98
112
|
const width = Math.max(...presets.map((p) => p.personaId.length));
|
|
99
113
|
for (const p of presets) console.log(` ${p.personaId.padEnd(width)} ${p.displayName} — ${p.role} · ${p.oneLine}`);
|
|
100
114
|
}
|
|
@@ -105,24 +119,27 @@ export function registerPersonaCommands(program: Command): void {
|
|
|
105
119
|
}
|
|
106
120
|
});
|
|
107
121
|
|
|
108
|
-
persona.command('add <
|
|
109
|
-
.description('Make your own persona from a preset
|
|
122
|
+
persona.command('add <name>')
|
|
123
|
+
.description('Make your own persona from a preset — the preset file is not changed')
|
|
124
|
+
.option('--from <preset>', 'Preset to clone (defaults to the named preset)')
|
|
110
125
|
.option('--name <name>', 'What to call it (default: the preset’s first name)')
|
|
111
126
|
.option('--as <name>', 'Clone this preset under a new, unique name')
|
|
112
127
|
.option('--title <title>', 'Optional title, e.g. CMO')
|
|
113
128
|
.option('--json', 'JSON output')
|
|
114
|
-
.action((query: string, opts: { name?: string; as?: string; title?: string; json?: boolean }) => {
|
|
129
|
+
.action((query: string, opts: { from?: string; name?: string; as?: string; title?: string; json?: boolean }) => {
|
|
115
130
|
const presets = loadPresets();
|
|
116
|
-
const
|
|
131
|
+
const presetQuery = opts.from ?? query;
|
|
132
|
+
const preset = findPreset(presets, presetQuery);
|
|
117
133
|
if (!preset) {
|
|
118
|
-
console.error(`프리셋 «${
|
|
134
|
+
console.error(`프리셋 «${presetQuery}» 을 찾지 못했습니다. 있는 것: ${presets.map((p) => p.personaId).join(', ')}`);
|
|
119
135
|
process.exitCode = 1;
|
|
120
136
|
return;
|
|
121
137
|
}
|
|
122
|
-
if (opts.as !== undefined) {
|
|
138
|
+
if (opts.as !== undefined || opts.from !== undefined) {
|
|
123
139
|
try {
|
|
124
|
-
if (opts.name !== undefined || opts.title !== undefined) throw new Error('--
|
|
125
|
-
|
|
140
|
+
if (opts.from !== undefined && (opts.as !== undefined || opts.name !== undefined || opts.title !== undefined)) throw new Error('--from cannot be combined with --as, --name or --title');
|
|
141
|
+
if (opts.as !== undefined && (opts.name !== undefined || opts.title !== undefined)) throw new Error('--as cannot be combined with --name or --title');
|
|
142
|
+
const result = clonePreset(preset, opts.from !== undefined ? query : opts.as!);
|
|
126
143
|
debug.log('persona.cli', 'cloned', { preset: preset.personaId, personaId: result.personaId });
|
|
127
144
|
if (opts.json) console.log(JSON.stringify({ personaId: result.personaId, displayName: result.displayName, preset: preset.personaId, path: result.path }, null, 2));
|
|
128
145
|
else {
|
|
@@ -170,7 +187,22 @@ export function registerPersonaCommands(program: Command): void {
|
|
|
170
187
|
console.log(` 파일: ${result.path}`);
|
|
171
188
|
}
|
|
172
189
|
} catch (err) {
|
|
173
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
190
|
+
console.error(err instanceof Error ? err.message.replace(/\s+/g, ' ').trim() : String(err));
|
|
191
|
+
process.exitCode = err instanceof UnknownPersonaSchemaKeyError ? 2 : 1;
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
persona.command('loop <name>')
|
|
196
|
+
.description('Shadow-pick one todo for a saved persona without executing it')
|
|
197
|
+
.requiredOption('--once', 'Pick exactly one todo')
|
|
198
|
+
.option('--json', 'JSON output')
|
|
199
|
+
.action(async (name: string, opts: { json?: boolean }) => {
|
|
200
|
+
try {
|
|
201
|
+
const entry = await runPersonaLoopOnce(name);
|
|
202
|
+
if (opts.json) console.log(JSON.stringify(entry));
|
|
203
|
+
else console.log(entry.todo ? `그림자 선택 — ${entry.todo.title} (${entry.todo.id})` : '할 일 없음');
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
174
206
|
process.exitCode = 1;
|
|
175
207
|
}
|
|
176
208
|
});
|
package/src/cli/pod-cli.ts
CHANGED
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
|
|
4
4
|
import type { Command } from 'commander';
|
|
5
5
|
import { debug } from '../debug/log.js';
|
|
6
|
+
import { readPodMemoryAdvice } from './pod-memory-advice.js';
|
|
6
7
|
import { getUserConfig } from '../user-config.js';
|
|
7
8
|
import { listCodexAccountsInStore } from '../oauth/codex-account-store.js';
|
|
8
|
-
import { parsePodPool, resolvePodPoolSpec, type PoolKubectl } from '../task-orchestrator/surfaces/pod-pool.js';
|
|
9
|
+
import { parsePodPool, resolvePodPoolSpec, podPoolHostLease, type PoolKubectl } from '../task-orchestrator/surfaces/pod-pool.js';
|
|
9
10
|
import { leaseKubectl, measurePoolLease, probePoolDns, recommendConcurrency, type PoolLeaseMeasure, type PoolDnsProbe } from '../task-orchestrator/surfaces/pod-lease.js';
|
|
10
11
|
import { POD_COMMAND_DEADLINE_SECONDS, runPodCommand, type PodCommandResult, type RunPodCommandOptions } from '../task-orchestrator/surfaces/pod-command-job.js';
|
|
12
|
+
import { hostLeaseCounts, leaseHasPendingPod } from '../pod-lease/host-lease.js';
|
|
11
13
|
|
|
12
14
|
export interface PodCliIo {
|
|
13
15
|
log: (line: string) => void;
|
|
@@ -24,6 +26,7 @@ export interface PodCliDeps {
|
|
|
24
26
|
perAccount?: () => number;
|
|
25
27
|
poolSpec?: (explicit?: string) => string | null;
|
|
26
28
|
harnessPool?: () => string | undefined;
|
|
29
|
+
memoryAdvice?: (logsDb?: string) => ReturnType<typeof readPodMemoryAdvice>;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
const HELP = `elanous pod run [--pool <스펙>] [--skill <이름,…>] [--llm grok] [--clone] [--deadline <초>] -- <명령…>
|
|
@@ -38,6 +41,21 @@ export function registerPodCommands(program: Command, deps: PodCliDeps = {}): vo
|
|
|
38
41
|
};
|
|
39
42
|
const run = deps.run ?? ((options: RunPodCommandOptions) => runPodCommand(options));
|
|
40
43
|
const pod = program.command('pod').description('Pod 명령 Job — 하니스 없이 명령 하나를 돌리고 산출을 돌려받는다');
|
|
44
|
+
pod.command('memory').description('실측 기반 Pod 메모리 권고 (읽기 전용)')
|
|
45
|
+
.command('advise').description('골 종류별 standard 16Gi / high 32Gi 권고와 근거')
|
|
46
|
+
.option('--json', '권고와 측정 근거를 JSON 으로')
|
|
47
|
+
.option('--logs-db <path>', '기존 logs.db 경로')
|
|
48
|
+
.action((opts: { json?: boolean; logsDb?: string }) => {
|
|
49
|
+
try {
|
|
50
|
+
const advice = (deps.memoryAdvice ?? readPodMemoryAdvice)(opts.logsDb);
|
|
51
|
+
if (opts.json) io.log(JSON.stringify(advice));
|
|
52
|
+
else for (const row of advice.byGoalType) io.log(`${row.goalType}: ${row.recommended} ${row.limit} · ${row.reason}`);
|
|
53
|
+
io.exit(0);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
io.error(err instanceof Error ? err.message : String(err));
|
|
56
|
+
io.exit(1);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
41
59
|
pod.command('lease').description('Pod 풀 동시 실행 권장 (DNS 점검용 Pod 를 잠깐 만들었다 지움)')
|
|
42
60
|
.command('status').description('지금 추가로 쏠 수 있는 Pod 골과 제한 근거')
|
|
43
61
|
.option('--pool <spec>', '풀 스펙 — 컨텍스트[@ssh호스트][:상한][#레지스트리:포트] 쉼표')
|
|
@@ -61,8 +79,13 @@ export function registerPodCommands(program: Command, deps: PodCliDeps = {}): vo
|
|
|
61
79
|
const perAccount = (deps.perAccount ?? (() => getUserConfig().pod?.lease?.perAccount ?? 4))();
|
|
62
80
|
const decision = recommendConcurrency(measure, { capacity: members.reduce((sum, m) => sum + m.capacity, 0), accounts: accounts ?? -1, perAccount });
|
|
63
81
|
if (!context) { decision.recommended = null; decision.limitedBy = null; decision.reason = '측정 불가: cluster (current context)'; }
|
|
64
|
-
const
|
|
65
|
-
|
|
82
|
+
const records = members.length ? podPoolHostLease(members).live() : [];
|
|
83
|
+
const host = hostLeaseCounts(records);
|
|
84
|
+
// Pending Pods and Job-only reservations are disjoint; a lease for an already Pending Job is the same Job.
|
|
85
|
+
const waitingJobs = decision.pending === null ? null : decision.pending +
|
|
86
|
+
records.filter((r) => r.stage === 'job' && !leaseHasPendingPod(r, decision.pendingJobs ?? [])).length;
|
|
87
|
+
const result = { pool: spec, ...measure, accounts, perAccount, ...decision, reserved: host.reserved, waitingJobs };
|
|
88
|
+
debug.log('pod.lease', 'status', { pool: spec ? members.map((m) => ({ capacity: m.capacity })) : null, running: decision.running, pending: decision.pending, reserved: host.reserved, waitingJobs, unleasedRunning: decision.unleasedRunning, recommended: decision.recommended, limitedBy: decision.limitedBy });
|
|
66
89
|
if (opts.json) io.log(JSON.stringify(result));
|
|
67
90
|
else {
|
|
68
91
|
io.log(`풀: ${(spec ?? context) || '?'}`);
|
|
@@ -72,6 +95,7 @@ export function registerPodCommands(program: Command, deps: PodCliDeps = {}): vo
|
|
|
72
95
|
io.log(`${m.context} | ${m.capacity} | ${m.running ?? '?'} | ${m.pending ?? '?'} | ${mem(m.memoryLimitBytes)}/${mem(m.allocatableMemoryBytes)} | ${m.allocatableCpuMillicores === null ? '?' : m.allocatableCpuMillicores / 1000}${m.reason ? ` (${m.reason})` : ''}`);
|
|
73
96
|
}
|
|
74
97
|
io.log(`권장 지금 ${decision.recommended ?? '?'} 개 더 (limitedBy=${decision.limitedBy ?? 'unknown'})${decision.reason ? ` · ${decision.reason}` : ''}`);
|
|
98
|
+
io.log(`예약(저작 중) ${host.reserved} · 대기 Job ${waitingJobs ?? '?'} · 실행 ${decision.running ?? '?'}`);
|
|
75
99
|
io.log(`임대 없는 실행 ${decision.unleasedRunning ?? '?'}`);
|
|
76
100
|
io.log(`capacity: ${decision.capacitySlots ?? '?'} 칸 (상한 ${members.reduce((sum, m) => sum + m.capacity, 0)} − Running ${decision.running ?? '?'} − Pending ${decision.pending ?? '?'}; 권장 수에서 건강한 멤버의 임대 없는 실행 차감)`);
|
|
77
101
|
io.log(`memory: ${decision.memorySlots ?? '?'} 칸 (할당 가능 − Running 하니스 상한 합; 골당 16Gi · 모든 네임스페이스의 노드별 기존 예약을 반영해 배치 가능 ${decision.placeableSlots ?? '?'} 칸)`);
|