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
|
@@ -58,6 +58,9 @@ import { envLiteral } from '../../platform/env-literal.js';
|
|
|
58
58
|
import { LLM_TIER_MAP_BY_PROVIDER, lookupLlmTierSpec, type LlmTierProvider } from '../../model-tier/llm-tier-map.js';
|
|
59
59
|
import { parseSelfImplementJson, type SelfImplementJobDone, type SelfImplementJobSpawn } from './self-implement.js';
|
|
60
60
|
import { codexQuotaPolicyFromConfig } from '../../oauth/codex-account-store.js';
|
|
61
|
+
import { goalTypeOf } from '../../../scripts/measure-pod-memory-by-goal.js';
|
|
62
|
+
import { readPodMemoryAdvice, type PodMemoryAdvice } from '../../cli/pod-memory-advice.js';
|
|
63
|
+
import { getUserConfig } from '../../user-config.js';
|
|
61
64
|
|
|
62
65
|
export type Kubectl = (args: readonly string[], input?: string) => { status: number | null; stdout: string; stderr: string };
|
|
63
66
|
|
|
@@ -132,6 +135,10 @@ export interface PodSpawnOptions {
|
|
|
132
135
|
env?: NodeJS.ProcessEnv;
|
|
133
136
|
/** Detail switch file (test injection; defaults to the host's local/production selection). */
|
|
134
137
|
liveDetailFile?: string;
|
|
138
|
+
/** Test seam for the opt-in user configuration; otherwise read the active user config. */
|
|
139
|
+
adviseDefaults?: boolean;
|
|
140
|
+
/** Test seam for the read-only measured advice. */
|
|
141
|
+
memoryAdvice?: () => PodMemoryAdvice;
|
|
135
142
|
}
|
|
136
143
|
|
|
137
144
|
export function defaultKubectl(args: readonly string[], input?: string): { status: number | null; stdout: string; stderr: string } {
|
|
@@ -411,7 +418,7 @@ export const POD_MEMORY_HIGH_DEFAULT = '32Gi';
|
|
|
411
418
|
export const POD_MEMORY_LITE_DEFAULT = '2Gi';
|
|
412
419
|
export const POD_MEMORY_TIERS = ['lite', 'standard', 'high'] as const;
|
|
413
420
|
export type PodMemoryTier = (typeof POD_MEMORY_TIERS)[number];
|
|
414
|
-
export type PodMemorySource = 'option' | 'goal-line' | 'pwa-auto' | 'default' | 'parent-goal-line' | 'parent-pwa-auto';
|
|
421
|
+
export type PodMemorySource = 'option' | 'goal-line' | 'pwa-auto' | 'default' | 'parent-goal-line' | 'parent-pwa-auto' | 'advise';
|
|
415
422
|
const PWA_PATH = /(?:^|[\s`'"(,·])apps\/pwa\//m;
|
|
416
423
|
const GOAL_LINE = /^[ \t]*(?:Pod 메모리|pod-memory)[ \t]*:[ \t]*(lite|standard|high)[ \t]*\r?$/im;
|
|
417
424
|
|
|
@@ -453,14 +460,21 @@ function memoryGi(v: string): number | null {
|
|
|
453
460
|
return m[2] === 'Gi' ? Number(m[1]) : Number(m[1]) / 1024;
|
|
454
461
|
}
|
|
455
462
|
|
|
456
|
-
export function podMemoryLimitFor(feature: string, env: NodeJS.ProcessEnv | Readonly<Record<string, string | undefined>>, parentGoal?: string): { limit: string; tier: PodMemoryTier; source: PodMemorySource } {
|
|
463
|
+
export function podMemoryLimitFor(feature: string, env: NodeJS.ProcessEnv | Readonly<Record<string, string | undefined>>, parentGoal?: string, advice?: PodMemoryAdvice): { limit: string; tier: PodMemoryTier; source: PodMemorySource } {
|
|
457
464
|
const option = parsePodMemoryTier(env.ELANOUS_POD_MEMORY_TIER);
|
|
458
465
|
const line = parsePodMemoryTier(GOAL_LINE.exec(feature)?.[1]);
|
|
459
|
-
const parentLine = !option && !line
|
|
466
|
+
const parentLine = !option && !line ? parsePodMemoryTier(GOAL_LINE.exec(parentGoal ?? '')?.[1]) : null;
|
|
467
|
+
const goalType = advice && !option && !line && !parentLine
|
|
468
|
+
? (broadPodTestWarning(feature) ? 'test' : goalTypeOf(feature))
|
|
469
|
+
?? (parentGoal ? (broadPodTestWarning(parentGoal) ? 'test' : goalTypeOf(parentGoal)) : null)
|
|
470
|
+
: null;
|
|
471
|
+
const recommendation = advice?.byGoalType.find((entry) => entry.goalType === goalType && entry.evidence.runs > 0)?.recommended;
|
|
460
472
|
const [tier, source]: [PodMemoryTier, PodMemorySource] = option ? [option, 'option']
|
|
461
473
|
: line ? [line, 'goal-line']
|
|
474
|
+
// 조각 자신의 PWA 경로가 부모 줄·실측 권고보다 먼저다 — 부모 «standard» 가 PWA 조각을 16Gi 로 내리면 OOM(POD7 · #23569 순서 회귀)
|
|
462
475
|
: goalTouchesPwa(feature) ? ['high', 'pwa-auto']
|
|
463
476
|
: parentLine ? [parentLine, 'parent-goal-line']
|
|
477
|
+
: recommendation ? [recommendation, 'advise']
|
|
464
478
|
: parentGoal && goalTouchesPwa(parentGoal) ? ['high', 'parent-pwa-auto']
|
|
465
479
|
: ['standard', 'default'];
|
|
466
480
|
if (tier === 'lite') return { limit: env.ELANOUS_POD_MEMORY_LITE?.trim() || POD_MEMORY_LITE_DEFAULT, tier, source };
|
|
@@ -640,7 +654,6 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
640
654
|
const hostSupervised = options.hostSupervised ?? true;
|
|
641
655
|
return (input) => {
|
|
642
656
|
const name = podJobName(input.spaceId);
|
|
643
|
-
const featureMemory = podMemoryLimitFor(input.feature, env);
|
|
644
657
|
let parentGoal: string | undefined;
|
|
645
658
|
let parentGoalUnreadable = false;
|
|
646
659
|
let inheritedFrom: string | undefined;
|
|
@@ -658,7 +671,14 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
658
671
|
inheritedFrom = withinRoot;
|
|
659
672
|
} catch { parentGoalUnreadable = true; }
|
|
660
673
|
}
|
|
661
|
-
|
|
674
|
+
let advice: PodMemoryAdvice | undefined;
|
|
675
|
+
const explicitMemory = Boolean(parsePodMemoryTier(env.ELANOUS_POD_MEMORY_TIER) || GOAL_LINE.test(input.feature) || GOAL_LINE.test(parentGoal ?? ''));
|
|
676
|
+
const useAdvice = options.adviseDefaults ?? getUserConfig().pod?.memory?.adviseDefaults === true;
|
|
677
|
+
if (useAdvice && !explicitMemory) {
|
|
678
|
+
try { advice = (options.memoryAdvice ?? readPodMemoryAdvice)(); }
|
|
679
|
+
catch (error) { debug.log('self-implement.pod', 'memory-advice-unavailable', { spaceId: input.spaceId, reason: error instanceof Error ? error.message : String(error) }, { level: 'warn' }); }
|
|
680
|
+
}
|
|
681
|
+
const { limit: selectedMemoryLimit, tier: memoryTier, source: memorySource } = podMemoryLimitFor(input.feature, env, parentGoal, advice);
|
|
662
682
|
for (const [where, text] of [['feature', input.feature], ['parent-goal', parentGoal]] as const) {
|
|
663
683
|
if (text && text.split(/\r?\n/u).some((line) => /Pod 메모리/u.test(line) && !GOAL_LINE.test(line))) {
|
|
664
684
|
const reason = '한 줄 단독 `Pod 메모리: high|standard` 형식이 아님';
|
|
@@ -716,7 +736,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
716
736
|
if (recoveryError) return { exitCode: 1, output: '', error: { code: 'pod-oom-recovery', message: recoveryError } };
|
|
717
737
|
// Each Job attempt has its own host lease; releasing the first after OOM lets the retry
|
|
718
738
|
// compete with other processes for the newly measured capacity before applying another Job.
|
|
719
|
-
let releaseAdmission: ((() => void) & { applied?: () => void }) | undefined;
|
|
739
|
+
let releaseAdmission: ((() => void) & { applied?: (job: string, context: string, namespace: string) => void; observed?: () => void }) | undefined;
|
|
720
740
|
try {
|
|
721
741
|
// Every attempt, including an OOM retry, must acquire a pool slot before applying a Job.
|
|
722
742
|
const acquireSlot = async (): Promise<PodPoolMember | null> => {
|
|
@@ -1067,6 +1087,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1067
1087
|
const job = podJobManifest({ name, namespace, image: jobImage, ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}), repoUrl, ...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), args, passEnv: jobPassEnv, deadlineSeconds: options.deadlineSeconds ?? POD_JOB_DEADLINE_SECONDS, ...(goalDoc ? { goalDoc } : {}), runId: launchRunId, ...(parentRunId ? { parentRunId } : {}), ...(jobArmEnv ? { armEnv: jobArmEnv } : {}), hostId: resolveHostId(env), skillEnvs: Object.keys(skillEnvs), memoryLimit, imageCommit: options.imageCommit !== undefined ? options.imageCommit : options.kubectl ? null : podImageFreshness({ image }).imageCommit, ...(grok ? { grokCredential: 'grokAuth' in creds && creds.grokAuth ? 'subscription' as const : 'api_key' as const } : {}), ...(codexAccounts ? { codexAccounts } : {}), ...(hostRefresh ? { appCredential: true } : {}), execution: { key: executionKey, attempt: oomRetried ? 2 : 1 }, hostLeaseAdmitted: !!releaseAdmission }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
|
|
1068
1088
|
const a = kubectl(['apply', '-f', '-'], JSON.stringify(job));
|
|
1069
1089
|
if (a.status !== 0) { cleanupSecret(); return { exitCode: 1, output: a.stderr, error: { code: 'pod-apply', message: a.stderr.trim() } }; }
|
|
1090
|
+
releaseAdmission?.applied?.(name, context, namespace);
|
|
1070
1091
|
if (options.source?.kind === 'bundle') {
|
|
1071
1092
|
const podName = await waitForRunningPod(kubectl, namespace, name, sleep);
|
|
1072
1093
|
if (!podName) {
|
|
@@ -1074,6 +1095,8 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1074
1095
|
cleanupSecret();
|
|
1075
1096
|
return { exitCode: 1, output: '', error: { code: 'pod-source', message: `Pod for ${name} did not become Running` } };
|
|
1076
1097
|
}
|
|
1098
|
+
releaseAdmission?.observed?.();
|
|
1099
|
+
if (releaseAdmission) delete releaseAdmission.observed;
|
|
1077
1100
|
const copied = kubectl(['cp', options.source.bundlePath, `${podName}:/tmp/source.bundle`, '-c', 'child', '-n', namespace]);
|
|
1078
1101
|
if (copied.status !== 0) {
|
|
1079
1102
|
debug.log('self-implement.pod', 'source-mismatch', { job: name, kind: 'bundle', headCommit: options.source.headCommit });
|
|
@@ -1106,7 +1129,6 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1106
1129
|
}
|
|
1107
1130
|
writePodFragment({ spaceId: input.spaceId, context, namespace, job: name, inboxDir: POD_CONTROL_INBOX_DIR, ...(liveChildRunId === normalizeRunId(liveChildRunId) ? { runId: liveChildRunId } : {}), ...(parentRunId && normalizeRunId(parentRunId) === parentRunId ? { parentRunId } : {}) }, env);
|
|
1108
1131
|
recorded = true;
|
|
1109
|
-
releaseAdmission?.applied?.();
|
|
1110
1132
|
debug.log('self-implement.pod', 'job-applied', { job: name, namespace, ...(member ? { context: member.context } : {}), image: jobImage, spaceId: input.spaceId, passEnv, extraArgs: options.extraArgs ?? [], ...(options.armEnv?.ELANOUS_ARM_ID ? { armId: options.armEnv.ELANOUS_ARM_ID } : {}) });
|
|
1111
1133
|
let state: 'complete' | 'failed' | 'aborted' = 'failed';
|
|
1112
1134
|
let failedReason = '';
|
|
@@ -1122,8 +1144,9 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1122
1144
|
}
|
|
1123
1145
|
const g = kubectl(['-n', namespace, 'get', 'job', name, '-o', 'jsonpath={.status.conditions[*].type}']);
|
|
1124
1146
|
const types = g.stdout;
|
|
1125
|
-
if (/Complete|SuccessCriteriaMet/.test(types)) { state = 'complete'; break; }
|
|
1147
|
+
if (/Complete|SuccessCriteriaMet/.test(types)) { releaseAdmission?.observed?.(); state = 'complete'; break; }
|
|
1126
1148
|
if (/Failed|FailureTarget/.test(types)) {
|
|
1149
|
+
releaseAdmission?.observed?.();
|
|
1127
1150
|
state = 'failed';
|
|
1128
1151
|
const reason = kubectl(['-n', namespace, 'get', 'job', name, '-o', 'jsonpath={.status.conditions[?(@.type=="Failed")].reason}']);
|
|
1129
1152
|
failedReason = reason.status === 0 ? reason.stdout.trim() : '';
|
|
@@ -1143,6 +1166,13 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1143
1166
|
debug.log('self-implement.pod', 'container-terminated', { job: name, container: 'child', reason: containerReason, exitCode: containerExitCode, jobReason: failedReason || null, memoryLimit });
|
|
1144
1167
|
break;
|
|
1145
1168
|
}
|
|
1169
|
+
if (releaseAdmission?.observed) {
|
|
1170
|
+
const podPhase = kubectl(['-n', namespace, 'get', 'pods', '-l', `job-name=${name}`, '-o', 'jsonpath={.items[*].status.phase}']);
|
|
1171
|
+
if (podPhase.status === 0 && /\b(?:Running|Succeeded|Failed)\b/.test(podPhase.stdout)) {
|
|
1172
|
+
releaseAdmission.observed();
|
|
1173
|
+
delete releaseAdmission.observed;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1146
1176
|
if (ghTokenExpiresAt !== null && ghTokenExpiresAt - now() <= 10 * 60_000) {
|
|
1147
1177
|
try {
|
|
1148
1178
|
const fresh = await issueVerifiedAppCredential();
|
|
@@ -42,7 +42,7 @@ export function defaultBaseUrl(deps: { runtime?: () => ReturnType<typeof readNex
|
|
|
42
42
|
return `http://${host}:${rt.httpPort}`;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
function defaultToken(): string | null {
|
|
45
|
+
export function defaultToken(): string | null {
|
|
46
46
|
const p = join(getElanousConfigDir(), 'acp-token');
|
|
47
47
|
try { return existsSync(p) ? readFileSync(p, 'utf-8').trim() || null : null; } catch { return null; }
|
|
48
48
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { defaultBaseUrl, defaultToken } from './telegram-dispatch-forward.js';
|
|
2
|
+
import type { FabricPlan } from './self-dev/fabric-plan-core.js';
|
|
3
|
+
import type { TelegramBot, TgIncoming } from './telegram.js';
|
|
4
|
+
|
|
5
|
+
export interface TelegramFabricPlanDeps {
|
|
6
|
+
fetchImpl?: typeof fetch;
|
|
7
|
+
baseUrl?: () => string | null;
|
|
8
|
+
token?: () => string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Only a successful daemon draft is offered for approval; no execution endpoint is called here. */
|
|
12
|
+
export function attachTelegramFabricPlan(
|
|
13
|
+
bot: Pick<TelegramBot, 'isOwnerAllowed' | 'sendMessage' | 'sendInlineKeyboard' | 'onCallbackQuery' | 'answerCallbackQuery'>,
|
|
14
|
+
deps: TelegramFabricPlanDeps = {},
|
|
15
|
+
): (ctx: TgIncoming) => Promise<boolean> {
|
|
16
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
17
|
+
const pending = new Map<string, { chatId: number; userId: number; threadId?: number; messageId: number }>();
|
|
18
|
+
const endpoint = (path: string): { url: string; token: string | null } => {
|
|
19
|
+
const base = (deps.baseUrl ?? defaultBaseUrl)();
|
|
20
|
+
if (!base) throw new Error('데몬에 연결할 수 없습니다');
|
|
21
|
+
return { url: `${base}${path}`, token: (deps.token ?? defaultToken)() };
|
|
22
|
+
};
|
|
23
|
+
const post = async (path: string, body?: unknown): Promise<unknown> => {
|
|
24
|
+
const { url, token } = endpoint(path);
|
|
25
|
+
const response = await fetchImpl(url, {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}) },
|
|
28
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
29
|
+
});
|
|
30
|
+
if (!response.ok) throw new Error(`데몬 응답 ${response.status}`);
|
|
31
|
+
return response.json();
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
bot.onCallbackQuery(async (q) => {
|
|
35
|
+
const match = /^fabric:(approve|edit):([a-f0-9-]{36})$/i.exec(q.data);
|
|
36
|
+
if (!match) return;
|
|
37
|
+
const planId = match[2]!;
|
|
38
|
+
const original = pending.get(planId);
|
|
39
|
+
if (!original || !bot.isOwnerAllowed(q.userId) || q.userId !== original.userId
|
|
40
|
+
|| q.chatId !== original.chatId || q.threadId !== original.threadId || q.messageId !== original.messageId) {
|
|
41
|
+
await bot.answerCallbackQuery(q.id, { text: '이 초안의 요청자만 선택할 수 있습니다' });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (match[1] === 'edit') {
|
|
45
|
+
pending.delete(planId);
|
|
46
|
+
await bot.answerCallbackQuery(q.id);
|
|
47
|
+
await bot.sendMessage(original.chatId, '고칠 내용을 반영해 «아크: <복합 문장>»으로 다시 보내세요. 이 초안은 승인되지 않았습니다.', { threadId: original.threadId });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
await post(`/v1/fabric/plans/${planId}/approve`);
|
|
52
|
+
pending.delete(planId);
|
|
53
|
+
await bot.answerCallbackQuery(q.id, { text: '승인되었습니다' });
|
|
54
|
+
await bot.sendMessage(original.chatId, `아크 초안 ${planId} 승인됨 — 실행은 별도 단계입니다.`, { threadId: original.threadId });
|
|
55
|
+
} catch (error) {
|
|
56
|
+
await bot.answerCallbackQuery(q.id, { text: `승인 실패: ${error instanceof Error ? error.message : String(error)}` });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return async (ctx) => {
|
|
61
|
+
if (ctx.attachments.length || ctx.video || !ctx.text.startsWith('아크:')) return false;
|
|
62
|
+
const request = ctx.text.slice('아크:'.length).trim();
|
|
63
|
+
if (!request || request.includes('\n')) {
|
|
64
|
+
await bot.sendMessage(ctx.chatId, '«아크: <복합 문장>» 한 줄로 보내세요.', { replyTo: ctx.messageId, threadId: ctx.threadId });
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const body = await post('/v1/fabric/decompose', { request }) as { plan?: FabricPlan };
|
|
69
|
+
const plan = body.plan;
|
|
70
|
+
if (!plan || plan.status !== 'draft' || !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(plan.id)
|
|
71
|
+
|| !Array.isArray(plan.nodes) || plan.nodes.length === 0) throw new Error('초안 형식 오류');
|
|
72
|
+
const steps = plan.nodes.map((node, i) => `${i + 1}. ${node.title}`).join('\n');
|
|
73
|
+
const preview = `아크 초안 · 실행 전 검토\n${steps}`;
|
|
74
|
+
const reply = { replyTo: ctx.messageId, threadId: ctx.threadId };
|
|
75
|
+
for (let offset = 0; offset + 4000 < preview.length; offset += 4000) {
|
|
76
|
+
const sent = await bot.sendMessage(ctx.chatId, preview.slice(offset, offset + 4000), reply);
|
|
77
|
+
if (sent?.messageId === undefined) throw new Error('단계 미리보기를 전부 전달하지 못했습니다 — 승인 불가');
|
|
78
|
+
}
|
|
79
|
+
const finalOffset = Math.floor((preview.length - 1) / 4000) * 4000;
|
|
80
|
+
const sent = await bot.sendInlineKeyboard(ctx.chatId, preview.slice(finalOffset), [[
|
|
81
|
+
{ text: '승인', data: `fabric:approve:${plan.id}` },
|
|
82
|
+
{ text: '고치기', data: `fabric:edit:${plan.id}` },
|
|
83
|
+
]], reply);
|
|
84
|
+
if (sent?.messageId !== undefined) pending.set(plan.id, { chatId: ctx.chatId, userId: ctx.userId, threadId: ctx.threadId, messageId: sent.messageId });
|
|
85
|
+
} catch (error) {
|
|
86
|
+
await bot.sendMessage(ctx.chatId, `아크 초안 생성 실패: ${error instanceof Error ? error.message : String(error)}`, { replyTo: ctx.messageId, threadId: ctx.threadId });
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -35,17 +35,23 @@ export function projectCommand(deps: TelegramProjectCommandDeps = {
|
|
|
35
35
|
debug.log('telegram.project', 'listed', { projects: projects.slice(0, 20).map(p => ({ id: p.id, name: p.name })) });
|
|
36
36
|
if (ctx.isDm && ctx.chatId === ctx.userId
|
|
37
37
|
&& String(ctx.userId) === telegramDecisionOwner(ctxOptions.userConfig) && ctxOptions.sendButtons) {
|
|
38
|
-
const
|
|
38
|
+
const eligible = projects.filter(p => {
|
|
39
39
|
const bytes = Buffer.byteLength(`prj:${p.id}`, 'utf8');
|
|
40
40
|
if (bytes <= 64) return true;
|
|
41
41
|
debug.log('telegram.project', 'button-rejected', { reason: 'callback-data-too-long', projectId: p.id, bytes });
|
|
42
42
|
return false;
|
|
43
|
-
})
|
|
44
|
-
|
|
43
|
+
});
|
|
44
|
+
const visible = eligible.slice(0, 7);
|
|
45
|
+
if (current && eligible.some(p => p.id === current.id) && !visible.some(p => p.id === current.id)) {
|
|
46
|
+
visible[visible.length - 1] = current;
|
|
47
|
+
}
|
|
48
|
+
const buttons = visible.map(p => ({ text: `${p.id === current?.id ? '✓ ' : ''}${p.name}`, data: `prj:${p.id}` }));
|
|
49
|
+
buttons.push({ text: `${session.projectId ? '' : '✓ '}받은 대화로`, data: 'prj:-' });
|
|
45
50
|
const rows: Array<Array<{ text: string; data: string }>> = [];
|
|
46
51
|
for (let i = 0; i < buttons.length; i += 2) rows.push(buttons.slice(i, i + 2));
|
|
47
52
|
try {
|
|
48
53
|
await ctxOptions.sendButtons('프로젝트를 선택하세요', rows);
|
|
54
|
+
return;
|
|
49
55
|
} catch (err) {
|
|
50
56
|
debug.log('telegram.project', 'button-rejected', {
|
|
51
57
|
reason: 'send-failed', error: err instanceof Error ? err.message : String(err),
|
|
@@ -140,6 +146,6 @@ export function attachTelegramProjectButtons(
|
|
|
140
146
|
deps.update(session.id, m => { m.projectId = project.id; });
|
|
141
147
|
debug.log('telegram.project', 'button-moved', { chatId, sessionId: session.id, projectId: project.id });
|
|
142
148
|
await bot.answerCallbackQuery(q.id);
|
|
143
|
-
await bot.sendMessage(chatId,
|
|
149
|
+
await bot.sendMessage(chatId, `지금 프로젝트: ${project.name}`);
|
|
144
150
|
});
|
|
145
151
|
}
|
package/src/telegram.ts
CHANGED
|
@@ -77,6 +77,7 @@ import { handleTelegramFieldNote } from './field-note/telegram.js';
|
|
|
77
77
|
import type { TranscriptionEngine, FieldAudio } from './field-note/transcribe.js';
|
|
78
78
|
import { maybeHandleCardPhoto, type CardPhotoDeps } from './telegram-card-followup.js';
|
|
79
79
|
import { attachTelegramProjectButtons } from './telegram-project-command.js';
|
|
80
|
+
import { attachTelegramFabricPlan } from './telegram-fabric-plan.js';
|
|
80
81
|
|
|
81
82
|
export type TgAttachmentKind = 'photo' | 'voice' | 'audio' | 'document';
|
|
82
83
|
|
|
@@ -229,6 +230,7 @@ export interface TelegramBotOpts {
|
|
|
229
230
|
fieldNoteDecodeAudio?: (file: string, recordedAt: string) => FieldAudio;
|
|
230
231
|
/** Test seam for the business-card graph and its private run root. */
|
|
231
232
|
cardFollowupDeps?: Pick<CardPhotoDeps, 'runGraph' | 'rootDir' | 'ocrText'>;
|
|
233
|
+
fabricPlanDeps?: import('./telegram-fabric-plan.js').TelegramFabricPlanDeps;
|
|
232
234
|
}
|
|
233
235
|
|
|
234
236
|
/** Surface-unification v2 (FU-2) — Telegram trigger event passed to
|
|
@@ -350,6 +352,7 @@ export class TelegramBot {
|
|
|
350
352
|
private readonly fieldNoteDecodeAudio?: (file: string, recordedAt: string) => FieldAudio;
|
|
351
353
|
private readonly cardFollowupDeps: Pick<CardPhotoDeps, 'runGraph' | 'rootDir' | 'ocrText'>;
|
|
352
354
|
private readonly seatWorkDeps: TelegramSeatWorkDeps;
|
|
355
|
+
private readonly handleFabricPlan: (ctx: TgIncoming) => Promise<boolean>;
|
|
353
356
|
private seatAskTimer: ReturnType<typeof setInterval> | null = null;
|
|
354
357
|
/** 앨범 id → `#현장` 행사 · 답장 디바운스. 태그된 앨범만 들어온다. */
|
|
355
358
|
private readonly fieldGroups = new Map<string, {
|
|
@@ -388,6 +391,7 @@ export class TelegramBot {
|
|
|
388
391
|
this.fieldNoteEngine = opts.fieldNoteEngine;
|
|
389
392
|
this.fieldNoteDecodeAudio = opts.fieldNoteDecodeAudio;
|
|
390
393
|
this.cardFollowupDeps = opts.cardFollowupDeps ?? {};
|
|
394
|
+
this.handleFabricPlan = attachTelegramFabricPlan(this, opts.fabricPlanDeps);
|
|
391
395
|
this.seatWorkDeps = { ...opts.seatWorkDeps, askDeps: { ...opts.seatWorkDeps?.askDeps,
|
|
392
396
|
channel: 'telegram', botId: this.botId,
|
|
393
397
|
send: opts.seatWorkDeps?.askDeps?.send ?? (async (origin, text) => {
|
|
@@ -1539,6 +1543,7 @@ export class TelegramBot {
|
|
|
1539
1543
|
await this.refuseMessage(ctx);
|
|
1540
1544
|
return;
|
|
1541
1545
|
}
|
|
1546
|
+
if (await this.handleFabricPlan(ctx)) return;
|
|
1542
1547
|
// 현장 업로드 — `#현장` 캡션(또는 태그된 앨범의 나머지)의 사진·영상은 LLM 대신 현장 폴더로.
|
|
1543
1548
|
if (await this.tryHandleFieldUpload(ctx)) return;
|
|
1544
1549
|
if (await handleTelegramFieldNote(ctx, {
|
|
@@ -3,6 +3,7 @@ import { StringDecoder } from 'node:string_decoder';
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { dirname, resolve } from 'node:path';
|
|
5
5
|
import type { LLMToolSpec } from '../llm.js';
|
|
6
|
+
import { stateDirSourceForChild } from '../agent/identity-env.js';
|
|
6
7
|
import { debug } from '../debug/log.js';
|
|
7
8
|
import { canonicalizeForBoundary, isWithinBoundary, resolveHarnessBoundary } from '../harness/harness-write-boundary.js';
|
|
8
9
|
import { getHarnessSpace } from '../harness/harness-space.js';
|
|
@@ -133,7 +134,7 @@ async function spawnHeldElanous(command: string[], cwd: string, instanceRoot: st
|
|
|
133
134
|
try {
|
|
134
135
|
child = deps.spawn(command[0]!, command.slice(1), {
|
|
135
136
|
cwd,
|
|
136
|
-
env: { ...process.env, ELANOUS_STATE_DIR: instanceRoot },
|
|
137
|
+
env: { ...process.env, ELANOUS_STATE_DIR: instanceRoot, ELANOUS_STATE_DIR_SOURCE: stateDirSourceForChild(instanceRoot) },
|
|
137
138
|
detached: true,
|
|
138
139
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
139
140
|
});
|
package/src/user-config.ts
CHANGED
|
@@ -3475,7 +3475,34 @@ export function resolveRoleModel(role: ModelRole, config: UserConfig = getUserCo
|
|
|
3475
3475
|
}
|
|
3476
3476
|
|
|
3477
3477
|
|
|
3478
|
-
export type
|
|
3478
|
+
export type OrchestratorSeat = 'OP' | 'TC' | 'MK' | 'UX';
|
|
3479
|
+
export interface OrchestratorLoopConfig {
|
|
3480
|
+
seatTrees: Partial<Record<OrchestratorSeat, string[]>>;
|
|
3481
|
+
seatCaps: Record<OrchestratorSeat, number>;
|
|
3482
|
+
trafficMode: 'shadow' | 'live';
|
|
3483
|
+
}
|
|
3484
|
+
|
|
3485
|
+
export const ORCHESTRATOR_DEFAULTS: OrchestratorLoopConfig = {
|
|
3486
|
+
seatTrees: {}, seatCaps: { TC: 8, UX: 6, MK: 6, OP: 4 }, trafficMode: 'shadow',
|
|
3487
|
+
};
|
|
3488
|
+
|
|
3489
|
+
export function parseOrchestratorLoopConfig(raw: unknown): OrchestratorLoopConfig {
|
|
3490
|
+
const value = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw as Record<string, unknown> : {};
|
|
3491
|
+
const rawTrees = value.seatTrees && typeof value.seatTrees === 'object' && !Array.isArray(value.seatTrees)
|
|
3492
|
+
? value.seatTrees as Record<string, unknown> : {};
|
|
3493
|
+
const rawCaps = value.seatCaps && typeof value.seatCaps === 'object' && !Array.isArray(value.seatCaps)
|
|
3494
|
+
? value.seatCaps as Record<string, unknown> : {};
|
|
3495
|
+
const seatTrees: OrchestratorLoopConfig['seatTrees'] = {};
|
|
3496
|
+
const seatCaps = { ...ORCHESTRATOR_DEFAULTS.seatCaps };
|
|
3497
|
+
for (const seat of ['OP', 'TC', 'MK', 'UX'] as const) {
|
|
3498
|
+
if (Array.isArray(rawTrees[seat])) seatTrees[seat] = rawTrees[seat].filter((path): path is string => typeof path === 'string' && isAbsolute(path));
|
|
3499
|
+
const cap = rawCaps[seat];
|
|
3500
|
+
if (typeof cap === 'number' && Number.isSafeInteger(cap) && cap >= 0) seatCaps[seat] = cap;
|
|
3501
|
+
}
|
|
3502
|
+
return { seatTrees, seatCaps, trafficMode: value.trafficMode === 'live' ? 'live' : 'shadow' };
|
|
3503
|
+
}
|
|
3504
|
+
|
|
3505
|
+
export type StewardLoopMode = 'off' | 'shadow' | 'live' | 'rescue';
|
|
3479
3506
|
|
|
3480
3507
|
export type SeatLoopMode = 'off' | 'shadow' | 'on';
|
|
3481
3508
|
|
|
@@ -3516,25 +3543,51 @@ export function parseEventsConfig(input: unknown): EventsConfig {
|
|
|
3516
3543
|
return { mode: events.mode === 'on' ? 'on' : 'shadow', routes, linearAssignees };
|
|
3517
3544
|
}
|
|
3518
3545
|
|
|
3546
|
+
export type HqFenceRole = 'telegram-poller' | 'cron' | 'seat-loop' | 'release-run' | 'conatus' | 'git-push' | 'ledger-cli';
|
|
3547
|
+
export interface HqConfig {
|
|
3548
|
+
/** ssh host that keeps the lease record — homes push to it; it never sshes back (default cloud-vm). */
|
|
3549
|
+
arbiter?: string;
|
|
3550
|
+
/** This host's identity in the lease (default os.hostname()). */
|
|
3551
|
+
hostName?: string;
|
|
3552
|
+
/** Standby host (default node-b) — the quorum's third member; promoted only by the arbiter. */
|
|
3553
|
+
standby?: string;
|
|
3554
|
+
/** Lease TTL in seconds (default 1500 = 25 min). */
|
|
3555
|
+
ttlSeconds?: number;
|
|
3556
|
+
/** Roles that may still run when this host is fenced only for lack of quorum (never when a newer generation exists). Absent = none. */
|
|
3557
|
+
failOpenRoles?: HqFenceRole[];
|
|
3558
|
+
/** Per-host nexus health URL over the tailnet (default https://<host>.<tailnetDomain>:31415/v1/health). */
|
|
3559
|
+
healthUrls?: Record<string, string>;
|
|
3560
|
+
/** MagicDNS suffix (default: read from `tailscale status --json`). */
|
|
3561
|
+
tailnetDomain?: string;
|
|
3562
|
+
/** `tailscale` binary (default tailscale). */
|
|
3563
|
+
tailscaleBin?: string;
|
|
3564
|
+
/** Highest lease generation this host ever saw (default ~/.elanous-hq/seen-generation · 600). */
|
|
3565
|
+
seenGenerationFile?: string;
|
|
3566
|
+
/** Per-probe timeout in seconds (default 5). */
|
|
3567
|
+
probeTimeoutSeconds?: number;
|
|
3568
|
+
}
|
|
3569
|
+
|
|
3519
3570
|
export interface UserConfig {
|
|
3520
3571
|
events?: EventsConfig;
|
|
3521
3572
|
/** Explicit HTTPS destination for doctor diagnostics; absent means no upload. */
|
|
3522
3573
|
diagnostics?: { uploadUrl?: string };
|
|
3574
|
+
/** HQ lease (본부 임대 · 10-04 HQ-HB/HQ-FENCE): arbiter host over ssh, this host's name, per-role fail-open when the arbiter is unreachable (default: fail closed). */
|
|
3575
|
+
hq?: HqConfig;
|
|
3523
3576
|
/** Root CLI help audience; absent or invalid means owner. */
|
|
3524
3577
|
cli?: { helpRole?: 'owner' | 'contributor' | 'general' };
|
|
3525
3578
|
/** Execution-phase HITL answer deadline; absent means 30 minutes. */
|
|
3526
3579
|
hitl?: { executionDeadlineMinutes?: number };
|
|
3527
3580
|
coo?: { linearProject?: string };
|
|
3528
|
-
decisions?: { linearProjection: { enabled: boolean } };
|
|
3581
|
+
decisions?: { linearProjection: { enabled: boolean }; requireCrossCheck: boolean };
|
|
3529
3582
|
/** Per-seat goal and Pod limits; missing or invalid limits use seat-budget defaults. */
|
|
3530
3583
|
org?: { budget?: Record<string, { dailyGoals?: number; concurrentPods?: number }> };
|
|
3531
3584
|
/** Steward and seat loops are parsed independently. An absent seat remains off. */
|
|
3532
|
-
loops?: { steward?: { mode?: StewardLoopMode; linearTeam?: string; roles?: Record<string, { maxConcurrent?: number }>; budget?: number; tracks?: Record<string, string>; alertAfterFailures?: number; launch?: 'off' | 'shadow' | 'live'; maxParallel?: number; podPool?: string }; seat?: SeatLoopConfig; persona?: PersonaLoopConfig };
|
|
3585
|
+
loops?: { orchestrator?: OrchestratorLoopConfig; steward?: { mode?: StewardLoopMode; linearTeam?: string; roles?: Record<string, { maxConcurrent?: number }>; budget?: number; tracks?: Record<string, string>; alertAfterFailures?: number; launch?: 'off' | 'shadow' | 'live'; maxParallel?: number; podPool?: string }; seat?: SeatLoopConfig; persona?: PersonaLoopConfig };
|
|
3533
3586
|
/** 🔐 Pod 의 Grok API 키 과금은 명시 opt-in 만. 생략·잘못된 값은 false.
|
|
3534
3587
|
* budgetGate 와 같은 `harness` 객체다 — 중복 선언이면 뒤 타입이 앞을 지운다. */
|
|
3535
|
-
harness?: { pod?: { grokApiKeyOptIn: boolean }; budgetGate?: HarnessBudgetGateConfig; difficultyPlacement?: boolean; defaultRepo?: string; substrate?: 'local' | 'pod'; podPool?: string; worktreeAddTimeoutSec?: number };
|
|
3588
|
+
harness?: { pod?: { grokApiKeyOptIn: boolean }; budgetGate?: HarnessBudgetGateConfig; difficultyPlacement?: boolean; defaultRepo?: string; substrate?: 'local' | 'pod'; podPool?: string; worktreeAddTimeoutSec?: number; queue?: { seatCap?: Partial<Record<'OP' | 'TC' | 'MK' | 'UX', number>> } };
|
|
3536
3589
|
/** ☸️ Pod 실행 칸 — `pool` = 기존 기본 풀(`컨텍스트[@ssh호스트][:상한][#k3d-레지스트리:포트]` 쉼표 · 앞이 우선). harness 실행은 인자·ELANOUS_POD_POOL·harness.podPool 다음으로 읽는다. */
|
|
3537
|
-
pod?: { pool?: string; /** 호스트 Git 미러 디렉터리 — Pod Job 에 읽기 전용으로 마운트한다. */ hostMirror?: string; /** 원격 그라운딩 엔드포인트(호스트 nexus · tailnet 주소) — Pod 가 토큰으로 «질의→인용»만 묻는다(P13). */ groundingUrl?: string; /** 읽기 전용 Pod lease 권장에 쓰는 계정별 동시 수. */ lease?: { perAccount?: number } };
|
|
3590
|
+
pod?: { pool?: string; /** 호스트 Git 미러 디렉터리 — Pod Job 에 읽기 전용으로 마운트한다. */ hostMirror?: string; /** 원격 그라운딩 엔드포인트(호스트 nexus · tailnet 주소) — Pod 가 토큰으로 «질의→인용»만 묻는다(P13). */ groundingUrl?: string; /** 읽기 전용 Pod lease 권장에 쓰는 계정별 동시 수. */ lease?: { perAccount?: number }; /** 실측 권고를 Pod 발사 기본값으로 쓸지 (기본 off). */ memory?: { adviseDefaults?: boolean } };
|
|
3538
3591
|
skillRouter: SkillRouterConfig;
|
|
3539
3592
|
llm: LLMConfig;
|
|
3540
3593
|
skills: SkillsConfig;
|
|
@@ -3860,8 +3913,8 @@ function defaultConfig(): UserConfig {
|
|
|
3860
3913
|
return {
|
|
3861
3914
|
cli: { helpRole: 'owner' },
|
|
3862
3915
|
coo: { linearProject: '외부 행정·큰 일 (COO)' },
|
|
3863
|
-
decisions: { linearProjection: { enabled: false } },
|
|
3864
|
-
loops: { steward: { mode: 'shadow' }, seat: { mode: 'off', questions: 'shadow', seats: ['MK'], podPool: 'pool-node-b@node-b:8' }, persona: { enabled: false } },
|
|
3916
|
+
decisions: { linearProjection: { enabled: false }, requireCrossCheck: false },
|
|
3917
|
+
loops: { orchestrator: parseOrchestratorLoopConfig(undefined), steward: { mode: 'shadow' }, seat: { mode: 'off', questions: 'shadow', seats: ['MK'], podPool: 'pool-node-b@node-b:8' }, persona: { enabled: false } },
|
|
3865
3918
|
skillRouter: { ...SR_DEFAULTS },
|
|
3866
3919
|
llm: { ...LLM_DEFAULTS },
|
|
3867
3920
|
skills: skillsDefaults(),
|
|
@@ -4388,7 +4441,7 @@ function parseStewardLoopsConfig(input: unknown): UserConfig['loops'] {
|
|
|
4388
4441
|
}
|
|
4389
4442
|
}
|
|
4390
4443
|
return { steward: {
|
|
4391
|
-
mode: s.mode === 'off' || s.mode === 'shadow' || s.mode === 'live' ? s.mode :
|
|
4444
|
+
mode: s.mode === 'off' || s.mode === 'shadow' || s.mode === 'live' || s.mode === 'rescue' ? s.mode :
|
|
4392
4445
|
s.launch === 'off' || s.launch === 'shadow' || s.launch === 'live' ? s.launch : 'shadow',
|
|
4393
4446
|
// A valid legacy launch setting remains effective when mode is absent; explicit mode always wins.
|
|
4394
4447
|
...(s.launch === 'off' || s.launch === 'shadow' || s.launch === 'live' ? { launch: s.launch } : {}),
|
|
@@ -4407,6 +4460,25 @@ export function parseCliHelpRole(raw: unknown): NonNullable<UserConfig['cli']>['
|
|
|
4407
4460
|
return cli.helpRole === 'general' || cli.helpRole === 'contributor' ? cli.helpRole : 'owner';
|
|
4408
4461
|
}
|
|
4409
4462
|
|
|
4463
|
+
const HQ_FENCE_ROLES: readonly HqFenceRole[] = ['telegram-poller', 'cron', 'seat-loop', 'release-run', 'conatus', 'git-push'];
|
|
4464
|
+
/** `hq` block (HQ-HB · HQ-FENCE): keep only well-typed fields — absent/invalid fields fall back to the hq module defaults. */
|
|
4465
|
+
export function parseHqConfig(raw: unknown): HqConfig | undefined {
|
|
4466
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return undefined;
|
|
4467
|
+
const r = raw as Record<string, unknown>;
|
|
4468
|
+
const str = (v: unknown) => typeof v === 'string' && v.trim() ? v.trim() : undefined;
|
|
4469
|
+
const out: HqConfig = {};
|
|
4470
|
+
for (const key of ['arbiter', 'hostName', 'standby', 'tailnetDomain', 'tailscaleBin', 'seenGenerationFile'] as const) {
|
|
4471
|
+
const v = str(r[key]); if (v) out[key] = v;
|
|
4472
|
+
}
|
|
4473
|
+
if (typeof r.ttlSeconds === 'number' && Number.isFinite(r.ttlSeconds) && r.ttlSeconds > 0) out.ttlSeconds = r.ttlSeconds;
|
|
4474
|
+
if (typeof r.probeTimeoutSeconds === 'number' && Number.isFinite(r.probeTimeoutSeconds) && r.probeTimeoutSeconds > 0) out.probeTimeoutSeconds = r.probeTimeoutSeconds;
|
|
4475
|
+
if (Array.isArray(r.failOpenRoles)) out.failOpenRoles = r.failOpenRoles.filter((v): v is HqFenceRole => HQ_FENCE_ROLES.includes(v as HqFenceRole));
|
|
4476
|
+
if (r.healthUrls && typeof r.healthUrls === 'object' && !Array.isArray(r.healthUrls)) {
|
|
4477
|
+
out.healthUrls = Object.fromEntries(Object.entries(r.healthUrls as Record<string, unknown>).filter((e): e is [string, string] => typeof e[1] === 'string'));
|
|
4478
|
+
}
|
|
4479
|
+
return Object.keys(out).length ? out : undefined;
|
|
4480
|
+
}
|
|
4481
|
+
|
|
4410
4482
|
export function buildUserConfig(path: string = defaultPath()): UserConfig {
|
|
4411
4483
|
if (!existsSync(path)) return defaultConfigWithRuntimeProvider();
|
|
4412
4484
|
let raw: unknown;
|
|
@@ -4532,6 +4604,8 @@ export function buildUserConfig(path: string = defaultPath()): UserConfig {
|
|
|
4532
4604
|
...(typeof legacyPod.pool === 'string' && legacyPod.pool.trim() ? { pool: legacyPod.pool.trim() } : {}),
|
|
4533
4605
|
...(typeof legacyPod.hostMirror === 'string' && legacyPod.hostMirror.trim() ? { hostMirror: legacyPod.hostMirror.trim() } : {}),
|
|
4534
4606
|
...(typeof legacyPod.groundingUrl === 'string' ? { groundingUrl: legacyPod.groundingUrl } : {}),
|
|
4607
|
+
memory: { adviseDefaults: Boolean(legacyPod.memory && typeof legacyPod.memory === 'object' && !Array.isArray(legacyPod.memory)
|
|
4608
|
+
&& (legacyPod.memory as Record<string, unknown>).adviseDefaults === true) },
|
|
4535
4609
|
lease: { perAccount: (() => {
|
|
4536
4610
|
const lease = legacyPod.lease && typeof legacyPod.lease === 'object' && !Array.isArray(legacyPod.lease)
|
|
4537
4611
|
? (legacyPod.lease as Record<string, unknown>) : {};
|
|
@@ -4542,6 +4616,12 @@ export function buildUserConfig(path: string = defaultPath()): UserConfig {
|
|
|
4542
4616
|
harness: {
|
|
4543
4617
|
pod: { grokApiKeyOptIn: harnessPod.grokApiKeyOptIn === true },
|
|
4544
4618
|
difficultyPlacement: harness.difficultyPlacement === true,
|
|
4619
|
+
queue: { seatCap: Object.fromEntries(Object.entries(
|
|
4620
|
+
harness.queue && typeof harness.queue === 'object' && !Array.isArray(harness.queue)
|
|
4621
|
+
&& (harness.queue as Record<string, unknown>).seatCap && typeof (harness.queue as Record<string, unknown>).seatCap === 'object'
|
|
4622
|
+
&& !Array.isArray((harness.queue as Record<string, unknown>).seatCap)
|
|
4623
|
+
? (harness.queue as { seatCap: Record<string, unknown> }).seatCap : {},
|
|
4624
|
+
).filter(([seat, cap]) => /^(OP|TC|MK|UX)$/.test(seat) && typeof cap === 'number' && Number.isSafeInteger(cap) && cap > 0)) },
|
|
4545
4625
|
...(harness.substrate === 'local' || harness.substrate === 'pod' ? { substrate: harness.substrate } : {}),
|
|
4546
4626
|
...(typeof harness.podPool === 'string' && harness.podPool.trim() ? { podPool: harness.podPool.trim() } : {}),
|
|
4547
4627
|
...(typeof harness.worktreeAddTimeoutSec === 'number' && Number.isSafeInteger(harness.worktreeAddTimeoutSec)
|
|
@@ -5186,11 +5266,13 @@ export function buildUserConfig(path: string = defaultPath()): UserConfig {
|
|
|
5186
5266
|
&& typeof (rawObj.hitl as Record<string, unknown>).executionDeadlineMinutes === 'number'
|
|
5187
5267
|
? { hitl: { executionDeadlineMinutes: (rawObj.hitl as { executionDeadlineMinutes: number }).executionDeadlineMinutes } }
|
|
5188
5268
|
: {}),
|
|
5269
|
+
...(parseHqConfig(rawObj.hq) ? { hq: parseHqConfig(rawObj.hq)! } : {}),
|
|
5189
5270
|
cli: { helpRole: parseCliHelpRole(rawObj.cli) },
|
|
5190
5271
|
coo: { linearProject: typeof (rawObj.coo as { linearProject?: unknown } | undefined)?.linearProject === 'string' && (rawObj.coo as { linearProject: string }).linearProject.trim()
|
|
5191
5272
|
? (rawObj.coo as { linearProject: string }).linearProject.trim() : '외부 행정·큰 일 (COO)' },
|
|
5192
|
-
decisions: { linearProjection: { enabled: ((rawObj.decisions as { linearProjection?: { enabled?: unknown } } | undefined)?.linearProjection?.enabled === true) }
|
|
5193
|
-
|
|
5273
|
+
decisions: { linearProjection: { enabled: ((rawObj.decisions as { linearProjection?: { enabled?: unknown } } | undefined)?.linearProjection?.enabled === true) },
|
|
5274
|
+
requireCrossCheck: (rawObj.decisions as { requireCrossCheck?: unknown } | undefined)?.requireCrossCheck === true },
|
|
5275
|
+
loops: { ...(stewardLoops ?? {}), orchestrator: parseOrchestratorLoopConfig((rawObj.loops as { orchestrator?: unknown } | undefined)?.orchestrator), seat: parseSeatLoopsConfig(rawObj.loops),
|
|
5194
5276
|
persona: { enabled: (rawObj.loops as { persona?: { enabled?: unknown } } | undefined)?.persona?.enabled === true } },
|
|
5195
5277
|
events: parseEventsConfig(rawObj.events),
|
|
5196
5278
|
// M1-1: sparse — undefined when the user hasn't set anything, so
|
|
@@ -5790,11 +5872,13 @@ export function saveUserConfig(
|
|
|
5790
5872
|
defaultRepo: cfg.harness?.defaultRepo ?? rawHarness.defaultRepo,
|
|
5791
5873
|
substrate: cfg.harness?.substrate,
|
|
5792
5874
|
podPool: cfg.harness?.podPool,
|
|
5875
|
+
queue: cfg.harness?.queue ?? rawHarness.queue,
|
|
5793
5876
|
}),
|
|
5794
5877
|
...rawRest,
|
|
5795
5878
|
...(cfg.loops ? { loops: {
|
|
5796
5879
|
...rawLoops,
|
|
5797
5880
|
...(cfg.loops.steward ? { steward: { ...rawSteward, mode: cfg.loops.steward.mode ?? cfg.loops.steward.launch ?? 'shadow' } } : {}),
|
|
5881
|
+
...(cfg.loops.orchestrator ? { orchestrator: cfg.loops.orchestrator } : {}),
|
|
5798
5882
|
...(cfg.loops.seat ? { seat: {
|
|
5799
5883
|
mode: cfg.loops.seat.mode,
|
|
5800
5884
|
questions: cfg.loops.seat.questions ?? 'shadow',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"commit":"
|
|
1
|
+
{"commit":"5116219907580581e7bb40b93018d155deafa5c2"}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
isClassifyNode,
|
|
19
19
|
isExtractNode,
|
|
20
20
|
isFilterNode,
|
|
21
|
+
isKnowledgeNode,
|
|
21
22
|
isHttpRequestNode,
|
|
22
23
|
isIfNode,
|
|
23
24
|
isIterationNode,
|
|
@@ -57,6 +58,7 @@ import { executeExtractNode } from './nodes/extract.js';
|
|
|
57
58
|
import { executeSetNode } from './nodes/set.js';
|
|
58
59
|
import { executeTaskNode } from './nodes/task.js';
|
|
59
60
|
import { executeFilterNode } from './nodes/filter.js';
|
|
61
|
+
import { executeKnowledgeNode } from './nodes/knowledge.js';
|
|
60
62
|
import { executeTemplateNode } from './nodes/template.js';
|
|
61
63
|
import { executeHttpRequestNode } from './nodes/http.js';
|
|
62
64
|
import { executeShowroomNode } from './nodes/showroom.js';
|
|
@@ -706,6 +708,7 @@ async function dispatchNode(
|
|
|
706
708
|
if (isExtractNode(node)) return executeExtractNode(node, ctx, deps);
|
|
707
709
|
if (isSetNode(node)) return executeSetNode(node, ctx, deps);
|
|
708
710
|
if (isFilterNode(node)) return executeFilterNode(node, ctx, deps);
|
|
711
|
+
if (isKnowledgeNode(node)) return executeKnowledgeNode(node, ctx, deps);
|
|
709
712
|
if (isTemplateNode(node)) return executeTemplateNode(node, ctx, deps);
|
|
710
713
|
if (isHttpRequestNode(node)) return executeHttpRequestNode(node, ctx, deps);
|
|
711
714
|
if (isShowroomNode(node)) return executeShowroomNode(node, ctx, deps);
|
|
@@ -748,6 +751,7 @@ function isTriggerVariant(node: DagNode): boolean {
|
|
|
748
751
|
|
|
749
752
|
function variantOf(node: DagNode): string {
|
|
750
753
|
if (isPluginKindNode(node)) return node.kind;
|
|
754
|
+
if (isKnowledgeNode(node)) return 'knowledge';
|
|
751
755
|
const guards: ReadonlyArray<(node: DagNode) => boolean> = [
|
|
752
756
|
isPromptNode, isBashNode, isSkillNode, isCftNode, isApprovalNode, isIfNode,
|
|
753
757
|
isSwitchNode, isIterationNode, isClassifyNode, isExtractNode, isSetNode,
|
|
@@ -207,6 +207,19 @@ export const NODE_CATALOG: readonly NodeSpec[] = [
|
|
|
207
207
|
condition: $item != 'skip'`,
|
|
208
208
|
related: ['iteration'],
|
|
209
209
|
},
|
|
210
|
+
{
|
|
211
|
+
kind: 'knowledge',
|
|
212
|
+
category: 'transform',
|
|
213
|
+
summary: '볼트 검색 · 찾은 조각을 다음 노드에',
|
|
214
|
+
yamlKey: 'knowledge',
|
|
215
|
+
required: ['query'],
|
|
216
|
+
optional: ['limit'],
|
|
217
|
+
example: `- id: notes
|
|
218
|
+
knowledge:
|
|
219
|
+
query: $ARGUMENTS
|
|
220
|
+
limit: 3`,
|
|
221
|
+
related: ['prompt'],
|
|
222
|
+
},
|
|
210
223
|
{
|
|
211
224
|
kind: 'template',
|
|
212
225
|
category: 'transform',
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { dispatchElanousObsidianSearch } from '../../tool-runtime/elanous-obsidian-search-runtime.js';
|
|
2
|
+
import type { KnowledgeNode, NodeExecContext, NodeOutput, WorkflowDeps } from '../types.js';
|
|
3
|
+
import { interpolate } from '../variables.js';
|
|
4
|
+
|
|
5
|
+
export async function executeKnowledgeNode(
|
|
6
|
+
node: KnowledgeNode,
|
|
7
|
+
ctx: NodeExecContext,
|
|
8
|
+
deps: WorkflowDeps,
|
|
9
|
+
): Promise<NodeOutput> {
|
|
10
|
+
const startedAt = Date.now();
|
|
11
|
+
try {
|
|
12
|
+
const query = interpolate(node.knowledge.query, {
|
|
13
|
+
arguments: ctx.arguments,
|
|
14
|
+
artifactsDir: ctx.artifactsDir,
|
|
15
|
+
outputs: ctx.outputs,
|
|
16
|
+
}).text;
|
|
17
|
+
const limit = node.knowledge.limit ?? 5;
|
|
18
|
+
const result = await (deps.searchObsidian ?? dispatchElanousObsidianSearch)({ query, limit });
|
|
19
|
+
const matches = result.matches;
|
|
20
|
+
return {
|
|
21
|
+
ok: true,
|
|
22
|
+
output: result.error ? '' : matches.slice(0, limit)
|
|
23
|
+
.map(match => `— ${match.path}:${match.lineNumber}\n${match.snippet.slice(0, 400)}`)
|
|
24
|
+
.join('\n\n'),
|
|
25
|
+
matches,
|
|
26
|
+
...(result.error ? { error: result.error } : matches.length === 0 ? { error: 'no matches' } : {}),
|
|
27
|
+
durationMs: Date.now() - startedAt,
|
|
28
|
+
};
|
|
29
|
+
} catch (err) {
|
|
30
|
+
return {
|
|
31
|
+
ok: true,
|
|
32
|
+
output: '',
|
|
33
|
+
matches: [],
|
|
34
|
+
error: err instanceof Error ? err.message : String(err),
|
|
35
|
+
durationMs: Date.now() - startedAt,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|