elanous 0.2.12 → 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-ee3795b112907122.js → 1394-931c9827067b2276.js} +1 -1
- 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-0b018a9e4fc56200.js → 7758-85c2b53f731da024.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{8605-0259994816604c49.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-cb169620945d1952.js → page-e5b4e1b256f41076.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-cefe8169e36cf62a.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-813c4a52c9d05ca8.js → page-4c1dbb21861beab2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-4731f2c215358937.js → page-c85b816e3b269dbb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/{page-a022b257950f39e6.js → page-f6bc66b919b2ea7d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-a2ffc717e04b8b0a.js → page-60603c1e056c9b25.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-b59ee4e70fc18fcf.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-c172dabd796eb7a3.js → page-36c6c4183353ac9e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/{page-1efd0cb9e0ed9d13.js → page-cb2eb0fd809980da.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-d5dc7ff69a37b4cf.js → page-31012264da8a341f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-50309aeb0bdd554a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-dfca9b7d2de0b135.js → page-0696ee3fac9fe56c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-793fa536b3a6ef37.js → page-66c27608f651b866.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-6dd3bfbbde2a5c2c.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-77447356baad75d2.js → page-7a2bf830038a2264.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1f327f18694fabca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e5cef2fe6e4028df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cf596128d2b420ba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/{page-6d604227e1664b71.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-4048543b4b7ad5e0.js → page-e7a6c80f288b1948.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-4c3a3126abed551a.js → page-29a709616df9b050.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-64b0aecb1ca75e75.js → page-fee842d7c2151129.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-78b40a344ed22f24.js +1 -0
- 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-a011dd5b0bff29c5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-9547b5cec7cf76bf.js → page-d92e32d51ab5157e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-5f21aed6932ef375.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-1e1a53946804debf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-2cddeb9e2aeedc55.js → page-a1833cd43d832601.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/{page-e297866051595d81.js → page-63d14a4d8fe1e1c1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/{page-baba2e047872fb0f.js → page-2a511d320f762e06.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-e895da4ec9cac345.js → page-1c3e03f88dac1e21.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c4d5289c2ea97a89.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b91f8952deaeaf27.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-71b43a2588489987.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-2e9e2fb01c9da1c9.js → page-2b256feed331546f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-d62ceb13411ebba4.js → page-505bb96d69a8622c.js} +1 -1
- 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/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/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/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/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/lecture/lecture-note.yaml +28 -0
- package/graphs/lecture/recipes.yaml +17 -0
- package/package.json +2 -1
- 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/release-loop/gate-node.ts +53 -15
- package/scripts/release-loop/ops-upgrade-node.ts +17 -5
- package/scripts/test-deterministic.ts +53 -3
- 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 +12 -7
- package/src/agent-mission/duo.ts +94 -0
- package/src/agent-mission/pty-decision.ts +1 -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/cli/decisions-cli.ts +51 -19
- package/src/cli/outputs-cli.ts +92 -0
- package/src/cli/persona-cli.ts +29 -0
- package/src/cli/pod-cli.ts +10 -3
- package/src/cli/pr-cli.ts +15 -1
- package/src/cli/release-cli.ts +108 -28
- 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 +15 -2
- package/src/context-bus/long-term-memory.ts +118 -22
- package/src/context-bus/nightly-condense.ts +1 -1
- package/src/decisions/decision-cards.ts +13 -7
- package/src/decisions/decision-ledger.ts +43 -17
- package/src/graph-kinds/registry.ts +2 -2
- package/src/graph-runner/graph-approval-card.ts +2 -2
- package/src/graph-runner/graph-grow.ts +64 -11
- package/src/graph-runner/graph-tick.ts +9 -2
- package/src/graph-runner/runner.ts +81 -17
- package/src/harness/harness-cli-command.ts +27 -1
- package/src/harness/harness-queue-child.ts +37 -0
- package/src/harness/harness-queue.ts +306 -0
- package/src/hooks/github-shadow.ts +72 -2
- package/src/hooks/receiver.ts +4 -2
- 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 +199 -7
- package/src/intake-plane/discord-seat-work.ts +13 -6
- 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 +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 +3 -3
- 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 +47 -0
- package/src/nexus/api/personas.ts +2 -1
- package/src/nexus/api/skill-exec-route.ts +97 -0
- package/src/persona/loader.ts +18 -0
- package/src/persona/persona-todo.ts +14 -9
- package/src/persona/presets.ts +2 -1
- package/src/persona/team.ts +105 -0
- package/src/persona/types.ts +4 -0
- package/src/pod-lease/host-lease.ts +130 -20
- package/src/release-loop/landed-but-yellow.ts +12 -0
- package/src/seat-dispatch/seat-ask.ts +57 -12
- package/src/seat-loop/seat-loop.ts +90 -16
- package/src/seat-loop/seat-pack.ts +127 -0
- package/src/self-dev/ask-launch-flow.ts +3 -0
- package/src/self-implement/l8-merge-queue.ts +157 -1
- 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-lease.ts +18 -6
- package/src/task-orchestrator/surfaces/pod-pool.ts +27 -10
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +38 -8
- package/src/telegram-dispatch-forward.ts +1 -1
- package/src/telegram-fabric-plan.ts +90 -0
- package/src/telegram-project-command.ts +10 -4
- package/src/telegram.ts +5 -0
- package/src/tool-runtime/elanous-control-runtimes.ts +2 -1
- package/src/user-config.ts +94 -10
- 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/1147-466ec15afacc1a65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7277-0b2fc7c16ba2d64b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9906-482c6619b3fdda20.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9920-5cfdc5e8fd209825.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-d2e35228bc497119.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-5c05b0da4bc1392c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e0ccdb576812b523.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-a67e11ba962337b5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-e71c84189e8c683e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-1d6a533ce5a6efcb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-490653e8d4cc8852.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-29e961191f6057df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-c822f55b884d9104.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fa4ccfc432424e52.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-9c7e8f744a765daf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4292d0149020e8a7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-db14d0be7f1c460c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-e5d8072cd7cfef73.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e6dcb83265530636.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-45248f08d6aecae5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-4344faf20216233b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-698bb29f11864b89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-bd6a6dc1ddc273f2.js +0 -1
- /package/apps/pwa/out/_next/static/{-6XrAoD3eU7NyMHAE4xPS → D51l8jUBplJrIpWvENsDK}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{-6XrAoD3eU7NyMHAE4xPS → D51l8jUBplJrIpWvENsDK}/_ssgManifest.js +0 -0
package/src/index.ts
CHANGED
|
@@ -41,6 +41,7 @@ import { registerRepoCommands } from './cli/repo-cli.js';
|
|
|
41
41
|
import { registerAgentCommands, runChatTurnCli, emitDetachedProgress } from './cli/agent-cli.js';
|
|
42
42
|
export { runChatTurnCli, buildCliAgentTools, setCliAgentDispatchForTesting, emitDetachedProgress, emitHarnessFeedbackProgress } from './cli/agent-cli.js';
|
|
43
43
|
import { registerOpsCommands } from './cli/ops-cli.js';
|
|
44
|
+
import { registerOutputsCommands } from './cli/outputs-cli.js';
|
|
44
45
|
import { registerPublishCommands } from './cli/publish-cli.js';
|
|
45
46
|
import { registerAutopilotCommands } from './cli/autopilot-cli.js';
|
|
46
47
|
import { registerRoleCommands } from './cli/role-cli.js';
|
|
@@ -103,7 +104,7 @@ import type { AcpPermissionApprover } from './acp/client.js';
|
|
|
103
104
|
import { runGitCommand } from './git-fs/runner.js';
|
|
104
105
|
import { debug, redactSecretText, redactSecrets } from './debug/log.js';
|
|
105
106
|
import { installHarnessCliCommand, type HarnessAskSayChildLlmOptions, type HarnessAskSayOptions, type HarnessPlanOptions } from './harness/harness-cli-command.js';
|
|
106
|
-
import { formatCliUserError, isCliUserError } from './cli/cli-user-error.js';
|
|
107
|
+
import { CliUserError, formatCliUserError, isCliUserError } from './cli/cli-user-error.js';
|
|
107
108
|
import { dispatchSolveMission } from './skills/tools/solve-mission.js';
|
|
108
109
|
import { performBrowserAction, type BrowserActionDeps, type BrowserActionResult } from './harness/browser-act.js';
|
|
109
110
|
import { decideTypeAction, type TypeActionDecision, type TypeTarget } from './harness/browser-act-type.js';
|
|
@@ -868,7 +869,75 @@ registerUsageCommand(program);
|
|
|
868
869
|
registerLiveCommands(program);
|
|
869
870
|
registerResearchCommand(program);
|
|
870
871
|
registerReleaseCommands(program);
|
|
872
|
+
// HQ-HB · HQ-FENCE (10-04): one writer across mbp · node-b · cloud-vm — lease on the arbiter, 2-of-3 quorum, generation fencing.
|
|
873
|
+
const hqCmd = program.command('hq').description('본부 임대 — 하트비트·중재·단일 작성자 래퍼');
|
|
874
|
+
async function hqSink(): Promise<void> {
|
|
875
|
+
try { await (await import('./domains/standalone-log-sink.js')).registerStandaloneLogSink('hq'); } catch { /* observation is fail-soft */ }
|
|
876
|
+
}
|
|
877
|
+
hqCmd.command('host').description('이 머신의 HQ 호스트 이름 설정')
|
|
878
|
+
.command('set <name>').description('호스트 로컬 ~/.elanous-hq/host 에 한 줄로 기록')
|
|
879
|
+
.action(async (name: string) => {
|
|
880
|
+
try {
|
|
881
|
+
const { hqHostSet } = await import('./hq/hq.js');
|
|
882
|
+
const path = hqHostSet(name);
|
|
883
|
+
console.log(`hq host set: ${name} (${path})`);
|
|
884
|
+
} catch (error) { console.error(`hq host set: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
885
|
+
});
|
|
886
|
+
hqCmd.command('lease').argument('<action>', 'acquire|renew|status|release')
|
|
887
|
+
.option('--host <name>', '이 호스트 이름(기본 ~/.elanous-hq/host · hq.hostName · os.hostname)')
|
|
888
|
+
.option('--json', 'JSON 출력')
|
|
889
|
+
.action(async (action: string, opts: { host?: string; json?: boolean }) => {
|
|
890
|
+
try {
|
|
891
|
+
if (!['acquire', 'renew', 'status', 'release'].includes(action)) throw new CliUserError(`hq lease: unknown action ${action}`, 'acquire|renew|status|release');
|
|
892
|
+
await hqSink();
|
|
893
|
+
const { hqLease } = await import('./hq/hq.js');
|
|
894
|
+
const result = hqLease(action as 'acquire', {}, opts.host ? { host: opts.host } : {});
|
|
895
|
+
if (opts.json) await writeStdoutJson(`${JSON.stringify(result)}\n`);
|
|
896
|
+
else console.log(result.ok ? `hq lease ${action}: ok${'record' in result && result.record ? ` · holder ${result.record.holder} · generation ${result.record.generation}` : ''}` : `hq lease ${action}: refused — ${'reason' in result ? result.reason : ''}`);
|
|
897
|
+
if (!result.ok) process.exitCode = 2;
|
|
898
|
+
} catch (error) { if (error instanceof CliUserError) throw error; console.error(`hq lease: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
899
|
+
});
|
|
900
|
+
hqCmd.command('heartbeat').description('보유자는 갱신 · 아니면 대기(보유자에 닿는지 중재자에 보고)').option('--json', 'JSON 출력')
|
|
901
|
+
.action(async (opts: { json?: boolean }) => {
|
|
902
|
+
try {
|
|
903
|
+
await hqSink();
|
|
904
|
+
const result = (await import('./hq/hq.js')).hqHeartbeat();
|
|
905
|
+
if (opts.json) await writeStdoutJson(`${JSON.stringify(result)}\n`); else console.log(`hq heartbeat: ${result.outcome}`);
|
|
906
|
+
} catch (error) { console.error(`hq heartbeat: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
907
|
+
});
|
|
908
|
+
hqCmd.command('arbiter-check').description('중재자(cloud-vm)에서: 보유자 탐침 · 연속 2회 이중 불통이면 대기 호스트 승격').option('--json', 'JSON 출력')
|
|
909
|
+
.action(async (opts: { json?: boolean }) => {
|
|
910
|
+
try {
|
|
911
|
+
await hqSink();
|
|
912
|
+
const result = (await import('./hq/hq.js')).hqArbiterCheck();
|
|
913
|
+
if (opts.json) await writeStdoutJson(`${JSON.stringify(result)}\n`); else console.log(`hq arbiter-check: ${result.promoted ? `promoted ${result.holder} gen ${result.generation}` : `streak ${result.streak}`}`);
|
|
914
|
+
} catch (error) { console.error(`hq arbiter-check: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
915
|
+
});
|
|
916
|
+
hqCmd.command('seen').description('이 호스트가 본 가장 높은 임대 세대(한 번도 못 봤으면 exit 3 — 드릴 전 기본 원천 판단용)').option('--json', 'JSON 출력')
|
|
917
|
+
.action(async (opts: { json?: boolean }) => {
|
|
918
|
+
const result = (await import('./hq/hq.js')).hqSeen();
|
|
919
|
+
if (opts.json) await writeStdoutJson(`${JSON.stringify(result)}\n`);
|
|
920
|
+
else console.log(result.seen ? `hq seen: generation ${result.generation}` : 'hq seen: never');
|
|
921
|
+
process.exitCode = result.seen ? 0 : 3;
|
|
922
|
+
});
|
|
923
|
+
hqCmd.command('fence').description('이 호스트가 임대를 쥐었을 때만 명령 실행(아니면 건너뜀 · exit 0)')
|
|
924
|
+
.requiredOption('--role <role>', 'telegram-poller|cron|seat-loop|release-run|conatus|git-push')
|
|
925
|
+
.argument('<command...>', '-- 뒤의 명령')
|
|
926
|
+
.action(async (command: string[], opts: { role: string }) => {
|
|
927
|
+
const { FENCE_ROLES, hqFenceRun } = await import('./hq/hq.js');
|
|
928
|
+
if (!(FENCE_ROLES as readonly string[]).includes(opts.role)) throw new CliUserError(`hq fence: unknown role ${opts.role}`, FENCE_ROLES.join('|'));
|
|
929
|
+
await hqSink();
|
|
930
|
+
process.exitCode = hqFenceRun(opts.role as 'cron', command);
|
|
931
|
+
});
|
|
871
932
|
const seatCmd = program.command('seat').description('분배된 자리의 하루 루프와 보고');
|
|
933
|
+
seatCmd.command('answer <askId> <answer>').description('자리 질문에 답을 남기고 원래 표면으로 회신 예약')
|
|
934
|
+
.action(async (askId: string, answer: string) => {
|
|
935
|
+
try {
|
|
936
|
+
const { answerSeatAsk } = await import('./seat-dispatch/seat-ask.js');
|
|
937
|
+
answerSeatAsk(askId, answer);
|
|
938
|
+
console.log(`자리 답변 기록: ${askId}`);
|
|
939
|
+
} catch (error) { console.error(`seat answer: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
940
|
+
});
|
|
872
941
|
seatCmd.command('loop').requiredOption('--seat <SEAT>', 'MK|OP|TC|UX')
|
|
873
942
|
.requiredOption('--once', '한 번 실행')
|
|
874
943
|
.option('--json', 'JSON 출력')
|
|
@@ -893,6 +962,20 @@ seatCmd.command('report').requiredOption('--seat <SEAT>', 'MK|OP|TC|UX')
|
|
|
893
962
|
else console.log(result.body);
|
|
894
963
|
} catch (error) { console.error(`seat report: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
895
964
|
});
|
|
965
|
+
seatCmd.command('pack').description('자리 묶음').command('export')
|
|
966
|
+
.requiredOption('--seat <SEAT>', '내보낼 자리')
|
|
967
|
+
.requiredOption('--out <dir>', '비어 있는 산출 폴더')
|
|
968
|
+
.action(async (opts: { seat: string; out: string }) => {
|
|
969
|
+
try {
|
|
970
|
+
const { exportSeatPack } = await import('./seat-loop/seat-pack.js');
|
|
971
|
+
const result = exportSeatPack(opts.seat, opts.out);
|
|
972
|
+
console.log(`seat pack ${result.name} ${result.version}: ${result.files.length} files → ${opts.out}`);
|
|
973
|
+
} catch (error) {
|
|
974
|
+
const { SeatPackLeakError } = await import('./seat-loop/seat-pack.js');
|
|
975
|
+
console.error(`seat pack export: ${error instanceof Error ? error.message : String(error)}`);
|
|
976
|
+
process.exitCode = error instanceof SeatPackLeakError ? 2 : 1;
|
|
977
|
+
}
|
|
978
|
+
});
|
|
896
979
|
registerModelWatchCommand(program);
|
|
897
980
|
registerDoctorCommand(program);
|
|
898
981
|
// runCli() → main() → program.parseAsync() dispatches control serve and resources where|list.
|
|
@@ -974,6 +1057,19 @@ marketCmd.command('keygen').description('Generate a local test index signing key
|
|
|
974
1057
|
console.log(`Generated local index key ${pair.keyId}`);
|
|
975
1058
|
});
|
|
976
1059
|
|
|
1060
|
+
const stewardCmd = program.command('steward').description('Steward loop controls');
|
|
1061
|
+
stewardCmd.command('readiness').description('Read-only shadow evidence for a live decision')
|
|
1062
|
+
.option('--since <duration>', 'Lookback duration, e.g. 24h', '24h')
|
|
1063
|
+
.option('--json', 'JSON output')
|
|
1064
|
+
.action(async (opts: { since: string; json?: boolean }) => {
|
|
1065
|
+
process.exitCode = (await import('./steward/readiness.js')).runStewardReadinessCli(opts);
|
|
1066
|
+
});
|
|
1067
|
+
stewardCmd.command('mode [mode]').description('Show or set the steward mode: off|shadow|live|rescue (rescue = HQ move: launch·harvest·alert·decision cards only)')
|
|
1068
|
+
.option('--json', 'JSON output')
|
|
1069
|
+
.action(async (mode: string | undefined, opts: { json?: boolean }) => {
|
|
1070
|
+
process.exitCode = await (await import('./steward/rescue.js')).runStewardModeCli(mode, opts);
|
|
1071
|
+
});
|
|
1072
|
+
|
|
977
1073
|
program.command('directive').description('Turn instructions into Linear issues')
|
|
978
1074
|
.command('add <text>').option('--source <source>', 'telegram|pwa|tui|cli', 'cli')
|
|
979
1075
|
.option('--dry-run', 'Preview the normalized directive without contacting Linear')
|
|
@@ -3029,6 +3125,39 @@ const harnessOrchestrateCmd = registerHarnessOrchestrateCapabilityOptions(
|
|
|
3029
3125
|
});
|
|
3030
3126
|
|
|
3031
3127
|
const contextCmd = program.command('context').description('외부 에이전트 맥락 이벤트');
|
|
3128
|
+
contextCmd.command('day')
|
|
3129
|
+
.description('자리 넷과 외부 세션의 최근 맥락 이벤트 수·마지막 시각 (읽기 전용)')
|
|
3130
|
+
.option('--since <hours>', '최근 시간 창 (예: 24h)', '24h')
|
|
3131
|
+
.option('--json', '구조화된 JSON 출력')
|
|
3132
|
+
.action(async (opts: { since: string; json?: boolean }) => {
|
|
3133
|
+
try {
|
|
3134
|
+
const { contextDay, renderContextDay } = await import('./context-bus/context-day.js');
|
|
3135
|
+
const report = contextDay(opts.since);
|
|
3136
|
+
await writeStdoutJson(`${opts.json ? JSON.stringify(report) : renderContextDay(report)}\n`);
|
|
3137
|
+
} catch (error) {
|
|
3138
|
+
console.error(`context day: ${error instanceof Error ? error.message : String(error)}`);
|
|
3139
|
+
process.exitCode = 2;
|
|
3140
|
+
}
|
|
3141
|
+
});
|
|
3142
|
+
contextCmd.command('condense')
|
|
3143
|
+
.description('최근 맥락 이벤트·결정·조율 요약을 프로젝트/자리별 장기 기억 후보 JSON ⊕ Markdown 으로 응축')
|
|
3144
|
+
.option('--since <hours>', '최근 시간 창 (예: 24h)', '24h')
|
|
3145
|
+
.option('--dry-run', '읽고 stdout 에만 출력 (기본 동작)')
|
|
3146
|
+
.option('--apply', '충돌은 OP 결정 원장으로 올리고 비충돌 후보를 기존 프로젝트 기억에 저장 · 이전 버전 은퇴')
|
|
3147
|
+
.option('--json', '구조화된 JSON 출력')
|
|
3148
|
+
.action(async (opts: { since: string; dryRun?: boolean; apply?: boolean; json?: boolean }) => {
|
|
3149
|
+
try {
|
|
3150
|
+
if (opts.apply && opts.dryRun) throw new Error('--apply and --dry-run cannot be combined');
|
|
3151
|
+
if (!/^[1-9]\d*h$/.test(opts.since)) throw new Error('--since requires positive hours, e.g. 24h');
|
|
3152
|
+
const { runContextCondense } = await import('./context-bus/condense.js');
|
|
3153
|
+
const report = await runContextCondense({ hours: Number(opts.since.slice(0, -1)), apply: opts.apply === true });
|
|
3154
|
+
await writeStdoutJson(JSON.stringify({ since: report.since, until: report.until, cards: report.cards, conflicts: report.conflicts, skipped: report.skipped, folded: report.folded, ...(report.applied ? { applied: report.applied } : {}) }) + '\n');
|
|
3155
|
+
if (!opts.json) await writeStdoutFully(report.markdown);
|
|
3156
|
+
} catch (error) {
|
|
3157
|
+
console.error(`context condense: ${error instanceof Error ? error.message : String(error)}`);
|
|
3158
|
+
process.exitCode = 2;
|
|
3159
|
+
}
|
|
3160
|
+
});
|
|
3032
3161
|
contextCmd.command('hooks').description('Claude Code 맥락 훅 설정').command('install')
|
|
3033
3162
|
.requiredOption('--print', '설정 조각만 출력하고 설정 파일은 수정하지 않음')
|
|
3034
3163
|
.action(async () => {
|
|
@@ -5616,6 +5745,7 @@ registerFleetCommands(program);
|
|
|
5616
5745
|
|
|
5617
5746
|
// ── ops (운영 관측 — 지금 뭐 도나·이상 없나·상태 전이) ──
|
|
5618
5747
|
registerOpsCommands(program);
|
|
5748
|
+
registerOutputsCommands(program);
|
|
5619
5749
|
|
|
5620
5750
|
registerPublishCommands(program);
|
|
5621
5751
|
|
|
@@ -5794,6 +5924,33 @@ agentCmd
|
|
|
5794
5924
|
process.exit(outcome.exitCode);
|
|
5795
5925
|
});
|
|
5796
5926
|
|
|
5927
|
+
agentCmd.command('duo [text...]')
|
|
5928
|
+
.description('Run Codex and Claude Code concurrently on the same mission in separate worktrees; summarize both PTY decisions and evidence.')
|
|
5929
|
+
.requiredOption('--left <backend>', 'codex or claude')
|
|
5930
|
+
.requiredOption('--right <backend>', 'codex or claude')
|
|
5931
|
+
.requiredOption('--branch <name>', 'base name for two distinct worktree branches')
|
|
5932
|
+
.option('--base <ref>', 'common branch base (default HEAD)')
|
|
5933
|
+
.option('--mission-file <path>', 'read the same verbatim mission from a file')
|
|
5934
|
+
.option('--evidence <mode>', 'doc|tsc|test (default tsc)', 'tsc')
|
|
5935
|
+
.option('--doc-dir <rel>', 'doc evidence directory')
|
|
5936
|
+
.option('--doc-glob <re>', 'doc evidence filename pattern')
|
|
5937
|
+
.option('--test-path <p>', 'test evidence test path')
|
|
5938
|
+
.option('--file <rel>', 'test evidence output file')
|
|
5939
|
+
.option('--max-rounds <n>', 'maximum PTY control rounds', '16')
|
|
5940
|
+
.option('--no-commit', 'do not commit completed worktrees')
|
|
5941
|
+
.action(async (textParts: string[], opts: import('./agent-mission/duo.js').DuoOptions) => {
|
|
5942
|
+
try {
|
|
5943
|
+
delete process.env.OPENAI_API_KEY;
|
|
5944
|
+
const { runAgentMissionDuo } = await import('./agent-mission/duo.js');
|
|
5945
|
+
const result = await runAgentMissionDuo(textParts, opts);
|
|
5946
|
+
console.log(`[duo] missionId=${result.missionId}\n${result.lines.join('\n')}`);
|
|
5947
|
+
process.exitCode = result.exitCode;
|
|
5948
|
+
} catch (error) {
|
|
5949
|
+
console.error(`duo: ${error instanceof Error ? error.message : String(error)}`);
|
|
5950
|
+
process.exitCode = 1;
|
|
5951
|
+
}
|
|
5952
|
+
});
|
|
5953
|
+
|
|
5797
5954
|
// ★ 리뷰 반응 완결 루프 (L1) — PR 리뷰(OK/보강/거절)를 트리거로 codex 가 자율 rework→재제출.
|
|
5798
5955
|
// ⭐ 옵션 정의·opts 조립은 `agent-mission/review-loop-cli.ts`(테스트 가능 seam) — 액션은 I/O 만 한다.
|
|
5799
5956
|
registerReviewLoopOptions(
|
|
@@ -6515,6 +6672,12 @@ const selfDevCmd = program
|
|
|
6515
6672
|
let devRunId = '';
|
|
6516
6673
|
let devRunIdSource: import('./harness/harness-space.js').RunIdSource | undefined;
|
|
6517
6674
|
let preparedWorktree: import('./harness/harness-worktree-auto.js').PreparedDevWorktree | undefined;
|
|
6675
|
+
let authoringLease: import('./pod-lease/host-lease.js').HostLeaseHandle | undefined;
|
|
6676
|
+
const releaseAuthoringLease = () => {
|
|
6677
|
+
if (!authoringLease) return;
|
|
6678
|
+
authoringLease(); authoringLease = undefined;
|
|
6679
|
+
process.off('exit', releaseAuthoringLease);
|
|
6680
|
+
};
|
|
6518
6681
|
try {
|
|
6519
6682
|
// ⛔ raw 입력 배타성 검증은 try 안의 selectDevAuthorInput 하나가 소유한다 — 실패도 아래 catch가 관측한다.
|
|
6520
6683
|
const { buildDevCommandInput, selectDevAuthorInput } = await import('./self-dev/dev-cli.js');
|
|
@@ -6607,6 +6770,23 @@ const selfDevCmd = program
|
|
|
6607
6770
|
priorBlockSamples: () => askIo.priorBlockSamplesFrom(askLogRows),
|
|
6608
6771
|
recentAuthoringSamples: () => askIo.recentAuthoringSamplesFrom(askLogRows),
|
|
6609
6772
|
authorGoal: (args, options) => runGoalAuthorCli([...args], options as never),
|
|
6773
|
+
...(devAskSubstrate?.substrate === 'pod' ? { beforeAuthoring: async () => {
|
|
6774
|
+
const { parsePodPool, podPoolHostLease } = await import('./task-orchestrator/surfaces/pod-pool.js');
|
|
6775
|
+
const { measurePoolLease, recommendConcurrency } = await import('./task-orchestrator/surfaces/pod-lease.js');
|
|
6776
|
+
const members = parsePodPool(devAskSubstrate.pool!);
|
|
6777
|
+
const host = podPoolHostLease(members);
|
|
6778
|
+
while (!authoringLease) {
|
|
6779
|
+
const status = recommendConcurrency(measurePoolLease(members), { capacity: members.reduce((n, m) => n + m.capacity, 0), accounts: 0, perAccount: 0 });
|
|
6780
|
+
if (status.recommended !== null && status.recommended > 0) {
|
|
6781
|
+
const { leaseHasPendingPod } = await import('./pod-lease/host-lease.js');
|
|
6782
|
+
const pendingJobs = status.pendingJobs ?? [];
|
|
6783
|
+
authoringLease = host.tryReserve((others) =>
|
|
6784
|
+
others - host.live().filter((r) => leaseHasPendingPod(r, pendingJobs)).length < status.recommended!, true) ?? undefined;
|
|
6785
|
+
if (authoringLease) process.once('exit', releaseAuthoringLease);
|
|
6786
|
+
}
|
|
6787
|
+
if (!authoringLease) await new Promise((resolve) => setTimeout(resolve, 15_000));
|
|
6788
|
+
}
|
|
6789
|
+
} } : {}),
|
|
6610
6790
|
relativeToCwd: (file) => relativeToCwdPath(process.cwd(), file),
|
|
6611
6791
|
});
|
|
6612
6792
|
// ⛔ 종료는 «표면»이 한다 — 흐름은 세 값만 낸다.
|
|
@@ -6616,6 +6796,7 @@ const selfDevCmd = program
|
|
|
6616
6796
|
}
|
|
6617
6797
|
askAuthoredFile = flowResult.goalFile;
|
|
6618
6798
|
if (flowResult.kind === 'stopped-by-preflight') {
|
|
6799
|
+
releaseAuthoringLease();
|
|
6619
6800
|
completionGuard.conclude();
|
|
6620
6801
|
process.exit(1);
|
|
6621
6802
|
}
|
|
@@ -6633,12 +6814,21 @@ const selfDevCmd = program
|
|
|
6633
6814
|
warnRecentIncidentBurst();
|
|
6634
6815
|
let podOutput = '';
|
|
6635
6816
|
let podRunId: string | undefined;
|
|
6636
|
-
const
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6817
|
+
const previousLeaseId = process.env.ELANOUS_POD_RESERVED_LEASE;
|
|
6818
|
+
if (authoringLease) process.env.ELANOUS_POD_RESERVED_LEASE = authoringLease.id;
|
|
6819
|
+
let status: number;
|
|
6820
|
+
try {
|
|
6821
|
+
status = await dispatchHarnessOnPod(devAskPodDispatchInput(opts, askAuthoredFile!, devAskSubstrate.pool!), {
|
|
6822
|
+
onOutput: (text) => {
|
|
6823
|
+
podRunId ??= harnessPodRunId(podOutput + text);
|
|
6824
|
+
podOutput = (podOutput + text).slice(-16_000);
|
|
6825
|
+
},
|
|
6826
|
+
});
|
|
6827
|
+
} finally {
|
|
6828
|
+
releaseAuthoringLease();
|
|
6829
|
+
if (previousLeaseId === undefined) delete process.env.ELANOUS_POD_RESERVED_LEASE;
|
|
6830
|
+
else process.env.ELANOUS_POD_RESERVED_LEASE = previousLeaseId;
|
|
6831
|
+
}
|
|
6642
6832
|
if (status !== 0) {
|
|
6643
6833
|
const classified = classifyHarnessPodExit({ status }, podOutput, { ...(podRunId ? { runId: podRunId } : {}) });
|
|
6644
6834
|
recordClassifiedHarnessPodExit(podRunId ?? harnessPodRunId(podOutput), classified.reason, status);
|
|
@@ -6940,9 +7130,11 @@ const selfDevCmd = program
|
|
|
6940
7130
|
completionGuard.conclude();
|
|
6941
7131
|
process.exit(r.result.exitCode);
|
|
6942
7132
|
}
|
|
7133
|
+
releaseAuthoringLease();
|
|
6943
7134
|
completionGuard.conclude();
|
|
6944
7135
|
process.exit(ok ? 0 : 2);
|
|
6945
7136
|
} catch (e) {
|
|
7137
|
+
releaseAuthoringLease();
|
|
6946
7138
|
const msg = e instanceof Error ? e.message : String(e);
|
|
6947
7139
|
// ⚠️ 관측은 결론을 막지 않는다(리뷰 should-fix 5R) — 여기서 로거가 던지면 아래 `console.error` 와
|
|
6948
7140
|
// 의도한 `exit(1)` 까지 통째로 우회해, 사용자는 이유도 모른 채 다른 종료코드를 받는다.
|
|
@@ -9,7 +9,7 @@ import { askSeat, parseSeatAsk, type SeatAskDeps } from '../seat-dispatch/seat-a
|
|
|
9
9
|
import { parseSeatAddress, resolveSeat } from '../seat-address/seat-address.js';
|
|
10
10
|
import { getUserConfig, type UserConfig } from '../user-config.js';
|
|
11
11
|
import { answerAsSeat } from './seat-answer.js';
|
|
12
|
-
import { answerAsPersona, resolvePersonaAddress, type PersonaAnswerDeps } from './persona-answer.js';
|
|
12
|
+
import { answerAsPersona, resolvePersonaAddress, seatOfPersona, type PersonaAnswerDeps } from './persona-answer.js';
|
|
13
13
|
import {
|
|
14
14
|
submitIntakeWork,
|
|
15
15
|
type SubmitIntakeWorkDeps,
|
|
@@ -72,14 +72,21 @@ export async function handleDiscordSeatWork(
|
|
|
72
72
|
return ['OP', 'TC', 'MK', 'UX'].includes(id) ? sendTask(id, text, auth!.cfg, auth!.id) : null;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
const
|
|
76
|
-
const hasUnresolved =
|
|
75
|
+
const initialSeats = address.seats.map((name) => ({ name, seat: resolveSeat(name) }));
|
|
76
|
+
const hasUnresolved = initialSeats.some(({ seat }) => !seat);
|
|
77
77
|
const auth = hasUnresolved ? owner() : null;
|
|
78
78
|
if (hasUnresolved && auth && !deps.personaSource) await awaitGlobalPersonaLoad();
|
|
79
79
|
const source = hasUnresolved && auth ? (deps.personaSource ?? getGlobalPersonaRegistry()) : null;
|
|
80
|
-
const addressed =
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
const addressed = initialSeats.map(({ name, seat }) => {
|
|
81
|
+
const persona = !seat && source ? resolvePersonaAddress(name, source) : null;
|
|
82
|
+
const personaSeat = persona ? seatOfPersona(persona) : null;
|
|
83
|
+
if (persona && personaSeat) {
|
|
84
|
+
debug.log('persona.address', 'seat-alias', { name, personaId: persona.personaId, seat: personaSeat.id, via: 'discord' });
|
|
85
|
+
return { name: personaSeat.id, seat: personaSeat, persona: null };
|
|
86
|
+
}
|
|
87
|
+
return { name, seat, persona };
|
|
88
|
+
});
|
|
89
|
+
const seats = addressed;
|
|
83
90
|
const unknown = addressed.filter(({ seat, persona }) => !seat && (!persona || !auth)).map(({ name }) => `@${name}`);
|
|
84
91
|
if (unknown.length) return `어느 좌석을 말씀하시나요? ${unknown.join(', ')}은(는) 등록된 좌석이 아닙니다. 좌석을 확인해 다시 보내 주세요.`;
|
|
85
92
|
const personas = addressed.filter(({ persona }) => persona);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { fetchLinearIssues } from '../connectors/linear.js';
|
|
2
|
+
import { debug } from '../debug/log.js';
|
|
3
|
+
import { getSecretAsync } from '../nexus/config/secrets/index.js';
|
|
4
|
+
import { CardStore } from '../task-cards/card-store.js';
|
|
5
|
+
import { createWishCard } from './wish-card.js';
|
|
6
|
+
|
|
7
|
+
export const LINEAR_WISH_KEY_MISSING = 'Linear 키가 없습니다 — elanous connector linear 로 먼저 등록하세요';
|
|
8
|
+
|
|
9
|
+
export async function scanLinearWishes({ teamKey, label = 'wish', store, deps = {} }: {
|
|
10
|
+
teamKey: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
store: CardStore;
|
|
13
|
+
deps?: {
|
|
14
|
+
getApiKey?: () => Promise<string | undefined>;
|
|
15
|
+
fetchIssues?: typeof fetchLinearIssues;
|
|
16
|
+
};
|
|
17
|
+
}): Promise<{ added: number; duplicate: number; failed: number }> {
|
|
18
|
+
const apiKey = await (deps.getApiKey ?? (() => getSecretAsync('connector.linear.apiKey')))();
|
|
19
|
+
if (!apiKey?.trim()) throw new Error(LINEAR_WISH_KEY_MISSING);
|
|
20
|
+
let issues: Awaited<ReturnType<typeof fetchLinearIssues>>;
|
|
21
|
+
try {
|
|
22
|
+
issues = await (deps.fetchIssues ?? fetchLinearIssues)({ apiKey, teamKey, labelOrPrefix: label });
|
|
23
|
+
} catch (error) {
|
|
24
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
25
|
+
throw new Error(message.replaceAll(apiKey, '[redacted]'));
|
|
26
|
+
}
|
|
27
|
+
let added = 0;
|
|
28
|
+
let duplicate = 0;
|
|
29
|
+
let failed = 0;
|
|
30
|
+
for (const issue of issues) {
|
|
31
|
+
try {
|
|
32
|
+
const result = createWishCard({
|
|
33
|
+
text: `${issue.title}\n\n${issue.body}\n\n${issue.url}`,
|
|
34
|
+
source: 'linear', ref: issue.identifier ?? '',
|
|
35
|
+
}, store);
|
|
36
|
+
if (result.created) added++;
|
|
37
|
+
else duplicate++;
|
|
38
|
+
} catch {
|
|
39
|
+
failed++;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
debug.log('intake.linear-wish', 'scanned', { teamKey, label, added, duplicate, failed });
|
|
43
|
+
return { added, duplicate, failed };
|
|
44
|
+
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { debug } from '../debug/log.js';
|
|
2
2
|
import { resolveMention, type PersonaSource } from '../persona/mention-parser.js';
|
|
3
3
|
import type { PersonaProfile } from '../persona/types.js';
|
|
4
|
+
import { resolveSeat, type SeatEntry } from '../seat-address/seat-address.js';
|
|
4
5
|
|
|
5
6
|
export interface PersonaAnswerDeps {
|
|
6
7
|
complete?: (prompt: string) => Promise<string>;
|
|
7
8
|
}
|
|
8
9
|
|
|
10
|
+
export function seatOfPersona(persona: PersonaProfile, resolve = resolveSeat): SeatEntry | null {
|
|
11
|
+
return persona.seat ? resolve(persona.seat) ?? null : null;
|
|
12
|
+
}
|
|
13
|
+
|
|
9
14
|
/** Resolve the registry mention first, then an exact display name. */
|
|
10
15
|
export function resolvePersonaAddress(name: string, source: PersonaSource): PersonaProfile | null {
|
|
11
16
|
const mention = resolveMention(name, source);
|
|
@@ -9,7 +9,7 @@ import { askSeat, parseSeatAsk, type SeatAskDeps } from '../seat-dispatch/seat-a
|
|
|
9
9
|
import { parseSeatAddress, resolveSeat } from '../seat-address/seat-address.js';
|
|
10
10
|
import { getUserConfig, type UserConfig } from '../user-config.js';
|
|
11
11
|
import { answerAsSeat } from './seat-answer.js';
|
|
12
|
-
import { answerAsPersona, resolvePersonaAddress, type PersonaAnswerDeps } from './persona-answer.js';
|
|
12
|
+
import { answerAsPersona, resolvePersonaAddress, seatOfPersona, type PersonaAnswerDeps } from './persona-answer.js';
|
|
13
13
|
import {
|
|
14
14
|
submitIntakeWork,
|
|
15
15
|
type SubmitIntakeWorkDeps,
|
|
@@ -66,16 +66,23 @@ export async function handleTelegramSeatWork(
|
|
|
66
66
|
const address = parseSeatAddress(text.replace(/^(@[A-Za-z][A-Za-z0-9_-]*(?:,@?[A-Za-z][A-Za-z0-9_-]*)*)/, (prefix) => prefix.replaceAll(',@', ',')));
|
|
67
67
|
if (!address) return null;
|
|
68
68
|
|
|
69
|
-
const
|
|
69
|
+
const initialSeats = address.seats.map((name) => ({ name, seat: resolveSeat(name) }));
|
|
70
70
|
const names = address.seats;
|
|
71
71
|
debug.log('seat-address.telegram', 'parsed', { seats: names });
|
|
72
|
-
const hasUnresolved =
|
|
72
|
+
const hasUnresolved = initialSeats.some(({ seat }) => !seat);
|
|
73
73
|
const auth = hasUnresolved ? owner() : null;
|
|
74
74
|
if (hasUnresolved && auth && !deps.personaSource) await awaitGlobalPersonaLoad();
|
|
75
75
|
const source = hasUnresolved && auth ? (deps.personaSource ?? getGlobalPersonaRegistry()) : null;
|
|
76
|
-
const addressed =
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
const addressed = initialSeats.map(({ name, seat }) => {
|
|
77
|
+
const persona = !seat && source ? resolvePersonaAddress(name, source) : null;
|
|
78
|
+
const personaSeat = persona ? seatOfPersona(persona) : null;
|
|
79
|
+
if (persona && personaSeat) {
|
|
80
|
+
debug.log('persona.address', 'seat-alias', { name, personaId: persona.personaId, seat: personaSeat.id, via: 'telegram' });
|
|
81
|
+
return { name: personaSeat.id, seat: personaSeat, persona: null };
|
|
82
|
+
}
|
|
83
|
+
return { name, seat, persona };
|
|
84
|
+
});
|
|
85
|
+
const seats = addressed;
|
|
79
86
|
const unknown = addressed.filter(({ seat, persona }) => !seat && (!persona || !auth)).map(({ name }) => `@${name}`);
|
|
80
87
|
if (unknown.length) {
|
|
81
88
|
debug.log('seat-address.telegram', 'rejected', { seats: names, reason: 'unknown-seat' });
|
|
@@ -2,7 +2,7 @@ import { debug } from '../debug/log.js';
|
|
|
2
2
|
import { CardStore } from '../task-cards/card-store.js';
|
|
3
3
|
|
|
4
4
|
export function createWishCard(
|
|
5
|
-
{ text, source, ref }: { text: string; source: 'telegram' | 'pwa' | 'tui'; ref: string },
|
|
5
|
+
{ text, source, ref }: { text: string; source: 'telegram' | 'pwa' | 'tui' | 'linear'; ref: string },
|
|
6
6
|
store: CardStore = new CardStore(),
|
|
7
7
|
): { cardId: string; title: string; created: boolean } {
|
|
8
8
|
const title = text.trim().split(/\r?\n/, 1)[0]!.trim().slice(0, 80);
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { Database } from 'bun:sqlite';
|
|
5
|
+
import { debug } from '../debug/log.js';
|
|
6
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
7
|
+
import * as registry from './registry.js';
|
|
8
|
+
import { cronMatches } from '../domains/cron-match.js';
|
|
9
|
+
import { resolveTimeZone } from '../time/format.js';
|
|
10
|
+
import type { LoopEntry, LoopRun } from './registry.js';
|
|
11
|
+
|
|
12
|
+
type LoopState = 'off' | 'unregistered' | 'failing' | 'late' | 'unknown' | 'alive';
|
|
13
|
+
export type LoopScope = 'graph-only' | 'registry';
|
|
14
|
+
export interface CheckEntry {
|
|
15
|
+
id: string;
|
|
16
|
+
owner?: string;
|
|
17
|
+
mode?: string;
|
|
18
|
+
expectEveryMinutes?: number;
|
|
19
|
+
/** The scheduled fire before the latest one (cron loops). A run older than this missed two scheduled fires. */
|
|
20
|
+
dueAt?: string;
|
|
21
|
+
lastRunAt?: string;
|
|
22
|
+
lastStatus?: string;
|
|
23
|
+
recentStatuses?: string[];
|
|
24
|
+
enabled: boolean;
|
|
25
|
+
registered: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface CheckResult extends CheckEntry {
|
|
28
|
+
state: LoopState;
|
|
29
|
+
reason: string;
|
|
30
|
+
}
|
|
31
|
+
export const LOOP_STATES = ['alive', 'late', 'failing', 'off', 'unregistered', 'unknown'] as const;
|
|
32
|
+
export type LoopCounts = Record<LoopState, number>;
|
|
33
|
+
|
|
34
|
+
export function checkLoops(entries: CheckEntry[], now: Date, opts: { failureStatuses?: readonly string[] } = {}): CheckResult[] {
|
|
35
|
+
const failures = opts.failureStatuses ?? ['failed'];
|
|
36
|
+
return entries.map(entry => {
|
|
37
|
+
let state: LoopState;
|
|
38
|
+
let reason: string;
|
|
39
|
+
const age = entry.lastRunAt ? now.getTime() - Date.parse(entry.lastRunAt) : NaN;
|
|
40
|
+
if (!entry.enabled) { state = 'off'; reason = 'disabled'; }
|
|
41
|
+
else if (!entry.registered) { state = 'unregistered'; reason = 'not registered'; }
|
|
42
|
+
else if (entry.recentStatuses?.length && entry.recentStatuses.length >= 3
|
|
43
|
+
&& entry.recentStatuses.slice(0, 3).every(status => failures.includes(status))) {
|
|
44
|
+
state = 'failing'; reason = 'last 3 runs failed';
|
|
45
|
+
} else if (Number.isFinite(age) && entry.dueAt && Number.isFinite(Date.parse(entry.dueAt))) {
|
|
46
|
+
// Judge against the real schedule, not a fixed interval: overnight or weekend gaps are not lateness.
|
|
47
|
+
if (Date.parse(entry.lastRunAt!) < Date.parse(entry.dueAt)) { state = 'late'; reason = `missed the scheduled runs since ${entry.dueAt}`; }
|
|
48
|
+
else { state = 'alive'; reason = 'ran at or after the previous scheduled fire'; }
|
|
49
|
+
} else if (Number.isFinite(age) && entry.expectEveryMinutes !== undefined
|
|
50
|
+
&& entry.expectEveryMinutes > 0 && age > entry.expectEveryMinutes * 2 * 60_000) {
|
|
51
|
+
state = 'late'; reason = `last run exceeds twice the expected ${entry.expectEveryMinutes}m interval`;
|
|
52
|
+
} else if (!entry.lastRunAt || !Number.isFinite(age)) { state = 'unknown'; reason = 'no valid run recorded'; }
|
|
53
|
+
else if (entry.expectEveryMinutes === undefined || !Number.isFinite(entry.expectEveryMinutes) || entry.expectEveryMinutes <= 0) {
|
|
54
|
+
state = 'unknown'; reason = 'expected interval unknown';
|
|
55
|
+
} else { state = 'alive'; reason = 'within expected interval'; }
|
|
56
|
+
return { ...entry, state, reason };
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function countLoopStates(results: CheckResult[]): LoopCounts {
|
|
61
|
+
const counts: LoopCounts = { alive: 0, late: 0, failing: 0, off: 0, unregistered: 0, unknown: 0 };
|
|
62
|
+
for (const result of results) counts[result.state]++;
|
|
63
|
+
return counts;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Latest cron fire at or before `at` (minute resolution, scans back up to one year). */
|
|
67
|
+
export function previousLoopFire(cron: string, at: Date, timeZone: string = resolveTimeZone().timeZone): string | null {
|
|
68
|
+
if (cron.trim().split(/\s+/).length !== 5) return null;
|
|
69
|
+
let minute = Math.floor(at.getTime() / 60000) * 60000;
|
|
70
|
+
for (let i = 0; i < 527040; i++, minute -= 60000) {
|
|
71
|
+
if (cronMatches(cron, new Date(minute), { timeZone })) return new Date(minute).toISOString();
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The fire before the latest scheduled fire — a run older than this has missed two scheduled fires. */
|
|
77
|
+
export function dueBefore(cron: string, now: Date, timeZone?: string): string | null {
|
|
78
|
+
const latest = previousLoopFire(cron, now, timeZone);
|
|
79
|
+
return latest ? previousLoopFire(cron, new Date(Date.parse(latest) - 60000), timeZone) : null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface RegistryAdapterDeps {
|
|
83
|
+
listLoops?: () => LoopEntry[];
|
|
84
|
+
listAllLoops?: () => CheckEntry[];
|
|
85
|
+
statusForLoop?: (id: string) => { recentRuns: Pick<LoopRun, 'status'>[] };
|
|
86
|
+
now?: Date;
|
|
87
|
+
timeZone?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function entriesFromRegistry(deps: RegistryAdapterDeps = {}): { entries: CheckEntry[]; scope: LoopScope } {
|
|
91
|
+
// The registry integration is optional until LOOP-REG1 lands. Do not import a missing named export.
|
|
92
|
+
const all = deps.listAllLoops ?? (registry as typeof registry & { listAllLoops?: () => CheckEntry[] }).listAllLoops;
|
|
93
|
+
if (typeof all === 'function') return { entries: all(), scope: 'registry' };
|
|
94
|
+
const now = deps.now ?? new Date();
|
|
95
|
+
return { scope: 'graph-only', entries: (deps.listLoops ?? registry.listLoops)().map(loop => {
|
|
96
|
+
const first = loop.trigger.cron ? registry.nextLoopFire(loop.trigger.cron, now) : null;
|
|
97
|
+
const second = first && loop.trigger.cron ? registry.nextLoopFire(loop.trigger.cron, new Date(first)) : null;
|
|
98
|
+
const interval = first && second ? (Date.parse(second) - Date.parse(first)) / 60_000 : undefined;
|
|
99
|
+
const statuses = loop.lastRun
|
|
100
|
+
? deps.statusForLoop ? deps.statusForLoop(loop.id).recentRuns.map(run => run.status)
|
|
101
|
+
: deps.listLoops ? [loop.lastRun.status]
|
|
102
|
+
: registry.loopStatus(loop.id).recentRuns.map(run => run.status)
|
|
103
|
+
: [];
|
|
104
|
+
const dueAt = loop.trigger.cron ? dueBefore(loop.trigger.cron, now, deps.timeZone) : null;
|
|
105
|
+
return { id: loop.id, enabled: loop.enabled, registered: true,
|
|
106
|
+
...(interval !== undefined ? { expectEveryMinutes: interval } : {}),
|
|
107
|
+
...(dueAt ? { dueAt } : {}),
|
|
108
|
+
...(loop.lastRun ? { lastRunAt: loop.lastRun.at, lastStatus: loop.lastRun.status } : {}),
|
|
109
|
+
recentStatuses: statuses };
|
|
110
|
+
}) };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface CheckerDeps {
|
|
114
|
+
root?: string;
|
|
115
|
+
now?: Date;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Appends only new daily keys to the seat request journal; never rewrites existing lines. */
|
|
119
|
+
export function notifyOwners(results: CheckResult[], deps: CheckerDeps = {}): number {
|
|
120
|
+
const eligible = results.filter(result => result.owner && (result.state === 'late' || result.state === 'failing'));
|
|
121
|
+
if (!eligible.length) return 0;
|
|
122
|
+
const now = deps.now ?? new Date();
|
|
123
|
+
const path = join(deps.root ?? effectiveInstanceRoot(), 'seat-requests', 'requests.jsonl');
|
|
124
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
125
|
+
// SQLite's OS locks are released when a process dies, including after SIGKILL.
|
|
126
|
+
// The journal itself remains append-only; this database only serializes check + append.
|
|
127
|
+
const lock = new Database(`${path}.loop-checker.lock.sqlite`);
|
|
128
|
+
try {
|
|
129
|
+
lock.exec('PRAGMA busy_timeout = 10000');
|
|
130
|
+
lock.exec('BEGIN EXCLUSIVE');
|
|
131
|
+
try {
|
|
132
|
+
const existing = new Set<string>();
|
|
133
|
+
if (existsSync(path)) {
|
|
134
|
+
for (const line of readFileSync(path, 'utf8').split('\n')) {
|
|
135
|
+
if (line.trim()) {
|
|
136
|
+
const row = JSON.parse(line) as { key?: string };
|
|
137
|
+
if (row.key) existing.add(row.key);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
let added = 0;
|
|
142
|
+
for (const result of eligible) {
|
|
143
|
+
const key = `loopcheck:${result.id}:${now.toISOString().slice(0, 10)}:${result.state}`;
|
|
144
|
+
if (existing.has(key)) continue;
|
|
145
|
+
const row = { key, receiptId: key, seat: result.owner, text: `${result.id} ${result.state} — 마지막 ${result.lastRunAt ?? '없음'} · ${result.reason}`,
|
|
146
|
+
status: 'queued', queuedAt: now.toISOString(), source: 'loop-checker' };
|
|
147
|
+
appendFileSync(path, JSON.stringify(row) + '\n');
|
|
148
|
+
existing.add(key);
|
|
149
|
+
added++;
|
|
150
|
+
}
|
|
151
|
+
lock.exec('COMMIT');
|
|
152
|
+
return added;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
lock.exec('ROLLBACK');
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
} finally { lock.close(); }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Persist the previous comparison only for explicit all-status checks. Notification is separate. */
|
|
161
|
+
export function observeLoopCheck(results: CheckResult[], scope: LoopScope, deps: CheckerDeps = {}): LoopCounts {
|
|
162
|
+
const path = join(deps.root ?? effectiveInstanceRoot(), 'loop', 'checker', 'last.json');
|
|
163
|
+
let previous: Record<string, LoopState> = {};
|
|
164
|
+
try { previous = JSON.parse(readFileSync(path, 'utf8')) as Record<string, LoopState>; }
|
|
165
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
|
|
166
|
+
const current: Record<string, LoopState> = {};
|
|
167
|
+
for (const { id, state } of results) {
|
|
168
|
+
current[id] = state;
|
|
169
|
+
if (previous[id] && previous[id] !== state) debug.log('loop.checker', 'state-change', { id, from: previous[id], to: state });
|
|
170
|
+
}
|
|
171
|
+
const counts = countLoopStates(results);
|
|
172
|
+
debug.log('loop.checker', 'checked', { counts, scope });
|
|
173
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
174
|
+
const tmp = `${path}.${randomUUID()}.tmp`;
|
|
175
|
+
try { writeFileSync(tmp, JSON.stringify(current)); renameSync(tmp, path); }
|
|
176
|
+
catch (error) { if (existsSync(tmp)) unlinkSync(tmp); throw error; }
|
|
177
|
+
return counts;
|
|
178
|
+
}
|