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
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { getGlobalWorkflowDir, getProjectWorkflowDir } from '../../workflow-runtime/discovery.js';
|
|
5
|
+
|
|
6
|
+
export interface WorkflowHistoryOpts {
|
|
7
|
+
scope?: 'project' | 'global';
|
|
8
|
+
cwd?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface WorkflowHistoryVersion {
|
|
12
|
+
id: string;
|
|
13
|
+
createdAt: string;
|
|
14
|
+
size: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const MAX_VERSIONS = 20;
|
|
18
|
+
const VERSION_ID = /^(\d{13})-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
19
|
+
|
|
20
|
+
function safeName(name: string): void {
|
|
21
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name) || name.includes('..')) {
|
|
22
|
+
throw new Error('invalid workflow name');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function safeVersionId(id: string): void {
|
|
27
|
+
if (!/^[A-Za-z0-9_-]+$/.test(id)) throw new Error('invalid workflow history version id');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function workflowDir(opts: WorkflowHistoryOpts): string {
|
|
31
|
+
return opts.scope === 'global'
|
|
32
|
+
? getGlobalWorkflowDir()
|
|
33
|
+
: getProjectWorkflowDir(opts.cwd ?? process.cwd());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function historyDir(name: string, opts: WorkflowHistoryOpts): string {
|
|
37
|
+
safeName(name);
|
|
38
|
+
return join(workflowDir(opts), '.history', name);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Capture the current YAML before its caller overwrites it; a missing file has no history. */
|
|
42
|
+
export function snapshotBeforeSave(name: string, opts: WorkflowHistoryOpts = {}): WorkflowHistoryVersion | null {
|
|
43
|
+
const dir = historyDir(name, opts);
|
|
44
|
+
const source = join(workflowDir(opts), `${name}.yaml`);
|
|
45
|
+
if (!existsSync(source)) return null;
|
|
46
|
+
const content = readFileSync(source);
|
|
47
|
+
const previous = listWorkflowHistory(name, opts)[0];
|
|
48
|
+
if (previous && readFileSync(join(dir, `${previous.id}.yaml`)).equals(content)) return null;
|
|
49
|
+
|
|
50
|
+
mkdirSync(dir, { recursive: true });
|
|
51
|
+
const timestamp = Math.max(Date.now(), previous ? Number(previous.id.slice(0, 13)) + 1 : 0);
|
|
52
|
+
const id = `${String(timestamp).padStart(13, '0')}-${randomUUID()}`;
|
|
53
|
+
const version: WorkflowHistoryVersion = {
|
|
54
|
+
id,
|
|
55
|
+
createdAt: new Date(Number(id.slice(0, 13))).toISOString(),
|
|
56
|
+
size: content.byteLength,
|
|
57
|
+
};
|
|
58
|
+
writeFileSync(join(dir, `${id}.yaml`), content, { flag: 'wx' });
|
|
59
|
+
for (const old of listWorkflowHistory(name, opts).slice(MAX_VERSIONS)) {
|
|
60
|
+
unlinkSync(join(dir, `${old.id}.yaml`));
|
|
61
|
+
}
|
|
62
|
+
return version;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Enumerate only this workflow's snapshots, newest first. */
|
|
66
|
+
export function listWorkflowHistory(name: string, opts: WorkflowHistoryOpts = {}): WorkflowHistoryVersion[] {
|
|
67
|
+
const dir = historyDir(name, opts);
|
|
68
|
+
if (!existsSync(dir)) return [];
|
|
69
|
+
return readdirSync(dir)
|
|
70
|
+
.filter(file => file.endsWith('.yaml') && VERSION_ID.test(file.slice(0, -5)))
|
|
71
|
+
.map(file => {
|
|
72
|
+
const id = file.slice(0, -5);
|
|
73
|
+
const filePath = join(dir, file);
|
|
74
|
+
const stat = statSync(filePath);
|
|
75
|
+
return stat.isFile() ? { id, createdAt: new Date(Number(id.slice(0, 13))).toISOString(), size: stat.size } : null;
|
|
76
|
+
})
|
|
77
|
+
.filter((version): version is WorkflowHistoryVersion => version !== null)
|
|
78
|
+
.sort((a, b) => b.id.localeCompare(a.id));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** A missing (well-formed) version returns null rather than an empty YAML draft. */
|
|
82
|
+
export function getWorkflowHistoryVersion(name: string, id: string, opts: WorkflowHistoryOpts = {}): string | null {
|
|
83
|
+
const dir = historyDir(name, opts);
|
|
84
|
+
safeVersionId(id);
|
|
85
|
+
const file = join(dir, `${id}.yaml`);
|
|
86
|
+
if (!VERSION_ID.test(id) || !existsSync(file)) return null;
|
|
87
|
+
return readFileSync(file, 'utf-8');
|
|
88
|
+
}
|
|
@@ -32,7 +32,9 @@ import { requirePosixShellCommand } from '../../platform/default-shell.js';
|
|
|
32
32
|
import { spawn } from 'child_process';
|
|
33
33
|
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
34
34
|
import { join } from 'path';
|
|
35
|
+
import { debug } from '../../debug/log.js';
|
|
35
36
|
import { elanousStateRoot } from '../../autopilot/state-paths.js';
|
|
37
|
+
import { getWorkflowHistoryVersion, listWorkflowHistory, snapshotBeforeSave } from './workflow-history.js';
|
|
36
38
|
import {
|
|
37
39
|
deleteWorkflow,
|
|
38
40
|
discoverWorkflows,
|
|
@@ -232,6 +234,45 @@ export function handleWorkflowGet(
|
|
|
232
234
|
);
|
|
233
235
|
}
|
|
234
236
|
|
|
237
|
+
export function handleWorkflowHistoryList(
|
|
238
|
+
req: Request,
|
|
239
|
+
name: string,
|
|
240
|
+
opts: MetaApiOpts,
|
|
241
|
+
): Response {
|
|
242
|
+
if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
243
|
+
if (req.method !== 'GET') return jsonResponse({ error: 'method-not-allowed', method: req.method }, 405);
|
|
244
|
+
const scope = new URL(req.url).searchParams.get('scope') === 'global' ? 'global' : 'project';
|
|
245
|
+
try {
|
|
246
|
+
return jsonResponse({ versions: listWorkflowHistory(name, { scope }) }, 200);
|
|
247
|
+
} catch (err) {
|
|
248
|
+
if (err instanceof Error && err.message === 'invalid workflow name') {
|
|
249
|
+
return jsonResponse({ error: 'bad_request', reason: err.message }, 400);
|
|
250
|
+
}
|
|
251
|
+
throw err;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function handleWorkflowHistoryVersionGet(
|
|
256
|
+
req: Request,
|
|
257
|
+
name: string,
|
|
258
|
+
id: string,
|
|
259
|
+
opts: MetaApiOpts,
|
|
260
|
+
): Response {
|
|
261
|
+
if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
262
|
+
if (req.method !== 'GET') return jsonResponse({ error: 'method-not-allowed', method: req.method }, 405);
|
|
263
|
+
const scope = new URL(req.url).searchParams.get('scope') === 'global' ? 'global' : 'project';
|
|
264
|
+
try {
|
|
265
|
+
const yaml = getWorkflowHistoryVersion(name, id, { scope });
|
|
266
|
+
if (yaml === null) return jsonResponse({ error: 'not_found', name, id }, 404);
|
|
267
|
+
return jsonResponse({ yaml }, 200);
|
|
268
|
+
} catch (err) {
|
|
269
|
+
if (err instanceof Error && (err.message === 'invalid workflow name' || err.message === 'invalid workflow history version id')) {
|
|
270
|
+
return jsonResponse({ error: 'bad_request', reason: err.message }, 400);
|
|
271
|
+
}
|
|
272
|
+
throw err;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
235
276
|
export async function handleWorkflowPut(
|
|
236
277
|
req: Request,
|
|
237
278
|
name: string,
|
|
@@ -248,6 +289,17 @@ export async function handleWorkflowPut(
|
|
|
248
289
|
return jsonResponse({ error: 'bad_request', reason: '`yaml` must be a string' }, 400);
|
|
249
290
|
}
|
|
250
291
|
const scope = scopeRaw === 'global' ? 'global' : 'project';
|
|
292
|
+
const parsed = parseWorkflowYaml(yamlText);
|
|
293
|
+
if (parsed.ok && parsed.workflow?.name === name) {
|
|
294
|
+
// Capture the existing file before saveWorkflow overwrites it; a failed snapshot is non-fatal.
|
|
295
|
+
try {
|
|
296
|
+
snapshotBeforeSave(name, { scope });
|
|
297
|
+
} catch (err) {
|
|
298
|
+
debug.log('nexus.workflow-history', 'snapshot-failed', {
|
|
299
|
+
name, scope, error: err instanceof Error ? err.message : String(err),
|
|
300
|
+
}, { level: 'warn' });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
251
303
|
const r = saveWorkflow(name, yamlText, { scope });
|
|
252
304
|
if (!r.ok) {
|
|
253
305
|
return jsonResponse(
|
|
@@ -52,20 +52,29 @@ export function deliveryDedupKey(kind: string, text: string): string {
|
|
|
52
52
|
return createHash('sha1').update(`${kind}\n${norm}`).digest('hex').slice(0, 16);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
/** ① 중복 발사 제어 —
|
|
56
|
-
*
|
|
57
|
-
|
|
58
|
-
export function recentlyDelivered(
|
|
55
|
+
/** ① 중복 발사 제어 — windowSec 내 동일 메시지가 한 채널 이상 성공했으면 그 messageId.
|
|
56
|
+
* 실패 기록은 재시도를 막지 않는다. 짧은 창(기본 120s) 밖의 재발송은 통과. */
|
|
57
|
+
export function successfulDeliveryId(
|
|
59
58
|
db: Database, dedupKey: string, windowSec = 120, nowMs: number = Date.now(),
|
|
60
|
-
):
|
|
59
|
+
): string | null {
|
|
61
60
|
// ⚠️ 축 ②(JS 임계 · db-window 참조) — 같은 파일의 `recentDeliveryCount` 와 동형이다.
|
|
62
61
|
// SQL 안의 `datetime('now')` 를 쓰면 **시계를 주입할 수 없어** 테스트가 실제 시각에
|
|
63
62
|
// 매달린다. 실제로 이 함수의 테스트가 고정 날짜로 seed 한 채 시간이 흘러 어느 날 빨개진
|
|
64
63
|
// 시한폭탄이었다. `deliveries.ts` 는 ISO 로만 쓰므로 raw 비교가 옳고 인덱스도 산다.
|
|
65
64
|
const row = db.prepare(
|
|
66
|
-
`SELECT
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
`SELECT message_id FROM deliveries
|
|
66
|
+
WHERE dedup_key = ? AND ts >= ?
|
|
67
|
+
AND EXISTS (SELECT 1 FROM json_each(deliveries.channels) WHERE json_extract(value, '$.ok') = 1)
|
|
68
|
+
ORDER BY ts DESC LIMIT 1`,
|
|
69
|
+
).get(dedupKey, sinceTs(SECONDS(windowSec), nowMs)) as { message_id: string } | null;
|
|
70
|
+
return row?.message_id ?? null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** 기존 boolean 호출부 호환 — 성공 배송 기록만 중복으로 센다. */
|
|
74
|
+
export function recentlyDelivered(
|
|
75
|
+
db: Database, dedupKey: string, windowSec = 120, nowMs: number = Date.now(),
|
|
76
|
+
): boolean {
|
|
77
|
+
return successfulDeliveryId(db, dedupKey, windowSec, nowMs) !== null;
|
|
69
78
|
}
|
|
70
79
|
|
|
71
80
|
/** 최근 창(windowSec) 내 총 발송 수 — 버스트(밀림) 판정용. 전 kind 합산.
|
|
@@ -27,13 +27,14 @@
|
|
|
27
27
|
// that boolean before falling back to direct Telegram).
|
|
28
28
|
|
|
29
29
|
import { createHash } from 'node:crypto';
|
|
30
|
+
import { debug } from '../../debug/log.js';
|
|
30
31
|
import { sendTelegramReport } from '../../telegram-report.js';
|
|
31
32
|
import { getPushcutClient } from '../../pushcut/client.js';
|
|
32
33
|
import type { UserConfig } from '../../user-config.js';
|
|
33
34
|
import { formatForChannel, type OutboundChannelType, type OutboundMsg } from './format.js';
|
|
34
35
|
import { spillLongContent } from '../../storage/content-spill.js';
|
|
35
36
|
import {
|
|
36
|
-
openDeliveryDb, deliveryDedupKey,
|
|
37
|
+
openDeliveryDb, deliveryDedupKey, successfulDeliveryId, recordDelivery, type ChannelDelivery,
|
|
37
38
|
} from './delivery-ledger.js';
|
|
38
39
|
|
|
39
40
|
// 채널 타입·메시지·분할기는 format.ts가 단일 출처 — 기존 소비처(test 등) 호환 재export.
|
|
@@ -71,6 +72,8 @@ export interface RouteResult {
|
|
|
71
72
|
channels: ChannelResult[];
|
|
72
73
|
/** 중복 발사 제어(dedup)로 재팬아웃이 억제됨 — 최근 동일 발송 존재. */
|
|
73
74
|
suppressed?: boolean;
|
|
75
|
+
/** 억제 근거가 된 앞선 성공 발송의 식별자. */
|
|
76
|
+
suppressedBy?: string;
|
|
74
77
|
/** 이 발송의 식별자(리드 동기화·회상 교차참조용). */
|
|
75
78
|
messageId?: string;
|
|
76
79
|
}
|
|
@@ -207,9 +210,11 @@ export async function routeOutbound(cfg: UserConfig, msg: OutboundMsg, deps: Rou
|
|
|
207
210
|
// ① 중복 발사 제어 — 최근(120s) 동일 발송이면 재팬아웃 억제.
|
|
208
211
|
if (ledger && deps.dedup !== false) {
|
|
209
212
|
try {
|
|
210
|
-
|
|
213
|
+
const suppressedBy = successfulDeliveryId(ledger, dedupKey);
|
|
214
|
+
if (suppressedBy) {
|
|
211
215
|
if (ownLedger) ledger.close();
|
|
212
|
-
|
|
216
|
+
try { debug.log('outbound.send', 'suppressed', { kind: msg.kind, suppressedBy }); } catch { /* 관측 실패는 발송 결과에 영향 없음 */ }
|
|
217
|
+
return { delivered: true, channels: [], suppressed: true, suppressedBy, messageId };
|
|
213
218
|
}
|
|
214
219
|
} catch { /* 원장 조회 실패 — 억제 없이 진행 */ }
|
|
215
220
|
}
|
package/src/persona/loader.ts
CHANGED
|
@@ -138,6 +138,22 @@ export function validatePersonaShape(
|
|
|
138
138
|
models = mr.models;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
let seat: string | undefined;
|
|
142
|
+
if (Object.hasOwn(obj, 'seat')) {
|
|
143
|
+
if (typeof obj['seat'] !== 'string' || !obj['seat'].trim()) {
|
|
144
|
+
return err(path, 'seat must be a non-empty string');
|
|
145
|
+
}
|
|
146
|
+
seat = obj['seat'];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let todo: string | undefined;
|
|
150
|
+
if (Object.hasOwn(obj, 'todo')) {
|
|
151
|
+
if (typeof obj['todo'] !== 'string' || !/^[a-z0-9_][a-z0-9_-]*\.todo\.jsonl$/i.test(obj['todo'])) {
|
|
152
|
+
return err(path, 'todo must be a per-persona .todo.jsonl filename');
|
|
153
|
+
}
|
|
154
|
+
todo = obj['todo'];
|
|
155
|
+
}
|
|
156
|
+
|
|
141
157
|
let mentionPatterns: readonly string[] | undefined;
|
|
142
158
|
if (obj['mentionPatterns'] !== undefined) {
|
|
143
159
|
if (!Array.isArray(obj['mentionPatterns'])
|
|
@@ -149,6 +165,8 @@ export function validatePersonaShape(
|
|
|
149
165
|
|
|
150
166
|
const profile: PersonaProfile = {
|
|
151
167
|
personaId, displayName,
|
|
168
|
+
...(seat !== undefined ? { seat } : {}),
|
|
169
|
+
...(todo !== undefined ? { todo } : {}),
|
|
152
170
|
...(description.value !== undefined ? { description: description.value } : {}),
|
|
153
171
|
...(systemPrompt.value !== undefined ? { systemPrompt: systemPrompt.value } : {}),
|
|
154
172
|
...(brand !== undefined ? { brand } : {}),
|
|
@@ -6,6 +6,8 @@ export interface PersonaTodo {
|
|
|
6
6
|
title: string;
|
|
7
7
|
status: 'open' | 'done';
|
|
8
8
|
createdAt: string;
|
|
9
|
+
priority?: number;
|
|
10
|
+
dueAt?: string;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
export interface PersonaTodoReadResult {
|
|
@@ -28,7 +30,9 @@ function isPersonaTodo(value: unknown): value is PersonaTodo {
|
|
|
28
30
|
&& typeof item.title === 'string' && item.title.length > 0
|
|
29
31
|
&& (item.status === 'open' || item.status === 'done')
|
|
30
32
|
&& typeof item.createdAt === 'string'
|
|
31
|
-
&& Number.isFinite(Date.parse(item.createdAt))
|
|
33
|
+
&& Number.isFinite(Date.parse(item.createdAt))
|
|
34
|
+
&& (item.priority === undefined || (typeof item.priority === 'number' && Number.isInteger(item.priority) && item.priority >= 0))
|
|
35
|
+
&& (item.dueAt === undefined || (typeof item.dueAt === 'string' && Number.isFinite(Date.parse(item.dueAt))));
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
/** Missing files are empty; malformed lines are skipped and returned for observation. */
|
|
@@ -67,13 +71,14 @@ export function writePersonaTodos(dir: string, personaId: string, todos: readonl
|
|
|
67
71
|
writeFileSync(path, todos.map(todo => JSON.stringify(todo)).join('\n') + (todos.length ? '\n' : ''), 'utf8');
|
|
68
72
|
}
|
|
69
73
|
|
|
70
|
-
/**
|
|
74
|
+
/** Higher priority first, then earlier deadline, then earlier creation; file order breaks ties. */
|
|
75
|
+
export function orderedPersonaTodos(todos: readonly PersonaTodo[]): PersonaTodo[] {
|
|
76
|
+
return [...todos].filter((todo) => todo.status === 'open').sort((a, b) =>
|
|
77
|
+
(b.priority ?? 0) - (a.priority ?? 0)
|
|
78
|
+
|| (a.dueAt && b.dueAt ? Date.parse(a.dueAt) - Date.parse(b.dueAt) : a.dueAt ? -1 : b.dueAt ? 1 : 0)
|
|
79
|
+
|| Date.parse(a.createdAt) - Date.parse(b.createdAt));
|
|
80
|
+
}
|
|
81
|
+
|
|
71
82
|
export function nextPersonaTodo(todos: readonly PersonaTodo[]): PersonaTodo | null {
|
|
72
|
-
|
|
73
|
-
for (const todo of todos) {
|
|
74
|
-
if (todo.status === 'open' && (!oldest || Date.parse(todo.createdAt) < Date.parse(oldest.createdAt))) {
|
|
75
|
-
oldest = todo;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return oldest;
|
|
83
|
+
return orderedPersonaTodos(todos)[0] ?? null;
|
|
79
84
|
}
|
package/src/persona/presets.ts
CHANGED
|
@@ -88,6 +88,7 @@ export function presetToProfileYaml(p: PersonaPreset, common: PresetCommon, opts
|
|
|
88
88
|
displayName: opts.name?.trim() ? `${opts.name.trim()} · ${p.role}` : p.displayName,
|
|
89
89
|
description: p.oneLine,
|
|
90
90
|
systemPrompt: presetSystemPrompt(p, common, name),
|
|
91
|
+
todo: `${opts.personaId}.todo.jsonl`,
|
|
91
92
|
preset: { id: p.personaId, role: p.role, ...(title ? { title } : {}), tools: p.tools.map((t) => t.name), firstQuestions: p.firstQuestions },
|
|
92
93
|
};
|
|
93
94
|
return `# 프리셋 «${p.personaId}» 에서 만든 페르소나 — elanous persona add (원본: persona-presets/${p.personaId}.yaml)\n${stringify(profile, { lineWidth: 0 })}`;
|
|
@@ -99,7 +100,7 @@ export interface PersonaWriteResult { personaId: string; displayName: string; pa
|
|
|
99
100
|
// Only fields in the runtime persona schema may be edited; provenance (`preset`) is retained, not editable.
|
|
100
101
|
const PROFILE_KEYS = new Set([
|
|
101
102
|
'personaId', 'displayName', 'description', 'systemPrompt', 'actionHosts', 'offsiteNavigation',
|
|
102
|
-
'brand', 'models', 'mentionPatterns', 'avatarUrl', 'brandColor', 'browserPort', 'residence', 'capabilities',
|
|
103
|
+
'brand', 'models', 'mentionPatterns', 'avatarUrl', 'brandColor', 'browserPort', 'residence', 'capabilities', 'todo',
|
|
103
104
|
]);
|
|
104
105
|
const NESTED_KEYS: Record<string, readonly string[]> = {
|
|
105
106
|
models: ['primary', 'fallback', 'providers'], 'models.providers': ['ollama', 'openai'],
|
|
@@ -111,11 +112,18 @@ function mapping(value: unknown): value is Record<string, unknown> {
|
|
|
111
112
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
112
113
|
}
|
|
113
114
|
|
|
115
|
+
export class UnknownPersonaSchemaKeyError extends Error {
|
|
116
|
+
constructor(key: string) {
|
|
117
|
+
super(`unknown persona schema key: ${key}`);
|
|
118
|
+
this.name = 'UnknownPersonaSchemaKeyError';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
114
122
|
function assertSchemaKeys(value: Record<string, unknown>, parent = ''): void {
|
|
115
123
|
const allowed: ReadonlySet<string> = parent ? new Set(NESTED_KEYS[parent] ?? []) : PROFILE_KEYS;
|
|
116
124
|
for (const [key, child] of Object.entries(value)) {
|
|
117
125
|
if (!allowed.has(key)) {
|
|
118
|
-
throw new
|
|
126
|
+
throw new UnknownPersonaSchemaKeyError(`${parent ? `${parent}.` : ''}${key}`);
|
|
119
127
|
}
|
|
120
128
|
const path = parent ? `${parent}.${key}` : key;
|
|
121
129
|
if (NESTED_KEYS[path] && mapping(child)) assertSchemaKeys(child, path);
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { parseDocument } from 'yaml';
|
|
4
|
+
import { parsePersonaYaml } from './loader.js';
|
|
5
|
+
|
|
6
|
+
export interface TeamMember {
|
|
7
|
+
persona: string;
|
|
8
|
+
title: string;
|
|
9
|
+
checklistTemplate: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface TeamHandoff {
|
|
13
|
+
from: string;
|
|
14
|
+
to: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface TeamDefinition {
|
|
18
|
+
name: string;
|
|
19
|
+
members: TeamMember[];
|
|
20
|
+
handoffs: TeamHandoff[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function mapping(value: unknown): value is Record<string, unknown> {
|
|
24
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function fields(value: Record<string, unknown>, required: readonly string[], label: string): void {
|
|
28
|
+
for (const key of required) {
|
|
29
|
+
if (!Object.hasOwn(value, key)) throw new Error(`${label}.${key} is required`);
|
|
30
|
+
}
|
|
31
|
+
for (const key of Object.keys(value)) {
|
|
32
|
+
if (!required.includes(key)) throw new Error(`${label}.${key} is not a team schema field`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function text(value: unknown, label: string): string {
|
|
37
|
+
if (typeof value !== 'string' || !value.trim()) throw new Error(`${label} must be a non-empty string`);
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Read-only validation: resolve persona paths relative to the team file, not the caller's cwd. */
|
|
42
|
+
export function validateTeamFile(file: string): TeamDefinition {
|
|
43
|
+
const document = parseDocument(readFileSync(file, 'utf8'), { uniqueKeys: true });
|
|
44
|
+
if (document.errors.length) throw new Error(`invalid team YAML: ${document.errors[0]!.message}`);
|
|
45
|
+
const raw: unknown = document.toJS();
|
|
46
|
+
if (!mapping(raw)) throw new Error('team must be a YAML mapping');
|
|
47
|
+
fields(raw, ['name', 'members', 'handoffs'], 'team');
|
|
48
|
+
const name = text(raw.name, 'team.name');
|
|
49
|
+
if (!Array.isArray(raw.members) || raw.members.length === 0) {
|
|
50
|
+
throw new Error('team.members must be a non-empty list');
|
|
51
|
+
}
|
|
52
|
+
const titles = new Set<string>();
|
|
53
|
+
const personaIds = new Set<string>();
|
|
54
|
+
const members: TeamMember[] = raw.members.map((item: unknown, index: number) => {
|
|
55
|
+
const label = `team.members[${index}]`;
|
|
56
|
+
if (!mapping(item)) throw new Error(`${label} must be a mapping`);
|
|
57
|
+
fields(item, ['persona', 'title', 'checklistTemplate'], label);
|
|
58
|
+
const persona = text(item.persona, `${label}.persona`);
|
|
59
|
+
const title = text(item.title, `${label}.title`);
|
|
60
|
+
if (titles.has(title)) throw new Error(`duplicate team title: ${title}`);
|
|
61
|
+
titles.add(title);
|
|
62
|
+
if (!Array.isArray(item.checklistTemplate) || item.checklistTemplate.length === 0) {
|
|
63
|
+
throw new Error(`${label}.checklistTemplate must be a non-empty list of strings`);
|
|
64
|
+
}
|
|
65
|
+
const checklistTemplate = item.checklistTemplate.map((entry: unknown) => text(entry, `${label}.checklistTemplate item`));
|
|
66
|
+
const personaPath = resolve(dirname(file), persona);
|
|
67
|
+
let yaml: string;
|
|
68
|
+
try {
|
|
69
|
+
yaml = readFileSync(personaPath, 'utf8');
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') throw new Error(`persona not found: ${persona} (${title})`);
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
const parsed = parsePersonaYaml(yaml, personaPath);
|
|
75
|
+
if (!parsed.ok) throw new Error(`invalid persona ${persona}: ${parsed.error.message}`);
|
|
76
|
+
if (parsed.profile.seat !== title) throw new Error(`persona seat mismatch: ${persona} requires seat ${title}`);
|
|
77
|
+
if (personaIds.has(parsed.profile.personaId)) throw new Error(`duplicate personaId: ${parsed.profile.personaId}`);
|
|
78
|
+
personaIds.add(parsed.profile.personaId);
|
|
79
|
+
return { persona, title, checklistTemplate };
|
|
80
|
+
});
|
|
81
|
+
if (!Array.isArray(raw.handoffs)) throw new Error('team.handoffs must be a list');
|
|
82
|
+
const handoffs: TeamHandoff[] = raw.handoffs.map((item: unknown, index: number) => {
|
|
83
|
+
const label = `team.handoffs[${index}]`;
|
|
84
|
+
if (!mapping(item)) throw new Error(`${label} must be a mapping`);
|
|
85
|
+
fields(item, ['from', 'to'], label);
|
|
86
|
+
const from = text(item.from, `${label}.from`);
|
|
87
|
+
const to = text(item.to, `${label}.to`);
|
|
88
|
+
if (!titles.has(from) || !titles.has(to)) throw new Error(`handoff references missing title: ${from} -> ${to}`);
|
|
89
|
+
return { from, to };
|
|
90
|
+
});
|
|
91
|
+
const edges = new Map([...titles].map((title) => [title, [] as string[]]));
|
|
92
|
+
for (const { from, to } of handoffs) edges.get(from)!.push(to);
|
|
93
|
+
const visiting = new Set<string>();
|
|
94
|
+
const visited = new Set<string>();
|
|
95
|
+
const visit = (title: string): void => {
|
|
96
|
+
if (visiting.has(title)) throw new Error(`handoff cycle at ${title}`);
|
|
97
|
+
if (visited.has(title)) return;
|
|
98
|
+
visiting.add(title);
|
|
99
|
+
for (const next of edges.get(title)!) visit(next);
|
|
100
|
+
visiting.delete(title);
|
|
101
|
+
visited.add(title);
|
|
102
|
+
};
|
|
103
|
+
for (const title of titles) visit(title);
|
|
104
|
+
return { name, members, handoffs };
|
|
105
|
+
}
|
package/src/persona/types.ts
CHANGED
|
@@ -19,6 +19,10 @@ export type PersonaBrand =
|
|
|
19
19
|
export interface PersonaProfile {
|
|
20
20
|
readonly personaId: string;
|
|
21
21
|
readonly displayName: string;
|
|
22
|
+
/** Seat address (title, id or alias) represented by this persona. */
|
|
23
|
+
readonly seat?: string;
|
|
24
|
+
/** Per-persona JSONL todo filename in the state persona directory. */
|
|
25
|
+
readonly todo?: string;
|
|
22
26
|
readonly description?: string;
|
|
23
27
|
|
|
24
28
|
/** System prompt to prepend in LLM calls. Markdown text. May
|
|
@@ -17,7 +17,8 @@ function packageFiles(dir: string, prefix = ''): string[] {
|
|
|
17
17
|
const files: string[] = [];
|
|
18
18
|
for (const entry of readdirSync(join(dir, prefix), { withFileTypes: true })) {
|
|
19
19
|
const path = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
20
|
-
if (entry.name === '.git' || entry.name === '.elanous' || entry.name === '
|
|
20
|
+
if (entry.name === '.git' || entry.name === '.elanous' || entry.name === '.elanous-local' ||
|
|
21
|
+
entry.name === '.elanous-test' || entry.name === 'graph-runs' || entry.name === 'node_modules') continue;
|
|
21
22
|
if (entry.isSymbolicLink() || (!entry.isFile() && !entry.isDirectory())) throw new Error(`unsafe market bundle entry: ${path}`);
|
|
22
23
|
if (entry.isDirectory()) files.push(...packageFiles(dir, path));
|
|
23
24
|
else files.push(path);
|