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
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
* X 는 media.variants 로 MP4 직링크를 주므로 yt-dlp 없이 curl 로 바로 받는다.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { execFile as execFileCb
|
|
11
|
+
import { execFile as execFileCb } from 'node:child_process';
|
|
12
12
|
import { mkdir, readdir, readFile, rm } from 'node:fs/promises';
|
|
13
|
-
import { existsSync } from 'node:fs';
|
|
13
|
+
import { accessSync, constants, existsSync } from 'node:fs';
|
|
14
14
|
import { tmpdir } from 'node:os';
|
|
15
|
-
import { join } from 'node:path';
|
|
15
|
+
import { join, delimiter } from 'node:path';
|
|
16
16
|
import { promisify } from 'node:util';
|
|
17
17
|
import { env } from './env.js';
|
|
18
18
|
import type { TweetMedia } from './types.js';
|
|
@@ -25,10 +25,22 @@ const MAX_DURATION_MS = Number(env('OMNI_DIGEST_MEDIA_MAX_MS', String(20 * 60 *
|
|
|
25
25
|
const CHUNK_SECONDS = 600;
|
|
26
26
|
|
|
27
27
|
function has(bin: string): boolean {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
for (const dir of (process.env.PATH ?? '').split(delimiter).filter(Boolean)) {
|
|
29
|
+
try { accessSync(join(dir, bin), constants.X_OK); return true; } catch { /* try next directory */ }
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const missingEngines = new Set<string>();
|
|
35
|
+
|
|
36
|
+
export function mediaEngineAvailable(engine: 'ffmpeg' | 'whisper'): boolean {
|
|
37
|
+
if (has(engine)) return true;
|
|
38
|
+
if (!missingEngines.has(engine)) {
|
|
39
|
+
missingEngines.add(engine);
|
|
40
|
+
if (engine === 'ffmpeg') console.error('ffmpeg 없음 — 영상 오디오 추출·전사 빠짐 · 설치하면 영상 전사 켜짐');
|
|
41
|
+
else console.error('whisper 없음 — 로컬 음성 전사 빠짐 · 설치하면 오프라인 전사 켜짐 (youtube-master 폴백은 계속)');
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
32
44
|
}
|
|
33
45
|
|
|
34
46
|
async function run(bin: string, args: string[]): Promise<string> {
|
|
@@ -47,7 +59,7 @@ export async function downloadVideo(url: string, outdir: string): Promise<string
|
|
|
47
59
|
|
|
48
60
|
/** 영상에서 16kHz 모노 mp3 를 추출한다 (STT 엔진 공통 입력 규격). */
|
|
49
61
|
export async function extractAudio(videoPath: string, outdir: string): Promise<string> {
|
|
50
|
-
if (!
|
|
62
|
+
if (!mediaEngineAvailable('ffmpeg')) throw new Error('ffmpeg 가 필요합니다 (brew install ffmpeg)');
|
|
51
63
|
const out = join(outdir, 'audio.mp3');
|
|
52
64
|
await run('ffmpeg', [
|
|
53
65
|
'-hide_banner', '-loglevel', 'error', '-y',
|
|
@@ -61,7 +73,7 @@ export async function extractAudio(videoPath: string, outdir: string): Promise<s
|
|
|
61
73
|
|
|
62
74
|
/** 로컬 whisper CLI 전사. 미설치면 null 을 반환해 폴백을 유도한다. */
|
|
63
75
|
async function transcribeLocalWhisper(audioPath: string, outdir: string, lang: string): Promise<string | null> {
|
|
64
|
-
if (!
|
|
76
|
+
if (!mediaEngineAvailable('whisper')) return null;
|
|
65
77
|
const model = env('OMNI_DIGEST_WHISPER_MODEL', 'small');
|
|
66
78
|
try {
|
|
67
79
|
await run('whisper', [
|
|
@@ -81,11 +93,12 @@ async function transcribeLocalWhisper(audioPath: string, outdir: string, lang: s
|
|
|
81
93
|
}
|
|
82
94
|
|
|
83
95
|
/** youtube-master 의 STT 체인(ElevenLabs → OpenAI → Gemini)으로 폴백. */
|
|
84
|
-
async function transcribeViaYoutubeMaster(audioPath: string, outdir: string): Promise<string | null> {
|
|
96
|
+
export async function transcribeViaYoutubeMaster(audioPath: string, outdir: string): Promise<string | null> {
|
|
85
97
|
const skillRoot = env('YOUTUBE_MASTER_ROOT', join(process.env.HOME || '', '.claude/skills/youtube-master'));
|
|
86
98
|
const script = join(skillRoot, 'scripts/transcribe-local.ts');
|
|
87
99
|
if (!existsSync(script)) return null;
|
|
88
100
|
|
|
101
|
+
if (!mediaEngineAvailable('ffmpeg')) return null;
|
|
89
102
|
// transcribe-local.ts 는 chunk_%03d.mp3 가 든 디렉터리를 입력으로 받는다
|
|
90
103
|
const chunksDir = join(outdir, 'chunks');
|
|
91
104
|
await mkdir(chunksDir, { recursive: true });
|
|
@@ -98,7 +111,7 @@ async function transcribeViaYoutubeMaster(audioPath: string, outdir: string): Pr
|
|
|
98
111
|
|
|
99
112
|
const outFile = join(outdir, 'transcript.txt');
|
|
100
113
|
try {
|
|
101
|
-
await run(
|
|
114
|
+
await run(process.execPath, [script, chunksDir, outFile]);
|
|
102
115
|
const text = (await readFile(outFile, 'utf-8')).trim();
|
|
103
116
|
return text || null;
|
|
104
117
|
} catch {
|
|
@@ -137,6 +150,7 @@ export async function transcribeTweetMedia(
|
|
|
137
150
|
console.log(` 영상 ${Math.round(video.durationMs / 1000)}s — 상한(${Math.round(MAX_DURATION_MS / 1000)}s) 초과로 전사 생략`);
|
|
138
151
|
return null;
|
|
139
152
|
}
|
|
153
|
+
if (!mediaEngineAvailable('ffmpeg')) return null;
|
|
140
154
|
|
|
141
155
|
const workdir = join(tmpdir(), `omni-digest-media-${video.mediaKey}`);
|
|
142
156
|
try {
|
|
@@ -13,7 +13,7 @@ import { mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'n
|
|
|
13
13
|
import { tmpdir } from 'node:os';
|
|
14
14
|
import { join } from 'node:path';
|
|
15
15
|
import { env, requireEnv } from './env.js';
|
|
16
|
-
import { downloadVideo } from './media.js';
|
|
16
|
+
import { downloadVideo, mediaEngineAvailable } from './media.js';
|
|
17
17
|
import type { TweetMedia } from './types.js';
|
|
18
18
|
|
|
19
19
|
const MAX_FRAMES = 8;
|
|
@@ -101,7 +101,8 @@ export async function absorbTweetVisuals(media: TweetMedia[], opts: { tweetId: s
|
|
|
101
101
|
} catch (e) { log(` [시각] 이미지 ${i + 1} 실패: ${String(e).slice(0, 120)}`); }
|
|
102
102
|
}
|
|
103
103
|
const videos = media.filter((m) => (m.type === 'video' || m.type === 'animated_gif') && (m.bestMp4Url || m.sttMp4Url));
|
|
104
|
-
|
|
104
|
+
const canExtractFrames = !videos.length || mediaEngineAvailable('ffmpeg');
|
|
105
|
+
for (const [i, v] of (canExtractFrames ? videos : []).entries()) {
|
|
105
106
|
if (v.durationMs && v.durationMs > MAX_VIDEO_MS_FOR_FRAMES) { log(` [시각] 영상 ${i + 1} 길어서 프레임 생략`); continue; }
|
|
106
107
|
try {
|
|
107
108
|
const dir = join(work, `v${i + 1}`);
|
|
@@ -5,7 +5,7 @@ import { resolveDateRange } from '../src/date.js';
|
|
|
5
5
|
import { route, listProviders } from '../src/router.js';
|
|
6
6
|
import { render } from '../src/render.js';
|
|
7
7
|
import type { Command } from '../src/types.js';
|
|
8
|
-
import { writeStdoutJson } from '
|
|
8
|
+
import { writeStdoutJson } from './stdout-json.js';
|
|
9
9
|
|
|
10
10
|
initEnv();
|
|
11
11
|
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Write JSON output completely before the process can exit. (Kept inside the skill so the skill runs outside this repository — public pack and lite Pod.) */
|
|
2
|
+
export async function writeStdoutJson(text: string): Promise<void> {
|
|
3
|
+
await new Promise<void>((resolve, reject) => process.stdout.write(text, error => error ? reject(error) : resolve()));
|
|
4
|
+
}
|
|
@@ -21,6 +21,17 @@
|
|
|
21
21
|
import { debug } from '../debug/log.js';
|
|
22
22
|
import { getCapturedEnv, capturedEnvAvailable } from '../shell-env-bootstrap.js';
|
|
23
23
|
import { resolveCurrentInstance } from '../instance/current.js';
|
|
24
|
+
import { treeDerivedRootFor } from '../instance/resolve.js';
|
|
25
|
+
|
|
26
|
+
/** Pin a child state root together with its origin; only reuse a parent's tag for the same root. */
|
|
27
|
+
export function stateDirSourceForChild(root: string, env: NodeJS.ProcessEnv = process.env): string {
|
|
28
|
+
if (env.ELANOUS_STATE_DIR === root && env.ELANOUS_STATE_DIR_SOURCE?.trim()) {
|
|
29
|
+
return env.ELANOUS_STATE_DIR_SOURCE;
|
|
30
|
+
}
|
|
31
|
+
const current = resolveCurrentInstance();
|
|
32
|
+
return root === treeDerivedRootFor(process.cwd()) || (current.root === root && current.layer === 'default')
|
|
33
|
+
? 'derived' : 'explicit';
|
|
34
|
+
}
|
|
24
35
|
|
|
25
36
|
// ── 전파 allowlist ─────────────────────────────────────────────────────────
|
|
26
37
|
//
|
|
@@ -491,6 +491,8 @@ export interface AgentMissionSpec {
|
|
|
491
491
|
memory?: boolean;
|
|
492
492
|
/** PTY 닉네임(휴먼 리더블·goto 로 나중 접근). 기본 branch. */
|
|
493
493
|
nickname?: string;
|
|
494
|
+
/** Concurrent missions may share one decision stream alias without sharing a PTY or worktree. */
|
|
495
|
+
decisionMissionId?: string;
|
|
494
496
|
/** ⭐⭐ 그 브랜치를 이미 쥔 «소유» 워크트리가 있으면 지우지 말고 그대로 재사용하라고 요청한다
|
|
495
497
|
* (`createWorktree` 의 같은 이름 인자로 그대로 내려간다 · 판정은 거기 `gateWorktreeReuse` 가 한다).
|
|
496
498
|
*
|
|
@@ -513,11 +515,14 @@ export interface AgentMissionResult {
|
|
|
513
515
|
paths?: string[];
|
|
514
516
|
rounds: number;
|
|
515
517
|
evidencePath: string | null;
|
|
518
|
+
/** The evidence check outcome, independent of the final mission and DRIVE-OK verdicts. */
|
|
519
|
+
evidenceSatisfied?: boolean;
|
|
516
520
|
committed: boolean;
|
|
517
521
|
usedOmniCrawl: boolean;
|
|
518
522
|
detail: string;
|
|
519
523
|
ptyId?: string;
|
|
520
524
|
webUrl?: string | null;
|
|
525
|
+
driveVerdict?: ReturnType<typeof verdictForFinalPtyScreen>['kind'];
|
|
521
526
|
}
|
|
522
527
|
/** @deprecated codex 특정 이름 — AgentMissionResult 을 쓰라. */
|
|
523
528
|
export type CodexMissionResult = AgentMissionResult;
|
|
@@ -1609,7 +1614,7 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
|
|
|
1609
1614
|
const finalEv = verifyEvidence(wt.path, spec.evidence);
|
|
1610
1615
|
if (!finalEv.ok) return {
|
|
1611
1616
|
ok: false, worktree: wt.path, branch: wt.branch, rounds: result.numTurns ?? 0,
|
|
1612
|
-
evidencePath: null, committed: false, usedOmniCrawl: false,
|
|
1617
|
+
evidencePath: null, evidenceSatisfied: false, committed: false, usedOmniCrawl: false,
|
|
1613
1618
|
detail: `미완(증거 부족): ${finalEv.retry ?? '증거가 아직 부족하다.'}`,
|
|
1614
1619
|
};
|
|
1615
1620
|
if (checklist.length) {
|
|
@@ -1627,13 +1632,13 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
|
|
|
1627
1632
|
if (!c.ok) {
|
|
1628
1633
|
debug.log('agent-mission', 'result', { ok: false, rounds: result.numTurns ?? 0, evidencePath: finalEv.path, committed: false, usedOmni: false });
|
|
1629
1634
|
return { ok: false, worktree: wt.path, branch: wt.branch, rounds: result.numTurns ?? 0,
|
|
1630
|
-
evidencePath: finalEv.path, committed: false, usedOmniCrawl: false,
|
|
1635
|
+
evidencePath: finalEv.path, evidenceSatisfied: true, committed: false, usedOmniCrawl: false,
|
|
1631
1636
|
detail: `미완(자동 커밋 실패): ${c.out.slice(0, 120)}` };
|
|
1632
1637
|
}
|
|
1633
1638
|
}
|
|
1634
1639
|
debug.log('agent-mission', 'result', { ok: true, rounds: result.numTurns ?? 0, evidencePath: finalEv.path, committed, usedOmni: false });
|
|
1635
1640
|
return { ok: true, worktree: wt.path, branch: wt.branch, rounds: result.numTurns ?? 0,
|
|
1636
|
-
evidencePath: finalEv.path, committed, usedOmniCrawl: false, detail: '완료(증거 충족)' };
|
|
1641
|
+
evidencePath: finalEv.path, evidenceSatisfied: true, committed, usedOmniCrawl: false, detail: '완료(증거 충족)' };
|
|
1637
1642
|
}
|
|
1638
1643
|
env.TERM = 'xterm-256color';
|
|
1639
1644
|
// ⭐run-identity — runId was resolved before either headless or PTY spawn.
|
|
@@ -1713,7 +1718,7 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
|
|
|
1713
1718
|
process.stderr.write(`[agent-mission] pty=${h.id} watch=${webAddress.webUrl ?? `(web unavailable: ${webAddress.pwaUnavailableReason ?? 'not-resolved'})`}\n`);
|
|
1714
1719
|
debug.log('agent-mission', 'pty-link', { id: h.id, webUrl: webAddress.webUrl, webUrlSource: webAddress.webUrlSource });
|
|
1715
1720
|
|
|
1716
|
-
const decisionIdentity = { missionId: runId, sessionId: runId, terminalId: h.id, agent: backend.name };
|
|
1721
|
+
const decisionIdentity = { missionId: spec.decisionMissionId ?? runId, sessionId: runId, terminalId: h.id, agent: backend.name };
|
|
1717
1722
|
const decisionEvent = (event: Omit<DecisionInput, keyof typeof decisionIdentity>): void => {
|
|
1718
1723
|
try { (deps.emitPtyDecision ?? emitPtyDecision)({ ...decisionIdentity, ...event } as DecisionInput); }
|
|
1719
1724
|
catch { /* Observation must not change mission control or handoff. */ }
|
|
@@ -2142,7 +2147,7 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
|
|
|
2142
2147
|
try { h.kill(); } catch { /* noop */ }
|
|
2143
2148
|
return { ok: true, worktree: wt.path, branch: wt.branch, rounds: control.steps, evidencePath: wt.path, committed: true, usedOmniCrawl: usedOmni,
|
|
2144
2149
|
...(handoffPrSkipped ? { pr: 'skipped' as const, reason: 'origin-not-github' as const } : {}),
|
|
2145
|
-
detail: handoffPrSkipped ? '완료(증거 게이트·push·PR 생략)' : '완료(증거 게이트·PR)' };
|
|
2150
|
+
evidenceSatisfied: true, detail: handoffPrSkipped ? '완료(증거 게이트·push·PR 생략)' : '완료(증거 게이트·PR)' };
|
|
2146
2151
|
}
|
|
2147
2152
|
const done = control.termination.kind === 'success';
|
|
2148
2153
|
const round = control.steps;
|
|
@@ -2216,8 +2221,8 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
|
|
|
2216
2221
|
|
|
2217
2222
|
return {
|
|
2218
2223
|
ok: accepted, worktree: wt.path, branch: wt.branch, rounds: round,
|
|
2219
|
-
evidencePath, committed, usedOmniCrawl: usedOmni,
|
|
2220
|
-
ptyId: h.id, webUrl: webAddress.webUrl,
|
|
2224
|
+
evidencePath, evidenceSatisfied: finalEv.ok, committed, usedOmniCrawl: usedOmni,
|
|
2225
|
+
ptyId: h.id, webUrl: webAddress.webUrl, driveVerdict: driveVerdict.kind,
|
|
2221
2226
|
detail: recoverExhausted ? control.termination.reason : driveVerdict.kind === 'done-but-failed' ? `미완(DRIVE-OK: ${driveVerdict.reason})` : done ? '완료(증거 충족)' : (finalEv.ok ? '증거 충족(루프 종료)' : '미완(증거 부족)'),
|
|
2222
2227
|
};
|
|
2223
2228
|
} finally { stopLive(); finishContext(false); }
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { runAgentMission, resolveBackend, type AgentBackend, type AgentMissionResult, type AgentMissionSpec, type EvidenceMode } from './driver.js';
|
|
4
|
+
|
|
5
|
+
export interface DuoOptions {
|
|
6
|
+
left: string;
|
|
7
|
+
right: string;
|
|
8
|
+
branch: string;
|
|
9
|
+
base?: string;
|
|
10
|
+
missionFile?: string;
|
|
11
|
+
evidence?: string;
|
|
12
|
+
docDir?: string;
|
|
13
|
+
docGlob?: string;
|
|
14
|
+
testPath?: string;
|
|
15
|
+
file?: string;
|
|
16
|
+
maxRounds?: string;
|
|
17
|
+
commit?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface DuoSide {
|
|
21
|
+
side: 'left' | 'right';
|
|
22
|
+
backend: string;
|
|
23
|
+
branch: string;
|
|
24
|
+
finished: number;
|
|
25
|
+
result?: AgentMissionResult;
|
|
26
|
+
error?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface DuoResult {
|
|
30
|
+
missionId: string;
|
|
31
|
+
sides: [DuoSide, DuoSide];
|
|
32
|
+
lines: [string, string];
|
|
33
|
+
exitCode: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface DuoDeps {
|
|
37
|
+
resolveBackend?: typeof resolveBackend;
|
|
38
|
+
runMission?: typeof runAgentMission;
|
|
39
|
+
readFile?: (path: string) => string;
|
|
40
|
+
id?: () => string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function evidenceFor(opts: DuoOptions): EvidenceMode {
|
|
44
|
+
if (opts.evidence === 'doc') return { kind: 'doc', dirRel: opts.docDir ?? 'docs/plans', glob: new RegExp(opts.docGlob ?? '^PLAN-.*[.]md$', 'i') };
|
|
45
|
+
if (opts.evidence === 'test') {
|
|
46
|
+
if (!opts.testPath) throw new Error('test 모드엔 --test-path 필요');
|
|
47
|
+
return { kind: 'test', testPath: opts.testPath, ...(opts.file ? { fileRel: opts.file } : {}) };
|
|
48
|
+
}
|
|
49
|
+
if (opts.evidence === undefined || opts.evidence === 'tsc') return { kind: 'tsc' };
|
|
50
|
+
throw new Error('--evidence 는 doc|tsc|test 여야 합니다');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function render(side: DuoSide, first: DuoSide | undefined): string {
|
|
54
|
+
const evidence = side.result?.evidenceSatisfied === true
|
|
55
|
+
? side.result.evidencePath ? `충족(${side.result.evidencePath})` : '충족'
|
|
56
|
+
: side.result?.evidenceSatisfied === false ? '미충족' : '미측정';
|
|
57
|
+
const drive = side.result?.driveVerdict ?? '미측정';
|
|
58
|
+
const detail = side.error ?? (!side.result?.ok ? side.result?.detail : undefined);
|
|
59
|
+
return `${side.side} ${side.backend} · branch=${side.branch} · terminalId=${side.result?.ptyId ?? '없음'} · ${first === side ? '먼저' : '나중'} · 증거 ${evidence} · DRIVE-OK ${drive} · ${side.result?.ok ? '완료' : '실패'}${detail ? ` (${detail})` : ''}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Run two independent existing agent missions; neither branch waits for or cancels its peer. */
|
|
63
|
+
export async function runAgentMissionDuo(textParts: string[], opts: DuoOptions, deps: DuoDeps = {}): Promise<DuoResult> {
|
|
64
|
+
if (!opts.branch?.trim()) throw new Error('--branch 필요');
|
|
65
|
+
if (opts.left !== 'codex' && opts.left !== 'claude') throw new Error('--left 는 codex|claude 여야 합니다');
|
|
66
|
+
if (opts.right !== 'codex' && opts.right !== 'claude') throw new Error('--right 는 codex|claude 여야 합니다');
|
|
67
|
+
if (opts.left === opts.right) throw new Error('duo 에는 서로 다른 backend 둘이 필요합니다');
|
|
68
|
+
const mission = opts.missionFile ? (deps.readFile ?? ((path) => readFileSync(path, 'utf8')))(opts.missionFile) : textParts.join(' ');
|
|
69
|
+
if (!mission.trim()) throw new Error('미션 텍스트가 비었다 — <text...> 또는 --mission-file 필요');
|
|
70
|
+
const evidence = evidenceFor(opts);
|
|
71
|
+
const rounds = opts.maxRounds === undefined ? 16 : Number(opts.maxRounds);
|
|
72
|
+
if (!Number.isSafeInteger(rounds) || rounds <= 0) throw new Error('--max-rounds 는 양의 정수여야 합니다');
|
|
73
|
+
const resolve = deps.resolveBackend ?? resolveBackend;
|
|
74
|
+
const backends: [AgentBackend, AgentBackend] = [resolve(opts.left), resolve(opts.right)];
|
|
75
|
+
if (backends[0].name !== opts.left || backends[1].name !== opts.right) throw new Error('backend 선택이 요청과 다릅니다');
|
|
76
|
+
const id = `duo-${(deps.id ?? randomUUID)()}`;
|
|
77
|
+
const run = deps.runMission ?? runAgentMission;
|
|
78
|
+
let completed = 0;
|
|
79
|
+
const branches = [`${opts.branch}-${opts.left}`, `${opts.branch}-${opts.right}`] as const;
|
|
80
|
+
const tasks = backends.map(async (backend, index): Promise<DuoSide> => {
|
|
81
|
+
const side = index === 0 ? 'left' : 'right';
|
|
82
|
+
const branch = branches[index]!;
|
|
83
|
+
const spec: AgentMissionSpec = {
|
|
84
|
+
mission, branch, agent: backend, decisionMissionId: id, evidence, maxRounds: rounds,
|
|
85
|
+
...(opts.base ? { base: opts.base } : {}), commit: opts.commit !== false,
|
|
86
|
+
enhance: false, memory: false, resources: 'off', entry: 'external-verbatim',
|
|
87
|
+
};
|
|
88
|
+
try { return { side, backend: backend.name, branch, result: await run(spec), finished: ++completed }; }
|
|
89
|
+
catch (error) { return { side, backend: backend.name, branch, error: error instanceof Error ? error.message : String(error), finished: ++completed }; }
|
|
90
|
+
});
|
|
91
|
+
const sides = await Promise.all(tasks) as [DuoSide, DuoSide];
|
|
92
|
+
const first = sides[0].finished <= sides[1].finished ? sides[0] : sides[1];
|
|
93
|
+
return { missionId: id, sides, lines: [render(sides[0], first), render(sides[1], first)], exitCode: sides.every((s) => s.result?.ok) ? 0 : 2 };
|
|
94
|
+
}
|
|
@@ -44,7 +44,7 @@ function safe(value: string): string {
|
|
|
44
44
|
export function emitPtyDecision(input: DecisionInput, log: typeof debug.log = (category, event, data) => debug.log(category, event, data)): PtyDecision {
|
|
45
45
|
let stream = missionStreams.get(input.missionId);
|
|
46
46
|
if (!stream) {
|
|
47
|
-
stream = { alias:
|
|
47
|
+
stream = { alias: `${input.missionId.startsWith('duo-') ? 'duo' : 'mission'}-${missionStreams.size + 1}`, seq: -1 };
|
|
48
48
|
missionStreams.set(input.missionId, stream);
|
|
49
49
|
}
|
|
50
50
|
stream.seq += 1;
|
|
@@ -2,6 +2,7 @@ import { execFile } from 'node:child_process';
|
|
|
2
2
|
import { appendFileSync, mkdirSync, readFileSync, rmdirSync } from 'node:fs';
|
|
3
3
|
import { dirname, join, resolve } from 'node:path';
|
|
4
4
|
import { promisify } from 'node:util';
|
|
5
|
+
import { stateDirSourceForChild } from '../agent/identity-env.js';
|
|
5
6
|
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
6
7
|
import type { TrackAgentDecision, TrackAgentInput, TrackAgentResult } from './track-agent.js';
|
|
7
8
|
|
|
@@ -127,7 +128,7 @@ export async function launchTrackAgentShadow(input: TrackAgentInput, options: Sh
|
|
|
127
128
|
const instanceRoot = effectiveInstanceRoot();
|
|
128
129
|
const { stdout } = await exec('bun', ['bin/elanous.mjs', '--config-dir', instanceRoot, ...values], {
|
|
129
130
|
cwd: process.env.ELANOUS_TOOL_CWD?.trim() || resolve(import.meta.dir, '../..'),
|
|
130
|
-
env: { ...process.env, ELANOUS_STATE_DIR: instanceRoot },
|
|
131
|
+
env: { ...process.env, ELANOUS_STATE_DIR: instanceRoot, ELANOUS_STATE_DIR_SOURCE: stateDirSourceForChild(instanceRoot) },
|
|
131
132
|
encoding: 'utf8', maxBuffer: 1024 * 1024,
|
|
132
133
|
});
|
|
133
134
|
return { stdout };
|
|
@@ -2,6 +2,7 @@ import { execFile, spawn } from 'node:child_process';
|
|
|
2
2
|
import { readdirSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
import { promisify } from 'node:util';
|
|
5
|
+
import { stateDirSourceForChild } from '../agent/identity-env.js';
|
|
5
6
|
import { debug, redactSecretText } from '../debug/log.js';
|
|
6
7
|
import { getOrCreatePersonaSession } from '../session/index.js';
|
|
7
8
|
import { getUserConfig } from '../user-config.js';
|
|
@@ -38,7 +39,9 @@ const log = (event: string, data: Record<string, unknown>) => {
|
|
|
38
39
|
};
|
|
39
40
|
/** The daemon's own universe, resolved once and pinned on the child (config ⊕ state) — the harness isolates its own workers. */
|
|
40
41
|
export function universeLaunch(args: readonly string[], root: string = effectiveInstanceRoot()): { argv: string[]; env: NodeJS.ProcessEnv } {
|
|
41
|
-
return { argv: ['bin/elanous.mjs', '--config-dir', root, ...args], env: {
|
|
42
|
+
return { argv: ['bin/elanous.mjs', '--config-dir', root, ...args], env: {
|
|
43
|
+
...process.env, ELANOUS_STATE_DIR: root, ELANOUS_STATE_DIR_SOURCE: stateDirSourceForChild(root),
|
|
44
|
+
} };
|
|
42
45
|
}
|
|
43
46
|
const runCli = async (args: string[]): Promise<{ stdout: string }> => {
|
|
44
47
|
const { argv, env } = universeLaunch(args);
|
|
@@ -38,6 +38,7 @@ export interface DaemonPromptBody {
|
|
|
38
38
|
* (`none` · `readonly` · `chat` · `webterm`); invalid values reject
|
|
39
39
|
* the request rather than silently falling back. */
|
|
40
40
|
tools?: DaemonToolSurfaceKind;
|
|
41
|
+
personaId?: string;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
export interface DaemonPromptRequest {
|
|
@@ -53,6 +54,7 @@ export interface DaemonPromptRequest {
|
|
|
53
54
|
* not opt in; the caller-side (`handlePromptStreamPost`) interprets
|
|
54
55
|
* null as "use the daemon's boot-time surface". */
|
|
55
56
|
tools: DaemonToolSurfaceKind | null;
|
|
57
|
+
personaId?: string;
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
export type DaemonPromptRequestParseResult =
|
|
@@ -117,6 +119,7 @@ export function parseDaemonPromptBody(
|
|
|
117
119
|
body.source ? writeInputSourceMeta(body.source) : undefined,
|
|
118
120
|
),
|
|
119
121
|
tools: body.tools ?? null,
|
|
122
|
+
...(typeof body.personaId === 'string' && body.personaId.trim() ? { personaId: body.personaId } : {}),
|
|
120
123
|
},
|
|
121
124
|
};
|
|
122
125
|
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
appendUserPromptBlocksAndBuildMessages,
|
|
11
11
|
} from './daemon-history-helper.js';
|
|
12
12
|
import type { ContentBlock as AcpContentBlock } from '@agentclientprotocol/sdk';
|
|
13
|
-
import type
|
|
13
|
+
import { personaPromptForChat, type DaemonSessionHistory } from './daemon-runtime.js';
|
|
14
14
|
import type { DaemonPromptRequest } from './daemon-prompt-request.js';
|
|
15
15
|
import type { DaemonToolSurface } from './daemon-tools/types.js';
|
|
16
16
|
import type { ConfirmChannel } from '../hitl/confirm.js';
|
|
@@ -199,8 +199,9 @@ export async function runDaemonPromptTurn(opts: {
|
|
|
199
199
|
// …" + active terminals) to the system prompt so the LLM has the
|
|
200
200
|
// session id + terminal ids without an extra discovery call. Helper
|
|
201
201
|
// returns the original prompt unchanged when there's nothing to add.
|
|
202
|
+
const personaPrompt = await personaPromptForChat(request.personaId);
|
|
202
203
|
const augmentedSystemPrompt = appendWebtermContext(
|
|
203
|
-
request.effectiveSystemPrompt,
|
|
204
|
+
personaPrompt ? [personaPrompt, request.effectiveSystemPrompt].filter(Boolean).join('\n\n') : request.effectiveSystemPrompt,
|
|
204
205
|
request.sessionId,
|
|
205
206
|
activeToolSurface,
|
|
206
207
|
);
|
|
@@ -291,6 +292,9 @@ export async function runDaemonPromptTurn(opts: {
|
|
|
291
292
|
if (!isFirstTurn && opts.projectInstructions) {
|
|
292
293
|
messages.unshift({ role: 'system', content: opts.projectInstructions });
|
|
293
294
|
}
|
|
295
|
+
if (!isFirstTurn && personaPrompt) {
|
|
296
|
+
messages.unshift({ role: 'system', content: personaPrompt });
|
|
297
|
+
}
|
|
294
298
|
const result = await runCoreTurn({
|
|
295
299
|
sessionId: request.sessionId,
|
|
296
300
|
userText: request.userText,
|
|
@@ -55,6 +55,33 @@ import { elanousSelfAccessPrompt } from '../agent/self-ambient.js';
|
|
|
55
55
|
import { createToolCwdResolver, type ToolCwdResolver } from './tool-cwd.js';
|
|
56
56
|
import { resolveSessionProjectContext } from '../project/session-context.js';
|
|
57
57
|
import { debug } from '../debug/log.js';
|
|
58
|
+
import { resolvePersonaAddress } from '../intake-plane/persona-answer.js';
|
|
59
|
+
import { awaitGlobalPersonaLoad, getGlobalPersonaRegistry } from '../persona/global-registry.js';
|
|
60
|
+
|
|
61
|
+
const turnPersonaPrompt = new AsyncLocalStorage<string | undefined>();
|
|
62
|
+
|
|
63
|
+
export function withChatPersonaPrompt<T>(prompt: string | undefined, run: () => T): T {
|
|
64
|
+
return turnPersonaPrompt.run(prompt, run);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function prependChatPersonaOnLaterTurn(
|
|
68
|
+
messages: LLMMessage[], isFirstTurn: boolean,
|
|
69
|
+
): LLMMessage[] {
|
|
70
|
+
const prompt = turnPersonaPrompt.getStore();
|
|
71
|
+
if (!isFirstTurn && prompt) messages.unshift({ role: 'system', content: prompt });
|
|
72
|
+
return messages;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function personaPromptForChat(
|
|
76
|
+
id: unknown,
|
|
77
|
+
deps: { load: () => Promise<unknown>; registry: () => Parameters<typeof resolvePersonaAddress>[1] } = {
|
|
78
|
+
load: awaitGlobalPersonaLoad, registry: getGlobalPersonaRegistry,
|
|
79
|
+
},
|
|
80
|
+
): Promise<string | undefined> {
|
|
81
|
+
if (typeof id !== 'string' || !id.trim()) return undefined;
|
|
82
|
+
await deps.load();
|
|
83
|
+
return resolvePersonaAddress(id, deps.registry())?.systemPrompt;
|
|
84
|
+
}
|
|
58
85
|
|
|
59
86
|
/** C4 (2026-07-12) — arm the non-detached-PTY kill ONCE per turn signal.
|
|
60
87
|
* Multiple tool calls in one turn share the same AbortSignal instance;
|
|
@@ -135,7 +162,7 @@ export function composeDaemonSystemPrompt(
|
|
|
135
162
|
promptMeta: Readonly<Record<string, unknown>> | undefined,
|
|
136
163
|
sessionId?: string,
|
|
137
164
|
): string {
|
|
138
|
-
return [basePrompt, buildDaemonInputSourceLine(promptMeta), elanousSelfAccessPrompt(sessionId)]
|
|
165
|
+
return [turnPersonaPrompt.getStore(), basePrompt, buildDaemonInputSourceLine(promptMeta), elanousSelfAccessPrompt(sessionId)]
|
|
139
166
|
.filter((part): part is string => typeof part === 'string' && part.trim().length > 0)
|
|
140
167
|
.join('\n\n');
|
|
141
168
|
}
|
|
@@ -604,12 +631,11 @@ export function createDaemonRunTurn(
|
|
|
604
631
|
const hasNonTextBlock = !!promptBlocks?.some(
|
|
605
632
|
(b) => (b as { type?: string }).type !== 'text',
|
|
606
633
|
);
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
);
|
|
611
|
-
|
|
612
|
-
return appendUserAndBuildMessages(history, sessionId, userText, systemPrompt);
|
|
634
|
+
const isFirstTurn = history.get(sessionId).length === 0;
|
|
635
|
+
const messages = hasNonTextBlock
|
|
636
|
+
? appendUserPromptBlocksAndBuildMessages(history, sessionId, promptBlocks!, systemPrompt)
|
|
637
|
+
: appendUserAndBuildMessages(history, sessionId, userText, systemPrompt);
|
|
638
|
+
return prependChatPersonaOnLaterTurn(messages, isFirstTurn);
|
|
613
639
|
},
|
|
614
640
|
getTools: () => surface.specs,
|
|
615
641
|
dispatchTool: terminalCapableDispatch(surfaceHasPtyShell, async (name, args, ctx) => {
|
|
@@ -723,15 +749,21 @@ export function createDaemonRunTurn(
|
|
|
723
749
|
});
|
|
724
750
|
debugBridge.activate();
|
|
725
751
|
}
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
752
|
+
const elanousMeta = turnCtx.promptMeta?.elanous;
|
|
753
|
+
const personaId = elanousMeta && typeof elanousMeta === 'object' && !Array.isArray(elanousMeta)
|
|
754
|
+
&& 'pwaPersonaId' in elanousMeta ? elanousMeta.pwaPersonaId : undefined;
|
|
755
|
+
const personaPrompt = await personaPromptForChat(personaId);
|
|
756
|
+
await withChatPersonaPrompt(personaPrompt, async () => {
|
|
757
|
+
const projectCwd = resolveSessionProjectContext(turnCtx.sessionId)?.cwd;
|
|
758
|
+
if (projectCwd || opts.acpSessionCwd) {
|
|
759
|
+
// An ACP session's explicit cwd wins; the project folder is only the default when none was given (IA3 review).
|
|
760
|
+
const explicitCwd = opts.acpSessionCwd && turnCtx.cwd ? turnCtx.cwd : undefined;
|
|
761
|
+
const resolver = createToolCwdResolver({ tools: opts.tools ?? 'none', toolCwd: explicitCwd ?? projectCwd ?? turnCtx.cwd });
|
|
762
|
+
await turnToolCwd.run(resolver, () => inner(turnCtx));
|
|
763
|
+
} else {
|
|
764
|
+
await inner(turnCtx);
|
|
765
|
+
}
|
|
766
|
+
});
|
|
735
767
|
} finally {
|
|
736
768
|
debugBridge?.dispose();
|
|
737
769
|
}
|